1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use display_map::*;
63pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
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 CollaboratorId, 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_id: Option<CollaboratorId>,
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 /// Whether we are temporarily displaying a diff other than git's
985 temporary_diff_override: bool,
986 selection_mark_mode: bool,
987 toggle_fold_multiple_buffers: Task<()>,
988 _scroll_cursor_center_top_bottom_task: Task<()>,
989 serialize_selections: Task<()>,
990 serialize_folds: Task<()>,
991 mouse_cursor_hidden: bool,
992 hide_mouse_mode: HideMouseMode,
993 pub change_list: ChangeList,
994 inline_value_cache: InlineValueCache,
995}
996
997#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
998enum NextScrollCursorCenterTopBottom {
999 #[default]
1000 Center,
1001 Top,
1002 Bottom,
1003}
1004
1005impl NextScrollCursorCenterTopBottom {
1006 fn next(&self) -> Self {
1007 match self {
1008 Self::Center => Self::Top,
1009 Self::Top => Self::Bottom,
1010 Self::Bottom => Self::Center,
1011 }
1012 }
1013}
1014
1015#[derive(Clone)]
1016pub struct EditorSnapshot {
1017 pub mode: EditorMode,
1018 show_gutter: bool,
1019 show_line_numbers: Option<bool>,
1020 show_git_diff_gutter: Option<bool>,
1021 show_code_actions: Option<bool>,
1022 show_runnables: Option<bool>,
1023 show_breakpoints: Option<bool>,
1024 git_blame_gutter_max_author_length: Option<usize>,
1025 pub display_snapshot: DisplaySnapshot,
1026 pub placeholder_text: Option<Arc<str>>,
1027 is_focused: bool,
1028 scroll_anchor: ScrollAnchor,
1029 ongoing_scroll: OngoingScroll,
1030 current_line_highlight: CurrentLineHighlight,
1031 gutter_hovered: bool,
1032}
1033
1034#[derive(Default, Debug, Clone, Copy)]
1035pub struct GutterDimensions {
1036 pub left_padding: Pixels,
1037 pub right_padding: Pixels,
1038 pub width: Pixels,
1039 pub margin: Pixels,
1040 pub git_blame_entries_width: Option<Pixels>,
1041}
1042
1043impl GutterDimensions {
1044 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1045 Self {
1046 margin: Self::default_gutter_margin(font_id, font_size, cx),
1047 ..Default::default()
1048 }
1049 }
1050
1051 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1052 -cx.text_system().descent(font_id, font_size)
1053 }
1054 /// The full width of the space taken up by the gutter.
1055 pub fn full_width(&self) -> Pixels {
1056 self.margin + self.width
1057 }
1058
1059 /// The width of the space reserved for the fold indicators,
1060 /// use alongside 'justify_end' and `gutter_width` to
1061 /// right align content with the line numbers
1062 pub fn fold_area_width(&self) -> Pixels {
1063 self.margin + self.right_padding
1064 }
1065}
1066
1067#[derive(Debug)]
1068pub struct RemoteSelection {
1069 pub replica_id: ReplicaId,
1070 pub selection: Selection<Anchor>,
1071 pub cursor_shape: CursorShape,
1072 pub collaborator_id: CollaboratorId,
1073 pub line_mode: bool,
1074 pub user_name: Option<SharedString>,
1075 pub color: PlayerColor,
1076}
1077
1078#[derive(Clone, Debug)]
1079struct SelectionHistoryEntry {
1080 selections: Arc<[Selection<Anchor>]>,
1081 select_next_state: Option<SelectNextState>,
1082 select_prev_state: Option<SelectNextState>,
1083 add_selections_state: Option<AddSelectionsState>,
1084}
1085
1086enum SelectionHistoryMode {
1087 Normal,
1088 Undoing,
1089 Redoing,
1090}
1091
1092#[derive(Clone, PartialEq, Eq, Hash)]
1093struct HoveredCursor {
1094 replica_id: u16,
1095 selection_id: usize,
1096}
1097
1098impl Default for SelectionHistoryMode {
1099 fn default() -> Self {
1100 Self::Normal
1101 }
1102}
1103
1104#[derive(Default)]
1105struct SelectionHistory {
1106 #[allow(clippy::type_complexity)]
1107 selections_by_transaction:
1108 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1109 mode: SelectionHistoryMode,
1110 undo_stack: VecDeque<SelectionHistoryEntry>,
1111 redo_stack: VecDeque<SelectionHistoryEntry>,
1112}
1113
1114impl SelectionHistory {
1115 fn insert_transaction(
1116 &mut self,
1117 transaction_id: TransactionId,
1118 selections: Arc<[Selection<Anchor>]>,
1119 ) {
1120 self.selections_by_transaction
1121 .insert(transaction_id, (selections, None));
1122 }
1123
1124 #[allow(clippy::type_complexity)]
1125 fn transaction(
1126 &self,
1127 transaction_id: TransactionId,
1128 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1129 self.selections_by_transaction.get(&transaction_id)
1130 }
1131
1132 #[allow(clippy::type_complexity)]
1133 fn transaction_mut(
1134 &mut self,
1135 transaction_id: TransactionId,
1136 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1137 self.selections_by_transaction.get_mut(&transaction_id)
1138 }
1139
1140 fn push(&mut self, entry: SelectionHistoryEntry) {
1141 if !entry.selections.is_empty() {
1142 match self.mode {
1143 SelectionHistoryMode::Normal => {
1144 self.push_undo(entry);
1145 self.redo_stack.clear();
1146 }
1147 SelectionHistoryMode::Undoing => self.push_redo(entry),
1148 SelectionHistoryMode::Redoing => self.push_undo(entry),
1149 }
1150 }
1151 }
1152
1153 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1154 if self
1155 .undo_stack
1156 .back()
1157 .map_or(true, |e| e.selections != entry.selections)
1158 {
1159 self.undo_stack.push_back(entry);
1160 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1161 self.undo_stack.pop_front();
1162 }
1163 }
1164 }
1165
1166 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1167 if self
1168 .redo_stack
1169 .back()
1170 .map_or(true, |e| e.selections != entry.selections)
1171 {
1172 self.redo_stack.push_back(entry);
1173 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1174 self.redo_stack.pop_front();
1175 }
1176 }
1177 }
1178}
1179
1180#[derive(Clone, Copy)]
1181pub struct RowHighlightOptions {
1182 pub autoscroll: bool,
1183 pub include_gutter: bool,
1184}
1185
1186impl Default for RowHighlightOptions {
1187 fn default() -> Self {
1188 Self {
1189 autoscroll: Default::default(),
1190 include_gutter: true,
1191 }
1192 }
1193}
1194
1195struct RowHighlight {
1196 index: usize,
1197 range: Range<Anchor>,
1198 color: Hsla,
1199 options: RowHighlightOptions,
1200 type_id: TypeId,
1201}
1202
1203#[derive(Clone, Debug)]
1204struct AddSelectionsState {
1205 above: bool,
1206 stack: Vec<usize>,
1207}
1208
1209#[derive(Clone)]
1210struct SelectNextState {
1211 query: AhoCorasick,
1212 wordwise: bool,
1213 done: bool,
1214}
1215
1216impl std::fmt::Debug for SelectNextState {
1217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1218 f.debug_struct(std::any::type_name::<Self>())
1219 .field("wordwise", &self.wordwise)
1220 .field("done", &self.done)
1221 .finish()
1222 }
1223}
1224
1225#[derive(Debug)]
1226struct AutocloseRegion {
1227 selection_id: usize,
1228 range: Range<Anchor>,
1229 pair: BracketPair,
1230}
1231
1232#[derive(Debug)]
1233struct SnippetState {
1234 ranges: Vec<Vec<Range<Anchor>>>,
1235 active_index: usize,
1236 choices: Vec<Option<Vec<String>>>,
1237}
1238
1239#[doc(hidden)]
1240pub struct RenameState {
1241 pub range: Range<Anchor>,
1242 pub old_name: Arc<str>,
1243 pub editor: Entity<Editor>,
1244 block_id: CustomBlockId,
1245}
1246
1247struct InvalidationStack<T>(Vec<T>);
1248
1249struct RegisteredInlineCompletionProvider {
1250 provider: Arc<dyn InlineCompletionProviderHandle>,
1251 _subscription: Subscription,
1252}
1253
1254#[derive(Debug, PartialEq, Eq)]
1255pub struct ActiveDiagnosticGroup {
1256 pub active_range: Range<Anchor>,
1257 pub active_message: String,
1258 pub group_id: usize,
1259 pub blocks: HashSet<CustomBlockId>,
1260}
1261
1262#[derive(Debug, PartialEq, Eq)]
1263#[allow(clippy::large_enum_variant)]
1264pub(crate) enum ActiveDiagnostic {
1265 None,
1266 All,
1267 Group(ActiveDiagnosticGroup),
1268}
1269
1270#[derive(Serialize, Deserialize, Clone, Debug)]
1271pub struct ClipboardSelection {
1272 /// The number of bytes in this selection.
1273 pub len: usize,
1274 /// Whether this was a full-line selection.
1275 pub is_entire_line: bool,
1276 /// The indentation of the first line when this content was originally copied.
1277 pub first_line_indent: u32,
1278}
1279
1280// selections, scroll behavior, was newest selection reversed
1281type SelectSyntaxNodeHistoryState = (
1282 Box<[Selection<usize>]>,
1283 SelectSyntaxNodeScrollBehavior,
1284 bool,
1285);
1286
1287#[derive(Default)]
1288struct SelectSyntaxNodeHistory {
1289 stack: Vec<SelectSyntaxNodeHistoryState>,
1290 // disable temporarily to allow changing selections without losing the stack
1291 pub disable_clearing: bool,
1292}
1293
1294impl SelectSyntaxNodeHistory {
1295 pub fn try_clear(&mut self) {
1296 if !self.disable_clearing {
1297 self.stack.clear();
1298 }
1299 }
1300
1301 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1302 self.stack.push(selection);
1303 }
1304
1305 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1306 self.stack.pop()
1307 }
1308}
1309
1310enum SelectSyntaxNodeScrollBehavior {
1311 CursorTop,
1312 FitSelection,
1313 CursorBottom,
1314}
1315
1316#[derive(Debug)]
1317pub(crate) struct NavigationData {
1318 cursor_anchor: Anchor,
1319 cursor_position: Point,
1320 scroll_anchor: ScrollAnchor,
1321 scroll_top_row: u32,
1322}
1323
1324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1325pub enum GotoDefinitionKind {
1326 Symbol,
1327 Declaration,
1328 Type,
1329 Implementation,
1330}
1331
1332#[derive(Debug, Clone)]
1333enum InlayHintRefreshReason {
1334 ModifiersChanged(bool),
1335 Toggle(bool),
1336 SettingsChange(InlayHintSettings),
1337 NewLinesShown,
1338 BufferEdited(HashSet<Arc<Language>>),
1339 RefreshRequested,
1340 ExcerptsRemoved(Vec<ExcerptId>),
1341}
1342
1343impl InlayHintRefreshReason {
1344 fn description(&self) -> &'static str {
1345 match self {
1346 Self::ModifiersChanged(_) => "modifiers changed",
1347 Self::Toggle(_) => "toggle",
1348 Self::SettingsChange(_) => "settings change",
1349 Self::NewLinesShown => "new lines shown",
1350 Self::BufferEdited(_) => "buffer edited",
1351 Self::RefreshRequested => "refresh requested",
1352 Self::ExcerptsRemoved(_) => "excerpts removed",
1353 }
1354 }
1355}
1356
1357pub enum FormatTarget {
1358 Buffers,
1359 Ranges(Vec<Range<MultiBufferPoint>>),
1360}
1361
1362pub(crate) struct FocusedBlock {
1363 id: BlockId,
1364 focus_handle: WeakFocusHandle,
1365}
1366
1367#[derive(Clone)]
1368enum JumpData {
1369 MultiBufferRow {
1370 row: MultiBufferRow,
1371 line_offset_from_top: u32,
1372 },
1373 MultiBufferPoint {
1374 excerpt_id: ExcerptId,
1375 position: Point,
1376 anchor: text::Anchor,
1377 line_offset_from_top: u32,
1378 },
1379}
1380
1381pub enum MultibufferSelectionMode {
1382 First,
1383 All,
1384}
1385
1386#[derive(Clone, Copy, Debug, Default)]
1387pub struct RewrapOptions {
1388 pub override_language_settings: bool,
1389 pub preserve_existing_whitespace: bool,
1390}
1391
1392impl Editor {
1393 pub fn single_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(
1397 EditorMode::SingleLine { auto_width: false },
1398 buffer,
1399 None,
1400 window,
1401 cx,
1402 )
1403 }
1404
1405 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1406 let buffer = cx.new(|cx| Buffer::local("", cx));
1407 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1408 Self::new(EditorMode::full(), buffer, None, window, cx)
1409 }
1410
1411 pub fn auto_width(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::SingleLine { auto_width: true },
1416 buffer,
1417 None,
1418 window,
1419 cx,
1420 )
1421 }
1422
1423 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1424 let buffer = cx.new(|cx| Buffer::local("", cx));
1425 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1426 Self::new(
1427 EditorMode::AutoHeight { max_lines },
1428 buffer,
1429 None,
1430 window,
1431 cx,
1432 )
1433 }
1434
1435 pub fn for_buffer(
1436 buffer: Entity<Buffer>,
1437 project: Option<Entity<Project>>,
1438 window: &mut Window,
1439 cx: &mut Context<Self>,
1440 ) -> Self {
1441 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1442 Self::new(EditorMode::full(), buffer, project, window, cx)
1443 }
1444
1445 pub fn for_multibuffer(
1446 buffer: Entity<MultiBuffer>,
1447 project: Option<Entity<Project>>,
1448 window: &mut Window,
1449 cx: &mut Context<Self>,
1450 ) -> Self {
1451 Self::new(EditorMode::full(), buffer, project, window, cx)
1452 }
1453
1454 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1455 let mut clone = Self::new(
1456 self.mode,
1457 self.buffer.clone(),
1458 self.project.clone(),
1459 window,
1460 cx,
1461 );
1462 self.display_map.update(cx, |display_map, cx| {
1463 let snapshot = display_map.snapshot(cx);
1464 clone.display_map.update(cx, |display_map, cx| {
1465 display_map.set_state(&snapshot, cx);
1466 });
1467 });
1468 clone.folds_did_change(cx);
1469 clone.selections.clone_state(&self.selections);
1470 clone.scroll_manager.clone_state(&self.scroll_manager);
1471 clone.searchable = self.searchable;
1472 clone.read_only = self.read_only;
1473 clone
1474 }
1475
1476 pub fn new(
1477 mode: EditorMode,
1478 buffer: Entity<MultiBuffer>,
1479 project: Option<Entity<Project>>,
1480 window: &mut Window,
1481 cx: &mut Context<Self>,
1482 ) -> Self {
1483 let style = window.text_style();
1484 let font_size = style.font_size.to_pixels(window.rem_size());
1485 let editor = cx.entity().downgrade();
1486 let fold_placeholder = FoldPlaceholder {
1487 constrain_width: true,
1488 render: Arc::new(move |fold_id, fold_range, cx| {
1489 let editor = editor.clone();
1490 div()
1491 .id(fold_id)
1492 .bg(cx.theme().colors().ghost_element_background)
1493 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1494 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1495 .rounded_xs()
1496 .size_full()
1497 .cursor_pointer()
1498 .child("⋯")
1499 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1500 .on_click(move |_, _window, cx| {
1501 editor
1502 .update(cx, |editor, cx| {
1503 editor.unfold_ranges(
1504 &[fold_range.start..fold_range.end],
1505 true,
1506 false,
1507 cx,
1508 );
1509 cx.stop_propagation();
1510 })
1511 .ok();
1512 })
1513 .into_any()
1514 }),
1515 merge_adjacent: true,
1516 ..Default::default()
1517 };
1518 let display_map = cx.new(|cx| {
1519 DisplayMap::new(
1520 buffer.clone(),
1521 style.font(),
1522 font_size,
1523 None,
1524 FILE_HEADER_HEIGHT,
1525 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1526 fold_placeholder,
1527 cx,
1528 )
1529 });
1530
1531 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1532
1533 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1534
1535 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1536 .then(|| language_settings::SoftWrap::None);
1537
1538 let mut project_subscriptions = Vec::new();
1539 if mode.is_full() {
1540 if let Some(project) = project.as_ref() {
1541 project_subscriptions.push(cx.subscribe_in(
1542 project,
1543 window,
1544 |editor, _, event, window, cx| match event {
1545 project::Event::RefreshCodeLens => {
1546 // we always query lens with actions, without storing them, always refreshing them
1547 }
1548 project::Event::RefreshInlayHints => {
1549 editor
1550 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1551 }
1552 project::Event::SnippetEdit(id, snippet_edits) => {
1553 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1554 let focus_handle = editor.focus_handle(cx);
1555 if focus_handle.is_focused(window) {
1556 let snapshot = buffer.read(cx).snapshot();
1557 for (range, snippet) in snippet_edits {
1558 let editor_range =
1559 language::range_from_lsp(*range).to_offset(&snapshot);
1560 editor
1561 .insert_snippet(
1562 &[editor_range],
1563 snippet.clone(),
1564 window,
1565 cx,
1566 )
1567 .ok();
1568 }
1569 }
1570 }
1571 }
1572 _ => {}
1573 },
1574 ));
1575 if let Some(task_inventory) = project
1576 .read(cx)
1577 .task_store()
1578 .read(cx)
1579 .task_inventory()
1580 .cloned()
1581 {
1582 project_subscriptions.push(cx.observe_in(
1583 &task_inventory,
1584 window,
1585 |editor, _, window, cx| {
1586 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1587 },
1588 ));
1589 };
1590
1591 project_subscriptions.push(cx.subscribe_in(
1592 &project.read(cx).breakpoint_store(),
1593 window,
1594 |editor, _, event, window, cx| match event {
1595 BreakpointStoreEvent::ClearDebugLines => {
1596 editor.clear_row_highlights::<ActiveDebugLine>();
1597 editor.refresh_inline_values(cx);
1598 }
1599 BreakpointStoreEvent::SetDebugLine => {
1600 if editor.go_to_active_debug_line(window, cx) {
1601 cx.stop_propagation();
1602 }
1603
1604 editor.refresh_inline_values(cx);
1605 }
1606 _ => {}
1607 },
1608 ));
1609 }
1610 }
1611
1612 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1613
1614 let inlay_hint_settings =
1615 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1616 let focus_handle = cx.focus_handle();
1617 cx.on_focus(&focus_handle, window, Self::handle_focus)
1618 .detach();
1619 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1620 .detach();
1621 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1622 .detach();
1623 cx.on_blur(&focus_handle, window, Self::handle_blur)
1624 .detach();
1625
1626 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1627 Some(false)
1628 } else {
1629 None
1630 };
1631
1632 let breakpoint_store = match (mode, project.as_ref()) {
1633 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1634 _ => None,
1635 };
1636
1637 let mut code_action_providers = Vec::new();
1638 let mut load_uncommitted_diff = None;
1639 if let Some(project) = project.clone() {
1640 load_uncommitted_diff = Some(
1641 update_uncommitted_diff_for_buffer(
1642 cx.entity(),
1643 &project,
1644 buffer.read(cx).all_buffers(),
1645 buffer.clone(),
1646 cx,
1647 )
1648 .shared(),
1649 );
1650 code_action_providers.push(Rc::new(project) as Rc<_>);
1651 }
1652
1653 let mut this = Self {
1654 focus_handle,
1655 show_cursor_when_unfocused: false,
1656 last_focused_descendant: None,
1657 buffer: buffer.clone(),
1658 display_map: display_map.clone(),
1659 selections,
1660 scroll_manager: ScrollManager::new(cx),
1661 columnar_selection_tail: None,
1662 add_selections_state: None,
1663 select_next_state: None,
1664 select_prev_state: None,
1665 selection_history: Default::default(),
1666 autoclose_regions: Default::default(),
1667 snippet_stack: Default::default(),
1668 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1669 ime_transaction: Default::default(),
1670 active_diagnostics: ActiveDiagnostic::None,
1671 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1672 inline_diagnostics_update: Task::ready(()),
1673 inline_diagnostics: Vec::new(),
1674 soft_wrap_mode_override,
1675 hard_wrap: None,
1676 completion_provider: project.clone().map(|project| Box::new(project) as _),
1677 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1678 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1679 project,
1680 blink_manager: blink_manager.clone(),
1681 show_local_selections: true,
1682 show_scrollbars: true,
1683 mode,
1684 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1685 show_gutter: mode.is_full(),
1686 show_line_numbers: None,
1687 use_relative_line_numbers: None,
1688 disable_expand_excerpt_buttons: false,
1689 show_git_diff_gutter: None,
1690 show_code_actions: None,
1691 show_runnables: None,
1692 show_breakpoints: None,
1693 show_wrap_guides: None,
1694 show_indent_guides,
1695 placeholder_text: None,
1696 highlight_order: 0,
1697 highlighted_rows: HashMap::default(),
1698 background_highlights: Default::default(),
1699 gutter_highlights: TreeMap::default(),
1700 scrollbar_marker_state: ScrollbarMarkerState::default(),
1701 active_indent_guides_state: ActiveIndentGuidesState::default(),
1702 nav_history: None,
1703 context_menu: RefCell::new(None),
1704 context_menu_options: None,
1705 mouse_context_menu: None,
1706 completion_tasks: Default::default(),
1707 inline_blame_popover: Default::default(),
1708 signature_help_state: SignatureHelpState::default(),
1709 auto_signature_help: None,
1710 find_all_references_task_sources: Vec::new(),
1711 next_completion_id: 0,
1712 next_inlay_id: 0,
1713 code_action_providers,
1714 available_code_actions: Default::default(),
1715 code_actions_task: Default::default(),
1716 quick_selection_highlight_task: Default::default(),
1717 debounced_selection_highlight_task: Default::default(),
1718 document_highlights_task: Default::default(),
1719 linked_editing_range_task: Default::default(),
1720 pending_rename: Default::default(),
1721 searchable: true,
1722 cursor_shape: EditorSettings::get_global(cx)
1723 .cursor_shape
1724 .unwrap_or_default(),
1725 current_line_highlight: None,
1726 autoindent_mode: Some(AutoindentMode::EachLine),
1727 collapse_matches: false,
1728 workspace: None,
1729 input_enabled: true,
1730 use_modal_editing: mode.is_full(),
1731 read_only: false,
1732 use_autoclose: true,
1733 use_auto_surround: true,
1734 auto_replace_emoji_shortcode: false,
1735 jsx_tag_auto_close_enabled_in_any_buffer: false,
1736 leader_id: None,
1737 remote_id: None,
1738 hover_state: Default::default(),
1739 pending_mouse_down: None,
1740 hovered_link_state: Default::default(),
1741 edit_prediction_provider: None,
1742 active_inline_completion: None,
1743 stale_inline_completion_in_menu: None,
1744 edit_prediction_preview: EditPredictionPreview::Inactive {
1745 released_too_fast: false,
1746 },
1747 inline_diagnostics_enabled: mode.is_full(),
1748 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1749 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1750
1751 gutter_hovered: false,
1752 pixel_position_of_newest_cursor: None,
1753 last_bounds: None,
1754 last_position_map: None,
1755 expect_bounds_change: None,
1756 gutter_dimensions: GutterDimensions::default(),
1757 style: None,
1758 show_cursor_names: false,
1759 hovered_cursors: Default::default(),
1760 next_editor_action_id: EditorActionId::default(),
1761 editor_actions: Rc::default(),
1762 inline_completions_hidden_for_vim_mode: false,
1763 show_inline_completions_override: None,
1764 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1765 edit_prediction_settings: EditPredictionSettings::Disabled,
1766 edit_prediction_indent_conflict: false,
1767 edit_prediction_requires_modifier_in_indent_conflict: true,
1768 custom_context_menu: None,
1769 show_git_blame_gutter: false,
1770 show_git_blame_inline: false,
1771 show_selection_menu: None,
1772 show_git_blame_inline_delay_task: None,
1773 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1774 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1775 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1776 .session
1777 .restore_unsaved_buffers,
1778 blame: None,
1779 blame_subscription: None,
1780 tasks: Default::default(),
1781
1782 breakpoint_store,
1783 gutter_breakpoint_indicator: (None, None),
1784 _subscriptions: vec![
1785 cx.observe(&buffer, Self::on_buffer_changed),
1786 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1787 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1788 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1789 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1790 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1791 cx.observe_window_activation(window, |editor, window, cx| {
1792 let active = window.is_window_active();
1793 editor.blink_manager.update(cx, |blink_manager, cx| {
1794 if active {
1795 blink_manager.enable(cx);
1796 } else {
1797 blink_manager.disable(cx);
1798 }
1799 });
1800 }),
1801 ],
1802 tasks_update_task: None,
1803 linked_edit_ranges: Default::default(),
1804 in_project_search: false,
1805 previous_search_ranges: None,
1806 breadcrumb_header: None,
1807 focused_block: None,
1808 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1809 addons: HashMap::default(),
1810 registered_buffers: HashMap::default(),
1811 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1812 selection_mark_mode: false,
1813 toggle_fold_multiple_buffers: Task::ready(()),
1814 serialize_selections: Task::ready(()),
1815 serialize_folds: Task::ready(()),
1816 text_style_refinement: None,
1817 load_diff_task: load_uncommitted_diff,
1818 temporary_diff_override: false,
1819 mouse_cursor_hidden: false,
1820 hide_mouse_mode: EditorSettings::get_global(cx)
1821 .hide_mouse
1822 .unwrap_or_default(),
1823 change_list: ChangeList::new(),
1824 };
1825 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1826 this._subscriptions
1827 .push(cx.observe(breakpoints, |_, _, cx| {
1828 cx.notify();
1829 }));
1830 }
1831 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1832 this._subscriptions.extend(project_subscriptions);
1833
1834 this._subscriptions.push(cx.subscribe_in(
1835 &cx.entity(),
1836 window,
1837 |editor, _, e: &EditorEvent, window, cx| match e {
1838 EditorEvent::ScrollPositionChanged { local, .. } => {
1839 if *local {
1840 let new_anchor = editor.scroll_manager.anchor();
1841 let snapshot = editor.snapshot(window, cx);
1842 editor.update_restoration_data(cx, move |data| {
1843 data.scroll_position = (
1844 new_anchor.top_row(&snapshot.buffer_snapshot),
1845 new_anchor.offset,
1846 );
1847 });
1848 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1849 editor.inline_blame_popover.take();
1850 }
1851 }
1852 EditorEvent::Edited { .. } => {
1853 if !vim_enabled(cx) {
1854 let (map, selections) = editor.selections.all_adjusted_display(cx);
1855 let pop_state = editor
1856 .change_list
1857 .last()
1858 .map(|previous| {
1859 previous.len() == selections.len()
1860 && previous.iter().enumerate().all(|(ix, p)| {
1861 p.to_display_point(&map).row()
1862 == selections[ix].head().row()
1863 })
1864 })
1865 .unwrap_or(false);
1866 let new_positions = selections
1867 .into_iter()
1868 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1869 .collect();
1870 editor
1871 .change_list
1872 .push_to_change_list(pop_state, new_positions);
1873 }
1874 }
1875 _ => (),
1876 },
1877 ));
1878
1879 if let Some(dap_store) = this
1880 .project
1881 .as_ref()
1882 .map(|project| project.read(cx).dap_store())
1883 {
1884 let weak_editor = cx.weak_entity();
1885
1886 this._subscriptions
1887 .push(
1888 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1889 let session_entity = cx.entity();
1890 weak_editor
1891 .update(cx, |editor, cx| {
1892 editor._subscriptions.push(
1893 cx.subscribe(&session_entity, Self::on_debug_session_event),
1894 );
1895 })
1896 .ok();
1897 }),
1898 );
1899
1900 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1901 this._subscriptions
1902 .push(cx.subscribe(&session, Self::on_debug_session_event));
1903 }
1904 }
1905
1906 this.end_selection(window, cx);
1907 this.scroll_manager.show_scrollbars(window, cx);
1908 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1909
1910 if mode.is_full() {
1911 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1912 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1913
1914 if this.git_blame_inline_enabled {
1915 this.git_blame_inline_enabled = true;
1916 this.start_git_blame_inline(false, window, cx);
1917 }
1918
1919 this.go_to_active_debug_line(window, cx);
1920
1921 if let Some(buffer) = buffer.read(cx).as_singleton() {
1922 if let Some(project) = this.project.as_ref() {
1923 let handle = project.update(cx, |project, cx| {
1924 project.register_buffer_with_language_servers(&buffer, cx)
1925 });
1926 this.registered_buffers
1927 .insert(buffer.read(cx).remote_id(), handle);
1928 }
1929 }
1930 }
1931
1932 this.report_editor_event("Editor Opened", None, cx);
1933 this
1934 }
1935
1936 pub fn deploy_mouse_context_menu(
1937 &mut self,
1938 position: gpui::Point<Pixels>,
1939 context_menu: Entity<ContextMenu>,
1940 window: &mut Window,
1941 cx: &mut Context<Self>,
1942 ) {
1943 self.mouse_context_menu = Some(MouseContextMenu::new(
1944 self,
1945 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1946 context_menu,
1947 window,
1948 cx,
1949 ));
1950 }
1951
1952 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1953 self.mouse_context_menu
1954 .as_ref()
1955 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1956 }
1957
1958 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1959 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1960 }
1961
1962 fn key_context_internal(
1963 &self,
1964 has_active_edit_prediction: bool,
1965 window: &Window,
1966 cx: &App,
1967 ) -> KeyContext {
1968 let mut key_context = KeyContext::new_with_defaults();
1969 key_context.add("Editor");
1970 let mode = match self.mode {
1971 EditorMode::SingleLine { .. } => "single_line",
1972 EditorMode::AutoHeight { .. } => "auto_height",
1973 EditorMode::Full { .. } => "full",
1974 };
1975
1976 if EditorSettings::jupyter_enabled(cx) {
1977 key_context.add("jupyter");
1978 }
1979
1980 key_context.set("mode", mode);
1981 if self.pending_rename.is_some() {
1982 key_context.add("renaming");
1983 }
1984
1985 match self.context_menu.borrow().as_ref() {
1986 Some(CodeContextMenu::Completions(_)) => {
1987 key_context.add("menu");
1988 key_context.add("showing_completions");
1989 }
1990 Some(CodeContextMenu::CodeActions(_)) => {
1991 key_context.add("menu");
1992 key_context.add("showing_code_actions")
1993 }
1994 None => {}
1995 }
1996
1997 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1998 if !self.focus_handle(cx).contains_focused(window, cx)
1999 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2000 {
2001 for addon in self.addons.values() {
2002 addon.extend_key_context(&mut key_context, cx)
2003 }
2004 }
2005
2006 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2007 if let Some(extension) = singleton_buffer
2008 .read(cx)
2009 .file()
2010 .and_then(|file| file.path().extension()?.to_str())
2011 {
2012 key_context.set("extension", extension.to_string());
2013 }
2014 } else {
2015 key_context.add("multibuffer");
2016 }
2017
2018 if has_active_edit_prediction {
2019 if self.edit_prediction_in_conflict() {
2020 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2021 } else {
2022 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2023 key_context.add("copilot_suggestion");
2024 }
2025 }
2026
2027 if self.selection_mark_mode {
2028 key_context.add("selection_mode");
2029 }
2030
2031 key_context
2032 }
2033
2034 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2035 self.mouse_cursor_hidden = match origin {
2036 HideMouseCursorOrigin::TypingAction => {
2037 matches!(
2038 self.hide_mouse_mode,
2039 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2040 )
2041 }
2042 HideMouseCursorOrigin::MovementAction => {
2043 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2044 }
2045 };
2046 }
2047
2048 pub fn edit_prediction_in_conflict(&self) -> bool {
2049 if !self.show_edit_predictions_in_menu() {
2050 return false;
2051 }
2052
2053 let showing_completions = self
2054 .context_menu
2055 .borrow()
2056 .as_ref()
2057 .map_or(false, |context| {
2058 matches!(context, CodeContextMenu::Completions(_))
2059 });
2060
2061 showing_completions
2062 || self.edit_prediction_requires_modifier()
2063 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2064 // bindings to insert tab characters.
2065 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2066 }
2067
2068 pub fn accept_edit_prediction_keybind(
2069 &self,
2070 window: &Window,
2071 cx: &App,
2072 ) -> AcceptEditPredictionBinding {
2073 let key_context = self.key_context_internal(true, window, cx);
2074 let in_conflict = self.edit_prediction_in_conflict();
2075
2076 AcceptEditPredictionBinding(
2077 window
2078 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2079 .into_iter()
2080 .filter(|binding| {
2081 !in_conflict
2082 || binding
2083 .keystrokes()
2084 .first()
2085 .map_or(false, |keystroke| keystroke.modifiers.modified())
2086 })
2087 .rev()
2088 .min_by_key(|binding| {
2089 binding
2090 .keystrokes()
2091 .first()
2092 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2093 }),
2094 )
2095 }
2096
2097 pub fn new_file(
2098 workspace: &mut Workspace,
2099 _: &workspace::NewFile,
2100 window: &mut Window,
2101 cx: &mut Context<Workspace>,
2102 ) {
2103 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2104 "Failed to create buffer",
2105 window,
2106 cx,
2107 |e, _, _| match e.error_code() {
2108 ErrorCode::RemoteUpgradeRequired => Some(format!(
2109 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2110 e.error_tag("required").unwrap_or("the latest version")
2111 )),
2112 _ => None,
2113 },
2114 );
2115 }
2116
2117 pub fn new_in_workspace(
2118 workspace: &mut Workspace,
2119 window: &mut Window,
2120 cx: &mut Context<Workspace>,
2121 ) -> Task<Result<Entity<Editor>>> {
2122 let project = workspace.project().clone();
2123 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2124
2125 cx.spawn_in(window, async move |workspace, cx| {
2126 let buffer = create.await?;
2127 workspace.update_in(cx, |workspace, window, cx| {
2128 let editor =
2129 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2130 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2131 editor
2132 })
2133 })
2134 }
2135
2136 fn new_file_vertical(
2137 workspace: &mut Workspace,
2138 _: &workspace::NewFileSplitVertical,
2139 window: &mut Window,
2140 cx: &mut Context<Workspace>,
2141 ) {
2142 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2143 }
2144
2145 fn new_file_horizontal(
2146 workspace: &mut Workspace,
2147 _: &workspace::NewFileSplitHorizontal,
2148 window: &mut Window,
2149 cx: &mut Context<Workspace>,
2150 ) {
2151 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2152 }
2153
2154 fn new_file_in_direction(
2155 workspace: &mut Workspace,
2156 direction: SplitDirection,
2157 window: &mut Window,
2158 cx: &mut Context<Workspace>,
2159 ) {
2160 let project = workspace.project().clone();
2161 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2162
2163 cx.spawn_in(window, async move |workspace, cx| {
2164 let buffer = create.await?;
2165 workspace.update_in(cx, move |workspace, window, cx| {
2166 workspace.split_item(
2167 direction,
2168 Box::new(
2169 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2170 ),
2171 window,
2172 cx,
2173 )
2174 })?;
2175 anyhow::Ok(())
2176 })
2177 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2178 match e.error_code() {
2179 ErrorCode::RemoteUpgradeRequired => Some(format!(
2180 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2181 e.error_tag("required").unwrap_or("the latest version")
2182 )),
2183 _ => None,
2184 }
2185 });
2186 }
2187
2188 pub fn leader_id(&self) -> Option<CollaboratorId> {
2189 self.leader_id
2190 }
2191
2192 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2193 &self.buffer
2194 }
2195
2196 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2197 self.workspace.as_ref()?.0.upgrade()
2198 }
2199
2200 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2201 self.buffer().read(cx).title(cx)
2202 }
2203
2204 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2205 let git_blame_gutter_max_author_length = self
2206 .render_git_blame_gutter(cx)
2207 .then(|| {
2208 if let Some(blame) = self.blame.as_ref() {
2209 let max_author_length =
2210 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2211 Some(max_author_length)
2212 } else {
2213 None
2214 }
2215 })
2216 .flatten();
2217
2218 EditorSnapshot {
2219 mode: self.mode,
2220 show_gutter: self.show_gutter,
2221 show_line_numbers: self.show_line_numbers,
2222 show_git_diff_gutter: self.show_git_diff_gutter,
2223 show_code_actions: self.show_code_actions,
2224 show_runnables: self.show_runnables,
2225 show_breakpoints: self.show_breakpoints,
2226 git_blame_gutter_max_author_length,
2227 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2228 scroll_anchor: self.scroll_manager.anchor(),
2229 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2230 placeholder_text: self.placeholder_text.clone(),
2231 is_focused: self.focus_handle.is_focused(window),
2232 current_line_highlight: self
2233 .current_line_highlight
2234 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2235 gutter_hovered: self.gutter_hovered,
2236 }
2237 }
2238
2239 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2240 self.buffer.read(cx).language_at(point, cx)
2241 }
2242
2243 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2244 self.buffer.read(cx).read(cx).file_at(point).cloned()
2245 }
2246
2247 pub fn active_excerpt(
2248 &self,
2249 cx: &App,
2250 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2251 self.buffer
2252 .read(cx)
2253 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2254 }
2255
2256 pub fn mode(&self) -> EditorMode {
2257 self.mode
2258 }
2259
2260 pub fn set_mode(&mut self, mode: EditorMode) {
2261 self.mode = mode;
2262 }
2263
2264 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2265 self.collaboration_hub.as_deref()
2266 }
2267
2268 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2269 self.collaboration_hub = Some(hub);
2270 }
2271
2272 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2273 self.in_project_search = in_project_search;
2274 }
2275
2276 pub fn set_custom_context_menu(
2277 &mut self,
2278 f: impl 'static
2279 + Fn(
2280 &mut Self,
2281 DisplayPoint,
2282 &mut Window,
2283 &mut Context<Self>,
2284 ) -> Option<Entity<ui::ContextMenu>>,
2285 ) {
2286 self.custom_context_menu = Some(Box::new(f))
2287 }
2288
2289 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2290 self.completion_provider = provider;
2291 }
2292
2293 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2294 self.semantics_provider.clone()
2295 }
2296
2297 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2298 self.semantics_provider = provider;
2299 }
2300
2301 pub fn set_edit_prediction_provider<T>(
2302 &mut self,
2303 provider: Option<Entity<T>>,
2304 window: &mut Window,
2305 cx: &mut Context<Self>,
2306 ) where
2307 T: EditPredictionProvider,
2308 {
2309 self.edit_prediction_provider =
2310 provider.map(|provider| RegisteredInlineCompletionProvider {
2311 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2312 if this.focus_handle.is_focused(window) {
2313 this.update_visible_inline_completion(window, cx);
2314 }
2315 }),
2316 provider: Arc::new(provider),
2317 });
2318 self.update_edit_prediction_settings(cx);
2319 self.refresh_inline_completion(false, false, window, cx);
2320 }
2321
2322 pub fn placeholder_text(&self) -> Option<&str> {
2323 self.placeholder_text.as_deref()
2324 }
2325
2326 pub fn set_placeholder_text(
2327 &mut self,
2328 placeholder_text: impl Into<Arc<str>>,
2329 cx: &mut Context<Self>,
2330 ) {
2331 let placeholder_text = Some(placeholder_text.into());
2332 if self.placeholder_text != placeholder_text {
2333 self.placeholder_text = placeholder_text;
2334 cx.notify();
2335 }
2336 }
2337
2338 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2339 self.cursor_shape = cursor_shape;
2340
2341 // Disrupt blink for immediate user feedback that the cursor shape has changed
2342 self.blink_manager.update(cx, BlinkManager::show_cursor);
2343
2344 cx.notify();
2345 }
2346
2347 pub fn set_current_line_highlight(
2348 &mut self,
2349 current_line_highlight: Option<CurrentLineHighlight>,
2350 ) {
2351 self.current_line_highlight = current_line_highlight;
2352 }
2353
2354 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2355 self.collapse_matches = collapse_matches;
2356 }
2357
2358 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2359 let buffers = self.buffer.read(cx).all_buffers();
2360 let Some(project) = self.project.as_ref() else {
2361 return;
2362 };
2363 project.update(cx, |project, cx| {
2364 for buffer in buffers {
2365 self.registered_buffers
2366 .entry(buffer.read(cx).remote_id())
2367 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2368 }
2369 })
2370 }
2371
2372 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2373 if self.collapse_matches {
2374 return range.start..range.start;
2375 }
2376 range.clone()
2377 }
2378
2379 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2380 if self.display_map.read(cx).clip_at_line_ends != clip {
2381 self.display_map
2382 .update(cx, |map, _| map.clip_at_line_ends = clip);
2383 }
2384 }
2385
2386 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2387 self.input_enabled = input_enabled;
2388 }
2389
2390 pub fn set_inline_completions_hidden_for_vim_mode(
2391 &mut self,
2392 hidden: bool,
2393 window: &mut Window,
2394 cx: &mut Context<Self>,
2395 ) {
2396 if hidden != self.inline_completions_hidden_for_vim_mode {
2397 self.inline_completions_hidden_for_vim_mode = hidden;
2398 if hidden {
2399 self.update_visible_inline_completion(window, cx);
2400 } else {
2401 self.refresh_inline_completion(true, false, window, cx);
2402 }
2403 }
2404 }
2405
2406 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2407 self.menu_inline_completions_policy = value;
2408 }
2409
2410 pub fn set_autoindent(&mut self, autoindent: bool) {
2411 if autoindent {
2412 self.autoindent_mode = Some(AutoindentMode::EachLine);
2413 } else {
2414 self.autoindent_mode = None;
2415 }
2416 }
2417
2418 pub fn read_only(&self, cx: &App) -> bool {
2419 self.read_only || self.buffer.read(cx).read_only()
2420 }
2421
2422 pub fn set_read_only(&mut self, read_only: bool) {
2423 self.read_only = read_only;
2424 }
2425
2426 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2427 self.use_autoclose = autoclose;
2428 }
2429
2430 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2431 self.use_auto_surround = auto_surround;
2432 }
2433
2434 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2435 self.auto_replace_emoji_shortcode = auto_replace;
2436 }
2437
2438 pub fn toggle_edit_predictions(
2439 &mut self,
2440 _: &ToggleEditPrediction,
2441 window: &mut Window,
2442 cx: &mut Context<Self>,
2443 ) {
2444 if self.show_inline_completions_override.is_some() {
2445 self.set_show_edit_predictions(None, window, cx);
2446 } else {
2447 let show_edit_predictions = !self.edit_predictions_enabled();
2448 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2449 }
2450 }
2451
2452 pub fn set_show_edit_predictions(
2453 &mut self,
2454 show_edit_predictions: Option<bool>,
2455 window: &mut Window,
2456 cx: &mut Context<Self>,
2457 ) {
2458 self.show_inline_completions_override = show_edit_predictions;
2459 self.update_edit_prediction_settings(cx);
2460
2461 if let Some(false) = show_edit_predictions {
2462 self.discard_inline_completion(false, cx);
2463 } else {
2464 self.refresh_inline_completion(false, true, window, cx);
2465 }
2466 }
2467
2468 fn inline_completions_disabled_in_scope(
2469 &self,
2470 buffer: &Entity<Buffer>,
2471 buffer_position: language::Anchor,
2472 cx: &App,
2473 ) -> bool {
2474 let snapshot = buffer.read(cx).snapshot();
2475 let settings = snapshot.settings_at(buffer_position, cx);
2476
2477 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2478 return false;
2479 };
2480
2481 scope.override_name().map_or(false, |scope_name| {
2482 settings
2483 .edit_predictions_disabled_in
2484 .iter()
2485 .any(|s| s == scope_name)
2486 })
2487 }
2488
2489 pub fn set_use_modal_editing(&mut self, to: bool) {
2490 self.use_modal_editing = to;
2491 }
2492
2493 pub fn use_modal_editing(&self) -> bool {
2494 self.use_modal_editing
2495 }
2496
2497 fn selections_did_change(
2498 &mut self,
2499 local: bool,
2500 old_cursor_position: &Anchor,
2501 show_completions: bool,
2502 window: &mut Window,
2503 cx: &mut Context<Self>,
2504 ) {
2505 window.invalidate_character_coordinates();
2506
2507 // Copy selections to primary selection buffer
2508 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2509 if local {
2510 let selections = self.selections.all::<usize>(cx);
2511 let buffer_handle = self.buffer.read(cx).read(cx);
2512
2513 let mut text = String::new();
2514 for (index, selection) in selections.iter().enumerate() {
2515 let text_for_selection = buffer_handle
2516 .text_for_range(selection.start..selection.end)
2517 .collect::<String>();
2518
2519 text.push_str(&text_for_selection);
2520 if index != selections.len() - 1 {
2521 text.push('\n');
2522 }
2523 }
2524
2525 if !text.is_empty() {
2526 cx.write_to_primary(ClipboardItem::new_string(text));
2527 }
2528 }
2529
2530 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2531 self.buffer.update(cx, |buffer, cx| {
2532 buffer.set_active_selections(
2533 &self.selections.disjoint_anchors(),
2534 self.selections.line_mode,
2535 self.cursor_shape,
2536 cx,
2537 )
2538 });
2539 }
2540 let display_map = self
2541 .display_map
2542 .update(cx, |display_map, cx| display_map.snapshot(cx));
2543 let buffer = &display_map.buffer_snapshot;
2544 self.add_selections_state = None;
2545 self.select_next_state = None;
2546 self.select_prev_state = None;
2547 self.select_syntax_node_history.try_clear();
2548 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2549 self.snippet_stack
2550 .invalidate(&self.selections.disjoint_anchors(), buffer);
2551 self.take_rename(false, window, cx);
2552
2553 let new_cursor_position = self.selections.newest_anchor().head();
2554
2555 self.push_to_nav_history(
2556 *old_cursor_position,
2557 Some(new_cursor_position.to_point(buffer)),
2558 false,
2559 cx,
2560 );
2561
2562 if local {
2563 let new_cursor_position = self.selections.newest_anchor().head();
2564 let mut context_menu = self.context_menu.borrow_mut();
2565 let completion_menu = match context_menu.as_ref() {
2566 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2567 _ => {
2568 *context_menu = None;
2569 None
2570 }
2571 };
2572 if let Some(buffer_id) = new_cursor_position.buffer_id {
2573 if !self.registered_buffers.contains_key(&buffer_id) {
2574 if let Some(project) = self.project.as_ref() {
2575 project.update(cx, |project, cx| {
2576 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2577 return;
2578 };
2579 self.registered_buffers.insert(
2580 buffer_id,
2581 project.register_buffer_with_language_servers(&buffer, cx),
2582 );
2583 })
2584 }
2585 }
2586 }
2587
2588 if let Some(completion_menu) = completion_menu {
2589 let cursor_position = new_cursor_position.to_offset(buffer);
2590 let (word_range, kind) =
2591 buffer.surrounding_word(completion_menu.initial_position, true);
2592 if kind == Some(CharKind::Word)
2593 && word_range.to_inclusive().contains(&cursor_position)
2594 {
2595 let mut completion_menu = completion_menu.clone();
2596 drop(context_menu);
2597
2598 let query = Self::completion_query(buffer, cursor_position);
2599 cx.spawn(async move |this, cx| {
2600 completion_menu
2601 .filter(query.as_deref(), cx.background_executor().clone())
2602 .await;
2603
2604 this.update(cx, |this, cx| {
2605 let mut context_menu = this.context_menu.borrow_mut();
2606 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2607 else {
2608 return;
2609 };
2610
2611 if menu.id > completion_menu.id {
2612 return;
2613 }
2614
2615 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2616 drop(context_menu);
2617 cx.notify();
2618 })
2619 })
2620 .detach();
2621
2622 if show_completions {
2623 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2624 }
2625 } else {
2626 drop(context_menu);
2627 self.hide_context_menu(window, cx);
2628 }
2629 } else {
2630 drop(context_menu);
2631 }
2632
2633 hide_hover(self, cx);
2634
2635 if old_cursor_position.to_display_point(&display_map).row()
2636 != new_cursor_position.to_display_point(&display_map).row()
2637 {
2638 self.available_code_actions.take();
2639 }
2640 self.refresh_code_actions(window, cx);
2641 self.refresh_document_highlights(cx);
2642 self.refresh_selected_text_highlights(false, window, cx);
2643 refresh_matching_bracket_highlights(self, window, cx);
2644 self.update_visible_inline_completion(window, cx);
2645 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2646 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2647 self.inline_blame_popover.take();
2648 if self.git_blame_inline_enabled {
2649 self.start_inline_blame_timer(window, cx);
2650 }
2651 }
2652
2653 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2654 cx.emit(EditorEvent::SelectionsChanged { local });
2655
2656 let selections = &self.selections.disjoint;
2657 if selections.len() == 1 {
2658 cx.emit(SearchEvent::ActiveMatchChanged)
2659 }
2660 if local {
2661 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2662 let inmemory_selections = selections
2663 .iter()
2664 .map(|s| {
2665 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2666 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2667 })
2668 .collect();
2669 self.update_restoration_data(cx, |data| {
2670 data.selections = inmemory_selections;
2671 });
2672
2673 if WorkspaceSettings::get(None, cx).restore_on_startup
2674 != RestoreOnStartupBehavior::None
2675 {
2676 if let Some(workspace_id) =
2677 self.workspace.as_ref().and_then(|workspace| workspace.1)
2678 {
2679 let snapshot = self.buffer().read(cx).snapshot(cx);
2680 let selections = selections.clone();
2681 let background_executor = cx.background_executor().clone();
2682 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2683 self.serialize_selections = cx.background_spawn(async move {
2684 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2685 let db_selections = selections
2686 .iter()
2687 .map(|selection| {
2688 (
2689 selection.start.to_offset(&snapshot),
2690 selection.end.to_offset(&snapshot),
2691 )
2692 })
2693 .collect();
2694
2695 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2696 .await
2697 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2698 .log_err();
2699 });
2700 }
2701 }
2702 }
2703 }
2704
2705 cx.notify();
2706 }
2707
2708 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2709 use text::ToOffset as _;
2710 use text::ToPoint as _;
2711
2712 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2713 return;
2714 }
2715
2716 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2717 return;
2718 };
2719
2720 let snapshot = singleton.read(cx).snapshot();
2721 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2722 let display_snapshot = display_map.snapshot(cx);
2723
2724 display_snapshot
2725 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2726 .map(|fold| {
2727 fold.range.start.text_anchor.to_point(&snapshot)
2728 ..fold.range.end.text_anchor.to_point(&snapshot)
2729 })
2730 .collect()
2731 });
2732 self.update_restoration_data(cx, |data| {
2733 data.folds = inmemory_folds;
2734 });
2735
2736 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2737 return;
2738 };
2739 let background_executor = cx.background_executor().clone();
2740 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2741 let db_folds = self.display_map.update(cx, |display_map, cx| {
2742 display_map
2743 .snapshot(cx)
2744 .folds_in_range(0..snapshot.len())
2745 .map(|fold| {
2746 (
2747 fold.range.start.text_anchor.to_offset(&snapshot),
2748 fold.range.end.text_anchor.to_offset(&snapshot),
2749 )
2750 })
2751 .collect()
2752 });
2753 self.serialize_folds = cx.background_spawn(async move {
2754 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2755 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2756 .await
2757 .with_context(|| {
2758 format!(
2759 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2760 )
2761 })
2762 .log_err();
2763 });
2764 }
2765
2766 pub fn sync_selections(
2767 &mut self,
2768 other: Entity<Editor>,
2769 cx: &mut Context<Self>,
2770 ) -> gpui::Subscription {
2771 let other_selections = other.read(cx).selections.disjoint.to_vec();
2772 self.selections.change_with(cx, |selections| {
2773 selections.select_anchors(other_selections);
2774 });
2775
2776 let other_subscription =
2777 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2778 EditorEvent::SelectionsChanged { local: true } => {
2779 let other_selections = other.read(cx).selections.disjoint.to_vec();
2780 if other_selections.is_empty() {
2781 return;
2782 }
2783 this.selections.change_with(cx, |selections| {
2784 selections.select_anchors(other_selections);
2785 });
2786 }
2787 _ => {}
2788 });
2789
2790 let this_subscription =
2791 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2792 EditorEvent::SelectionsChanged { local: true } => {
2793 let these_selections = this.selections.disjoint.to_vec();
2794 if these_selections.is_empty() {
2795 return;
2796 }
2797 other.update(cx, |other_editor, cx| {
2798 other_editor.selections.change_with(cx, |selections| {
2799 selections.select_anchors(these_selections);
2800 })
2801 });
2802 }
2803 _ => {}
2804 });
2805
2806 Subscription::join(other_subscription, this_subscription)
2807 }
2808
2809 pub fn change_selections<R>(
2810 &mut self,
2811 autoscroll: Option<Autoscroll>,
2812 window: &mut Window,
2813 cx: &mut Context<Self>,
2814 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2815 ) -> R {
2816 self.change_selections_inner(autoscroll, true, window, cx, change)
2817 }
2818
2819 fn change_selections_inner<R>(
2820 &mut self,
2821 autoscroll: Option<Autoscroll>,
2822 request_completions: bool,
2823 window: &mut Window,
2824 cx: &mut Context<Self>,
2825 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2826 ) -> R {
2827 let old_cursor_position = self.selections.newest_anchor().head();
2828 self.push_to_selection_history();
2829
2830 let (changed, result) = self.selections.change_with(cx, change);
2831
2832 if changed {
2833 if let Some(autoscroll) = autoscroll {
2834 self.request_autoscroll(autoscroll, cx);
2835 }
2836 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2837
2838 if self.should_open_signature_help_automatically(
2839 &old_cursor_position,
2840 self.signature_help_state.backspace_pressed(),
2841 cx,
2842 ) {
2843 self.show_signature_help(&ShowSignatureHelp, window, cx);
2844 }
2845 self.signature_help_state.set_backspace_pressed(false);
2846 }
2847
2848 result
2849 }
2850
2851 pub fn edit<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
2862 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2863 }
2864
2865 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2866 where
2867 I: IntoIterator<Item = (Range<S>, T)>,
2868 S: ToOffset,
2869 T: Into<Arc<str>>,
2870 {
2871 if self.read_only(cx) {
2872 return;
2873 }
2874
2875 self.buffer.update(cx, |buffer, cx| {
2876 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2877 });
2878 }
2879
2880 pub fn edit_with_block_indent<I, S, T>(
2881 &mut self,
2882 edits: I,
2883 original_indent_columns: Vec<Option<u32>>,
2884 cx: &mut Context<Self>,
2885 ) where
2886 I: IntoIterator<Item = (Range<S>, T)>,
2887 S: ToOffset,
2888 T: Into<Arc<str>>,
2889 {
2890 if self.read_only(cx) {
2891 return;
2892 }
2893
2894 self.buffer.update(cx, |buffer, cx| {
2895 buffer.edit(
2896 edits,
2897 Some(AutoindentMode::Block {
2898 original_indent_columns,
2899 }),
2900 cx,
2901 )
2902 });
2903 }
2904
2905 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2906 self.hide_context_menu(window, cx);
2907
2908 match phase {
2909 SelectPhase::Begin {
2910 position,
2911 add,
2912 click_count,
2913 } => self.begin_selection(position, add, click_count, window, cx),
2914 SelectPhase::BeginColumnar {
2915 position,
2916 goal_column,
2917 reset,
2918 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2919 SelectPhase::Extend {
2920 position,
2921 click_count,
2922 } => self.extend_selection(position, click_count, window, cx),
2923 SelectPhase::Update {
2924 position,
2925 goal_column,
2926 scroll_delta,
2927 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2928 SelectPhase::End => self.end_selection(window, cx),
2929 }
2930 }
2931
2932 fn extend_selection(
2933 &mut self,
2934 position: DisplayPoint,
2935 click_count: usize,
2936 window: &mut Window,
2937 cx: &mut Context<Self>,
2938 ) {
2939 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2940 let tail = self.selections.newest::<usize>(cx).tail();
2941 self.begin_selection(position, false, click_count, window, cx);
2942
2943 let position = position.to_offset(&display_map, Bias::Left);
2944 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2945
2946 let mut pending_selection = self
2947 .selections
2948 .pending_anchor()
2949 .expect("extend_selection not called with pending selection");
2950 if position >= tail {
2951 pending_selection.start = tail_anchor;
2952 } else {
2953 pending_selection.end = tail_anchor;
2954 pending_selection.reversed = true;
2955 }
2956
2957 let mut pending_mode = self.selections.pending_mode().unwrap();
2958 match &mut pending_mode {
2959 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2960 _ => {}
2961 }
2962
2963 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2964 s.set_pending(pending_selection, pending_mode)
2965 });
2966 }
2967
2968 fn begin_selection(
2969 &mut self,
2970 position: DisplayPoint,
2971 add: bool,
2972 click_count: usize,
2973 window: &mut Window,
2974 cx: &mut Context<Self>,
2975 ) {
2976 if !self.focus_handle.is_focused(window) {
2977 self.last_focused_descendant = None;
2978 window.focus(&self.focus_handle);
2979 }
2980
2981 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2982 let buffer = &display_map.buffer_snapshot;
2983 let newest_selection = self.selections.newest_anchor().clone();
2984 let position = display_map.clip_point(position, Bias::Left);
2985
2986 let start;
2987 let end;
2988 let mode;
2989 let mut auto_scroll;
2990 match click_count {
2991 1 => {
2992 start = buffer.anchor_before(position.to_point(&display_map));
2993 end = start;
2994 mode = SelectMode::Character;
2995 auto_scroll = true;
2996 }
2997 2 => {
2998 let range = movement::surrounding_word(&display_map, position);
2999 start = buffer.anchor_before(range.start.to_point(&display_map));
3000 end = buffer.anchor_before(range.end.to_point(&display_map));
3001 mode = SelectMode::Word(start..end);
3002 auto_scroll = true;
3003 }
3004 3 => {
3005 let position = display_map
3006 .clip_point(position, Bias::Left)
3007 .to_point(&display_map);
3008 let line_start = display_map.prev_line_boundary(position).0;
3009 let next_line_start = buffer.clip_point(
3010 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3011 Bias::Left,
3012 );
3013 start = buffer.anchor_before(line_start);
3014 end = buffer.anchor_before(next_line_start);
3015 mode = SelectMode::Line(start..end);
3016 auto_scroll = true;
3017 }
3018 _ => {
3019 start = buffer.anchor_before(0);
3020 end = buffer.anchor_before(buffer.len());
3021 mode = SelectMode::All;
3022 auto_scroll = false;
3023 }
3024 }
3025 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3026
3027 let point_to_delete: Option<usize> = {
3028 let selected_points: Vec<Selection<Point>> =
3029 self.selections.disjoint_in_range(start..end, cx);
3030
3031 if !add || click_count > 1 {
3032 None
3033 } else if !selected_points.is_empty() {
3034 Some(selected_points[0].id)
3035 } else {
3036 let clicked_point_already_selected =
3037 self.selections.disjoint.iter().find(|selection| {
3038 selection.start.to_point(buffer) == start.to_point(buffer)
3039 || selection.end.to_point(buffer) == end.to_point(buffer)
3040 });
3041
3042 clicked_point_already_selected.map(|selection| selection.id)
3043 }
3044 };
3045
3046 let selections_count = self.selections.count();
3047
3048 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3049 if let Some(point_to_delete) = point_to_delete {
3050 s.delete(point_to_delete);
3051
3052 if selections_count == 1 {
3053 s.set_pending_anchor_range(start..end, mode);
3054 }
3055 } else {
3056 if !add {
3057 s.clear_disjoint();
3058 } else if click_count > 1 {
3059 s.delete(newest_selection.id)
3060 }
3061
3062 s.set_pending_anchor_range(start..end, mode);
3063 }
3064 });
3065 }
3066
3067 fn begin_columnar_selection(
3068 &mut self,
3069 position: DisplayPoint,
3070 goal_column: u32,
3071 reset: bool,
3072 window: &mut Window,
3073 cx: &mut Context<Self>,
3074 ) {
3075 if !self.focus_handle.is_focused(window) {
3076 self.last_focused_descendant = None;
3077 window.focus(&self.focus_handle);
3078 }
3079
3080 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3081
3082 if reset {
3083 let pointer_position = display_map
3084 .buffer_snapshot
3085 .anchor_before(position.to_point(&display_map));
3086
3087 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3088 s.clear_disjoint();
3089 s.set_pending_anchor_range(
3090 pointer_position..pointer_position,
3091 SelectMode::Character,
3092 );
3093 });
3094 }
3095
3096 let tail = self.selections.newest::<Point>(cx).tail();
3097 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3098
3099 if !reset {
3100 self.select_columns(
3101 tail.to_display_point(&display_map),
3102 position,
3103 goal_column,
3104 &display_map,
3105 window,
3106 cx,
3107 );
3108 }
3109 }
3110
3111 fn update_selection(
3112 &mut self,
3113 position: DisplayPoint,
3114 goal_column: u32,
3115 scroll_delta: gpui::Point<f32>,
3116 window: &mut Window,
3117 cx: &mut Context<Self>,
3118 ) {
3119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3120
3121 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3122 let tail = tail.to_display_point(&display_map);
3123 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3124 } else if let Some(mut pending) = self.selections.pending_anchor() {
3125 let buffer = self.buffer.read(cx).snapshot(cx);
3126 let head;
3127 let tail;
3128 let mode = self.selections.pending_mode().unwrap();
3129 match &mode {
3130 SelectMode::Character => {
3131 head = position.to_point(&display_map);
3132 tail = pending.tail().to_point(&buffer);
3133 }
3134 SelectMode::Word(original_range) => {
3135 let original_display_range = original_range.start.to_display_point(&display_map)
3136 ..original_range.end.to_display_point(&display_map);
3137 let original_buffer_range = original_display_range.start.to_point(&display_map)
3138 ..original_display_range.end.to_point(&display_map);
3139 if movement::is_inside_word(&display_map, position)
3140 || original_display_range.contains(&position)
3141 {
3142 let word_range = movement::surrounding_word(&display_map, position);
3143 if word_range.start < original_display_range.start {
3144 head = word_range.start.to_point(&display_map);
3145 } else {
3146 head = word_range.end.to_point(&display_map);
3147 }
3148 } else {
3149 head = position.to_point(&display_map);
3150 }
3151
3152 if head <= original_buffer_range.start {
3153 tail = original_buffer_range.end;
3154 } else {
3155 tail = original_buffer_range.start;
3156 }
3157 }
3158 SelectMode::Line(original_range) => {
3159 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3160
3161 let position = display_map
3162 .clip_point(position, Bias::Left)
3163 .to_point(&display_map);
3164 let line_start = display_map.prev_line_boundary(position).0;
3165 let next_line_start = buffer.clip_point(
3166 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3167 Bias::Left,
3168 );
3169
3170 if line_start < original_range.start {
3171 head = line_start
3172 } else {
3173 head = next_line_start
3174 }
3175
3176 if head <= original_range.start {
3177 tail = original_range.end;
3178 } else {
3179 tail = original_range.start;
3180 }
3181 }
3182 SelectMode::All => {
3183 return;
3184 }
3185 };
3186
3187 if head < tail {
3188 pending.start = buffer.anchor_before(head);
3189 pending.end = buffer.anchor_before(tail);
3190 pending.reversed = true;
3191 } else {
3192 pending.start = buffer.anchor_before(tail);
3193 pending.end = buffer.anchor_before(head);
3194 pending.reversed = false;
3195 }
3196
3197 self.change_selections(None, window, cx, |s| {
3198 s.set_pending(pending, mode);
3199 });
3200 } else {
3201 log::error!("update_selection dispatched with no pending selection");
3202 return;
3203 }
3204
3205 self.apply_scroll_delta(scroll_delta, window, cx);
3206 cx.notify();
3207 }
3208
3209 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3210 self.columnar_selection_tail.take();
3211 if self.selections.pending_anchor().is_some() {
3212 let selections = self.selections.all::<usize>(cx);
3213 self.change_selections(None, window, cx, |s| {
3214 s.select(selections);
3215 s.clear_pending();
3216 });
3217 }
3218 }
3219
3220 fn select_columns(
3221 &mut self,
3222 tail: DisplayPoint,
3223 head: DisplayPoint,
3224 goal_column: u32,
3225 display_map: &DisplaySnapshot,
3226 window: &mut Window,
3227 cx: &mut Context<Self>,
3228 ) {
3229 let start_row = cmp::min(tail.row(), head.row());
3230 let end_row = cmp::max(tail.row(), head.row());
3231 let start_column = cmp::min(tail.column(), goal_column);
3232 let end_column = cmp::max(tail.column(), goal_column);
3233 let reversed = start_column < tail.column();
3234
3235 let selection_ranges = (start_row.0..=end_row.0)
3236 .map(DisplayRow)
3237 .filter_map(|row| {
3238 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3239 let start = display_map
3240 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3241 .to_point(display_map);
3242 let end = display_map
3243 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3244 .to_point(display_map);
3245 if reversed {
3246 Some(end..start)
3247 } else {
3248 Some(start..end)
3249 }
3250 } else {
3251 None
3252 }
3253 })
3254 .collect::<Vec<_>>();
3255
3256 self.change_selections(None, window, cx, |s| {
3257 s.select_ranges(selection_ranges);
3258 });
3259 cx.notify();
3260 }
3261
3262 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3263 self.selections
3264 .all_adjusted(cx)
3265 .iter()
3266 .any(|selection| !selection.is_empty())
3267 }
3268
3269 pub fn has_pending_nonempty_selection(&self) -> bool {
3270 let pending_nonempty_selection = match self.selections.pending_anchor() {
3271 Some(Selection { start, end, .. }) => start != end,
3272 None => false,
3273 };
3274
3275 pending_nonempty_selection
3276 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3277 }
3278
3279 pub fn has_pending_selection(&self) -> bool {
3280 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3281 }
3282
3283 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3284 self.selection_mark_mode = false;
3285
3286 if self.clear_expanded_diff_hunks(cx) {
3287 cx.notify();
3288 return;
3289 }
3290 if self.dismiss_menus_and_popups(true, window, cx) {
3291 return;
3292 }
3293
3294 if self.mode.is_full()
3295 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3296 {
3297 return;
3298 }
3299
3300 cx.propagate();
3301 }
3302
3303 pub fn dismiss_menus_and_popups(
3304 &mut self,
3305 is_user_requested: bool,
3306 window: &mut Window,
3307 cx: &mut Context<Self>,
3308 ) -> bool {
3309 if self.take_rename(false, window, cx).is_some() {
3310 return true;
3311 }
3312
3313 if hide_hover(self, cx) {
3314 return true;
3315 }
3316
3317 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3318 return true;
3319 }
3320
3321 if self.hide_context_menu(window, cx).is_some() {
3322 return true;
3323 }
3324
3325 if self.mouse_context_menu.take().is_some() {
3326 return true;
3327 }
3328
3329 if is_user_requested && self.discard_inline_completion(true, cx) {
3330 return true;
3331 }
3332
3333 if self.snippet_stack.pop().is_some() {
3334 return true;
3335 }
3336
3337 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3338 self.dismiss_diagnostics(cx);
3339 return true;
3340 }
3341
3342 false
3343 }
3344
3345 fn linked_editing_ranges_for(
3346 &self,
3347 selection: Range<text::Anchor>,
3348 cx: &App,
3349 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3350 if self.linked_edit_ranges.is_empty() {
3351 return None;
3352 }
3353 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3354 selection.end.buffer_id.and_then(|end_buffer_id| {
3355 if selection.start.buffer_id != Some(end_buffer_id) {
3356 return None;
3357 }
3358 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3359 let snapshot = buffer.read(cx).snapshot();
3360 self.linked_edit_ranges
3361 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3362 .map(|ranges| (ranges, snapshot, buffer))
3363 })?;
3364 use text::ToOffset as TO;
3365 // find offset from the start of current range to current cursor position
3366 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3367
3368 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3369 let start_difference = start_offset - start_byte_offset;
3370 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3371 let end_difference = end_offset - start_byte_offset;
3372 // Current range has associated linked ranges.
3373 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3374 for range in linked_ranges.iter() {
3375 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3376 let end_offset = start_offset + end_difference;
3377 let start_offset = start_offset + start_difference;
3378 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3379 continue;
3380 }
3381 if self.selections.disjoint_anchor_ranges().any(|s| {
3382 if s.start.buffer_id != selection.start.buffer_id
3383 || s.end.buffer_id != selection.end.buffer_id
3384 {
3385 return false;
3386 }
3387 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3388 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3389 }) {
3390 continue;
3391 }
3392 let start = buffer_snapshot.anchor_after(start_offset);
3393 let end = buffer_snapshot.anchor_after(end_offset);
3394 linked_edits
3395 .entry(buffer.clone())
3396 .or_default()
3397 .push(start..end);
3398 }
3399 Some(linked_edits)
3400 }
3401
3402 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3403 let text: Arc<str> = text.into();
3404
3405 if self.read_only(cx) {
3406 return;
3407 }
3408
3409 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3410
3411 let selections = self.selections.all_adjusted(cx);
3412 let mut bracket_inserted = false;
3413 let mut edits = Vec::new();
3414 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3415 let mut new_selections = Vec::with_capacity(selections.len());
3416 let mut new_autoclose_regions = Vec::new();
3417 let snapshot = self.buffer.read(cx).read(cx);
3418 let mut clear_linked_edit_ranges = false;
3419
3420 for (selection, autoclose_region) in
3421 self.selections_with_autoclose_regions(selections, &snapshot)
3422 {
3423 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3424 // Determine if the inserted text matches the opening or closing
3425 // bracket of any of this language's bracket pairs.
3426 let mut bracket_pair = None;
3427 let mut is_bracket_pair_start = false;
3428 let mut is_bracket_pair_end = false;
3429 if !text.is_empty() {
3430 let mut bracket_pair_matching_end = None;
3431 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3432 // and they are removing the character that triggered IME popup.
3433 for (pair, enabled) in scope.brackets() {
3434 if !pair.close && !pair.surround {
3435 continue;
3436 }
3437
3438 if enabled && pair.start.ends_with(text.as_ref()) {
3439 let prefix_len = pair.start.len() - text.len();
3440 let preceding_text_matches_prefix = prefix_len == 0
3441 || (selection.start.column >= (prefix_len as u32)
3442 && snapshot.contains_str_at(
3443 Point::new(
3444 selection.start.row,
3445 selection.start.column - (prefix_len as u32),
3446 ),
3447 &pair.start[..prefix_len],
3448 ));
3449 if preceding_text_matches_prefix {
3450 bracket_pair = Some(pair.clone());
3451 is_bracket_pair_start = true;
3452 break;
3453 }
3454 }
3455 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3456 {
3457 // take first bracket pair matching end, but don't break in case a later bracket
3458 // pair matches start
3459 bracket_pair_matching_end = Some(pair.clone());
3460 }
3461 }
3462 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3463 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3464 is_bracket_pair_end = true;
3465 }
3466 }
3467
3468 if let Some(bracket_pair) = bracket_pair {
3469 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3470 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3471 let auto_surround =
3472 self.use_auto_surround && snapshot_settings.use_auto_surround;
3473 if selection.is_empty() {
3474 if is_bracket_pair_start {
3475 // If the inserted text is a suffix of an opening bracket and the
3476 // selection is preceded by the rest of the opening bracket, then
3477 // insert the closing bracket.
3478 let following_text_allows_autoclose = snapshot
3479 .chars_at(selection.start)
3480 .next()
3481 .map_or(true, |c| scope.should_autoclose_before(c));
3482
3483 let preceding_text_allows_autoclose = selection.start.column == 0
3484 || snapshot.reversed_chars_at(selection.start).next().map_or(
3485 true,
3486 |c| {
3487 bracket_pair.start != bracket_pair.end
3488 || !snapshot
3489 .char_classifier_at(selection.start)
3490 .is_word(c)
3491 },
3492 );
3493
3494 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3495 && bracket_pair.start.len() == 1
3496 {
3497 let target = bracket_pair.start.chars().next().unwrap();
3498 let current_line_count = snapshot
3499 .reversed_chars_at(selection.start)
3500 .take_while(|&c| c != '\n')
3501 .filter(|&c| c == target)
3502 .count();
3503 current_line_count % 2 == 1
3504 } else {
3505 false
3506 };
3507
3508 if autoclose
3509 && bracket_pair.close
3510 && following_text_allows_autoclose
3511 && preceding_text_allows_autoclose
3512 && !is_closing_quote
3513 {
3514 let anchor = snapshot.anchor_before(selection.end);
3515 new_selections.push((selection.map(|_| anchor), text.len()));
3516 new_autoclose_regions.push((
3517 anchor,
3518 text.len(),
3519 selection.id,
3520 bracket_pair.clone(),
3521 ));
3522 edits.push((
3523 selection.range(),
3524 format!("{}{}", text, bracket_pair.end).into(),
3525 ));
3526 bracket_inserted = true;
3527 continue;
3528 }
3529 }
3530
3531 if let Some(region) = autoclose_region {
3532 // If the selection is followed by an auto-inserted closing bracket,
3533 // then don't insert that closing bracket again; just move the selection
3534 // past the closing bracket.
3535 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3536 && text.as_ref() == region.pair.end.as_str();
3537 if should_skip {
3538 let anchor = snapshot.anchor_after(selection.end);
3539 new_selections
3540 .push((selection.map(|_| anchor), region.pair.end.len()));
3541 continue;
3542 }
3543 }
3544
3545 let always_treat_brackets_as_autoclosed = snapshot
3546 .language_settings_at(selection.start, cx)
3547 .always_treat_brackets_as_autoclosed;
3548 if always_treat_brackets_as_autoclosed
3549 && is_bracket_pair_end
3550 && snapshot.contains_str_at(selection.end, text.as_ref())
3551 {
3552 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3553 // and the inserted text is a closing bracket and the selection is followed
3554 // by the closing bracket then move the selection past the closing bracket.
3555 let anchor = snapshot.anchor_after(selection.end);
3556 new_selections.push((selection.map(|_| anchor), text.len()));
3557 continue;
3558 }
3559 }
3560 // If an opening bracket is 1 character long and is typed while
3561 // text is selected, then surround that text with the bracket pair.
3562 else if auto_surround
3563 && bracket_pair.surround
3564 && is_bracket_pair_start
3565 && bracket_pair.start.chars().count() == 1
3566 {
3567 edits.push((selection.start..selection.start, text.clone()));
3568 edits.push((
3569 selection.end..selection.end,
3570 bracket_pair.end.as_str().into(),
3571 ));
3572 bracket_inserted = true;
3573 new_selections.push((
3574 Selection {
3575 id: selection.id,
3576 start: snapshot.anchor_after(selection.start),
3577 end: snapshot.anchor_before(selection.end),
3578 reversed: selection.reversed,
3579 goal: selection.goal,
3580 },
3581 0,
3582 ));
3583 continue;
3584 }
3585 }
3586 }
3587
3588 if self.auto_replace_emoji_shortcode
3589 && selection.is_empty()
3590 && text.as_ref().ends_with(':')
3591 {
3592 if let Some(possible_emoji_short_code) =
3593 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3594 {
3595 if !possible_emoji_short_code.is_empty() {
3596 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3597 let emoji_shortcode_start = Point::new(
3598 selection.start.row,
3599 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3600 );
3601
3602 // Remove shortcode from buffer
3603 edits.push((
3604 emoji_shortcode_start..selection.start,
3605 "".to_string().into(),
3606 ));
3607 new_selections.push((
3608 Selection {
3609 id: selection.id,
3610 start: snapshot.anchor_after(emoji_shortcode_start),
3611 end: snapshot.anchor_before(selection.start),
3612 reversed: selection.reversed,
3613 goal: selection.goal,
3614 },
3615 0,
3616 ));
3617
3618 // Insert emoji
3619 let selection_start_anchor = snapshot.anchor_after(selection.start);
3620 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3621 edits.push((selection.start..selection.end, emoji.to_string().into()));
3622
3623 continue;
3624 }
3625 }
3626 }
3627 }
3628
3629 // If not handling any auto-close operation, then just replace the selected
3630 // text with the given input and move the selection to the end of the
3631 // newly inserted text.
3632 let anchor = snapshot.anchor_after(selection.end);
3633 if !self.linked_edit_ranges.is_empty() {
3634 let start_anchor = snapshot.anchor_before(selection.start);
3635
3636 let is_word_char = text.chars().next().map_or(true, |char| {
3637 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3638 classifier.is_word(char)
3639 });
3640
3641 if is_word_char {
3642 if let Some(ranges) = self
3643 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3644 {
3645 for (buffer, edits) in ranges {
3646 linked_edits
3647 .entry(buffer.clone())
3648 .or_default()
3649 .extend(edits.into_iter().map(|range| (range, text.clone())));
3650 }
3651 }
3652 } else {
3653 clear_linked_edit_ranges = true;
3654 }
3655 }
3656
3657 new_selections.push((selection.map(|_| anchor), 0));
3658 edits.push((selection.start..selection.end, text.clone()));
3659 }
3660
3661 drop(snapshot);
3662
3663 self.transact(window, cx, |this, window, cx| {
3664 if clear_linked_edit_ranges {
3665 this.linked_edit_ranges.clear();
3666 }
3667 let initial_buffer_versions =
3668 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3669
3670 this.buffer.update(cx, |buffer, cx| {
3671 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3672 });
3673 for (buffer, edits) in linked_edits {
3674 buffer.update(cx, |buffer, cx| {
3675 let snapshot = buffer.snapshot();
3676 let edits = edits
3677 .into_iter()
3678 .map(|(range, text)| {
3679 use text::ToPoint as TP;
3680 let end_point = TP::to_point(&range.end, &snapshot);
3681 let start_point = TP::to_point(&range.start, &snapshot);
3682 (start_point..end_point, text)
3683 })
3684 .sorted_by_key(|(range, _)| range.start);
3685 buffer.edit(edits, None, cx);
3686 })
3687 }
3688 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3689 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3690 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3691 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3692 .zip(new_selection_deltas)
3693 .map(|(selection, delta)| Selection {
3694 id: selection.id,
3695 start: selection.start + delta,
3696 end: selection.end + delta,
3697 reversed: selection.reversed,
3698 goal: SelectionGoal::None,
3699 })
3700 .collect::<Vec<_>>();
3701
3702 let mut i = 0;
3703 for (position, delta, selection_id, pair) in new_autoclose_regions {
3704 let position = position.to_offset(&map.buffer_snapshot) + delta;
3705 let start = map.buffer_snapshot.anchor_before(position);
3706 let end = map.buffer_snapshot.anchor_after(position);
3707 while let Some(existing_state) = this.autoclose_regions.get(i) {
3708 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3709 Ordering::Less => i += 1,
3710 Ordering::Greater => break,
3711 Ordering::Equal => {
3712 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3713 Ordering::Less => i += 1,
3714 Ordering::Equal => break,
3715 Ordering::Greater => break,
3716 }
3717 }
3718 }
3719 }
3720 this.autoclose_regions.insert(
3721 i,
3722 AutocloseRegion {
3723 selection_id,
3724 range: start..end,
3725 pair,
3726 },
3727 );
3728 }
3729
3730 let had_active_inline_completion = this.has_active_inline_completion();
3731 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3732 s.select(new_selections)
3733 });
3734
3735 if !bracket_inserted {
3736 if let Some(on_type_format_task) =
3737 this.trigger_on_type_formatting(text.to_string(), window, cx)
3738 {
3739 on_type_format_task.detach_and_log_err(cx);
3740 }
3741 }
3742
3743 let editor_settings = EditorSettings::get_global(cx);
3744 if bracket_inserted
3745 && (editor_settings.auto_signature_help
3746 || editor_settings.show_signature_help_after_edits)
3747 {
3748 this.show_signature_help(&ShowSignatureHelp, window, cx);
3749 }
3750
3751 let trigger_in_words =
3752 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3753 if this.hard_wrap.is_some() {
3754 let latest: Range<Point> = this.selections.newest(cx).range();
3755 if latest.is_empty()
3756 && this
3757 .buffer()
3758 .read(cx)
3759 .snapshot(cx)
3760 .line_len(MultiBufferRow(latest.start.row))
3761 == latest.start.column
3762 {
3763 this.rewrap_impl(
3764 RewrapOptions {
3765 override_language_settings: true,
3766 preserve_existing_whitespace: true,
3767 },
3768 cx,
3769 )
3770 }
3771 }
3772 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3773 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3774 this.refresh_inline_completion(true, false, window, cx);
3775 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3776 });
3777 }
3778
3779 fn find_possible_emoji_shortcode_at_position(
3780 snapshot: &MultiBufferSnapshot,
3781 position: Point,
3782 ) -> Option<String> {
3783 let mut chars = Vec::new();
3784 let mut found_colon = false;
3785 for char in snapshot.reversed_chars_at(position).take(100) {
3786 // Found a possible emoji shortcode in the middle of the buffer
3787 if found_colon {
3788 if char.is_whitespace() {
3789 chars.reverse();
3790 return Some(chars.iter().collect());
3791 }
3792 // If the previous character is not a whitespace, we are in the middle of a word
3793 // and we only want to complete the shortcode if the word is made up of other emojis
3794 let mut containing_word = String::new();
3795 for ch in snapshot
3796 .reversed_chars_at(position)
3797 .skip(chars.len() + 1)
3798 .take(100)
3799 {
3800 if ch.is_whitespace() {
3801 break;
3802 }
3803 containing_word.push(ch);
3804 }
3805 let containing_word = containing_word.chars().rev().collect::<String>();
3806 if util::word_consists_of_emojis(containing_word.as_str()) {
3807 chars.reverse();
3808 return Some(chars.iter().collect());
3809 }
3810 }
3811
3812 if char.is_whitespace() || !char.is_ascii() {
3813 return None;
3814 }
3815 if char == ':' {
3816 found_colon = true;
3817 } else {
3818 chars.push(char);
3819 }
3820 }
3821 // Found a possible emoji shortcode at the beginning of the buffer
3822 chars.reverse();
3823 Some(chars.iter().collect())
3824 }
3825
3826 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3827 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3828 self.transact(window, cx, |this, window, cx| {
3829 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3830 let selections = this.selections.all::<usize>(cx);
3831 let multi_buffer = this.buffer.read(cx);
3832 let buffer = multi_buffer.snapshot(cx);
3833 selections
3834 .iter()
3835 .map(|selection| {
3836 let start_point = selection.start.to_point(&buffer);
3837 let mut indent =
3838 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3839 indent.len = cmp::min(indent.len, start_point.column);
3840 let start = selection.start;
3841 let end = selection.end;
3842 let selection_is_empty = start == end;
3843 let language_scope = buffer.language_scope_at(start);
3844 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3845 &language_scope
3846 {
3847 let insert_extra_newline =
3848 insert_extra_newline_brackets(&buffer, start..end, language)
3849 || insert_extra_newline_tree_sitter(&buffer, start..end);
3850
3851 // Comment extension on newline is allowed only for cursor selections
3852 let comment_delimiter = maybe!({
3853 if !selection_is_empty {
3854 return None;
3855 }
3856
3857 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3858 return None;
3859 }
3860
3861 let delimiters = language.line_comment_prefixes();
3862 let max_len_of_delimiter =
3863 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3864 let (snapshot, range) =
3865 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3866
3867 let mut index_of_first_non_whitespace = 0;
3868 let comment_candidate = snapshot
3869 .chars_for_range(range)
3870 .skip_while(|c| {
3871 let should_skip = c.is_whitespace();
3872 if should_skip {
3873 index_of_first_non_whitespace += 1;
3874 }
3875 should_skip
3876 })
3877 .take(max_len_of_delimiter)
3878 .collect::<String>();
3879 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3880 comment_candidate.starts_with(comment_prefix.as_ref())
3881 })?;
3882 let cursor_is_placed_after_comment_marker =
3883 index_of_first_non_whitespace + comment_prefix.len()
3884 <= start_point.column as usize;
3885 if cursor_is_placed_after_comment_marker {
3886 Some(comment_prefix.clone())
3887 } else {
3888 None
3889 }
3890 });
3891 (comment_delimiter, insert_extra_newline)
3892 } else {
3893 (None, false)
3894 };
3895
3896 let capacity_for_delimiter = comment_delimiter
3897 .as_deref()
3898 .map(str::len)
3899 .unwrap_or_default();
3900 let mut new_text =
3901 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3902 new_text.push('\n');
3903 new_text.extend(indent.chars());
3904 if let Some(delimiter) = &comment_delimiter {
3905 new_text.push_str(delimiter);
3906 }
3907 if insert_extra_newline {
3908 new_text = new_text.repeat(2);
3909 }
3910
3911 let anchor = buffer.anchor_after(end);
3912 let new_selection = selection.map(|_| anchor);
3913 (
3914 (start..end, new_text),
3915 (insert_extra_newline, new_selection),
3916 )
3917 })
3918 .unzip()
3919 };
3920
3921 this.edit_with_autoindent(edits, cx);
3922 let buffer = this.buffer.read(cx).snapshot(cx);
3923 let new_selections = selection_fixup_info
3924 .into_iter()
3925 .map(|(extra_newline_inserted, new_selection)| {
3926 let mut cursor = new_selection.end.to_point(&buffer);
3927 if extra_newline_inserted {
3928 cursor.row -= 1;
3929 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3930 }
3931 new_selection.map(|_| cursor)
3932 })
3933 .collect();
3934
3935 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3936 s.select(new_selections)
3937 });
3938 this.refresh_inline_completion(true, false, window, cx);
3939 });
3940 }
3941
3942 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3943 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3944
3945 let buffer = self.buffer.read(cx);
3946 let snapshot = buffer.snapshot(cx);
3947
3948 let mut edits = Vec::new();
3949 let mut rows = Vec::new();
3950
3951 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3952 let cursor = selection.head();
3953 let row = cursor.row;
3954
3955 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3956
3957 let newline = "\n".to_string();
3958 edits.push((start_of_line..start_of_line, newline));
3959
3960 rows.push(row + rows_inserted as u32);
3961 }
3962
3963 self.transact(window, cx, |editor, window, cx| {
3964 editor.edit(edits, cx);
3965
3966 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3967 let mut index = 0;
3968 s.move_cursors_with(|map, _, _| {
3969 let row = rows[index];
3970 index += 1;
3971
3972 let point = Point::new(row, 0);
3973 let boundary = map.next_line_boundary(point).1;
3974 let clipped = map.clip_point(boundary, Bias::Left);
3975
3976 (clipped, SelectionGoal::None)
3977 });
3978 });
3979
3980 let mut indent_edits = Vec::new();
3981 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3982 for row in rows {
3983 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3984 for (row, indent) in indents {
3985 if indent.len == 0 {
3986 continue;
3987 }
3988
3989 let text = match indent.kind {
3990 IndentKind::Space => " ".repeat(indent.len as usize),
3991 IndentKind::Tab => "\t".repeat(indent.len as usize),
3992 };
3993 let point = Point::new(row.0, 0);
3994 indent_edits.push((point..point, text));
3995 }
3996 }
3997 editor.edit(indent_edits, cx);
3998 });
3999 }
4000
4001 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4002 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4003
4004 let buffer = self.buffer.read(cx);
4005 let snapshot = buffer.snapshot(cx);
4006
4007 let mut edits = Vec::new();
4008 let mut rows = Vec::new();
4009 let mut rows_inserted = 0;
4010
4011 for selection in self.selections.all_adjusted(cx) {
4012 let cursor = selection.head();
4013 let row = cursor.row;
4014
4015 let point = Point::new(row + 1, 0);
4016 let start_of_line = snapshot.clip_point(point, Bias::Left);
4017
4018 let newline = "\n".to_string();
4019 edits.push((start_of_line..start_of_line, newline));
4020
4021 rows_inserted += 1;
4022 rows.push(row + rows_inserted);
4023 }
4024
4025 self.transact(window, cx, |editor, window, cx| {
4026 editor.edit(edits, cx);
4027
4028 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4029 let mut index = 0;
4030 s.move_cursors_with(|map, _, _| {
4031 let row = rows[index];
4032 index += 1;
4033
4034 let point = Point::new(row, 0);
4035 let boundary = map.next_line_boundary(point).1;
4036 let clipped = map.clip_point(boundary, Bias::Left);
4037
4038 (clipped, SelectionGoal::None)
4039 });
4040 });
4041
4042 let mut indent_edits = Vec::new();
4043 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4044 for row in rows {
4045 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4046 for (row, indent) in indents {
4047 if indent.len == 0 {
4048 continue;
4049 }
4050
4051 let text = match indent.kind {
4052 IndentKind::Space => " ".repeat(indent.len as usize),
4053 IndentKind::Tab => "\t".repeat(indent.len as usize),
4054 };
4055 let point = Point::new(row.0, 0);
4056 indent_edits.push((point..point, text));
4057 }
4058 }
4059 editor.edit(indent_edits, cx);
4060 });
4061 }
4062
4063 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4064 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4065 original_indent_columns: Vec::new(),
4066 });
4067 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4068 }
4069
4070 fn insert_with_autoindent_mode(
4071 &mut self,
4072 text: &str,
4073 autoindent_mode: Option<AutoindentMode>,
4074 window: &mut Window,
4075 cx: &mut Context<Self>,
4076 ) {
4077 if self.read_only(cx) {
4078 return;
4079 }
4080
4081 let text: Arc<str> = text.into();
4082 self.transact(window, cx, |this, window, cx| {
4083 let old_selections = this.selections.all_adjusted(cx);
4084 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4085 let anchors = {
4086 let snapshot = buffer.read(cx);
4087 old_selections
4088 .iter()
4089 .map(|s| {
4090 let anchor = snapshot.anchor_after(s.head());
4091 s.map(|_| anchor)
4092 })
4093 .collect::<Vec<_>>()
4094 };
4095 buffer.edit(
4096 old_selections
4097 .iter()
4098 .map(|s| (s.start..s.end, text.clone())),
4099 autoindent_mode,
4100 cx,
4101 );
4102 anchors
4103 });
4104
4105 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4106 s.select_anchors(selection_anchors);
4107 });
4108
4109 cx.notify();
4110 });
4111 }
4112
4113 fn trigger_completion_on_input(
4114 &mut self,
4115 text: &str,
4116 trigger_in_words: bool,
4117 window: &mut Window,
4118 cx: &mut Context<Self>,
4119 ) {
4120 let ignore_completion_provider = self
4121 .context_menu
4122 .borrow()
4123 .as_ref()
4124 .map(|menu| match menu {
4125 CodeContextMenu::Completions(completions_menu) => {
4126 completions_menu.ignore_completion_provider
4127 }
4128 CodeContextMenu::CodeActions(_) => false,
4129 })
4130 .unwrap_or(false);
4131
4132 if ignore_completion_provider {
4133 self.show_word_completions(&ShowWordCompletions, window, cx);
4134 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4135 self.show_completions(
4136 &ShowCompletions {
4137 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4138 },
4139 window,
4140 cx,
4141 );
4142 } else {
4143 self.hide_context_menu(window, cx);
4144 }
4145 }
4146
4147 fn is_completion_trigger(
4148 &self,
4149 text: &str,
4150 trigger_in_words: bool,
4151 cx: &mut Context<Self>,
4152 ) -> bool {
4153 let position = self.selections.newest_anchor().head();
4154 let multibuffer = self.buffer.read(cx);
4155 let Some(buffer) = position
4156 .buffer_id
4157 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4158 else {
4159 return false;
4160 };
4161
4162 if let Some(completion_provider) = &self.completion_provider {
4163 completion_provider.is_completion_trigger(
4164 &buffer,
4165 position.text_anchor,
4166 text,
4167 trigger_in_words,
4168 cx,
4169 )
4170 } else {
4171 false
4172 }
4173 }
4174
4175 /// If any empty selections is touching the start of its innermost containing autoclose
4176 /// region, expand it to select the brackets.
4177 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4178 let selections = self.selections.all::<usize>(cx);
4179 let buffer = self.buffer.read(cx).read(cx);
4180 let new_selections = self
4181 .selections_with_autoclose_regions(selections, &buffer)
4182 .map(|(mut selection, region)| {
4183 if !selection.is_empty() {
4184 return selection;
4185 }
4186
4187 if let Some(region) = region {
4188 let mut range = region.range.to_offset(&buffer);
4189 if selection.start == range.start && range.start >= region.pair.start.len() {
4190 range.start -= region.pair.start.len();
4191 if buffer.contains_str_at(range.start, ®ion.pair.start)
4192 && buffer.contains_str_at(range.end, ®ion.pair.end)
4193 {
4194 range.end += region.pair.end.len();
4195 selection.start = range.start;
4196 selection.end = range.end;
4197
4198 return selection;
4199 }
4200 }
4201 }
4202
4203 let always_treat_brackets_as_autoclosed = buffer
4204 .language_settings_at(selection.start, cx)
4205 .always_treat_brackets_as_autoclosed;
4206
4207 if !always_treat_brackets_as_autoclosed {
4208 return selection;
4209 }
4210
4211 if let Some(scope) = buffer.language_scope_at(selection.start) {
4212 for (pair, enabled) in scope.brackets() {
4213 if !enabled || !pair.close {
4214 continue;
4215 }
4216
4217 if buffer.contains_str_at(selection.start, &pair.end) {
4218 let pair_start_len = pair.start.len();
4219 if buffer.contains_str_at(
4220 selection.start.saturating_sub(pair_start_len),
4221 &pair.start,
4222 ) {
4223 selection.start -= pair_start_len;
4224 selection.end += pair.end.len();
4225
4226 return selection;
4227 }
4228 }
4229 }
4230 }
4231
4232 selection
4233 })
4234 .collect();
4235
4236 drop(buffer);
4237 self.change_selections(None, window, cx, |selections| {
4238 selections.select(new_selections)
4239 });
4240 }
4241
4242 /// Iterate the given selections, and for each one, find the smallest surrounding
4243 /// autoclose region. This uses the ordering of the selections and the autoclose
4244 /// regions to avoid repeated comparisons.
4245 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4246 &'a self,
4247 selections: impl IntoIterator<Item = Selection<D>>,
4248 buffer: &'a MultiBufferSnapshot,
4249 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4250 let mut i = 0;
4251 let mut regions = self.autoclose_regions.as_slice();
4252 selections.into_iter().map(move |selection| {
4253 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4254
4255 let mut enclosing = None;
4256 while let Some(pair_state) = regions.get(i) {
4257 if pair_state.range.end.to_offset(buffer) < range.start {
4258 regions = ®ions[i + 1..];
4259 i = 0;
4260 } else if pair_state.range.start.to_offset(buffer) > range.end {
4261 break;
4262 } else {
4263 if pair_state.selection_id == selection.id {
4264 enclosing = Some(pair_state);
4265 }
4266 i += 1;
4267 }
4268 }
4269
4270 (selection, enclosing)
4271 })
4272 }
4273
4274 /// Remove any autoclose regions that no longer contain their selection.
4275 fn invalidate_autoclose_regions(
4276 &mut self,
4277 mut selections: &[Selection<Anchor>],
4278 buffer: &MultiBufferSnapshot,
4279 ) {
4280 self.autoclose_regions.retain(|state| {
4281 let mut i = 0;
4282 while let Some(selection) = selections.get(i) {
4283 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4284 selections = &selections[1..];
4285 continue;
4286 }
4287 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4288 break;
4289 }
4290 if selection.id == state.selection_id {
4291 return true;
4292 } else {
4293 i += 1;
4294 }
4295 }
4296 false
4297 });
4298 }
4299
4300 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4301 let offset = position.to_offset(buffer);
4302 let (word_range, kind) = buffer.surrounding_word(offset, true);
4303 if offset > word_range.start && kind == Some(CharKind::Word) {
4304 Some(
4305 buffer
4306 .text_for_range(word_range.start..offset)
4307 .collect::<String>(),
4308 )
4309 } else {
4310 None
4311 }
4312 }
4313
4314 pub fn toggle_inline_values(
4315 &mut self,
4316 _: &ToggleInlineValues,
4317 _: &mut Window,
4318 cx: &mut Context<Self>,
4319 ) {
4320 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4321
4322 self.refresh_inline_values(cx);
4323 }
4324
4325 pub fn toggle_inlay_hints(
4326 &mut self,
4327 _: &ToggleInlayHints,
4328 _: &mut Window,
4329 cx: &mut Context<Self>,
4330 ) {
4331 self.refresh_inlay_hints(
4332 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4333 cx,
4334 );
4335 }
4336
4337 pub fn inlay_hints_enabled(&self) -> bool {
4338 self.inlay_hint_cache.enabled
4339 }
4340
4341 pub fn inline_values_enabled(&self) -> bool {
4342 self.inline_value_cache.enabled
4343 }
4344
4345 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4346 if self.semantics_provider.is_none() || !self.mode.is_full() {
4347 return;
4348 }
4349
4350 let reason_description = reason.description();
4351 let ignore_debounce = matches!(
4352 reason,
4353 InlayHintRefreshReason::SettingsChange(_)
4354 | InlayHintRefreshReason::Toggle(_)
4355 | InlayHintRefreshReason::ExcerptsRemoved(_)
4356 | InlayHintRefreshReason::ModifiersChanged(_)
4357 );
4358 let (invalidate_cache, required_languages) = match reason {
4359 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4360 match self.inlay_hint_cache.modifiers_override(enabled) {
4361 Some(enabled) => {
4362 if enabled {
4363 (InvalidationStrategy::RefreshRequested, None)
4364 } else {
4365 self.splice_inlays(
4366 &self
4367 .visible_inlay_hints(cx)
4368 .iter()
4369 .map(|inlay| inlay.id)
4370 .collect::<Vec<InlayId>>(),
4371 Vec::new(),
4372 cx,
4373 );
4374 return;
4375 }
4376 }
4377 None => return,
4378 }
4379 }
4380 InlayHintRefreshReason::Toggle(enabled) => {
4381 if self.inlay_hint_cache.toggle(enabled) {
4382 if enabled {
4383 (InvalidationStrategy::RefreshRequested, None)
4384 } else {
4385 self.splice_inlays(
4386 &self
4387 .visible_inlay_hints(cx)
4388 .iter()
4389 .map(|inlay| inlay.id)
4390 .collect::<Vec<InlayId>>(),
4391 Vec::new(),
4392 cx,
4393 );
4394 return;
4395 }
4396 } else {
4397 return;
4398 }
4399 }
4400 InlayHintRefreshReason::SettingsChange(new_settings) => {
4401 match self.inlay_hint_cache.update_settings(
4402 &self.buffer,
4403 new_settings,
4404 self.visible_inlay_hints(cx),
4405 cx,
4406 ) {
4407 ControlFlow::Break(Some(InlaySplice {
4408 to_remove,
4409 to_insert,
4410 })) => {
4411 self.splice_inlays(&to_remove, to_insert, cx);
4412 return;
4413 }
4414 ControlFlow::Break(None) => return,
4415 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4416 }
4417 }
4418 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4419 if let Some(InlaySplice {
4420 to_remove,
4421 to_insert,
4422 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4423 {
4424 self.splice_inlays(&to_remove, to_insert, cx);
4425 }
4426 self.display_map.update(cx, |display_map, _| {
4427 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4428 });
4429 return;
4430 }
4431 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4432 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4433 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4434 }
4435 InlayHintRefreshReason::RefreshRequested => {
4436 (InvalidationStrategy::RefreshRequested, None)
4437 }
4438 };
4439
4440 if let Some(InlaySplice {
4441 to_remove,
4442 to_insert,
4443 }) = self.inlay_hint_cache.spawn_hint_refresh(
4444 reason_description,
4445 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4446 invalidate_cache,
4447 ignore_debounce,
4448 cx,
4449 ) {
4450 self.splice_inlays(&to_remove, to_insert, cx);
4451 }
4452 }
4453
4454 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4455 self.display_map
4456 .read(cx)
4457 .current_inlays()
4458 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4459 .cloned()
4460 .collect()
4461 }
4462
4463 pub fn excerpts_for_inlay_hints_query(
4464 &self,
4465 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4466 cx: &mut Context<Editor>,
4467 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4468 let Some(project) = self.project.as_ref() else {
4469 return HashMap::default();
4470 };
4471 let project = project.read(cx);
4472 let multi_buffer = self.buffer().read(cx);
4473 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4474 let multi_buffer_visible_start = self
4475 .scroll_manager
4476 .anchor()
4477 .anchor
4478 .to_point(&multi_buffer_snapshot);
4479 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4480 multi_buffer_visible_start
4481 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4482 Bias::Left,
4483 );
4484 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4485 multi_buffer_snapshot
4486 .range_to_buffer_ranges(multi_buffer_visible_range)
4487 .into_iter()
4488 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4489 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4490 let buffer_file = project::File::from_dyn(buffer.file())?;
4491 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4492 let worktree_entry = buffer_worktree
4493 .read(cx)
4494 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4495 if worktree_entry.is_ignored {
4496 return None;
4497 }
4498
4499 let language = buffer.language()?;
4500 if let Some(restrict_to_languages) = restrict_to_languages {
4501 if !restrict_to_languages.contains(language) {
4502 return None;
4503 }
4504 }
4505 Some((
4506 excerpt_id,
4507 (
4508 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4509 buffer.version().clone(),
4510 excerpt_visible_range,
4511 ),
4512 ))
4513 })
4514 .collect()
4515 }
4516
4517 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4518 TextLayoutDetails {
4519 text_system: window.text_system().clone(),
4520 editor_style: self.style.clone().unwrap(),
4521 rem_size: window.rem_size(),
4522 scroll_anchor: self.scroll_manager.anchor(),
4523 visible_rows: self.visible_line_count(),
4524 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4525 }
4526 }
4527
4528 pub fn splice_inlays(
4529 &self,
4530 to_remove: &[InlayId],
4531 to_insert: Vec<Inlay>,
4532 cx: &mut Context<Self>,
4533 ) {
4534 self.display_map.update(cx, |display_map, cx| {
4535 display_map.splice_inlays(to_remove, to_insert, cx)
4536 });
4537 cx.notify();
4538 }
4539
4540 fn trigger_on_type_formatting(
4541 &self,
4542 input: String,
4543 window: &mut Window,
4544 cx: &mut Context<Self>,
4545 ) -> Option<Task<Result<()>>> {
4546 if input.len() != 1 {
4547 return None;
4548 }
4549
4550 let project = self.project.as_ref()?;
4551 let position = self.selections.newest_anchor().head();
4552 let (buffer, buffer_position) = self
4553 .buffer
4554 .read(cx)
4555 .text_anchor_for_position(position, cx)?;
4556
4557 let settings = language_settings::language_settings(
4558 buffer
4559 .read(cx)
4560 .language_at(buffer_position)
4561 .map(|l| l.name()),
4562 buffer.read(cx).file(),
4563 cx,
4564 );
4565 if !settings.use_on_type_format {
4566 return None;
4567 }
4568
4569 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4570 // hence we do LSP request & edit on host side only — add formats to host's history.
4571 let push_to_lsp_host_history = true;
4572 // If this is not the host, append its history with new edits.
4573 let push_to_client_history = project.read(cx).is_via_collab();
4574
4575 let on_type_formatting = project.update(cx, |project, cx| {
4576 project.on_type_format(
4577 buffer.clone(),
4578 buffer_position,
4579 input,
4580 push_to_lsp_host_history,
4581 cx,
4582 )
4583 });
4584 Some(cx.spawn_in(window, async move |editor, cx| {
4585 if let Some(transaction) = on_type_formatting.await? {
4586 if push_to_client_history {
4587 buffer
4588 .update(cx, |buffer, _| {
4589 buffer.push_transaction(transaction, Instant::now());
4590 buffer.finalize_last_transaction();
4591 })
4592 .ok();
4593 }
4594 editor.update(cx, |editor, cx| {
4595 editor.refresh_document_highlights(cx);
4596 })?;
4597 }
4598 Ok(())
4599 }))
4600 }
4601
4602 pub fn show_word_completions(
4603 &mut self,
4604 _: &ShowWordCompletions,
4605 window: &mut Window,
4606 cx: &mut Context<Self>,
4607 ) {
4608 self.open_completions_menu(true, None, window, cx);
4609 }
4610
4611 pub fn show_completions(
4612 &mut self,
4613 options: &ShowCompletions,
4614 window: &mut Window,
4615 cx: &mut Context<Self>,
4616 ) {
4617 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4618 }
4619
4620 fn open_completions_menu(
4621 &mut self,
4622 ignore_completion_provider: bool,
4623 trigger: Option<&str>,
4624 window: &mut Window,
4625 cx: &mut Context<Self>,
4626 ) {
4627 if self.pending_rename.is_some() {
4628 return;
4629 }
4630 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4631 return;
4632 }
4633
4634 let position = self.selections.newest_anchor().head();
4635 if position.diff_base_anchor.is_some() {
4636 return;
4637 }
4638 let (buffer, buffer_position) =
4639 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4640 output
4641 } else {
4642 return;
4643 };
4644 let buffer_snapshot = buffer.read(cx).snapshot();
4645 let show_completion_documentation = buffer_snapshot
4646 .settings_at(buffer_position, cx)
4647 .show_completion_documentation;
4648
4649 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4650
4651 let trigger_kind = match trigger {
4652 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4653 CompletionTriggerKind::TRIGGER_CHARACTER
4654 }
4655 _ => CompletionTriggerKind::INVOKED,
4656 };
4657 let completion_context = CompletionContext {
4658 trigger_character: trigger.and_then(|trigger| {
4659 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4660 Some(String::from(trigger))
4661 } else {
4662 None
4663 }
4664 }),
4665 trigger_kind,
4666 };
4667
4668 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4669 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4670 let word_to_exclude = buffer_snapshot
4671 .text_for_range(old_range.clone())
4672 .collect::<String>();
4673 (
4674 buffer_snapshot.anchor_before(old_range.start)
4675 ..buffer_snapshot.anchor_after(old_range.end),
4676 Some(word_to_exclude),
4677 )
4678 } else {
4679 (buffer_position..buffer_position, None)
4680 };
4681
4682 let completion_settings = language_settings(
4683 buffer_snapshot
4684 .language_at(buffer_position)
4685 .map(|language| language.name()),
4686 buffer_snapshot.file(),
4687 cx,
4688 )
4689 .completions;
4690
4691 // The document can be large, so stay in reasonable bounds when searching for words,
4692 // otherwise completion pop-up might be slow to appear.
4693 const WORD_LOOKUP_ROWS: u32 = 5_000;
4694 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4695 let min_word_search = buffer_snapshot.clip_point(
4696 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4697 Bias::Left,
4698 );
4699 let max_word_search = buffer_snapshot.clip_point(
4700 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4701 Bias::Right,
4702 );
4703 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4704 ..buffer_snapshot.point_to_offset(max_word_search);
4705
4706 let provider = self
4707 .completion_provider
4708 .as_ref()
4709 .filter(|_| !ignore_completion_provider);
4710 let skip_digits = query
4711 .as_ref()
4712 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4713
4714 let (mut words, provided_completions) = match provider {
4715 Some(provider) => {
4716 let completions = provider.completions(
4717 position.excerpt_id,
4718 &buffer,
4719 buffer_position,
4720 completion_context,
4721 window,
4722 cx,
4723 );
4724
4725 let words = match completion_settings.words {
4726 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4727 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4728 .background_spawn(async move {
4729 buffer_snapshot.words_in_range(WordsQuery {
4730 fuzzy_contents: None,
4731 range: word_search_range,
4732 skip_digits,
4733 })
4734 }),
4735 };
4736
4737 (words, completions)
4738 }
4739 None => (
4740 cx.background_spawn(async move {
4741 buffer_snapshot.words_in_range(WordsQuery {
4742 fuzzy_contents: None,
4743 range: word_search_range,
4744 skip_digits,
4745 })
4746 }),
4747 Task::ready(Ok(None)),
4748 ),
4749 };
4750
4751 let sort_completions = provider
4752 .as_ref()
4753 .map_or(false, |provider| provider.sort_completions());
4754
4755 let filter_completions = provider
4756 .as_ref()
4757 .map_or(true, |provider| provider.filter_completions());
4758
4759 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
4760
4761 let id = post_inc(&mut self.next_completion_id);
4762 let task = cx.spawn_in(window, async move |editor, cx| {
4763 async move {
4764 editor.update(cx, |this, _| {
4765 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4766 })?;
4767
4768 let mut completions = Vec::new();
4769 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4770 completions.extend(provided_completions);
4771 if completion_settings.words == WordsCompletionMode::Fallback {
4772 words = Task::ready(BTreeMap::default());
4773 }
4774 }
4775
4776 let mut words = words.await;
4777 if let Some(word_to_exclude) = &word_to_exclude {
4778 words.remove(word_to_exclude);
4779 }
4780 for lsp_completion in &completions {
4781 words.remove(&lsp_completion.new_text);
4782 }
4783 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4784 replace_range: old_range.clone(),
4785 new_text: word.clone(),
4786 label: CodeLabel::plain(word, None),
4787 icon_path: None,
4788 documentation: None,
4789 source: CompletionSource::BufferWord {
4790 word_range,
4791 resolved: false,
4792 },
4793 insert_text_mode: Some(InsertTextMode::AS_IS),
4794 confirm: None,
4795 }));
4796
4797 let menu = if completions.is_empty() {
4798 None
4799 } else {
4800 let mut menu = CompletionsMenu::new(
4801 id,
4802 sort_completions,
4803 show_completion_documentation,
4804 ignore_completion_provider,
4805 position,
4806 buffer.clone(),
4807 completions.into(),
4808 snippet_sort_order,
4809 );
4810
4811 menu.filter(
4812 if filter_completions {
4813 query.as_deref()
4814 } else {
4815 None
4816 },
4817 cx.background_executor().clone(),
4818 )
4819 .await;
4820
4821 menu.visible().then_some(menu)
4822 };
4823
4824 editor.update_in(cx, |editor, window, cx| {
4825 match editor.context_menu.borrow().as_ref() {
4826 None => {}
4827 Some(CodeContextMenu::Completions(prev_menu)) => {
4828 if prev_menu.id > id {
4829 return;
4830 }
4831 }
4832 _ => return,
4833 }
4834
4835 if editor.focus_handle.is_focused(window) && menu.is_some() {
4836 let mut menu = menu.unwrap();
4837 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4838
4839 *editor.context_menu.borrow_mut() =
4840 Some(CodeContextMenu::Completions(menu));
4841
4842 if editor.show_edit_predictions_in_menu() {
4843 editor.update_visible_inline_completion(window, cx);
4844 } else {
4845 editor.discard_inline_completion(false, cx);
4846 }
4847
4848 cx.notify();
4849 } else if editor.completion_tasks.len() <= 1 {
4850 // If there are no more completion tasks and the last menu was
4851 // empty, we should hide it.
4852 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4853 // If it was already hidden and we don't show inline
4854 // completions in the menu, we should also show the
4855 // inline-completion when available.
4856 if was_hidden && editor.show_edit_predictions_in_menu() {
4857 editor.update_visible_inline_completion(window, cx);
4858 }
4859 }
4860 })?;
4861
4862 anyhow::Ok(())
4863 }
4864 .log_err()
4865 .await
4866 });
4867
4868 self.completion_tasks.push((id, task));
4869 }
4870
4871 #[cfg(feature = "test-support")]
4872 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4873 let menu = self.context_menu.borrow();
4874 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4875 let completions = menu.completions.borrow();
4876 Some(completions.to_vec())
4877 } else {
4878 None
4879 }
4880 }
4881
4882 pub fn confirm_completion(
4883 &mut self,
4884 action: &ConfirmCompletion,
4885 window: &mut Window,
4886 cx: &mut Context<Self>,
4887 ) -> Option<Task<Result<()>>> {
4888 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4889 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4890 }
4891
4892 pub fn confirm_completion_insert(
4893 &mut self,
4894 _: &ConfirmCompletionInsert,
4895 window: &mut Window,
4896 cx: &mut Context<Self>,
4897 ) -> Option<Task<Result<()>>> {
4898 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4899 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4900 }
4901
4902 pub fn confirm_completion_replace(
4903 &mut self,
4904 _: &ConfirmCompletionReplace,
4905 window: &mut Window,
4906 cx: &mut Context<Self>,
4907 ) -> Option<Task<Result<()>>> {
4908 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4909 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4910 }
4911
4912 pub fn compose_completion(
4913 &mut self,
4914 action: &ComposeCompletion,
4915 window: &mut Window,
4916 cx: &mut Context<Self>,
4917 ) -> Option<Task<Result<()>>> {
4918 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4919 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4920 }
4921
4922 fn do_completion(
4923 &mut self,
4924 item_ix: Option<usize>,
4925 intent: CompletionIntent,
4926 window: &mut Window,
4927 cx: &mut Context<Editor>,
4928 ) -> Option<Task<Result<()>>> {
4929 use language::ToOffset as _;
4930
4931 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4932 else {
4933 return None;
4934 };
4935
4936 let candidate_id = {
4937 let entries = completions_menu.entries.borrow();
4938 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4939 if self.show_edit_predictions_in_menu() {
4940 self.discard_inline_completion(true, cx);
4941 }
4942 mat.candidate_id
4943 };
4944
4945 let buffer_handle = completions_menu.buffer;
4946 let completion = completions_menu
4947 .completions
4948 .borrow()
4949 .get(candidate_id)?
4950 .clone();
4951 cx.stop_propagation();
4952
4953 let snippet;
4954 let new_text;
4955 if completion.is_snippet() {
4956 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4957 new_text = snippet.as_ref().unwrap().text.clone();
4958 } else {
4959 snippet = None;
4960 new_text = completion.new_text.clone();
4961 };
4962
4963 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4964 let buffer = buffer_handle.read(cx);
4965 let snapshot = self.buffer.read(cx).snapshot(cx);
4966 let replace_range_multibuffer = {
4967 let excerpt = snapshot
4968 .excerpt_containing(self.selections.newest_anchor().range())
4969 .unwrap();
4970 let multibuffer_anchor = snapshot
4971 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
4972 .unwrap()
4973 ..snapshot
4974 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
4975 .unwrap();
4976 multibuffer_anchor.start.to_offset(&snapshot)
4977 ..multibuffer_anchor.end.to_offset(&snapshot)
4978 };
4979 let newest_anchor = self.selections.newest_anchor();
4980 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
4981 return None;
4982 }
4983
4984 let old_text = buffer
4985 .text_for_range(replace_range.clone())
4986 .collect::<String>();
4987 let lookbehind = newest_anchor
4988 .start
4989 .text_anchor
4990 .to_offset(buffer)
4991 .saturating_sub(replace_range.start);
4992 let lookahead = replace_range
4993 .end
4994 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
4995 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
4996 let suffix = &old_text[lookbehind.min(old_text.len())..];
4997
4998 let selections = self.selections.all::<usize>(cx);
4999 let mut ranges = Vec::new();
5000 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5001
5002 for selection in &selections {
5003 let range = if selection.id == newest_anchor.id {
5004 replace_range_multibuffer.clone()
5005 } else {
5006 let mut range = selection.range();
5007
5008 // if prefix is present, don't duplicate it
5009 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5010 range.start = range.start.saturating_sub(lookbehind);
5011
5012 // if suffix is also present, mimic the newest cursor and replace it
5013 if selection.id != newest_anchor.id
5014 && snapshot.contains_str_at(range.end, suffix)
5015 {
5016 range.end += lookahead;
5017 }
5018 }
5019 range
5020 };
5021
5022 ranges.push(range.clone());
5023
5024 if !self.linked_edit_ranges.is_empty() {
5025 let start_anchor = snapshot.anchor_before(range.start);
5026 let end_anchor = snapshot.anchor_after(range.end);
5027 if let Some(ranges) = self
5028 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5029 {
5030 for (buffer, edits) in ranges {
5031 linked_edits
5032 .entry(buffer.clone())
5033 .or_default()
5034 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5035 }
5036 }
5037 }
5038 }
5039
5040 cx.emit(EditorEvent::InputHandled {
5041 utf16_range_to_replace: None,
5042 text: new_text.clone().into(),
5043 });
5044
5045 self.transact(window, cx, |this, window, cx| {
5046 if let Some(mut snippet) = snippet {
5047 snippet.text = new_text.to_string();
5048 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5049 } else {
5050 this.buffer.update(cx, |buffer, cx| {
5051 let auto_indent = match completion.insert_text_mode {
5052 Some(InsertTextMode::AS_IS) => None,
5053 _ => this.autoindent_mode.clone(),
5054 };
5055 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5056 buffer.edit(edits, auto_indent, cx);
5057 });
5058 }
5059 for (buffer, edits) in linked_edits {
5060 buffer.update(cx, |buffer, cx| {
5061 let snapshot = buffer.snapshot();
5062 let edits = edits
5063 .into_iter()
5064 .map(|(range, text)| {
5065 use text::ToPoint as TP;
5066 let end_point = TP::to_point(&range.end, &snapshot);
5067 let start_point = TP::to_point(&range.start, &snapshot);
5068 (start_point..end_point, text)
5069 })
5070 .sorted_by_key(|(range, _)| range.start);
5071 buffer.edit(edits, None, cx);
5072 })
5073 }
5074
5075 this.refresh_inline_completion(true, false, window, cx);
5076 });
5077
5078 let show_new_completions_on_confirm = completion
5079 .confirm
5080 .as_ref()
5081 .map_or(false, |confirm| confirm(intent, window, cx));
5082 if show_new_completions_on_confirm {
5083 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5084 }
5085
5086 let provider = self.completion_provider.as_ref()?;
5087 drop(completion);
5088 let apply_edits = provider.apply_additional_edits_for_completion(
5089 buffer_handle,
5090 completions_menu.completions.clone(),
5091 candidate_id,
5092 true,
5093 cx,
5094 );
5095
5096 let editor_settings = EditorSettings::get_global(cx);
5097 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5098 // After the code completion is finished, users often want to know what signatures are needed.
5099 // so we should automatically call signature_help
5100 self.show_signature_help(&ShowSignatureHelp, window, cx);
5101 }
5102
5103 Some(cx.foreground_executor().spawn(async move {
5104 apply_edits.await?;
5105 Ok(())
5106 }))
5107 }
5108
5109 pub fn toggle_code_actions(
5110 &mut self,
5111 action: &ToggleCodeActions,
5112 window: &mut Window,
5113 cx: &mut Context<Self>,
5114 ) {
5115 let quick_launch = action.quick_launch;
5116 let mut context_menu = self.context_menu.borrow_mut();
5117 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5118 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5119 // Toggle if we're selecting the same one
5120 *context_menu = None;
5121 cx.notify();
5122 return;
5123 } else {
5124 // Otherwise, clear it and start a new one
5125 *context_menu = None;
5126 cx.notify();
5127 }
5128 }
5129 drop(context_menu);
5130 let snapshot = self.snapshot(window, cx);
5131 let deployed_from_indicator = action.deployed_from_indicator;
5132 let mut task = self.code_actions_task.take();
5133 let action = action.clone();
5134 cx.spawn_in(window, async move |editor, cx| {
5135 while let Some(prev_task) = task {
5136 prev_task.await.log_err();
5137 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5138 }
5139
5140 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5141 if editor.focus_handle.is_focused(window) {
5142 let multibuffer_point = action
5143 .deployed_from_indicator
5144 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5145 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5146 let (buffer, buffer_row) = snapshot
5147 .buffer_snapshot
5148 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5149 .and_then(|(buffer_snapshot, range)| {
5150 editor
5151 .buffer
5152 .read(cx)
5153 .buffer(buffer_snapshot.remote_id())
5154 .map(|buffer| (buffer, range.start.row))
5155 })?;
5156 let (_, code_actions) = editor
5157 .available_code_actions
5158 .clone()
5159 .and_then(|(location, code_actions)| {
5160 let snapshot = location.buffer.read(cx).snapshot();
5161 let point_range = location.range.to_point(&snapshot);
5162 let point_range = point_range.start.row..=point_range.end.row;
5163 if point_range.contains(&buffer_row) {
5164 Some((location, code_actions))
5165 } else {
5166 None
5167 }
5168 })
5169 .unzip();
5170 let buffer_id = buffer.read(cx).remote_id();
5171 let tasks = editor
5172 .tasks
5173 .get(&(buffer_id, buffer_row))
5174 .map(|t| Arc::new(t.to_owned()));
5175 if tasks.is_none() && code_actions.is_none() {
5176 return None;
5177 }
5178
5179 editor.completion_tasks.clear();
5180 editor.discard_inline_completion(false, cx);
5181 let task_context =
5182 tasks
5183 .as_ref()
5184 .zip(editor.project.clone())
5185 .map(|(tasks, project)| {
5186 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5187 });
5188
5189 Some(cx.spawn_in(window, async move |editor, cx| {
5190 let task_context = match task_context {
5191 Some(task_context) => task_context.await,
5192 None => None,
5193 };
5194 let resolved_tasks =
5195 tasks
5196 .zip(task_context.clone())
5197 .map(|(tasks, task_context)| ResolvedTasks {
5198 templates: tasks.resolve(&task_context).collect(),
5199 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5200 multibuffer_point.row,
5201 tasks.column,
5202 )),
5203 });
5204 let spawn_straight_away = quick_launch
5205 && resolved_tasks
5206 .as_ref()
5207 .map_or(false, |tasks| tasks.templates.len() == 1)
5208 && code_actions
5209 .as_ref()
5210 .map_or(true, |actions| actions.is_empty());
5211 let debug_scenarios = editor.update(cx, |editor, cx| {
5212 if cx.has_flag::<DebuggerFeatureFlag>() {
5213 maybe!({
5214 let project = editor.project.as_ref()?;
5215 let dap_store = project.read(cx).dap_store();
5216 let mut scenarios = vec![];
5217 let resolved_tasks = resolved_tasks.as_ref()?;
5218 let debug_adapter: SharedString = buffer
5219 .read(cx)
5220 .language()?
5221 .context_provider()?
5222 .debug_adapter()?
5223 .into();
5224 dap_store.update(cx, |this, cx| {
5225 for (_, task) in &resolved_tasks.templates {
5226 if let Some(scenario) = this
5227 .debug_scenario_for_build_task(
5228 task.resolved.clone(),
5229 SharedString::from(
5230 task.original_task().label.clone(),
5231 ),
5232 debug_adapter.clone(),
5233 cx,
5234 )
5235 {
5236 scenarios.push(scenario);
5237 }
5238 }
5239 });
5240 Some(scenarios)
5241 })
5242 .unwrap_or_default()
5243 } else {
5244 vec![]
5245 }
5246 })?;
5247 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5248 *editor.context_menu.borrow_mut() =
5249 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5250 buffer,
5251 actions: CodeActionContents::new(
5252 resolved_tasks,
5253 code_actions,
5254 debug_scenarios,
5255 task_context.unwrap_or_default(),
5256 ),
5257 selected_item: Default::default(),
5258 scroll_handle: UniformListScrollHandle::default(),
5259 deployed_from_indicator,
5260 }));
5261 if spawn_straight_away {
5262 if let Some(task) = editor.confirm_code_action(
5263 &ConfirmCodeAction { item_ix: Some(0) },
5264 window,
5265 cx,
5266 ) {
5267 cx.notify();
5268 return task;
5269 }
5270 }
5271 cx.notify();
5272 Task::ready(Ok(()))
5273 }) {
5274 task.await
5275 } else {
5276 Ok(())
5277 }
5278 }))
5279 } else {
5280 Some(Task::ready(Ok(())))
5281 }
5282 })?;
5283 if let Some(task) = spawned_test_task {
5284 task.await?;
5285 }
5286
5287 Ok::<_, anyhow::Error>(())
5288 })
5289 .detach_and_log_err(cx);
5290 }
5291
5292 pub fn confirm_code_action(
5293 &mut self,
5294 action: &ConfirmCodeAction,
5295 window: &mut Window,
5296 cx: &mut Context<Self>,
5297 ) -> Option<Task<Result<()>>> {
5298 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5299
5300 let actions_menu =
5301 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5302 menu
5303 } else {
5304 return None;
5305 };
5306
5307 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5308 let action = actions_menu.actions.get(action_ix)?;
5309 let title = action.label();
5310 let buffer = actions_menu.buffer;
5311 let workspace = self.workspace()?;
5312
5313 match action {
5314 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5315 workspace.update(cx, |workspace, cx| {
5316 workspace.schedule_resolved_task(
5317 task_source_kind,
5318 resolved_task,
5319 false,
5320 window,
5321 cx,
5322 );
5323
5324 Some(Task::ready(Ok(())))
5325 })
5326 }
5327 CodeActionsItem::CodeAction {
5328 excerpt_id,
5329 action,
5330 provider,
5331 } => {
5332 let apply_code_action =
5333 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5334 let workspace = workspace.downgrade();
5335 Some(cx.spawn_in(window, async move |editor, cx| {
5336 let project_transaction = apply_code_action.await?;
5337 Self::open_project_transaction(
5338 &editor,
5339 workspace,
5340 project_transaction,
5341 title,
5342 cx,
5343 )
5344 .await
5345 }))
5346 }
5347 CodeActionsItem::DebugScenario(scenario) => {
5348 let context = actions_menu.actions.context.clone();
5349
5350 workspace.update(cx, |workspace, cx| {
5351 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5352 });
5353 Some(Task::ready(Ok(())))
5354 }
5355 }
5356 }
5357
5358 pub async fn open_project_transaction(
5359 this: &WeakEntity<Editor>,
5360 workspace: WeakEntity<Workspace>,
5361 transaction: ProjectTransaction,
5362 title: String,
5363 cx: &mut AsyncWindowContext,
5364 ) -> Result<()> {
5365 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5366 cx.update(|_, cx| {
5367 entries.sort_unstable_by_key(|(buffer, _)| {
5368 buffer.read(cx).file().map(|f| f.path().clone())
5369 });
5370 })?;
5371
5372 // If the project transaction's edits are all contained within this editor, then
5373 // avoid opening a new editor to display them.
5374
5375 if let Some((buffer, transaction)) = entries.first() {
5376 if entries.len() == 1 {
5377 let excerpt = this.update(cx, |editor, cx| {
5378 editor
5379 .buffer()
5380 .read(cx)
5381 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5382 })?;
5383 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5384 if excerpted_buffer == *buffer {
5385 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5386 let excerpt_range = excerpt_range.to_offset(buffer);
5387 buffer
5388 .edited_ranges_for_transaction::<usize>(transaction)
5389 .all(|range| {
5390 excerpt_range.start <= range.start
5391 && excerpt_range.end >= range.end
5392 })
5393 })?;
5394
5395 if all_edits_within_excerpt {
5396 return Ok(());
5397 }
5398 }
5399 }
5400 }
5401 } else {
5402 return Ok(());
5403 }
5404
5405 let mut ranges_to_highlight = Vec::new();
5406 let excerpt_buffer = cx.new(|cx| {
5407 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5408 for (buffer_handle, transaction) in &entries {
5409 let edited_ranges = buffer_handle
5410 .read(cx)
5411 .edited_ranges_for_transaction::<Point>(transaction)
5412 .collect::<Vec<_>>();
5413 let (ranges, _) = multibuffer.set_excerpts_for_path(
5414 PathKey::for_buffer(buffer_handle, cx),
5415 buffer_handle.clone(),
5416 edited_ranges,
5417 DEFAULT_MULTIBUFFER_CONTEXT,
5418 cx,
5419 );
5420
5421 ranges_to_highlight.extend(ranges);
5422 }
5423 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5424 multibuffer
5425 })?;
5426
5427 workspace.update_in(cx, |workspace, window, cx| {
5428 let project = workspace.project().clone();
5429 let editor =
5430 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5431 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5432 editor.update(cx, |editor, cx| {
5433 editor.highlight_background::<Self>(
5434 &ranges_to_highlight,
5435 |theme| theme.editor_highlighted_line_background,
5436 cx,
5437 );
5438 });
5439 })?;
5440
5441 Ok(())
5442 }
5443
5444 pub fn clear_code_action_providers(&mut self) {
5445 self.code_action_providers.clear();
5446 self.available_code_actions.take();
5447 }
5448
5449 pub fn add_code_action_provider(
5450 &mut self,
5451 provider: Rc<dyn CodeActionProvider>,
5452 window: &mut Window,
5453 cx: &mut Context<Self>,
5454 ) {
5455 if self
5456 .code_action_providers
5457 .iter()
5458 .any(|existing_provider| existing_provider.id() == provider.id())
5459 {
5460 return;
5461 }
5462
5463 self.code_action_providers.push(provider);
5464 self.refresh_code_actions(window, cx);
5465 }
5466
5467 pub fn remove_code_action_provider(
5468 &mut self,
5469 id: Arc<str>,
5470 window: &mut Window,
5471 cx: &mut Context<Self>,
5472 ) {
5473 self.code_action_providers
5474 .retain(|provider| provider.id() != id);
5475 self.refresh_code_actions(window, cx);
5476 }
5477
5478 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5479 let newest_selection = self.selections.newest_anchor().clone();
5480 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5481 let buffer = self.buffer.read(cx);
5482 if newest_selection.head().diff_base_anchor.is_some() {
5483 return None;
5484 }
5485 let (start_buffer, start) =
5486 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5487 let (end_buffer, end) =
5488 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5489 if start_buffer != end_buffer {
5490 return None;
5491 }
5492
5493 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5494 cx.background_executor()
5495 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5496 .await;
5497
5498 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5499 let providers = this.code_action_providers.clone();
5500 let tasks = this
5501 .code_action_providers
5502 .iter()
5503 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5504 .collect::<Vec<_>>();
5505 (providers, tasks)
5506 })?;
5507
5508 let mut actions = Vec::new();
5509 for (provider, provider_actions) in
5510 providers.into_iter().zip(future::join_all(tasks).await)
5511 {
5512 if let Some(provider_actions) = provider_actions.log_err() {
5513 actions.extend(provider_actions.into_iter().map(|action| {
5514 AvailableCodeAction {
5515 excerpt_id: newest_selection.start.excerpt_id,
5516 action,
5517 provider: provider.clone(),
5518 }
5519 }));
5520 }
5521 }
5522
5523 this.update(cx, |this, cx| {
5524 this.available_code_actions = if actions.is_empty() {
5525 None
5526 } else {
5527 Some((
5528 Location {
5529 buffer: start_buffer,
5530 range: start..end,
5531 },
5532 actions.into(),
5533 ))
5534 };
5535 cx.notify();
5536 })
5537 }));
5538 None
5539 }
5540
5541 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5542 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5543 self.show_git_blame_inline = false;
5544
5545 self.show_git_blame_inline_delay_task =
5546 Some(cx.spawn_in(window, async move |this, cx| {
5547 cx.background_executor().timer(delay).await;
5548
5549 this.update(cx, |this, cx| {
5550 this.show_git_blame_inline = true;
5551 cx.notify();
5552 })
5553 .log_err();
5554 }));
5555 }
5556 }
5557
5558 fn show_blame_popover(
5559 &mut self,
5560 blame_entry: &BlameEntry,
5561 position: gpui::Point<Pixels>,
5562 cx: &mut Context<Self>,
5563 ) {
5564 if let Some(state) = &mut self.inline_blame_popover {
5565 state.hide_task.take();
5566 cx.notify();
5567 } else {
5568 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5569 let show_task = cx.spawn(async move |editor, cx| {
5570 cx.background_executor()
5571 .timer(std::time::Duration::from_millis(delay))
5572 .await;
5573 editor
5574 .update(cx, |editor, cx| {
5575 if let Some(state) = &mut editor.inline_blame_popover {
5576 state.show_task = None;
5577 cx.notify();
5578 }
5579 })
5580 .ok();
5581 });
5582 let Some(blame) = self.blame.as_ref() else {
5583 return;
5584 };
5585 let blame = blame.read(cx);
5586 let details = blame.details_for_entry(&blame_entry);
5587 let markdown = cx.new(|cx| {
5588 Markdown::new(
5589 details
5590 .as_ref()
5591 .map(|message| message.message.clone())
5592 .unwrap_or_default(),
5593 None,
5594 None,
5595 cx,
5596 )
5597 });
5598 self.inline_blame_popover = Some(InlineBlamePopover {
5599 position,
5600 show_task: Some(show_task),
5601 hide_task: None,
5602 popover_bounds: None,
5603 popover_state: InlineBlamePopoverState {
5604 scroll_handle: ScrollHandle::new(),
5605 commit_message: details,
5606 markdown,
5607 },
5608 });
5609 }
5610 }
5611
5612 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5613 if let Some(state) = &mut self.inline_blame_popover {
5614 if state.show_task.is_some() {
5615 self.inline_blame_popover.take();
5616 cx.notify();
5617 } else {
5618 let hide_task = cx.spawn(async move |editor, cx| {
5619 cx.background_executor()
5620 .timer(std::time::Duration::from_millis(100))
5621 .await;
5622 editor
5623 .update(cx, |editor, cx| {
5624 editor.inline_blame_popover.take();
5625 cx.notify();
5626 })
5627 .ok();
5628 });
5629 state.hide_task = Some(hide_task);
5630 }
5631 }
5632 }
5633
5634 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5635 if self.pending_rename.is_some() {
5636 return None;
5637 }
5638
5639 let provider = self.semantics_provider.clone()?;
5640 let buffer = self.buffer.read(cx);
5641 let newest_selection = self.selections.newest_anchor().clone();
5642 let cursor_position = newest_selection.head();
5643 let (cursor_buffer, cursor_buffer_position) =
5644 buffer.text_anchor_for_position(cursor_position, cx)?;
5645 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5646 if cursor_buffer != tail_buffer {
5647 return None;
5648 }
5649 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5650 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5651 cx.background_executor()
5652 .timer(Duration::from_millis(debounce))
5653 .await;
5654
5655 let highlights = if let Some(highlights) = cx
5656 .update(|cx| {
5657 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5658 })
5659 .ok()
5660 .flatten()
5661 {
5662 highlights.await.log_err()
5663 } else {
5664 None
5665 };
5666
5667 if let Some(highlights) = highlights {
5668 this.update(cx, |this, cx| {
5669 if this.pending_rename.is_some() {
5670 return;
5671 }
5672
5673 let buffer_id = cursor_position.buffer_id;
5674 let buffer = this.buffer.read(cx);
5675 if !buffer
5676 .text_anchor_for_position(cursor_position, cx)
5677 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5678 {
5679 return;
5680 }
5681
5682 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5683 let mut write_ranges = Vec::new();
5684 let mut read_ranges = Vec::new();
5685 for highlight in highlights {
5686 for (excerpt_id, excerpt_range) in
5687 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5688 {
5689 let start = highlight
5690 .range
5691 .start
5692 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5693 let end = highlight
5694 .range
5695 .end
5696 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5697 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5698 continue;
5699 }
5700
5701 let range = Anchor {
5702 buffer_id,
5703 excerpt_id,
5704 text_anchor: start,
5705 diff_base_anchor: None,
5706 }..Anchor {
5707 buffer_id,
5708 excerpt_id,
5709 text_anchor: end,
5710 diff_base_anchor: None,
5711 };
5712 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5713 write_ranges.push(range);
5714 } else {
5715 read_ranges.push(range);
5716 }
5717 }
5718 }
5719
5720 this.highlight_background::<DocumentHighlightRead>(
5721 &read_ranges,
5722 |theme| theme.editor_document_highlight_read_background,
5723 cx,
5724 );
5725 this.highlight_background::<DocumentHighlightWrite>(
5726 &write_ranges,
5727 |theme| theme.editor_document_highlight_write_background,
5728 cx,
5729 );
5730 cx.notify();
5731 })
5732 .log_err();
5733 }
5734 }));
5735 None
5736 }
5737
5738 fn prepare_highlight_query_from_selection(
5739 &mut self,
5740 cx: &mut Context<Editor>,
5741 ) -> Option<(String, Range<Anchor>)> {
5742 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5743 return None;
5744 }
5745 if !EditorSettings::get_global(cx).selection_highlight {
5746 return None;
5747 }
5748 if self.selections.count() != 1 || self.selections.line_mode {
5749 return None;
5750 }
5751 let selection = self.selections.newest::<Point>(cx);
5752 if selection.is_empty() || selection.start.row != selection.end.row {
5753 return None;
5754 }
5755 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5756 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5757 let query = multi_buffer_snapshot
5758 .text_for_range(selection_anchor_range.clone())
5759 .collect::<String>();
5760 if query.trim().is_empty() {
5761 return None;
5762 }
5763 Some((query, selection_anchor_range))
5764 }
5765
5766 fn update_selection_occurrence_highlights(
5767 &mut self,
5768 query_text: String,
5769 query_range: Range<Anchor>,
5770 multi_buffer_range_to_query: Range<Point>,
5771 use_debounce: bool,
5772 window: &mut Window,
5773 cx: &mut Context<Editor>,
5774 ) -> Task<()> {
5775 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5776 cx.spawn_in(window, async move |editor, cx| {
5777 if use_debounce {
5778 cx.background_executor()
5779 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5780 .await;
5781 }
5782 let match_task = cx.background_spawn(async move {
5783 let buffer_ranges = multi_buffer_snapshot
5784 .range_to_buffer_ranges(multi_buffer_range_to_query)
5785 .into_iter()
5786 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5787 let mut match_ranges = Vec::new();
5788 let Ok(regex) = project::search::SearchQuery::text(
5789 query_text.clone(),
5790 false,
5791 false,
5792 false,
5793 Default::default(),
5794 Default::default(),
5795 false,
5796 None,
5797 ) else {
5798 return Vec::default();
5799 };
5800 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5801 match_ranges.extend(
5802 regex
5803 .search(&buffer_snapshot, Some(search_range.clone()))
5804 .await
5805 .into_iter()
5806 .filter_map(|match_range| {
5807 let match_start = buffer_snapshot
5808 .anchor_after(search_range.start + match_range.start);
5809 let match_end = buffer_snapshot
5810 .anchor_before(search_range.start + match_range.end);
5811 let match_anchor_range = Anchor::range_in_buffer(
5812 excerpt_id,
5813 buffer_snapshot.remote_id(),
5814 match_start..match_end,
5815 );
5816 (match_anchor_range != query_range).then_some(match_anchor_range)
5817 }),
5818 );
5819 }
5820 match_ranges
5821 });
5822 let match_ranges = match_task.await;
5823 editor
5824 .update_in(cx, |editor, _, cx| {
5825 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5826 if !match_ranges.is_empty() {
5827 editor.highlight_background::<SelectedTextHighlight>(
5828 &match_ranges,
5829 |theme| theme.editor_document_highlight_bracket_background,
5830 cx,
5831 )
5832 }
5833 })
5834 .log_err();
5835 })
5836 }
5837
5838 fn refresh_selected_text_highlights(
5839 &mut self,
5840 on_buffer_edit: bool,
5841 window: &mut Window,
5842 cx: &mut Context<Editor>,
5843 ) {
5844 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5845 else {
5846 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5847 self.quick_selection_highlight_task.take();
5848 self.debounced_selection_highlight_task.take();
5849 return;
5850 };
5851 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5852 if on_buffer_edit
5853 || self
5854 .quick_selection_highlight_task
5855 .as_ref()
5856 .map_or(true, |(prev_anchor_range, _)| {
5857 prev_anchor_range != &query_range
5858 })
5859 {
5860 let multi_buffer_visible_start = self
5861 .scroll_manager
5862 .anchor()
5863 .anchor
5864 .to_point(&multi_buffer_snapshot);
5865 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5866 multi_buffer_visible_start
5867 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5868 Bias::Left,
5869 );
5870 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5871 self.quick_selection_highlight_task = Some((
5872 query_range.clone(),
5873 self.update_selection_occurrence_highlights(
5874 query_text.clone(),
5875 query_range.clone(),
5876 multi_buffer_visible_range,
5877 false,
5878 window,
5879 cx,
5880 ),
5881 ));
5882 }
5883 if on_buffer_edit
5884 || self
5885 .debounced_selection_highlight_task
5886 .as_ref()
5887 .map_or(true, |(prev_anchor_range, _)| {
5888 prev_anchor_range != &query_range
5889 })
5890 {
5891 let multi_buffer_start = multi_buffer_snapshot
5892 .anchor_before(0)
5893 .to_point(&multi_buffer_snapshot);
5894 let multi_buffer_end = multi_buffer_snapshot
5895 .anchor_after(multi_buffer_snapshot.len())
5896 .to_point(&multi_buffer_snapshot);
5897 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
5898 self.debounced_selection_highlight_task = Some((
5899 query_range.clone(),
5900 self.update_selection_occurrence_highlights(
5901 query_text,
5902 query_range,
5903 multi_buffer_full_range,
5904 true,
5905 window,
5906 cx,
5907 ),
5908 ));
5909 }
5910 }
5911
5912 pub fn refresh_inline_completion(
5913 &mut self,
5914 debounce: bool,
5915 user_requested: bool,
5916 window: &mut Window,
5917 cx: &mut Context<Self>,
5918 ) -> Option<()> {
5919 let provider = self.edit_prediction_provider()?;
5920 let cursor = self.selections.newest_anchor().head();
5921 let (buffer, cursor_buffer_position) =
5922 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5923
5924 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5925 self.discard_inline_completion(false, cx);
5926 return None;
5927 }
5928
5929 if !user_requested
5930 && (!self.should_show_edit_predictions()
5931 || !self.is_focused(window)
5932 || buffer.read(cx).is_empty())
5933 {
5934 self.discard_inline_completion(false, cx);
5935 return None;
5936 }
5937
5938 self.update_visible_inline_completion(window, cx);
5939 provider.refresh(
5940 self.project.clone(),
5941 buffer,
5942 cursor_buffer_position,
5943 debounce,
5944 cx,
5945 );
5946 Some(())
5947 }
5948
5949 fn show_edit_predictions_in_menu(&self) -> bool {
5950 match self.edit_prediction_settings {
5951 EditPredictionSettings::Disabled => false,
5952 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5953 }
5954 }
5955
5956 pub fn edit_predictions_enabled(&self) -> bool {
5957 match self.edit_prediction_settings {
5958 EditPredictionSettings::Disabled => false,
5959 EditPredictionSettings::Enabled { .. } => true,
5960 }
5961 }
5962
5963 fn edit_prediction_requires_modifier(&self) -> bool {
5964 match self.edit_prediction_settings {
5965 EditPredictionSettings::Disabled => false,
5966 EditPredictionSettings::Enabled {
5967 preview_requires_modifier,
5968 ..
5969 } => preview_requires_modifier,
5970 }
5971 }
5972
5973 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5974 if self.edit_prediction_provider.is_none() {
5975 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5976 } else {
5977 let selection = self.selections.newest_anchor();
5978 let cursor = selection.head();
5979
5980 if let Some((buffer, cursor_buffer_position)) =
5981 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5982 {
5983 self.edit_prediction_settings =
5984 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5985 }
5986 }
5987 }
5988
5989 fn edit_prediction_settings_at_position(
5990 &self,
5991 buffer: &Entity<Buffer>,
5992 buffer_position: language::Anchor,
5993 cx: &App,
5994 ) -> EditPredictionSettings {
5995 if !self.mode.is_full()
5996 || !self.show_inline_completions_override.unwrap_or(true)
5997 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5998 {
5999 return EditPredictionSettings::Disabled;
6000 }
6001
6002 let buffer = buffer.read(cx);
6003
6004 let file = buffer.file();
6005
6006 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6007 return EditPredictionSettings::Disabled;
6008 };
6009
6010 let by_provider = matches!(
6011 self.menu_inline_completions_policy,
6012 MenuInlineCompletionsPolicy::ByProvider
6013 );
6014
6015 let show_in_menu = by_provider
6016 && self
6017 .edit_prediction_provider
6018 .as_ref()
6019 .map_or(false, |provider| {
6020 provider.provider.show_completions_in_menu()
6021 });
6022
6023 let preview_requires_modifier =
6024 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6025
6026 EditPredictionSettings::Enabled {
6027 show_in_menu,
6028 preview_requires_modifier,
6029 }
6030 }
6031
6032 fn should_show_edit_predictions(&self) -> bool {
6033 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6034 }
6035
6036 pub fn edit_prediction_preview_is_active(&self) -> bool {
6037 matches!(
6038 self.edit_prediction_preview,
6039 EditPredictionPreview::Active { .. }
6040 )
6041 }
6042
6043 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6044 let cursor = self.selections.newest_anchor().head();
6045 if let Some((buffer, cursor_position)) =
6046 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6047 {
6048 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6049 } else {
6050 false
6051 }
6052 }
6053
6054 fn edit_predictions_enabled_in_buffer(
6055 &self,
6056 buffer: &Entity<Buffer>,
6057 buffer_position: language::Anchor,
6058 cx: &App,
6059 ) -> bool {
6060 maybe!({
6061 if self.read_only(cx) {
6062 return Some(false);
6063 }
6064 let provider = self.edit_prediction_provider()?;
6065 if !provider.is_enabled(&buffer, buffer_position, cx) {
6066 return Some(false);
6067 }
6068 let buffer = buffer.read(cx);
6069 let Some(file) = buffer.file() else {
6070 return Some(true);
6071 };
6072 let settings = all_language_settings(Some(file), cx);
6073 Some(settings.edit_predictions_enabled_for_file(file, cx))
6074 })
6075 .unwrap_or(false)
6076 }
6077
6078 fn cycle_inline_completion(
6079 &mut self,
6080 direction: Direction,
6081 window: &mut Window,
6082 cx: &mut Context<Self>,
6083 ) -> Option<()> {
6084 let provider = self.edit_prediction_provider()?;
6085 let cursor = self.selections.newest_anchor().head();
6086 let (buffer, cursor_buffer_position) =
6087 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6088 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6089 return None;
6090 }
6091
6092 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6093 self.update_visible_inline_completion(window, cx);
6094
6095 Some(())
6096 }
6097
6098 pub fn show_inline_completion(
6099 &mut self,
6100 _: &ShowEditPrediction,
6101 window: &mut Window,
6102 cx: &mut Context<Self>,
6103 ) {
6104 if !self.has_active_inline_completion() {
6105 self.refresh_inline_completion(false, true, window, cx);
6106 return;
6107 }
6108
6109 self.update_visible_inline_completion(window, cx);
6110 }
6111
6112 pub fn display_cursor_names(
6113 &mut self,
6114 _: &DisplayCursorNames,
6115 window: &mut Window,
6116 cx: &mut Context<Self>,
6117 ) {
6118 self.show_cursor_names(window, cx);
6119 }
6120
6121 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6122 self.show_cursor_names = true;
6123 cx.notify();
6124 cx.spawn_in(window, async move |this, cx| {
6125 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6126 this.update(cx, |this, cx| {
6127 this.show_cursor_names = false;
6128 cx.notify()
6129 })
6130 .ok()
6131 })
6132 .detach();
6133 }
6134
6135 pub fn next_edit_prediction(
6136 &mut self,
6137 _: &NextEditPrediction,
6138 window: &mut Window,
6139 cx: &mut Context<Self>,
6140 ) {
6141 if self.has_active_inline_completion() {
6142 self.cycle_inline_completion(Direction::Next, window, cx);
6143 } else {
6144 let is_copilot_disabled = self
6145 .refresh_inline_completion(false, true, window, cx)
6146 .is_none();
6147 if is_copilot_disabled {
6148 cx.propagate();
6149 }
6150 }
6151 }
6152
6153 pub fn previous_edit_prediction(
6154 &mut self,
6155 _: &PreviousEditPrediction,
6156 window: &mut Window,
6157 cx: &mut Context<Self>,
6158 ) {
6159 if self.has_active_inline_completion() {
6160 self.cycle_inline_completion(Direction::Prev, window, cx);
6161 } else {
6162 let is_copilot_disabled = self
6163 .refresh_inline_completion(false, true, window, cx)
6164 .is_none();
6165 if is_copilot_disabled {
6166 cx.propagate();
6167 }
6168 }
6169 }
6170
6171 pub fn accept_edit_prediction(
6172 &mut self,
6173 _: &AcceptEditPrediction,
6174 window: &mut Window,
6175 cx: &mut Context<Self>,
6176 ) {
6177 if self.show_edit_predictions_in_menu() {
6178 self.hide_context_menu(window, cx);
6179 }
6180
6181 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6182 return;
6183 };
6184
6185 self.report_inline_completion_event(
6186 active_inline_completion.completion_id.clone(),
6187 true,
6188 cx,
6189 );
6190
6191 match &active_inline_completion.completion {
6192 InlineCompletion::Move { target, .. } => {
6193 let target = *target;
6194
6195 if let Some(position_map) = &self.last_position_map {
6196 if position_map
6197 .visible_row_range
6198 .contains(&target.to_display_point(&position_map.snapshot).row())
6199 || !self.edit_prediction_requires_modifier()
6200 {
6201 self.unfold_ranges(&[target..target], true, false, cx);
6202 // Note that this is also done in vim's handler of the Tab action.
6203 self.change_selections(
6204 Some(Autoscroll::newest()),
6205 window,
6206 cx,
6207 |selections| {
6208 selections.select_anchor_ranges([target..target]);
6209 },
6210 );
6211 self.clear_row_highlights::<EditPredictionPreview>();
6212
6213 self.edit_prediction_preview
6214 .set_previous_scroll_position(None);
6215 } else {
6216 self.edit_prediction_preview
6217 .set_previous_scroll_position(Some(
6218 position_map.snapshot.scroll_anchor,
6219 ));
6220
6221 self.highlight_rows::<EditPredictionPreview>(
6222 target..target,
6223 cx.theme().colors().editor_highlighted_line_background,
6224 RowHighlightOptions {
6225 autoscroll: true,
6226 ..Default::default()
6227 },
6228 cx,
6229 );
6230 self.request_autoscroll(Autoscroll::fit(), cx);
6231 }
6232 }
6233 }
6234 InlineCompletion::Edit { edits, .. } => {
6235 if let Some(provider) = self.edit_prediction_provider() {
6236 provider.accept(cx);
6237 }
6238
6239 let snapshot = self.buffer.read(cx).snapshot(cx);
6240 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6241
6242 self.buffer.update(cx, |buffer, cx| {
6243 buffer.edit(edits.iter().cloned(), None, cx)
6244 });
6245
6246 self.change_selections(None, window, cx, |s| {
6247 s.select_anchor_ranges([last_edit_end..last_edit_end])
6248 });
6249
6250 self.update_visible_inline_completion(window, cx);
6251 if self.active_inline_completion.is_none() {
6252 self.refresh_inline_completion(true, true, window, cx);
6253 }
6254
6255 cx.notify();
6256 }
6257 }
6258
6259 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6260 }
6261
6262 pub fn accept_partial_inline_completion(
6263 &mut self,
6264 _: &AcceptPartialEditPrediction,
6265 window: &mut Window,
6266 cx: &mut Context<Self>,
6267 ) {
6268 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6269 return;
6270 };
6271 if self.selections.count() != 1 {
6272 return;
6273 }
6274
6275 self.report_inline_completion_event(
6276 active_inline_completion.completion_id.clone(),
6277 true,
6278 cx,
6279 );
6280
6281 match &active_inline_completion.completion {
6282 InlineCompletion::Move { target, .. } => {
6283 let target = *target;
6284 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6285 selections.select_anchor_ranges([target..target]);
6286 });
6287 }
6288 InlineCompletion::Edit { edits, .. } => {
6289 // Find an insertion that starts at the cursor position.
6290 let snapshot = self.buffer.read(cx).snapshot(cx);
6291 let cursor_offset = self.selections.newest::<usize>(cx).head();
6292 let insertion = edits.iter().find_map(|(range, text)| {
6293 let range = range.to_offset(&snapshot);
6294 if range.is_empty() && range.start == cursor_offset {
6295 Some(text)
6296 } else {
6297 None
6298 }
6299 });
6300
6301 if let Some(text) = insertion {
6302 let mut partial_completion = text
6303 .chars()
6304 .by_ref()
6305 .take_while(|c| c.is_alphabetic())
6306 .collect::<String>();
6307 if partial_completion.is_empty() {
6308 partial_completion = text
6309 .chars()
6310 .by_ref()
6311 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6312 .collect::<String>();
6313 }
6314
6315 cx.emit(EditorEvent::InputHandled {
6316 utf16_range_to_replace: None,
6317 text: partial_completion.clone().into(),
6318 });
6319
6320 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6321
6322 self.refresh_inline_completion(true, true, window, cx);
6323 cx.notify();
6324 } else {
6325 self.accept_edit_prediction(&Default::default(), window, cx);
6326 }
6327 }
6328 }
6329 }
6330
6331 fn discard_inline_completion(
6332 &mut self,
6333 should_report_inline_completion_event: bool,
6334 cx: &mut Context<Self>,
6335 ) -> bool {
6336 if should_report_inline_completion_event {
6337 let completion_id = self
6338 .active_inline_completion
6339 .as_ref()
6340 .and_then(|active_completion| active_completion.completion_id.clone());
6341
6342 self.report_inline_completion_event(completion_id, false, cx);
6343 }
6344
6345 if let Some(provider) = self.edit_prediction_provider() {
6346 provider.discard(cx);
6347 }
6348
6349 self.take_active_inline_completion(cx)
6350 }
6351
6352 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6353 let Some(provider) = self.edit_prediction_provider() else {
6354 return;
6355 };
6356
6357 let Some((_, buffer, _)) = self
6358 .buffer
6359 .read(cx)
6360 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6361 else {
6362 return;
6363 };
6364
6365 let extension = buffer
6366 .read(cx)
6367 .file()
6368 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6369
6370 let event_type = match accepted {
6371 true => "Edit Prediction Accepted",
6372 false => "Edit Prediction Discarded",
6373 };
6374 telemetry::event!(
6375 event_type,
6376 provider = provider.name(),
6377 prediction_id = id,
6378 suggestion_accepted = accepted,
6379 file_extension = extension,
6380 );
6381 }
6382
6383 pub fn has_active_inline_completion(&self) -> bool {
6384 self.active_inline_completion.is_some()
6385 }
6386
6387 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6388 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6389 return false;
6390 };
6391
6392 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6393 self.clear_highlights::<InlineCompletionHighlight>(cx);
6394 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6395 true
6396 }
6397
6398 /// Returns true when we're displaying the edit prediction popover below the cursor
6399 /// like we are not previewing and the LSP autocomplete menu is visible
6400 /// or we are in `when_holding_modifier` mode.
6401 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6402 if self.edit_prediction_preview_is_active()
6403 || !self.show_edit_predictions_in_menu()
6404 || !self.edit_predictions_enabled()
6405 {
6406 return false;
6407 }
6408
6409 if self.has_visible_completions_menu() {
6410 return true;
6411 }
6412
6413 has_completion && self.edit_prediction_requires_modifier()
6414 }
6415
6416 fn handle_modifiers_changed(
6417 &mut self,
6418 modifiers: Modifiers,
6419 position_map: &PositionMap,
6420 window: &mut Window,
6421 cx: &mut Context<Self>,
6422 ) {
6423 if self.show_edit_predictions_in_menu() {
6424 self.update_edit_prediction_preview(&modifiers, window, cx);
6425 }
6426
6427 self.update_selection_mode(&modifiers, position_map, window, cx);
6428
6429 let mouse_position = window.mouse_position();
6430 if !position_map.text_hitbox.is_hovered(window) {
6431 return;
6432 }
6433
6434 self.update_hovered_link(
6435 position_map.point_for_position(mouse_position),
6436 &position_map.snapshot,
6437 modifiers,
6438 window,
6439 cx,
6440 )
6441 }
6442
6443 fn update_selection_mode(
6444 &mut self,
6445 modifiers: &Modifiers,
6446 position_map: &PositionMap,
6447 window: &mut Window,
6448 cx: &mut Context<Self>,
6449 ) {
6450 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6451 return;
6452 }
6453
6454 let mouse_position = window.mouse_position();
6455 let point_for_position = position_map.point_for_position(mouse_position);
6456 let position = point_for_position.previous_valid;
6457
6458 self.select(
6459 SelectPhase::BeginColumnar {
6460 position,
6461 reset: false,
6462 goal_column: point_for_position.exact_unclipped.column(),
6463 },
6464 window,
6465 cx,
6466 );
6467 }
6468
6469 fn update_edit_prediction_preview(
6470 &mut self,
6471 modifiers: &Modifiers,
6472 window: &mut Window,
6473 cx: &mut Context<Self>,
6474 ) {
6475 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6476 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6477 return;
6478 };
6479
6480 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6481 if matches!(
6482 self.edit_prediction_preview,
6483 EditPredictionPreview::Inactive { .. }
6484 ) {
6485 self.edit_prediction_preview = EditPredictionPreview::Active {
6486 previous_scroll_position: None,
6487 since: Instant::now(),
6488 };
6489
6490 self.update_visible_inline_completion(window, cx);
6491 cx.notify();
6492 }
6493 } else if let EditPredictionPreview::Active {
6494 previous_scroll_position,
6495 since,
6496 } = self.edit_prediction_preview
6497 {
6498 if let (Some(previous_scroll_position), Some(position_map)) =
6499 (previous_scroll_position, self.last_position_map.as_ref())
6500 {
6501 self.set_scroll_position(
6502 previous_scroll_position
6503 .scroll_position(&position_map.snapshot.display_snapshot),
6504 window,
6505 cx,
6506 );
6507 }
6508
6509 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6510 released_too_fast: since.elapsed() < Duration::from_millis(200),
6511 };
6512 self.clear_row_highlights::<EditPredictionPreview>();
6513 self.update_visible_inline_completion(window, cx);
6514 cx.notify();
6515 }
6516 }
6517
6518 fn update_visible_inline_completion(
6519 &mut self,
6520 _window: &mut Window,
6521 cx: &mut Context<Self>,
6522 ) -> Option<()> {
6523 let selection = self.selections.newest_anchor();
6524 let cursor = selection.head();
6525 let multibuffer = self.buffer.read(cx).snapshot(cx);
6526 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6527 let excerpt_id = cursor.excerpt_id;
6528
6529 let show_in_menu = self.show_edit_predictions_in_menu();
6530 let completions_menu_has_precedence = !show_in_menu
6531 && (self.context_menu.borrow().is_some()
6532 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6533
6534 if completions_menu_has_precedence
6535 || !offset_selection.is_empty()
6536 || self
6537 .active_inline_completion
6538 .as_ref()
6539 .map_or(false, |completion| {
6540 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6541 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6542 !invalidation_range.contains(&offset_selection.head())
6543 })
6544 {
6545 self.discard_inline_completion(false, cx);
6546 return None;
6547 }
6548
6549 self.take_active_inline_completion(cx);
6550 let Some(provider) = self.edit_prediction_provider() else {
6551 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6552 return None;
6553 };
6554
6555 let (buffer, cursor_buffer_position) =
6556 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6557
6558 self.edit_prediction_settings =
6559 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6560
6561 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6562
6563 if self.edit_prediction_indent_conflict {
6564 let cursor_point = cursor.to_point(&multibuffer);
6565
6566 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6567
6568 if let Some((_, indent)) = indents.iter().next() {
6569 if indent.len == cursor_point.column {
6570 self.edit_prediction_indent_conflict = false;
6571 }
6572 }
6573 }
6574
6575 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6576 let edits = inline_completion
6577 .edits
6578 .into_iter()
6579 .flat_map(|(range, new_text)| {
6580 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6581 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6582 Some((start..end, new_text))
6583 })
6584 .collect::<Vec<_>>();
6585 if edits.is_empty() {
6586 return None;
6587 }
6588
6589 let first_edit_start = edits.first().unwrap().0.start;
6590 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6591 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6592
6593 let last_edit_end = edits.last().unwrap().0.end;
6594 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6595 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6596
6597 let cursor_row = cursor.to_point(&multibuffer).row;
6598
6599 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6600
6601 let mut inlay_ids = Vec::new();
6602 let invalidation_row_range;
6603 let move_invalidation_row_range = if cursor_row < edit_start_row {
6604 Some(cursor_row..edit_end_row)
6605 } else if cursor_row > edit_end_row {
6606 Some(edit_start_row..cursor_row)
6607 } else {
6608 None
6609 };
6610 let is_move =
6611 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6612 let completion = if is_move {
6613 invalidation_row_range =
6614 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6615 let target = first_edit_start;
6616 InlineCompletion::Move { target, snapshot }
6617 } else {
6618 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6619 && !self.inline_completions_hidden_for_vim_mode;
6620
6621 if show_completions_in_buffer {
6622 if edits
6623 .iter()
6624 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6625 {
6626 let mut inlays = Vec::new();
6627 for (range, new_text) in &edits {
6628 let inlay = Inlay::inline_completion(
6629 post_inc(&mut self.next_inlay_id),
6630 range.start,
6631 new_text.as_str(),
6632 );
6633 inlay_ids.push(inlay.id);
6634 inlays.push(inlay);
6635 }
6636
6637 self.splice_inlays(&[], inlays, cx);
6638 } else {
6639 let background_color = cx.theme().status().deleted_background;
6640 self.highlight_text::<InlineCompletionHighlight>(
6641 edits.iter().map(|(range, _)| range.clone()).collect(),
6642 HighlightStyle {
6643 background_color: Some(background_color),
6644 ..Default::default()
6645 },
6646 cx,
6647 );
6648 }
6649 }
6650
6651 invalidation_row_range = edit_start_row..edit_end_row;
6652
6653 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6654 if provider.show_tab_accept_marker() {
6655 EditDisplayMode::TabAccept
6656 } else {
6657 EditDisplayMode::Inline
6658 }
6659 } else {
6660 EditDisplayMode::DiffPopover
6661 };
6662
6663 InlineCompletion::Edit {
6664 edits,
6665 edit_preview: inline_completion.edit_preview,
6666 display_mode,
6667 snapshot,
6668 }
6669 };
6670
6671 let invalidation_range = multibuffer
6672 .anchor_before(Point::new(invalidation_row_range.start, 0))
6673 ..multibuffer.anchor_after(Point::new(
6674 invalidation_row_range.end,
6675 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6676 ));
6677
6678 self.stale_inline_completion_in_menu = None;
6679 self.active_inline_completion = Some(InlineCompletionState {
6680 inlay_ids,
6681 completion,
6682 completion_id: inline_completion.id,
6683 invalidation_range,
6684 });
6685
6686 cx.notify();
6687
6688 Some(())
6689 }
6690
6691 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6692 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6693 }
6694
6695 fn render_code_actions_indicator(
6696 &self,
6697 _style: &EditorStyle,
6698 row: DisplayRow,
6699 is_active: bool,
6700 breakpoint: Option<&(Anchor, Breakpoint)>,
6701 cx: &mut Context<Self>,
6702 ) -> Option<IconButton> {
6703 let color = Color::Muted;
6704 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6705 let show_tooltip = !self.context_menu_visible();
6706
6707 if self.available_code_actions.is_some() {
6708 Some(
6709 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6710 .shape(ui::IconButtonShape::Square)
6711 .icon_size(IconSize::XSmall)
6712 .icon_color(color)
6713 .toggle_state(is_active)
6714 .when(show_tooltip, |this| {
6715 this.tooltip({
6716 let focus_handle = self.focus_handle.clone();
6717 move |window, cx| {
6718 Tooltip::for_action_in(
6719 "Toggle Code Actions",
6720 &ToggleCodeActions {
6721 deployed_from_indicator: None,
6722 quick_launch: false,
6723 },
6724 &focus_handle,
6725 window,
6726 cx,
6727 )
6728 }
6729 })
6730 })
6731 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
6732 let quick_launch = e.down.button == MouseButton::Left;
6733 window.focus(&editor.focus_handle(cx));
6734 editor.toggle_code_actions(
6735 &ToggleCodeActions {
6736 deployed_from_indicator: Some(row),
6737 quick_launch,
6738 },
6739 window,
6740 cx,
6741 );
6742 }))
6743 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6744 editor.set_breakpoint_context_menu(
6745 row,
6746 position,
6747 event.down.position,
6748 window,
6749 cx,
6750 );
6751 })),
6752 )
6753 } else {
6754 None
6755 }
6756 }
6757
6758 fn clear_tasks(&mut self) {
6759 self.tasks.clear()
6760 }
6761
6762 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6763 if self.tasks.insert(key, value).is_some() {
6764 // This case should hopefully be rare, but just in case...
6765 log::error!(
6766 "multiple different run targets found on a single line, only the last target will be rendered"
6767 )
6768 }
6769 }
6770
6771 /// Get all display points of breakpoints that will be rendered within editor
6772 ///
6773 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6774 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6775 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6776 fn active_breakpoints(
6777 &self,
6778 range: Range<DisplayRow>,
6779 window: &mut Window,
6780 cx: &mut Context<Self>,
6781 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6782 let mut breakpoint_display_points = HashMap::default();
6783
6784 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6785 return breakpoint_display_points;
6786 };
6787
6788 let snapshot = self.snapshot(window, cx);
6789
6790 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6791 let Some(project) = self.project.as_ref() else {
6792 return breakpoint_display_points;
6793 };
6794
6795 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6796 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6797
6798 for (buffer_snapshot, range, excerpt_id) in
6799 multi_buffer_snapshot.range_to_buffer_ranges(range)
6800 {
6801 let Some(buffer) = project.read_with(cx, |this, cx| {
6802 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6803 }) else {
6804 continue;
6805 };
6806 let breakpoints = breakpoint_store.read(cx).breakpoints(
6807 &buffer,
6808 Some(
6809 buffer_snapshot.anchor_before(range.start)
6810 ..buffer_snapshot.anchor_after(range.end),
6811 ),
6812 buffer_snapshot,
6813 cx,
6814 );
6815 for (anchor, breakpoint) in breakpoints {
6816 let multi_buffer_anchor =
6817 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6818 let position = multi_buffer_anchor
6819 .to_point(&multi_buffer_snapshot)
6820 .to_display_point(&snapshot);
6821
6822 breakpoint_display_points
6823 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6824 }
6825 }
6826
6827 breakpoint_display_points
6828 }
6829
6830 fn breakpoint_context_menu(
6831 &self,
6832 anchor: Anchor,
6833 window: &mut Window,
6834 cx: &mut Context<Self>,
6835 ) -> Entity<ui::ContextMenu> {
6836 let weak_editor = cx.weak_entity();
6837 let focus_handle = self.focus_handle(cx);
6838
6839 let row = self
6840 .buffer
6841 .read(cx)
6842 .snapshot(cx)
6843 .summary_for_anchor::<Point>(&anchor)
6844 .row;
6845
6846 let breakpoint = self
6847 .breakpoint_at_row(row, window, cx)
6848 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6849
6850 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6851 "Edit Log Breakpoint"
6852 } else {
6853 "Set Log Breakpoint"
6854 };
6855
6856 let condition_breakpoint_msg = if breakpoint
6857 .as_ref()
6858 .is_some_and(|bp| bp.1.condition.is_some())
6859 {
6860 "Edit Condition Breakpoint"
6861 } else {
6862 "Set Condition Breakpoint"
6863 };
6864
6865 let hit_condition_breakpoint_msg = if breakpoint
6866 .as_ref()
6867 .is_some_and(|bp| bp.1.hit_condition.is_some())
6868 {
6869 "Edit Hit Condition Breakpoint"
6870 } else {
6871 "Set Hit Condition Breakpoint"
6872 };
6873
6874 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6875 "Unset Breakpoint"
6876 } else {
6877 "Set Breakpoint"
6878 };
6879
6880 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6881 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6882
6883 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6884 BreakpointState::Enabled => Some("Disable"),
6885 BreakpointState::Disabled => Some("Enable"),
6886 });
6887
6888 let (anchor, breakpoint) =
6889 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6890
6891 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6892 menu.on_blur_subscription(Subscription::new(|| {}))
6893 .context(focus_handle)
6894 .when(run_to_cursor, |this| {
6895 let weak_editor = weak_editor.clone();
6896 this.entry("Run to cursor", None, move |window, cx| {
6897 weak_editor
6898 .update(cx, |editor, cx| {
6899 editor.change_selections(None, window, cx, |s| {
6900 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6901 });
6902 })
6903 .ok();
6904
6905 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6906 })
6907 .separator()
6908 })
6909 .when_some(toggle_state_msg, |this, msg| {
6910 this.entry(msg, None, {
6911 let weak_editor = weak_editor.clone();
6912 let breakpoint = breakpoint.clone();
6913 move |_window, cx| {
6914 weak_editor
6915 .update(cx, |this, cx| {
6916 this.edit_breakpoint_at_anchor(
6917 anchor,
6918 breakpoint.as_ref().clone(),
6919 BreakpointEditAction::InvertState,
6920 cx,
6921 );
6922 })
6923 .log_err();
6924 }
6925 })
6926 })
6927 .entry(set_breakpoint_msg, None, {
6928 let weak_editor = weak_editor.clone();
6929 let breakpoint = breakpoint.clone();
6930 move |_window, cx| {
6931 weak_editor
6932 .update(cx, |this, cx| {
6933 this.edit_breakpoint_at_anchor(
6934 anchor,
6935 breakpoint.as_ref().clone(),
6936 BreakpointEditAction::Toggle,
6937 cx,
6938 );
6939 })
6940 .log_err();
6941 }
6942 })
6943 .entry(log_breakpoint_msg, None, {
6944 let breakpoint = breakpoint.clone();
6945 let weak_editor = weak_editor.clone();
6946 move |window, cx| {
6947 weak_editor
6948 .update(cx, |this, cx| {
6949 this.add_edit_breakpoint_block(
6950 anchor,
6951 breakpoint.as_ref(),
6952 BreakpointPromptEditAction::Log,
6953 window,
6954 cx,
6955 );
6956 })
6957 .log_err();
6958 }
6959 })
6960 .entry(condition_breakpoint_msg, None, {
6961 let breakpoint = breakpoint.clone();
6962 let weak_editor = weak_editor.clone();
6963 move |window, cx| {
6964 weak_editor
6965 .update(cx, |this, cx| {
6966 this.add_edit_breakpoint_block(
6967 anchor,
6968 breakpoint.as_ref(),
6969 BreakpointPromptEditAction::Condition,
6970 window,
6971 cx,
6972 );
6973 })
6974 .log_err();
6975 }
6976 })
6977 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6978 weak_editor
6979 .update(cx, |this, cx| {
6980 this.add_edit_breakpoint_block(
6981 anchor,
6982 breakpoint.as_ref(),
6983 BreakpointPromptEditAction::HitCondition,
6984 window,
6985 cx,
6986 );
6987 })
6988 .log_err();
6989 })
6990 })
6991 }
6992
6993 fn render_breakpoint(
6994 &self,
6995 position: Anchor,
6996 row: DisplayRow,
6997 breakpoint: &Breakpoint,
6998 cx: &mut Context<Self>,
6999 ) -> IconButton {
7000 // Is it a breakpoint that shows up when hovering over gutter?
7001 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7002 (false, false),
7003 |PhantomBreakpointIndicator {
7004 is_active,
7005 display_row,
7006 collides_with_existing_breakpoint,
7007 }| {
7008 (
7009 is_active && display_row == row,
7010 collides_with_existing_breakpoint,
7011 )
7012 },
7013 );
7014
7015 let (color, icon) = {
7016 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7017 (false, false) => ui::IconName::DebugBreakpoint,
7018 (true, false) => ui::IconName::DebugLogBreakpoint,
7019 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7020 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7021 };
7022
7023 let color = if is_phantom {
7024 Color::Hint
7025 } else {
7026 Color::Debugger
7027 };
7028
7029 (color, icon)
7030 };
7031
7032 let breakpoint = Arc::from(breakpoint.clone());
7033
7034 let alt_as_text = gpui::Keystroke {
7035 modifiers: Modifiers::secondary_key(),
7036 ..Default::default()
7037 };
7038 let primary_action_text = if breakpoint.is_disabled() {
7039 "enable"
7040 } else if is_phantom && !collides_with_existing {
7041 "set"
7042 } else {
7043 "unset"
7044 };
7045 let mut primary_text = format!("Click to {primary_action_text}");
7046 if collides_with_existing && !breakpoint.is_disabled() {
7047 use std::fmt::Write;
7048 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7049 }
7050 let primary_text = SharedString::from(primary_text);
7051 let focus_handle = self.focus_handle.clone();
7052 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7053 .icon_size(IconSize::XSmall)
7054 .size(ui::ButtonSize::None)
7055 .icon_color(color)
7056 .style(ButtonStyle::Transparent)
7057 .on_click(cx.listener({
7058 let breakpoint = breakpoint.clone();
7059
7060 move |editor, event: &ClickEvent, window, cx| {
7061 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7062 BreakpointEditAction::InvertState
7063 } else {
7064 BreakpointEditAction::Toggle
7065 };
7066
7067 window.focus(&editor.focus_handle(cx));
7068 editor.edit_breakpoint_at_anchor(
7069 position,
7070 breakpoint.as_ref().clone(),
7071 edit_action,
7072 cx,
7073 );
7074 }
7075 }))
7076 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7077 editor.set_breakpoint_context_menu(
7078 row,
7079 Some(position),
7080 event.down.position,
7081 window,
7082 cx,
7083 );
7084 }))
7085 .tooltip(move |window, cx| {
7086 Tooltip::with_meta_in(
7087 primary_text.clone(),
7088 None,
7089 "Right-click for more options",
7090 &focus_handle,
7091 window,
7092 cx,
7093 )
7094 })
7095 }
7096
7097 fn build_tasks_context(
7098 project: &Entity<Project>,
7099 buffer: &Entity<Buffer>,
7100 buffer_row: u32,
7101 tasks: &Arc<RunnableTasks>,
7102 cx: &mut Context<Self>,
7103 ) -> Task<Option<task::TaskContext>> {
7104 let position = Point::new(buffer_row, tasks.column);
7105 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7106 let location = Location {
7107 buffer: buffer.clone(),
7108 range: range_start..range_start,
7109 };
7110 // Fill in the environmental variables from the tree-sitter captures
7111 let mut captured_task_variables = TaskVariables::default();
7112 for (capture_name, value) in tasks.extra_variables.clone() {
7113 captured_task_variables.insert(
7114 task::VariableName::Custom(capture_name.into()),
7115 value.clone(),
7116 );
7117 }
7118 project.update(cx, |project, cx| {
7119 project.task_store().update(cx, |task_store, cx| {
7120 task_store.task_context_for_location(captured_task_variables, location, cx)
7121 })
7122 })
7123 }
7124
7125 pub fn spawn_nearest_task(
7126 &mut self,
7127 action: &SpawnNearestTask,
7128 window: &mut Window,
7129 cx: &mut Context<Self>,
7130 ) {
7131 let Some((workspace, _)) = self.workspace.clone() else {
7132 return;
7133 };
7134 let Some(project) = self.project.clone() else {
7135 return;
7136 };
7137
7138 // Try to find a closest, enclosing node using tree-sitter that has a
7139 // task
7140 let Some((buffer, buffer_row, tasks)) = self
7141 .find_enclosing_node_task(cx)
7142 // Or find the task that's closest in row-distance.
7143 .or_else(|| self.find_closest_task(cx))
7144 else {
7145 return;
7146 };
7147
7148 let reveal_strategy = action.reveal;
7149 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7150 cx.spawn_in(window, async move |_, cx| {
7151 let context = task_context.await?;
7152 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7153
7154 let resolved = &mut resolved_task.resolved;
7155 resolved.reveal = reveal_strategy;
7156
7157 workspace
7158 .update_in(cx, |workspace, window, cx| {
7159 workspace.schedule_resolved_task(
7160 task_source_kind,
7161 resolved_task,
7162 false,
7163 window,
7164 cx,
7165 );
7166 })
7167 .ok()
7168 })
7169 .detach();
7170 }
7171
7172 fn find_closest_task(
7173 &mut self,
7174 cx: &mut Context<Self>,
7175 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7176 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7177
7178 let ((buffer_id, row), tasks) = self
7179 .tasks
7180 .iter()
7181 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7182
7183 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7184 let tasks = Arc::new(tasks.to_owned());
7185 Some((buffer, *row, tasks))
7186 }
7187
7188 fn find_enclosing_node_task(
7189 &mut self,
7190 cx: &mut Context<Self>,
7191 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7192 let snapshot = self.buffer.read(cx).snapshot(cx);
7193 let offset = self.selections.newest::<usize>(cx).head();
7194 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7195 let buffer_id = excerpt.buffer().remote_id();
7196
7197 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7198 let mut cursor = layer.node().walk();
7199
7200 while cursor.goto_first_child_for_byte(offset).is_some() {
7201 if cursor.node().end_byte() == offset {
7202 cursor.goto_next_sibling();
7203 }
7204 }
7205
7206 // Ascend to the smallest ancestor that contains the range and has a task.
7207 loop {
7208 let node = cursor.node();
7209 let node_range = node.byte_range();
7210 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7211
7212 // Check if this node contains our offset
7213 if node_range.start <= offset && node_range.end >= offset {
7214 // If it contains offset, check for task
7215 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7216 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7217 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7218 }
7219 }
7220
7221 if !cursor.goto_parent() {
7222 break;
7223 }
7224 }
7225 None
7226 }
7227
7228 fn render_run_indicator(
7229 &self,
7230 _style: &EditorStyle,
7231 is_active: bool,
7232 row: DisplayRow,
7233 breakpoint: Option<(Anchor, Breakpoint)>,
7234 cx: &mut Context<Self>,
7235 ) -> IconButton {
7236 let color = Color::Muted;
7237 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
7238
7239 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7240 .shape(ui::IconButtonShape::Square)
7241 .icon_size(IconSize::XSmall)
7242 .icon_color(color)
7243 .toggle_state(is_active)
7244 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7245 let quick_launch = e.down.button == MouseButton::Left;
7246 window.focus(&editor.focus_handle(cx));
7247 editor.toggle_code_actions(
7248 &ToggleCodeActions {
7249 deployed_from_indicator: Some(row),
7250 quick_launch,
7251 },
7252 window,
7253 cx,
7254 );
7255 }))
7256 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7257 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7258 }))
7259 }
7260
7261 pub fn context_menu_visible(&self) -> bool {
7262 !self.edit_prediction_preview_is_active()
7263 && self
7264 .context_menu
7265 .borrow()
7266 .as_ref()
7267 .map_or(false, |menu| menu.visible())
7268 }
7269
7270 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7271 self.context_menu
7272 .borrow()
7273 .as_ref()
7274 .map(|menu| menu.origin())
7275 }
7276
7277 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7278 self.context_menu_options = Some(options);
7279 }
7280
7281 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7282 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7283
7284 fn render_edit_prediction_popover(
7285 &mut self,
7286 text_bounds: &Bounds<Pixels>,
7287 content_origin: gpui::Point<Pixels>,
7288 editor_snapshot: &EditorSnapshot,
7289 visible_row_range: Range<DisplayRow>,
7290 scroll_top: f32,
7291 scroll_bottom: f32,
7292 line_layouts: &[LineWithInvisibles],
7293 line_height: Pixels,
7294 scroll_pixel_position: gpui::Point<Pixels>,
7295 newest_selection_head: Option<DisplayPoint>,
7296 editor_width: Pixels,
7297 style: &EditorStyle,
7298 window: &mut Window,
7299 cx: &mut App,
7300 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7301 let active_inline_completion = self.active_inline_completion.as_ref()?;
7302
7303 if self.edit_prediction_visible_in_cursor_popover(true) {
7304 return None;
7305 }
7306
7307 match &active_inline_completion.completion {
7308 InlineCompletion::Move { target, .. } => {
7309 let target_display_point = target.to_display_point(editor_snapshot);
7310
7311 if self.edit_prediction_requires_modifier() {
7312 if !self.edit_prediction_preview_is_active() {
7313 return None;
7314 }
7315
7316 self.render_edit_prediction_modifier_jump_popover(
7317 text_bounds,
7318 content_origin,
7319 visible_row_range,
7320 line_layouts,
7321 line_height,
7322 scroll_pixel_position,
7323 newest_selection_head,
7324 target_display_point,
7325 window,
7326 cx,
7327 )
7328 } else {
7329 self.render_edit_prediction_eager_jump_popover(
7330 text_bounds,
7331 content_origin,
7332 editor_snapshot,
7333 visible_row_range,
7334 scroll_top,
7335 scroll_bottom,
7336 line_height,
7337 scroll_pixel_position,
7338 target_display_point,
7339 editor_width,
7340 window,
7341 cx,
7342 )
7343 }
7344 }
7345 InlineCompletion::Edit {
7346 display_mode: EditDisplayMode::Inline,
7347 ..
7348 } => None,
7349 InlineCompletion::Edit {
7350 display_mode: EditDisplayMode::TabAccept,
7351 edits,
7352 ..
7353 } => {
7354 let range = &edits.first()?.0;
7355 let target_display_point = range.end.to_display_point(editor_snapshot);
7356
7357 self.render_edit_prediction_end_of_line_popover(
7358 "Accept",
7359 editor_snapshot,
7360 visible_row_range,
7361 target_display_point,
7362 line_height,
7363 scroll_pixel_position,
7364 content_origin,
7365 editor_width,
7366 window,
7367 cx,
7368 )
7369 }
7370 InlineCompletion::Edit {
7371 edits,
7372 edit_preview,
7373 display_mode: EditDisplayMode::DiffPopover,
7374 snapshot,
7375 } => self.render_edit_prediction_diff_popover(
7376 text_bounds,
7377 content_origin,
7378 editor_snapshot,
7379 visible_row_range,
7380 line_layouts,
7381 line_height,
7382 scroll_pixel_position,
7383 newest_selection_head,
7384 editor_width,
7385 style,
7386 edits,
7387 edit_preview,
7388 snapshot,
7389 window,
7390 cx,
7391 ),
7392 }
7393 }
7394
7395 fn render_edit_prediction_modifier_jump_popover(
7396 &mut self,
7397 text_bounds: &Bounds<Pixels>,
7398 content_origin: gpui::Point<Pixels>,
7399 visible_row_range: Range<DisplayRow>,
7400 line_layouts: &[LineWithInvisibles],
7401 line_height: Pixels,
7402 scroll_pixel_position: gpui::Point<Pixels>,
7403 newest_selection_head: Option<DisplayPoint>,
7404 target_display_point: DisplayPoint,
7405 window: &mut Window,
7406 cx: &mut App,
7407 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7408 let scrolled_content_origin =
7409 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7410
7411 const SCROLL_PADDING_Y: Pixels = px(12.);
7412
7413 if target_display_point.row() < visible_row_range.start {
7414 return self.render_edit_prediction_scroll_popover(
7415 |_| SCROLL_PADDING_Y,
7416 IconName::ArrowUp,
7417 visible_row_range,
7418 line_layouts,
7419 newest_selection_head,
7420 scrolled_content_origin,
7421 window,
7422 cx,
7423 );
7424 } else if target_display_point.row() >= visible_row_range.end {
7425 return self.render_edit_prediction_scroll_popover(
7426 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7427 IconName::ArrowDown,
7428 visible_row_range,
7429 line_layouts,
7430 newest_selection_head,
7431 scrolled_content_origin,
7432 window,
7433 cx,
7434 );
7435 }
7436
7437 const POLE_WIDTH: Pixels = px(2.);
7438
7439 let line_layout =
7440 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7441 let target_column = target_display_point.column() as usize;
7442
7443 let target_x = line_layout.x_for_index(target_column);
7444 let target_y =
7445 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7446
7447 let flag_on_right = target_x < text_bounds.size.width / 2.;
7448
7449 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7450 border_color.l += 0.001;
7451
7452 let mut element = v_flex()
7453 .items_end()
7454 .when(flag_on_right, |el| el.items_start())
7455 .child(if flag_on_right {
7456 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7457 .rounded_bl(px(0.))
7458 .rounded_tl(px(0.))
7459 .border_l_2()
7460 .border_color(border_color)
7461 } else {
7462 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7463 .rounded_br(px(0.))
7464 .rounded_tr(px(0.))
7465 .border_r_2()
7466 .border_color(border_color)
7467 })
7468 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7469 .into_any();
7470
7471 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7472
7473 let mut origin = scrolled_content_origin + point(target_x, target_y)
7474 - point(
7475 if flag_on_right {
7476 POLE_WIDTH
7477 } else {
7478 size.width - POLE_WIDTH
7479 },
7480 size.height - line_height,
7481 );
7482
7483 origin.x = origin.x.max(content_origin.x);
7484
7485 element.prepaint_at(origin, window, cx);
7486
7487 Some((element, origin))
7488 }
7489
7490 fn render_edit_prediction_scroll_popover(
7491 &mut self,
7492 to_y: impl Fn(Size<Pixels>) -> Pixels,
7493 scroll_icon: IconName,
7494 visible_row_range: Range<DisplayRow>,
7495 line_layouts: &[LineWithInvisibles],
7496 newest_selection_head: Option<DisplayPoint>,
7497 scrolled_content_origin: gpui::Point<Pixels>,
7498 window: &mut Window,
7499 cx: &mut App,
7500 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7501 let mut element = self
7502 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7503 .into_any();
7504
7505 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7506
7507 let cursor = newest_selection_head?;
7508 let cursor_row_layout =
7509 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7510 let cursor_column = cursor.column() as usize;
7511
7512 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7513
7514 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7515
7516 element.prepaint_at(origin, window, cx);
7517 Some((element, origin))
7518 }
7519
7520 fn render_edit_prediction_eager_jump_popover(
7521 &mut self,
7522 text_bounds: &Bounds<Pixels>,
7523 content_origin: gpui::Point<Pixels>,
7524 editor_snapshot: &EditorSnapshot,
7525 visible_row_range: Range<DisplayRow>,
7526 scroll_top: f32,
7527 scroll_bottom: f32,
7528 line_height: Pixels,
7529 scroll_pixel_position: gpui::Point<Pixels>,
7530 target_display_point: DisplayPoint,
7531 editor_width: Pixels,
7532 window: &mut Window,
7533 cx: &mut App,
7534 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7535 if target_display_point.row().as_f32() < scroll_top {
7536 let mut element = self
7537 .render_edit_prediction_line_popover(
7538 "Jump to Edit",
7539 Some(IconName::ArrowUp),
7540 window,
7541 cx,
7542 )?
7543 .into_any();
7544
7545 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7546 let offset = point(
7547 (text_bounds.size.width - size.width) / 2.,
7548 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7549 );
7550
7551 let origin = text_bounds.origin + offset;
7552 element.prepaint_at(origin, window, cx);
7553 Some((element, origin))
7554 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7555 let mut element = self
7556 .render_edit_prediction_line_popover(
7557 "Jump to Edit",
7558 Some(IconName::ArrowDown),
7559 window,
7560 cx,
7561 )?
7562 .into_any();
7563
7564 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7565 let offset = point(
7566 (text_bounds.size.width - size.width) / 2.,
7567 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7568 );
7569
7570 let origin = text_bounds.origin + offset;
7571 element.prepaint_at(origin, window, cx);
7572 Some((element, origin))
7573 } else {
7574 self.render_edit_prediction_end_of_line_popover(
7575 "Jump to Edit",
7576 editor_snapshot,
7577 visible_row_range,
7578 target_display_point,
7579 line_height,
7580 scroll_pixel_position,
7581 content_origin,
7582 editor_width,
7583 window,
7584 cx,
7585 )
7586 }
7587 }
7588
7589 fn render_edit_prediction_end_of_line_popover(
7590 self: &mut Editor,
7591 label: &'static str,
7592 editor_snapshot: &EditorSnapshot,
7593 visible_row_range: Range<DisplayRow>,
7594 target_display_point: DisplayPoint,
7595 line_height: Pixels,
7596 scroll_pixel_position: gpui::Point<Pixels>,
7597 content_origin: gpui::Point<Pixels>,
7598 editor_width: Pixels,
7599 window: &mut Window,
7600 cx: &mut App,
7601 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7602 let target_line_end = DisplayPoint::new(
7603 target_display_point.row(),
7604 editor_snapshot.line_len(target_display_point.row()),
7605 );
7606
7607 let mut element = self
7608 .render_edit_prediction_line_popover(label, None, window, cx)?
7609 .into_any();
7610
7611 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7612
7613 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7614
7615 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7616 let mut origin = start_point
7617 + line_origin
7618 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7619 origin.x = origin.x.max(content_origin.x);
7620
7621 let max_x = content_origin.x + editor_width - size.width;
7622
7623 if origin.x > max_x {
7624 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7625
7626 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7627 origin.y += offset;
7628 IconName::ArrowUp
7629 } else {
7630 origin.y -= offset;
7631 IconName::ArrowDown
7632 };
7633
7634 element = self
7635 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7636 .into_any();
7637
7638 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7639
7640 origin.x = content_origin.x + editor_width - size.width - px(2.);
7641 }
7642
7643 element.prepaint_at(origin, window, cx);
7644 Some((element, origin))
7645 }
7646
7647 fn render_edit_prediction_diff_popover(
7648 self: &Editor,
7649 text_bounds: &Bounds<Pixels>,
7650 content_origin: gpui::Point<Pixels>,
7651 editor_snapshot: &EditorSnapshot,
7652 visible_row_range: Range<DisplayRow>,
7653 line_layouts: &[LineWithInvisibles],
7654 line_height: Pixels,
7655 scroll_pixel_position: gpui::Point<Pixels>,
7656 newest_selection_head: Option<DisplayPoint>,
7657 editor_width: Pixels,
7658 style: &EditorStyle,
7659 edits: &Vec<(Range<Anchor>, String)>,
7660 edit_preview: &Option<language::EditPreview>,
7661 snapshot: &language::BufferSnapshot,
7662 window: &mut Window,
7663 cx: &mut App,
7664 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7665 let edit_start = edits
7666 .first()
7667 .unwrap()
7668 .0
7669 .start
7670 .to_display_point(editor_snapshot);
7671 let edit_end = edits
7672 .last()
7673 .unwrap()
7674 .0
7675 .end
7676 .to_display_point(editor_snapshot);
7677
7678 let is_visible = visible_row_range.contains(&edit_start.row())
7679 || visible_row_range.contains(&edit_end.row());
7680 if !is_visible {
7681 return None;
7682 }
7683
7684 let highlighted_edits =
7685 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7686
7687 let styled_text = highlighted_edits.to_styled_text(&style.text);
7688 let line_count = highlighted_edits.text.lines().count();
7689
7690 const BORDER_WIDTH: Pixels = px(1.);
7691
7692 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7693 let has_keybind = keybind.is_some();
7694
7695 let mut element = h_flex()
7696 .items_start()
7697 .child(
7698 h_flex()
7699 .bg(cx.theme().colors().editor_background)
7700 .border(BORDER_WIDTH)
7701 .shadow_sm()
7702 .border_color(cx.theme().colors().border)
7703 .rounded_l_lg()
7704 .when(line_count > 1, |el| el.rounded_br_lg())
7705 .pr_1()
7706 .child(styled_text),
7707 )
7708 .child(
7709 h_flex()
7710 .h(line_height + BORDER_WIDTH * 2.)
7711 .px_1p5()
7712 .gap_1()
7713 // Workaround: For some reason, there's a gap if we don't do this
7714 .ml(-BORDER_WIDTH)
7715 .shadow(smallvec![gpui::BoxShadow {
7716 color: gpui::black().opacity(0.05),
7717 offset: point(px(1.), px(1.)),
7718 blur_radius: px(2.),
7719 spread_radius: px(0.),
7720 }])
7721 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7722 .border(BORDER_WIDTH)
7723 .border_color(cx.theme().colors().border)
7724 .rounded_r_lg()
7725 .id("edit_prediction_diff_popover_keybind")
7726 .when(!has_keybind, |el| {
7727 let status_colors = cx.theme().status();
7728
7729 el.bg(status_colors.error_background)
7730 .border_color(status_colors.error.opacity(0.6))
7731 .child(Icon::new(IconName::Info).color(Color::Error))
7732 .cursor_default()
7733 .hoverable_tooltip(move |_window, cx| {
7734 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7735 })
7736 })
7737 .children(keybind),
7738 )
7739 .into_any();
7740
7741 let longest_row =
7742 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7743 let longest_line_width = if visible_row_range.contains(&longest_row) {
7744 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7745 } else {
7746 layout_line(
7747 longest_row,
7748 editor_snapshot,
7749 style,
7750 editor_width,
7751 |_| false,
7752 window,
7753 cx,
7754 )
7755 .width
7756 };
7757
7758 let viewport_bounds =
7759 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7760 right: -EditorElement::SCROLLBAR_WIDTH,
7761 ..Default::default()
7762 });
7763
7764 let x_after_longest =
7765 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7766 - scroll_pixel_position.x;
7767
7768 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7769
7770 // Fully visible if it can be displayed within the window (allow overlapping other
7771 // panes). However, this is only allowed if the popover starts within text_bounds.
7772 let can_position_to_the_right = x_after_longest < text_bounds.right()
7773 && x_after_longest + element_bounds.width < viewport_bounds.right();
7774
7775 let mut origin = if can_position_to_the_right {
7776 point(
7777 x_after_longest,
7778 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7779 - scroll_pixel_position.y,
7780 )
7781 } else {
7782 let cursor_row = newest_selection_head.map(|head| head.row());
7783 let above_edit = edit_start
7784 .row()
7785 .0
7786 .checked_sub(line_count as u32)
7787 .map(DisplayRow);
7788 let below_edit = Some(edit_end.row() + 1);
7789 let above_cursor =
7790 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7791 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7792
7793 // Place the edit popover adjacent to the edit if there is a location
7794 // available that is onscreen and does not obscure the cursor. Otherwise,
7795 // place it adjacent to the cursor.
7796 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7797 .into_iter()
7798 .flatten()
7799 .find(|&start_row| {
7800 let end_row = start_row + line_count as u32;
7801 visible_row_range.contains(&start_row)
7802 && visible_row_range.contains(&end_row)
7803 && cursor_row.map_or(true, |cursor_row| {
7804 !((start_row..end_row).contains(&cursor_row))
7805 })
7806 })?;
7807
7808 content_origin
7809 + point(
7810 -scroll_pixel_position.x,
7811 row_target.as_f32() * line_height - scroll_pixel_position.y,
7812 )
7813 };
7814
7815 origin.x -= BORDER_WIDTH;
7816
7817 window.defer_draw(element, origin, 1);
7818
7819 // Do not return an element, since it will already be drawn due to defer_draw.
7820 None
7821 }
7822
7823 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7824 px(30.)
7825 }
7826
7827 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7828 if self.read_only(cx) {
7829 cx.theme().players().read_only()
7830 } else {
7831 self.style.as_ref().unwrap().local_player
7832 }
7833 }
7834
7835 fn render_edit_prediction_accept_keybind(
7836 &self,
7837 window: &mut Window,
7838 cx: &App,
7839 ) -> Option<AnyElement> {
7840 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7841 let accept_keystroke = accept_binding.keystroke()?;
7842
7843 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7844
7845 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7846 Color::Accent
7847 } else {
7848 Color::Muted
7849 };
7850
7851 h_flex()
7852 .px_0p5()
7853 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7854 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7855 .text_size(TextSize::XSmall.rems(cx))
7856 .child(h_flex().children(ui::render_modifiers(
7857 &accept_keystroke.modifiers,
7858 PlatformStyle::platform(),
7859 Some(modifiers_color),
7860 Some(IconSize::XSmall.rems().into()),
7861 true,
7862 )))
7863 .when(is_platform_style_mac, |parent| {
7864 parent.child(accept_keystroke.key.clone())
7865 })
7866 .when(!is_platform_style_mac, |parent| {
7867 parent.child(
7868 Key::new(
7869 util::capitalize(&accept_keystroke.key),
7870 Some(Color::Default),
7871 )
7872 .size(Some(IconSize::XSmall.rems().into())),
7873 )
7874 })
7875 .into_any()
7876 .into()
7877 }
7878
7879 fn render_edit_prediction_line_popover(
7880 &self,
7881 label: impl Into<SharedString>,
7882 icon: Option<IconName>,
7883 window: &mut Window,
7884 cx: &App,
7885 ) -> Option<Stateful<Div>> {
7886 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7887
7888 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7889 let has_keybind = keybind.is_some();
7890
7891 let result = h_flex()
7892 .id("ep-line-popover")
7893 .py_0p5()
7894 .pl_1()
7895 .pr(padding_right)
7896 .gap_1()
7897 .rounded_md()
7898 .border_1()
7899 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7900 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7901 .shadow_sm()
7902 .when(!has_keybind, |el| {
7903 let status_colors = cx.theme().status();
7904
7905 el.bg(status_colors.error_background)
7906 .border_color(status_colors.error.opacity(0.6))
7907 .pl_2()
7908 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7909 .cursor_default()
7910 .hoverable_tooltip(move |_window, cx| {
7911 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7912 })
7913 })
7914 .children(keybind)
7915 .child(
7916 Label::new(label)
7917 .size(LabelSize::Small)
7918 .when(!has_keybind, |el| {
7919 el.color(cx.theme().status().error.into()).strikethrough()
7920 }),
7921 )
7922 .when(!has_keybind, |el| {
7923 el.child(
7924 h_flex().ml_1().child(
7925 Icon::new(IconName::Info)
7926 .size(IconSize::Small)
7927 .color(cx.theme().status().error.into()),
7928 ),
7929 )
7930 })
7931 .when_some(icon, |element, icon| {
7932 element.child(
7933 div()
7934 .mt(px(1.5))
7935 .child(Icon::new(icon).size(IconSize::Small)),
7936 )
7937 });
7938
7939 Some(result)
7940 }
7941
7942 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7943 let accent_color = cx.theme().colors().text_accent;
7944 let editor_bg_color = cx.theme().colors().editor_background;
7945 editor_bg_color.blend(accent_color.opacity(0.1))
7946 }
7947
7948 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7949 let accent_color = cx.theme().colors().text_accent;
7950 let editor_bg_color = cx.theme().colors().editor_background;
7951 editor_bg_color.blend(accent_color.opacity(0.6))
7952 }
7953
7954 fn render_edit_prediction_cursor_popover(
7955 &self,
7956 min_width: Pixels,
7957 max_width: Pixels,
7958 cursor_point: Point,
7959 style: &EditorStyle,
7960 accept_keystroke: Option<&gpui::Keystroke>,
7961 _window: &Window,
7962 cx: &mut Context<Editor>,
7963 ) -> Option<AnyElement> {
7964 let provider = self.edit_prediction_provider.as_ref()?;
7965
7966 if provider.provider.needs_terms_acceptance(cx) {
7967 return Some(
7968 h_flex()
7969 .min_w(min_width)
7970 .flex_1()
7971 .px_2()
7972 .py_1()
7973 .gap_3()
7974 .elevation_2(cx)
7975 .hover(|style| style.bg(cx.theme().colors().element_hover))
7976 .id("accept-terms")
7977 .cursor_pointer()
7978 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7979 .on_click(cx.listener(|this, _event, window, cx| {
7980 cx.stop_propagation();
7981 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7982 window.dispatch_action(
7983 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7984 cx,
7985 );
7986 }))
7987 .child(
7988 h_flex()
7989 .flex_1()
7990 .gap_2()
7991 .child(Icon::new(IconName::ZedPredict))
7992 .child(Label::new("Accept Terms of Service"))
7993 .child(div().w_full())
7994 .child(
7995 Icon::new(IconName::ArrowUpRight)
7996 .color(Color::Muted)
7997 .size(IconSize::Small),
7998 )
7999 .into_any_element(),
8000 )
8001 .into_any(),
8002 );
8003 }
8004
8005 let is_refreshing = provider.provider.is_refreshing(cx);
8006
8007 fn pending_completion_container() -> Div {
8008 h_flex()
8009 .h_full()
8010 .flex_1()
8011 .gap_2()
8012 .child(Icon::new(IconName::ZedPredict))
8013 }
8014
8015 let completion = match &self.active_inline_completion {
8016 Some(prediction) => {
8017 if !self.has_visible_completions_menu() {
8018 const RADIUS: Pixels = px(6.);
8019 const BORDER_WIDTH: Pixels = px(1.);
8020
8021 return Some(
8022 h_flex()
8023 .elevation_2(cx)
8024 .border(BORDER_WIDTH)
8025 .border_color(cx.theme().colors().border)
8026 .when(accept_keystroke.is_none(), |el| {
8027 el.border_color(cx.theme().status().error)
8028 })
8029 .rounded(RADIUS)
8030 .rounded_tl(px(0.))
8031 .overflow_hidden()
8032 .child(div().px_1p5().child(match &prediction.completion {
8033 InlineCompletion::Move { target, snapshot } => {
8034 use text::ToPoint as _;
8035 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8036 {
8037 Icon::new(IconName::ZedPredictDown)
8038 } else {
8039 Icon::new(IconName::ZedPredictUp)
8040 }
8041 }
8042 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8043 }))
8044 .child(
8045 h_flex()
8046 .gap_1()
8047 .py_1()
8048 .px_2()
8049 .rounded_r(RADIUS - BORDER_WIDTH)
8050 .border_l_1()
8051 .border_color(cx.theme().colors().border)
8052 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8053 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8054 el.child(
8055 Label::new("Hold")
8056 .size(LabelSize::Small)
8057 .when(accept_keystroke.is_none(), |el| {
8058 el.strikethrough()
8059 })
8060 .line_height_style(LineHeightStyle::UiLabel),
8061 )
8062 })
8063 .id("edit_prediction_cursor_popover_keybind")
8064 .when(accept_keystroke.is_none(), |el| {
8065 let status_colors = cx.theme().status();
8066
8067 el.bg(status_colors.error_background)
8068 .border_color(status_colors.error.opacity(0.6))
8069 .child(Icon::new(IconName::Info).color(Color::Error))
8070 .cursor_default()
8071 .hoverable_tooltip(move |_window, cx| {
8072 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8073 .into()
8074 })
8075 })
8076 .when_some(
8077 accept_keystroke.as_ref(),
8078 |el, accept_keystroke| {
8079 el.child(h_flex().children(ui::render_modifiers(
8080 &accept_keystroke.modifiers,
8081 PlatformStyle::platform(),
8082 Some(Color::Default),
8083 Some(IconSize::XSmall.rems().into()),
8084 false,
8085 )))
8086 },
8087 ),
8088 )
8089 .into_any(),
8090 );
8091 }
8092
8093 self.render_edit_prediction_cursor_popover_preview(
8094 prediction,
8095 cursor_point,
8096 style,
8097 cx,
8098 )?
8099 }
8100
8101 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8102 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8103 stale_completion,
8104 cursor_point,
8105 style,
8106 cx,
8107 )?,
8108
8109 None => {
8110 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8111 }
8112 },
8113
8114 None => pending_completion_container().child(Label::new("No Prediction")),
8115 };
8116
8117 let completion = if is_refreshing {
8118 completion
8119 .with_animation(
8120 "loading-completion",
8121 Animation::new(Duration::from_secs(2))
8122 .repeat()
8123 .with_easing(pulsating_between(0.4, 0.8)),
8124 |label, delta| label.opacity(delta),
8125 )
8126 .into_any_element()
8127 } else {
8128 completion.into_any_element()
8129 };
8130
8131 let has_completion = self.active_inline_completion.is_some();
8132
8133 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8134 Some(
8135 h_flex()
8136 .min_w(min_width)
8137 .max_w(max_width)
8138 .flex_1()
8139 .elevation_2(cx)
8140 .border_color(cx.theme().colors().border)
8141 .child(
8142 div()
8143 .flex_1()
8144 .py_1()
8145 .px_2()
8146 .overflow_hidden()
8147 .child(completion),
8148 )
8149 .when_some(accept_keystroke, |el, accept_keystroke| {
8150 if !accept_keystroke.modifiers.modified() {
8151 return el;
8152 }
8153
8154 el.child(
8155 h_flex()
8156 .h_full()
8157 .border_l_1()
8158 .rounded_r_lg()
8159 .border_color(cx.theme().colors().border)
8160 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8161 .gap_1()
8162 .py_1()
8163 .px_2()
8164 .child(
8165 h_flex()
8166 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8167 .when(is_platform_style_mac, |parent| parent.gap_1())
8168 .child(h_flex().children(ui::render_modifiers(
8169 &accept_keystroke.modifiers,
8170 PlatformStyle::platform(),
8171 Some(if !has_completion {
8172 Color::Muted
8173 } else {
8174 Color::Default
8175 }),
8176 None,
8177 false,
8178 ))),
8179 )
8180 .child(Label::new("Preview").into_any_element())
8181 .opacity(if has_completion { 1.0 } else { 0.4 }),
8182 )
8183 })
8184 .into_any(),
8185 )
8186 }
8187
8188 fn render_edit_prediction_cursor_popover_preview(
8189 &self,
8190 completion: &InlineCompletionState,
8191 cursor_point: Point,
8192 style: &EditorStyle,
8193 cx: &mut Context<Editor>,
8194 ) -> Option<Div> {
8195 use text::ToPoint as _;
8196
8197 fn render_relative_row_jump(
8198 prefix: impl Into<String>,
8199 current_row: u32,
8200 target_row: u32,
8201 ) -> Div {
8202 let (row_diff, arrow) = if target_row < current_row {
8203 (current_row - target_row, IconName::ArrowUp)
8204 } else {
8205 (target_row - current_row, IconName::ArrowDown)
8206 };
8207
8208 h_flex()
8209 .child(
8210 Label::new(format!("{}{}", prefix.into(), row_diff))
8211 .color(Color::Muted)
8212 .size(LabelSize::Small),
8213 )
8214 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8215 }
8216
8217 match &completion.completion {
8218 InlineCompletion::Move {
8219 target, snapshot, ..
8220 } => Some(
8221 h_flex()
8222 .px_2()
8223 .gap_2()
8224 .flex_1()
8225 .child(
8226 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8227 Icon::new(IconName::ZedPredictDown)
8228 } else {
8229 Icon::new(IconName::ZedPredictUp)
8230 },
8231 )
8232 .child(Label::new("Jump to Edit")),
8233 ),
8234
8235 InlineCompletion::Edit {
8236 edits,
8237 edit_preview,
8238 snapshot,
8239 display_mode: _,
8240 } => {
8241 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8242
8243 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8244 &snapshot,
8245 &edits,
8246 edit_preview.as_ref()?,
8247 true,
8248 cx,
8249 )
8250 .first_line_preview();
8251
8252 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8253 .with_default_highlights(&style.text, highlighted_edits.highlights);
8254
8255 let preview = h_flex()
8256 .gap_1()
8257 .min_w_16()
8258 .child(styled_text)
8259 .when(has_more_lines, |parent| parent.child("…"));
8260
8261 let left = if first_edit_row != cursor_point.row {
8262 render_relative_row_jump("", cursor_point.row, first_edit_row)
8263 .into_any_element()
8264 } else {
8265 Icon::new(IconName::ZedPredict).into_any_element()
8266 };
8267
8268 Some(
8269 h_flex()
8270 .h_full()
8271 .flex_1()
8272 .gap_2()
8273 .pr_1()
8274 .overflow_x_hidden()
8275 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8276 .child(left)
8277 .child(preview),
8278 )
8279 }
8280 }
8281 }
8282
8283 fn render_context_menu(
8284 &self,
8285 style: &EditorStyle,
8286 max_height_in_lines: u32,
8287 window: &mut Window,
8288 cx: &mut Context<Editor>,
8289 ) -> Option<AnyElement> {
8290 let menu = self.context_menu.borrow();
8291 let menu = menu.as_ref()?;
8292 if !menu.visible() {
8293 return None;
8294 };
8295 Some(menu.render(style, max_height_in_lines, window, cx))
8296 }
8297
8298 fn render_context_menu_aside(
8299 &mut self,
8300 max_size: Size<Pixels>,
8301 window: &mut Window,
8302 cx: &mut Context<Editor>,
8303 ) -> Option<AnyElement> {
8304 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8305 if menu.visible() {
8306 menu.render_aside(self, max_size, window, cx)
8307 } else {
8308 None
8309 }
8310 })
8311 }
8312
8313 fn hide_context_menu(
8314 &mut self,
8315 window: &mut Window,
8316 cx: &mut Context<Self>,
8317 ) -> Option<CodeContextMenu> {
8318 cx.notify();
8319 self.completion_tasks.clear();
8320 let context_menu = self.context_menu.borrow_mut().take();
8321 self.stale_inline_completion_in_menu.take();
8322 self.update_visible_inline_completion(window, cx);
8323 context_menu
8324 }
8325
8326 fn show_snippet_choices(
8327 &mut self,
8328 choices: &Vec<String>,
8329 selection: Range<Anchor>,
8330 cx: &mut Context<Self>,
8331 ) {
8332 if selection.start.buffer_id.is_none() {
8333 return;
8334 }
8335 let buffer_id = selection.start.buffer_id.unwrap();
8336 let buffer = self.buffer().read(cx).buffer(buffer_id);
8337 let id = post_inc(&mut self.next_completion_id);
8338 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8339
8340 if let Some(buffer) = buffer {
8341 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8342 CompletionsMenu::new_snippet_choices(
8343 id,
8344 true,
8345 choices,
8346 selection,
8347 buffer,
8348 snippet_sort_order,
8349 ),
8350 ));
8351 }
8352 }
8353
8354 pub fn insert_snippet(
8355 &mut self,
8356 insertion_ranges: &[Range<usize>],
8357 snippet: Snippet,
8358 window: &mut Window,
8359 cx: &mut Context<Self>,
8360 ) -> Result<()> {
8361 struct Tabstop<T> {
8362 is_end_tabstop: bool,
8363 ranges: Vec<Range<T>>,
8364 choices: Option<Vec<String>>,
8365 }
8366
8367 let tabstops = self.buffer.update(cx, |buffer, cx| {
8368 let snippet_text: Arc<str> = snippet.text.clone().into();
8369 let edits = insertion_ranges
8370 .iter()
8371 .cloned()
8372 .map(|range| (range, snippet_text.clone()));
8373 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8374
8375 let snapshot = &*buffer.read(cx);
8376 let snippet = &snippet;
8377 snippet
8378 .tabstops
8379 .iter()
8380 .map(|tabstop| {
8381 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8382 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8383 });
8384 let mut tabstop_ranges = tabstop
8385 .ranges
8386 .iter()
8387 .flat_map(|tabstop_range| {
8388 let mut delta = 0_isize;
8389 insertion_ranges.iter().map(move |insertion_range| {
8390 let insertion_start = insertion_range.start as isize + delta;
8391 delta +=
8392 snippet.text.len() as isize - insertion_range.len() as isize;
8393
8394 let start = ((insertion_start + tabstop_range.start) as usize)
8395 .min(snapshot.len());
8396 let end = ((insertion_start + tabstop_range.end) as usize)
8397 .min(snapshot.len());
8398 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8399 })
8400 })
8401 .collect::<Vec<_>>();
8402 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8403
8404 Tabstop {
8405 is_end_tabstop,
8406 ranges: tabstop_ranges,
8407 choices: tabstop.choices.clone(),
8408 }
8409 })
8410 .collect::<Vec<_>>()
8411 });
8412 if let Some(tabstop) = tabstops.first() {
8413 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8414 s.select_ranges(tabstop.ranges.iter().cloned());
8415 });
8416
8417 if let Some(choices) = &tabstop.choices {
8418 if let Some(selection) = tabstop.ranges.first() {
8419 self.show_snippet_choices(choices, selection.clone(), cx)
8420 }
8421 }
8422
8423 // If we're already at the last tabstop and it's at the end of the snippet,
8424 // we're done, we don't need to keep the state around.
8425 if !tabstop.is_end_tabstop {
8426 let choices = tabstops
8427 .iter()
8428 .map(|tabstop| tabstop.choices.clone())
8429 .collect();
8430
8431 let ranges = tabstops
8432 .into_iter()
8433 .map(|tabstop| tabstop.ranges)
8434 .collect::<Vec<_>>();
8435
8436 self.snippet_stack.push(SnippetState {
8437 active_index: 0,
8438 ranges,
8439 choices,
8440 });
8441 }
8442
8443 // Check whether the just-entered snippet ends with an auto-closable bracket.
8444 if self.autoclose_regions.is_empty() {
8445 let snapshot = self.buffer.read(cx).snapshot(cx);
8446 for selection in &mut self.selections.all::<Point>(cx) {
8447 let selection_head = selection.head();
8448 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8449 continue;
8450 };
8451
8452 let mut bracket_pair = None;
8453 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8454 let prev_chars = snapshot
8455 .reversed_chars_at(selection_head)
8456 .collect::<String>();
8457 for (pair, enabled) in scope.brackets() {
8458 if enabled
8459 && pair.close
8460 && prev_chars.starts_with(pair.start.as_str())
8461 && next_chars.starts_with(pair.end.as_str())
8462 {
8463 bracket_pair = Some(pair.clone());
8464 break;
8465 }
8466 }
8467 if let Some(pair) = bracket_pair {
8468 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8469 let autoclose_enabled =
8470 self.use_autoclose && snapshot_settings.use_autoclose;
8471 if autoclose_enabled {
8472 let start = snapshot.anchor_after(selection_head);
8473 let end = snapshot.anchor_after(selection_head);
8474 self.autoclose_regions.push(AutocloseRegion {
8475 selection_id: selection.id,
8476 range: start..end,
8477 pair,
8478 });
8479 }
8480 }
8481 }
8482 }
8483 }
8484 Ok(())
8485 }
8486
8487 pub fn move_to_next_snippet_tabstop(
8488 &mut self,
8489 window: &mut Window,
8490 cx: &mut Context<Self>,
8491 ) -> bool {
8492 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8493 }
8494
8495 pub fn move_to_prev_snippet_tabstop(
8496 &mut self,
8497 window: &mut Window,
8498 cx: &mut Context<Self>,
8499 ) -> bool {
8500 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8501 }
8502
8503 pub fn move_to_snippet_tabstop(
8504 &mut self,
8505 bias: Bias,
8506 window: &mut Window,
8507 cx: &mut Context<Self>,
8508 ) -> bool {
8509 if let Some(mut snippet) = self.snippet_stack.pop() {
8510 match bias {
8511 Bias::Left => {
8512 if snippet.active_index > 0 {
8513 snippet.active_index -= 1;
8514 } else {
8515 self.snippet_stack.push(snippet);
8516 return false;
8517 }
8518 }
8519 Bias::Right => {
8520 if snippet.active_index + 1 < snippet.ranges.len() {
8521 snippet.active_index += 1;
8522 } else {
8523 self.snippet_stack.push(snippet);
8524 return false;
8525 }
8526 }
8527 }
8528 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8529 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8530 s.select_anchor_ranges(current_ranges.iter().cloned())
8531 });
8532
8533 if let Some(choices) = &snippet.choices[snippet.active_index] {
8534 if let Some(selection) = current_ranges.first() {
8535 self.show_snippet_choices(&choices, selection.clone(), cx);
8536 }
8537 }
8538
8539 // If snippet state is not at the last tabstop, push it back on the stack
8540 if snippet.active_index + 1 < snippet.ranges.len() {
8541 self.snippet_stack.push(snippet);
8542 }
8543 return true;
8544 }
8545 }
8546
8547 false
8548 }
8549
8550 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8551 self.transact(window, cx, |this, window, cx| {
8552 this.select_all(&SelectAll, window, cx);
8553 this.insert("", window, cx);
8554 });
8555 }
8556
8557 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8558 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8559 self.transact(window, cx, |this, window, cx| {
8560 this.select_autoclose_pair(window, cx);
8561 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8562 if !this.linked_edit_ranges.is_empty() {
8563 let selections = this.selections.all::<MultiBufferPoint>(cx);
8564 let snapshot = this.buffer.read(cx).snapshot(cx);
8565
8566 for selection in selections.iter() {
8567 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8568 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8569 if selection_start.buffer_id != selection_end.buffer_id {
8570 continue;
8571 }
8572 if let Some(ranges) =
8573 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8574 {
8575 for (buffer, entries) in ranges {
8576 linked_ranges.entry(buffer).or_default().extend(entries);
8577 }
8578 }
8579 }
8580 }
8581
8582 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8583 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8584 for selection in &mut selections {
8585 if selection.is_empty() {
8586 let old_head = selection.head();
8587 let mut new_head =
8588 movement::left(&display_map, old_head.to_display_point(&display_map))
8589 .to_point(&display_map);
8590 if let Some((buffer, line_buffer_range)) = display_map
8591 .buffer_snapshot
8592 .buffer_line_for_row(MultiBufferRow(old_head.row))
8593 {
8594 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8595 let indent_len = match indent_size.kind {
8596 IndentKind::Space => {
8597 buffer.settings_at(line_buffer_range.start, cx).tab_size
8598 }
8599 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8600 };
8601 if old_head.column <= indent_size.len && old_head.column > 0 {
8602 let indent_len = indent_len.get();
8603 new_head = cmp::min(
8604 new_head,
8605 MultiBufferPoint::new(
8606 old_head.row,
8607 ((old_head.column - 1) / indent_len) * indent_len,
8608 ),
8609 );
8610 }
8611 }
8612
8613 selection.set_head(new_head, SelectionGoal::None);
8614 }
8615 }
8616
8617 this.signature_help_state.set_backspace_pressed(true);
8618 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8619 s.select(selections)
8620 });
8621 this.insert("", window, cx);
8622 let empty_str: Arc<str> = Arc::from("");
8623 for (buffer, edits) in linked_ranges {
8624 let snapshot = buffer.read(cx).snapshot();
8625 use text::ToPoint as TP;
8626
8627 let edits = edits
8628 .into_iter()
8629 .map(|range| {
8630 let end_point = TP::to_point(&range.end, &snapshot);
8631 let mut start_point = TP::to_point(&range.start, &snapshot);
8632
8633 if end_point == start_point {
8634 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8635 .saturating_sub(1);
8636 start_point =
8637 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8638 };
8639
8640 (start_point..end_point, empty_str.clone())
8641 })
8642 .sorted_by_key(|(range, _)| range.start)
8643 .collect::<Vec<_>>();
8644 buffer.update(cx, |this, cx| {
8645 this.edit(edits, None, cx);
8646 })
8647 }
8648 this.refresh_inline_completion(true, false, window, cx);
8649 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8650 });
8651 }
8652
8653 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8654 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8655 self.transact(window, cx, |this, window, cx| {
8656 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8657 s.move_with(|map, selection| {
8658 if selection.is_empty() {
8659 let cursor = movement::right(map, selection.head());
8660 selection.end = cursor;
8661 selection.reversed = true;
8662 selection.goal = SelectionGoal::None;
8663 }
8664 })
8665 });
8666 this.insert("", window, cx);
8667 this.refresh_inline_completion(true, false, window, cx);
8668 });
8669 }
8670
8671 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8672 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8673 if self.move_to_prev_snippet_tabstop(window, cx) {
8674 return;
8675 }
8676 self.outdent(&Outdent, window, cx);
8677 }
8678
8679 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8680 if self.move_to_next_snippet_tabstop(window, cx) {
8681 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8682 return;
8683 }
8684 if self.read_only(cx) {
8685 return;
8686 }
8687 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8688 let mut selections = self.selections.all_adjusted(cx);
8689 let buffer = self.buffer.read(cx);
8690 let snapshot = buffer.snapshot(cx);
8691 let rows_iter = selections.iter().map(|s| s.head().row);
8692 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8693
8694 let has_some_cursor_in_whitespace = selections
8695 .iter()
8696 .filter(|selection| selection.is_empty())
8697 .any(|selection| {
8698 let cursor = selection.head();
8699 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8700 cursor.column < current_indent.len
8701 });
8702
8703 let mut edits = Vec::new();
8704 let mut prev_edited_row = 0;
8705 let mut row_delta = 0;
8706 for selection in &mut selections {
8707 if selection.start.row != prev_edited_row {
8708 row_delta = 0;
8709 }
8710 prev_edited_row = selection.end.row;
8711
8712 // If the selection is non-empty, then increase the indentation of the selected lines.
8713 if !selection.is_empty() {
8714 row_delta =
8715 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8716 continue;
8717 }
8718
8719 // If the selection is empty and the cursor is in the leading whitespace before the
8720 // suggested indentation, then auto-indent the line.
8721 let cursor = selection.head();
8722 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8723 if let Some(suggested_indent) =
8724 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8725 {
8726 // If there exist any empty selection in the leading whitespace, then skip
8727 // indent for selections at the boundary.
8728 if has_some_cursor_in_whitespace
8729 && cursor.column == current_indent.len
8730 && current_indent.len == suggested_indent.len
8731 {
8732 continue;
8733 }
8734
8735 if cursor.column < suggested_indent.len
8736 && cursor.column <= current_indent.len
8737 && current_indent.len <= suggested_indent.len
8738 {
8739 selection.start = Point::new(cursor.row, suggested_indent.len);
8740 selection.end = selection.start;
8741 if row_delta == 0 {
8742 edits.extend(Buffer::edit_for_indent_size_adjustment(
8743 cursor.row,
8744 current_indent,
8745 suggested_indent,
8746 ));
8747 row_delta = suggested_indent.len - current_indent.len;
8748 }
8749 continue;
8750 }
8751 }
8752
8753 // Otherwise, insert a hard or soft tab.
8754 let settings = buffer.language_settings_at(cursor, cx);
8755 let tab_size = if settings.hard_tabs {
8756 IndentSize::tab()
8757 } else {
8758 let tab_size = settings.tab_size.get();
8759 let indent_remainder = snapshot
8760 .text_for_range(Point::new(cursor.row, 0)..cursor)
8761 .flat_map(str::chars)
8762 .fold(row_delta % tab_size, |counter: u32, c| {
8763 if c == '\t' {
8764 0
8765 } else {
8766 (counter + 1) % tab_size
8767 }
8768 });
8769
8770 let chars_to_next_tab_stop = tab_size - indent_remainder;
8771 IndentSize::spaces(chars_to_next_tab_stop)
8772 };
8773 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8774 selection.end = selection.start;
8775 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8776 row_delta += tab_size.len;
8777 }
8778
8779 self.transact(window, cx, |this, window, cx| {
8780 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8781 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8782 s.select(selections)
8783 });
8784 this.refresh_inline_completion(true, false, window, cx);
8785 });
8786 }
8787
8788 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8789 if self.read_only(cx) {
8790 return;
8791 }
8792 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8793 let mut selections = self.selections.all::<Point>(cx);
8794 let mut prev_edited_row = 0;
8795 let mut row_delta = 0;
8796 let mut edits = Vec::new();
8797 let buffer = self.buffer.read(cx);
8798 let snapshot = buffer.snapshot(cx);
8799 for selection in &mut selections {
8800 if selection.start.row != prev_edited_row {
8801 row_delta = 0;
8802 }
8803 prev_edited_row = selection.end.row;
8804
8805 row_delta =
8806 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8807 }
8808
8809 self.transact(window, cx, |this, window, cx| {
8810 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8811 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8812 s.select(selections)
8813 });
8814 });
8815 }
8816
8817 fn indent_selection(
8818 buffer: &MultiBuffer,
8819 snapshot: &MultiBufferSnapshot,
8820 selection: &mut Selection<Point>,
8821 edits: &mut Vec<(Range<Point>, String)>,
8822 delta_for_start_row: u32,
8823 cx: &App,
8824 ) -> u32 {
8825 let settings = buffer.language_settings_at(selection.start, cx);
8826 let tab_size = settings.tab_size.get();
8827 let indent_kind = if settings.hard_tabs {
8828 IndentKind::Tab
8829 } else {
8830 IndentKind::Space
8831 };
8832 let mut start_row = selection.start.row;
8833 let mut end_row = selection.end.row + 1;
8834
8835 // If a selection ends at the beginning of a line, don't indent
8836 // that last line.
8837 if selection.end.column == 0 && selection.end.row > selection.start.row {
8838 end_row -= 1;
8839 }
8840
8841 // Avoid re-indenting a row that has already been indented by a
8842 // previous selection, but still update this selection's column
8843 // to reflect that indentation.
8844 if delta_for_start_row > 0 {
8845 start_row += 1;
8846 selection.start.column += delta_for_start_row;
8847 if selection.end.row == selection.start.row {
8848 selection.end.column += delta_for_start_row;
8849 }
8850 }
8851
8852 let mut delta_for_end_row = 0;
8853 let has_multiple_rows = start_row + 1 != end_row;
8854 for row in start_row..end_row {
8855 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8856 let indent_delta = match (current_indent.kind, indent_kind) {
8857 (IndentKind::Space, IndentKind::Space) => {
8858 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8859 IndentSize::spaces(columns_to_next_tab_stop)
8860 }
8861 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8862 (_, IndentKind::Tab) => IndentSize::tab(),
8863 };
8864
8865 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8866 0
8867 } else {
8868 selection.start.column
8869 };
8870 let row_start = Point::new(row, start);
8871 edits.push((
8872 row_start..row_start,
8873 indent_delta.chars().collect::<String>(),
8874 ));
8875
8876 // Update this selection's endpoints to reflect the indentation.
8877 if row == selection.start.row {
8878 selection.start.column += indent_delta.len;
8879 }
8880 if row == selection.end.row {
8881 selection.end.column += indent_delta.len;
8882 delta_for_end_row = indent_delta.len;
8883 }
8884 }
8885
8886 if selection.start.row == selection.end.row {
8887 delta_for_start_row + delta_for_end_row
8888 } else {
8889 delta_for_end_row
8890 }
8891 }
8892
8893 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8894 if self.read_only(cx) {
8895 return;
8896 }
8897 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8898 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8899 let selections = self.selections.all::<Point>(cx);
8900 let mut deletion_ranges = Vec::new();
8901 let mut last_outdent = None;
8902 {
8903 let buffer = self.buffer.read(cx);
8904 let snapshot = buffer.snapshot(cx);
8905 for selection in &selections {
8906 let settings = buffer.language_settings_at(selection.start, cx);
8907 let tab_size = settings.tab_size.get();
8908 let mut rows = selection.spanned_rows(false, &display_map);
8909
8910 // Avoid re-outdenting a row that has already been outdented by a
8911 // previous selection.
8912 if let Some(last_row) = last_outdent {
8913 if last_row == rows.start {
8914 rows.start = rows.start.next_row();
8915 }
8916 }
8917 let has_multiple_rows = rows.len() > 1;
8918 for row in rows.iter_rows() {
8919 let indent_size = snapshot.indent_size_for_line(row);
8920 if indent_size.len > 0 {
8921 let deletion_len = match indent_size.kind {
8922 IndentKind::Space => {
8923 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8924 if columns_to_prev_tab_stop == 0 {
8925 tab_size
8926 } else {
8927 columns_to_prev_tab_stop
8928 }
8929 }
8930 IndentKind::Tab => 1,
8931 };
8932 let start = if has_multiple_rows
8933 || deletion_len > selection.start.column
8934 || indent_size.len < selection.start.column
8935 {
8936 0
8937 } else {
8938 selection.start.column - deletion_len
8939 };
8940 deletion_ranges.push(
8941 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8942 );
8943 last_outdent = Some(row);
8944 }
8945 }
8946 }
8947 }
8948
8949 self.transact(window, cx, |this, window, cx| {
8950 this.buffer.update(cx, |buffer, cx| {
8951 let empty_str: Arc<str> = Arc::default();
8952 buffer.edit(
8953 deletion_ranges
8954 .into_iter()
8955 .map(|range| (range, empty_str.clone())),
8956 None,
8957 cx,
8958 );
8959 });
8960 let selections = this.selections.all::<usize>(cx);
8961 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8962 s.select(selections)
8963 });
8964 });
8965 }
8966
8967 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8968 if self.read_only(cx) {
8969 return;
8970 }
8971 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8972 let selections = self
8973 .selections
8974 .all::<usize>(cx)
8975 .into_iter()
8976 .map(|s| s.range());
8977
8978 self.transact(window, cx, |this, window, cx| {
8979 this.buffer.update(cx, |buffer, cx| {
8980 buffer.autoindent_ranges(selections, cx);
8981 });
8982 let selections = this.selections.all::<usize>(cx);
8983 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8984 s.select(selections)
8985 });
8986 });
8987 }
8988
8989 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8990 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8991 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8992 let selections = self.selections.all::<Point>(cx);
8993
8994 let mut new_cursors = Vec::new();
8995 let mut edit_ranges = Vec::new();
8996 let mut selections = selections.iter().peekable();
8997 while let Some(selection) = selections.next() {
8998 let mut rows = selection.spanned_rows(false, &display_map);
8999 let goal_display_column = selection.head().to_display_point(&display_map).column();
9000
9001 // Accumulate contiguous regions of rows that we want to delete.
9002 while let Some(next_selection) = selections.peek() {
9003 let next_rows = next_selection.spanned_rows(false, &display_map);
9004 if next_rows.start <= rows.end {
9005 rows.end = next_rows.end;
9006 selections.next().unwrap();
9007 } else {
9008 break;
9009 }
9010 }
9011
9012 let buffer = &display_map.buffer_snapshot;
9013 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9014 let edit_end;
9015 let cursor_buffer_row;
9016 if buffer.max_point().row >= rows.end.0 {
9017 // If there's a line after the range, delete the \n from the end of the row range
9018 // and position the cursor on the next line.
9019 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9020 cursor_buffer_row = rows.end;
9021 } else {
9022 // If there isn't a line after the range, delete the \n from the line before the
9023 // start of the row range and position the cursor there.
9024 edit_start = edit_start.saturating_sub(1);
9025 edit_end = buffer.len();
9026 cursor_buffer_row = rows.start.previous_row();
9027 }
9028
9029 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9030 *cursor.column_mut() =
9031 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9032
9033 new_cursors.push((
9034 selection.id,
9035 buffer.anchor_after(cursor.to_point(&display_map)),
9036 ));
9037 edit_ranges.push(edit_start..edit_end);
9038 }
9039
9040 self.transact(window, cx, |this, window, cx| {
9041 let buffer = this.buffer.update(cx, |buffer, cx| {
9042 let empty_str: Arc<str> = Arc::default();
9043 buffer.edit(
9044 edit_ranges
9045 .into_iter()
9046 .map(|range| (range, empty_str.clone())),
9047 None,
9048 cx,
9049 );
9050 buffer.snapshot(cx)
9051 });
9052 let new_selections = new_cursors
9053 .into_iter()
9054 .map(|(id, cursor)| {
9055 let cursor = cursor.to_point(&buffer);
9056 Selection {
9057 id,
9058 start: cursor,
9059 end: cursor,
9060 reversed: false,
9061 goal: SelectionGoal::None,
9062 }
9063 })
9064 .collect();
9065
9066 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9067 s.select(new_selections);
9068 });
9069 });
9070 }
9071
9072 pub fn join_lines_impl(
9073 &mut self,
9074 insert_whitespace: bool,
9075 window: &mut Window,
9076 cx: &mut Context<Self>,
9077 ) {
9078 if self.read_only(cx) {
9079 return;
9080 }
9081 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9082 for selection in self.selections.all::<Point>(cx) {
9083 let start = MultiBufferRow(selection.start.row);
9084 // Treat single line selections as if they include the next line. Otherwise this action
9085 // would do nothing for single line selections individual cursors.
9086 let end = if selection.start.row == selection.end.row {
9087 MultiBufferRow(selection.start.row + 1)
9088 } else {
9089 MultiBufferRow(selection.end.row)
9090 };
9091
9092 if let Some(last_row_range) = row_ranges.last_mut() {
9093 if start <= last_row_range.end {
9094 last_row_range.end = end;
9095 continue;
9096 }
9097 }
9098 row_ranges.push(start..end);
9099 }
9100
9101 let snapshot = self.buffer.read(cx).snapshot(cx);
9102 let mut cursor_positions = Vec::new();
9103 for row_range in &row_ranges {
9104 let anchor = snapshot.anchor_before(Point::new(
9105 row_range.end.previous_row().0,
9106 snapshot.line_len(row_range.end.previous_row()),
9107 ));
9108 cursor_positions.push(anchor..anchor);
9109 }
9110
9111 self.transact(window, cx, |this, window, cx| {
9112 for row_range in row_ranges.into_iter().rev() {
9113 for row in row_range.iter_rows().rev() {
9114 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9115 let next_line_row = row.next_row();
9116 let indent = snapshot.indent_size_for_line(next_line_row);
9117 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9118
9119 let replace =
9120 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9121 " "
9122 } else {
9123 ""
9124 };
9125
9126 this.buffer.update(cx, |buffer, cx| {
9127 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9128 });
9129 }
9130 }
9131
9132 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9133 s.select_anchor_ranges(cursor_positions)
9134 });
9135 });
9136 }
9137
9138 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9139 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9140 self.join_lines_impl(true, window, cx);
9141 }
9142
9143 pub fn sort_lines_case_sensitive(
9144 &mut self,
9145 _: &SortLinesCaseSensitive,
9146 window: &mut Window,
9147 cx: &mut Context<Self>,
9148 ) {
9149 self.manipulate_lines(window, cx, |lines| lines.sort())
9150 }
9151
9152 pub fn sort_lines_case_insensitive(
9153 &mut self,
9154 _: &SortLinesCaseInsensitive,
9155 window: &mut Window,
9156 cx: &mut Context<Self>,
9157 ) {
9158 self.manipulate_lines(window, cx, |lines| {
9159 lines.sort_by_key(|line| line.to_lowercase())
9160 })
9161 }
9162
9163 pub fn unique_lines_case_insensitive(
9164 &mut self,
9165 _: &UniqueLinesCaseInsensitive,
9166 window: &mut Window,
9167 cx: &mut Context<Self>,
9168 ) {
9169 self.manipulate_lines(window, cx, |lines| {
9170 let mut seen = HashSet::default();
9171 lines.retain(|line| seen.insert(line.to_lowercase()));
9172 })
9173 }
9174
9175 pub fn unique_lines_case_sensitive(
9176 &mut self,
9177 _: &UniqueLinesCaseSensitive,
9178 window: &mut Window,
9179 cx: &mut Context<Self>,
9180 ) {
9181 self.manipulate_lines(window, cx, |lines| {
9182 let mut seen = HashSet::default();
9183 lines.retain(|line| seen.insert(*line));
9184 })
9185 }
9186
9187 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9188 let Some(project) = self.project.clone() else {
9189 return;
9190 };
9191 self.reload(project, window, cx)
9192 .detach_and_notify_err(window, cx);
9193 }
9194
9195 pub fn restore_file(
9196 &mut self,
9197 _: &::git::RestoreFile,
9198 window: &mut Window,
9199 cx: &mut Context<Self>,
9200 ) {
9201 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9202 let mut buffer_ids = HashSet::default();
9203 let snapshot = self.buffer().read(cx).snapshot(cx);
9204 for selection in self.selections.all::<usize>(cx) {
9205 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9206 }
9207
9208 let buffer = self.buffer().read(cx);
9209 let ranges = buffer_ids
9210 .into_iter()
9211 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9212 .collect::<Vec<_>>();
9213
9214 self.restore_hunks_in_ranges(ranges, window, cx);
9215 }
9216
9217 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9218 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9219 let selections = self
9220 .selections
9221 .all(cx)
9222 .into_iter()
9223 .map(|s| s.range())
9224 .collect();
9225 self.restore_hunks_in_ranges(selections, window, cx);
9226 }
9227
9228 pub fn restore_hunks_in_ranges(
9229 &mut self,
9230 ranges: Vec<Range<Point>>,
9231 window: &mut Window,
9232 cx: &mut Context<Editor>,
9233 ) {
9234 let mut revert_changes = HashMap::default();
9235 let chunk_by = self
9236 .snapshot(window, cx)
9237 .hunks_for_ranges(ranges)
9238 .into_iter()
9239 .chunk_by(|hunk| hunk.buffer_id);
9240 for (buffer_id, hunks) in &chunk_by {
9241 let hunks = hunks.collect::<Vec<_>>();
9242 for hunk in &hunks {
9243 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9244 }
9245 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9246 }
9247 drop(chunk_by);
9248 if !revert_changes.is_empty() {
9249 self.transact(window, cx, |editor, window, cx| {
9250 editor.restore(revert_changes, window, cx);
9251 });
9252 }
9253 }
9254
9255 pub fn open_active_item_in_terminal(
9256 &mut self,
9257 _: &OpenInTerminal,
9258 window: &mut Window,
9259 cx: &mut Context<Self>,
9260 ) {
9261 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9262 let project_path = buffer.read(cx).project_path(cx)?;
9263 let project = self.project.as_ref()?.read(cx);
9264 let entry = project.entry_for_path(&project_path, cx)?;
9265 let parent = match &entry.canonical_path {
9266 Some(canonical_path) => canonical_path.to_path_buf(),
9267 None => project.absolute_path(&project_path, cx)?,
9268 }
9269 .parent()?
9270 .to_path_buf();
9271 Some(parent)
9272 }) {
9273 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9274 }
9275 }
9276
9277 fn set_breakpoint_context_menu(
9278 &mut self,
9279 display_row: DisplayRow,
9280 position: Option<Anchor>,
9281 clicked_point: gpui::Point<Pixels>,
9282 window: &mut Window,
9283 cx: &mut Context<Self>,
9284 ) {
9285 if !cx.has_flag::<DebuggerFeatureFlag>() {
9286 return;
9287 }
9288 let source = self
9289 .buffer
9290 .read(cx)
9291 .snapshot(cx)
9292 .anchor_before(Point::new(display_row.0, 0u32));
9293
9294 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9295
9296 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9297 self,
9298 source,
9299 clicked_point,
9300 context_menu,
9301 window,
9302 cx,
9303 );
9304 }
9305
9306 fn add_edit_breakpoint_block(
9307 &mut self,
9308 anchor: Anchor,
9309 breakpoint: &Breakpoint,
9310 edit_action: BreakpointPromptEditAction,
9311 window: &mut Window,
9312 cx: &mut Context<Self>,
9313 ) {
9314 let weak_editor = cx.weak_entity();
9315 let bp_prompt = cx.new(|cx| {
9316 BreakpointPromptEditor::new(
9317 weak_editor,
9318 anchor,
9319 breakpoint.clone(),
9320 edit_action,
9321 window,
9322 cx,
9323 )
9324 });
9325
9326 let height = bp_prompt.update(cx, |this, cx| {
9327 this.prompt
9328 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9329 });
9330 let cloned_prompt = bp_prompt.clone();
9331 let blocks = vec![BlockProperties {
9332 style: BlockStyle::Sticky,
9333 placement: BlockPlacement::Above(anchor),
9334 height: Some(height),
9335 render: Arc::new(move |cx| {
9336 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
9337 cloned_prompt.clone().into_any_element()
9338 }),
9339 priority: 0,
9340 }];
9341
9342 let focus_handle = bp_prompt.focus_handle(cx);
9343 window.focus(&focus_handle);
9344
9345 let block_ids = self.insert_blocks(blocks, None, cx);
9346 bp_prompt.update(cx, |prompt, _| {
9347 prompt.add_block_ids(block_ids);
9348 });
9349 }
9350
9351 pub(crate) fn breakpoint_at_row(
9352 &self,
9353 row: u32,
9354 window: &mut Window,
9355 cx: &mut Context<Self>,
9356 ) -> Option<(Anchor, Breakpoint)> {
9357 let snapshot = self.snapshot(window, cx);
9358 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9359
9360 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9361 }
9362
9363 pub(crate) fn breakpoint_at_anchor(
9364 &self,
9365 breakpoint_position: Anchor,
9366 snapshot: &EditorSnapshot,
9367 cx: &mut Context<Self>,
9368 ) -> Option<(Anchor, Breakpoint)> {
9369 let project = self.project.clone()?;
9370
9371 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9372 snapshot
9373 .buffer_snapshot
9374 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9375 })?;
9376
9377 let enclosing_excerpt = breakpoint_position.excerpt_id;
9378 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9379 let buffer_snapshot = buffer.read(cx).snapshot();
9380
9381 let row = buffer_snapshot
9382 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9383 .row;
9384
9385 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9386 let anchor_end = snapshot
9387 .buffer_snapshot
9388 .anchor_after(Point::new(row, line_len));
9389
9390 let bp = self
9391 .breakpoint_store
9392 .as_ref()?
9393 .read_with(cx, |breakpoint_store, cx| {
9394 breakpoint_store
9395 .breakpoints(
9396 &buffer,
9397 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9398 &buffer_snapshot,
9399 cx,
9400 )
9401 .next()
9402 .and_then(|(anchor, bp)| {
9403 let breakpoint_row = buffer_snapshot
9404 .summary_for_anchor::<text::PointUtf16>(anchor)
9405 .row;
9406
9407 if breakpoint_row == row {
9408 snapshot
9409 .buffer_snapshot
9410 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9411 .map(|anchor| (anchor, bp.clone()))
9412 } else {
9413 None
9414 }
9415 })
9416 });
9417 bp
9418 }
9419
9420 pub fn edit_log_breakpoint(
9421 &mut self,
9422 _: &EditLogBreakpoint,
9423 window: &mut Window,
9424 cx: &mut Context<Self>,
9425 ) {
9426 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9427 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9428 message: None,
9429 state: BreakpointState::Enabled,
9430 condition: None,
9431 hit_condition: None,
9432 });
9433
9434 self.add_edit_breakpoint_block(
9435 anchor,
9436 &breakpoint,
9437 BreakpointPromptEditAction::Log,
9438 window,
9439 cx,
9440 );
9441 }
9442 }
9443
9444 fn breakpoints_at_cursors(
9445 &self,
9446 window: &mut Window,
9447 cx: &mut Context<Self>,
9448 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9449 let snapshot = self.snapshot(window, cx);
9450 let cursors = self
9451 .selections
9452 .disjoint_anchors()
9453 .into_iter()
9454 .map(|selection| {
9455 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9456
9457 let breakpoint_position = self
9458 .breakpoint_at_row(cursor_position.row, window, cx)
9459 .map(|bp| bp.0)
9460 .unwrap_or_else(|| {
9461 snapshot
9462 .display_snapshot
9463 .buffer_snapshot
9464 .anchor_after(Point::new(cursor_position.row, 0))
9465 });
9466
9467 let breakpoint = self
9468 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9469 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9470
9471 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9472 })
9473 // 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.
9474 .collect::<HashMap<Anchor, _>>();
9475
9476 cursors.into_iter().collect()
9477 }
9478
9479 pub fn enable_breakpoint(
9480 &mut self,
9481 _: &crate::actions::EnableBreakpoint,
9482 window: &mut Window,
9483 cx: &mut Context<Self>,
9484 ) {
9485 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9486 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9487 continue;
9488 };
9489 self.edit_breakpoint_at_anchor(
9490 anchor,
9491 breakpoint,
9492 BreakpointEditAction::InvertState,
9493 cx,
9494 );
9495 }
9496 }
9497
9498 pub fn disable_breakpoint(
9499 &mut self,
9500 _: &crate::actions::DisableBreakpoint,
9501 window: &mut Window,
9502 cx: &mut Context<Self>,
9503 ) {
9504 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9505 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9506 continue;
9507 };
9508 self.edit_breakpoint_at_anchor(
9509 anchor,
9510 breakpoint,
9511 BreakpointEditAction::InvertState,
9512 cx,
9513 );
9514 }
9515 }
9516
9517 pub fn toggle_breakpoint(
9518 &mut self,
9519 _: &crate::actions::ToggleBreakpoint,
9520 window: &mut Window,
9521 cx: &mut Context<Self>,
9522 ) {
9523 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9524 if let Some(breakpoint) = breakpoint {
9525 self.edit_breakpoint_at_anchor(
9526 anchor,
9527 breakpoint,
9528 BreakpointEditAction::Toggle,
9529 cx,
9530 );
9531 } else {
9532 self.edit_breakpoint_at_anchor(
9533 anchor,
9534 Breakpoint::new_standard(),
9535 BreakpointEditAction::Toggle,
9536 cx,
9537 );
9538 }
9539 }
9540 }
9541
9542 pub fn edit_breakpoint_at_anchor(
9543 &mut self,
9544 breakpoint_position: Anchor,
9545 breakpoint: Breakpoint,
9546 edit_action: BreakpointEditAction,
9547 cx: &mut Context<Self>,
9548 ) {
9549 let Some(breakpoint_store) = &self.breakpoint_store else {
9550 return;
9551 };
9552
9553 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9554 if breakpoint_position == Anchor::min() {
9555 self.buffer()
9556 .read(cx)
9557 .excerpt_buffer_ids()
9558 .into_iter()
9559 .next()
9560 } else {
9561 None
9562 }
9563 }) else {
9564 return;
9565 };
9566
9567 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9568 return;
9569 };
9570
9571 breakpoint_store.update(cx, |breakpoint_store, cx| {
9572 breakpoint_store.toggle_breakpoint(
9573 buffer,
9574 (breakpoint_position.text_anchor, breakpoint),
9575 edit_action,
9576 cx,
9577 );
9578 });
9579
9580 cx.notify();
9581 }
9582
9583 #[cfg(any(test, feature = "test-support"))]
9584 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9585 self.breakpoint_store.clone()
9586 }
9587
9588 pub fn prepare_restore_change(
9589 &self,
9590 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9591 hunk: &MultiBufferDiffHunk,
9592 cx: &mut App,
9593 ) -> Option<()> {
9594 if hunk.is_created_file() {
9595 return None;
9596 }
9597 let buffer = self.buffer.read(cx);
9598 let diff = buffer.diff_for(hunk.buffer_id)?;
9599 let buffer = buffer.buffer(hunk.buffer_id)?;
9600 let buffer = buffer.read(cx);
9601 let original_text = diff
9602 .read(cx)
9603 .base_text()
9604 .as_rope()
9605 .slice(hunk.diff_base_byte_range.clone());
9606 let buffer_snapshot = buffer.snapshot();
9607 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9608 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9609 probe
9610 .0
9611 .start
9612 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9613 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9614 }) {
9615 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9616 Some(())
9617 } else {
9618 None
9619 }
9620 }
9621
9622 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9623 self.manipulate_lines(window, cx, |lines| lines.reverse())
9624 }
9625
9626 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9627 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9628 }
9629
9630 fn manipulate_lines<Fn>(
9631 &mut self,
9632 window: &mut Window,
9633 cx: &mut Context<Self>,
9634 mut callback: Fn,
9635 ) where
9636 Fn: FnMut(&mut Vec<&str>),
9637 {
9638 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9639
9640 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9641 let buffer = self.buffer.read(cx).snapshot(cx);
9642
9643 let mut edits = Vec::new();
9644
9645 let selections = self.selections.all::<Point>(cx);
9646 let mut selections = selections.iter().peekable();
9647 let mut contiguous_row_selections = Vec::new();
9648 let mut new_selections = Vec::new();
9649 let mut added_lines = 0;
9650 let mut removed_lines = 0;
9651
9652 while let Some(selection) = selections.next() {
9653 let (start_row, end_row) = consume_contiguous_rows(
9654 &mut contiguous_row_selections,
9655 selection,
9656 &display_map,
9657 &mut selections,
9658 );
9659
9660 let start_point = Point::new(start_row.0, 0);
9661 let end_point = Point::new(
9662 end_row.previous_row().0,
9663 buffer.line_len(end_row.previous_row()),
9664 );
9665 let text = buffer
9666 .text_for_range(start_point..end_point)
9667 .collect::<String>();
9668
9669 let mut lines = text.split('\n').collect_vec();
9670
9671 let lines_before = lines.len();
9672 callback(&mut lines);
9673 let lines_after = lines.len();
9674
9675 edits.push((start_point..end_point, lines.join("\n")));
9676
9677 // Selections must change based on added and removed line count
9678 let start_row =
9679 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9680 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9681 new_selections.push(Selection {
9682 id: selection.id,
9683 start: start_row,
9684 end: end_row,
9685 goal: SelectionGoal::None,
9686 reversed: selection.reversed,
9687 });
9688
9689 if lines_after > lines_before {
9690 added_lines += lines_after - lines_before;
9691 } else if lines_before > lines_after {
9692 removed_lines += lines_before - lines_after;
9693 }
9694 }
9695
9696 self.transact(window, cx, |this, window, cx| {
9697 let buffer = this.buffer.update(cx, |buffer, cx| {
9698 buffer.edit(edits, None, cx);
9699 buffer.snapshot(cx)
9700 });
9701
9702 // Recalculate offsets on newly edited buffer
9703 let new_selections = new_selections
9704 .iter()
9705 .map(|s| {
9706 let start_point = Point::new(s.start.0, 0);
9707 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9708 Selection {
9709 id: s.id,
9710 start: buffer.point_to_offset(start_point),
9711 end: buffer.point_to_offset(end_point),
9712 goal: s.goal,
9713 reversed: s.reversed,
9714 }
9715 })
9716 .collect();
9717
9718 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9719 s.select(new_selections);
9720 });
9721
9722 this.request_autoscroll(Autoscroll::fit(), cx);
9723 });
9724 }
9725
9726 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9727 self.manipulate_text(window, cx, |text| {
9728 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9729 if has_upper_case_characters {
9730 text.to_lowercase()
9731 } else {
9732 text.to_uppercase()
9733 }
9734 })
9735 }
9736
9737 pub fn convert_to_upper_case(
9738 &mut self,
9739 _: &ConvertToUpperCase,
9740 window: &mut Window,
9741 cx: &mut Context<Self>,
9742 ) {
9743 self.manipulate_text(window, cx, |text| text.to_uppercase())
9744 }
9745
9746 pub fn convert_to_lower_case(
9747 &mut self,
9748 _: &ConvertToLowerCase,
9749 window: &mut Window,
9750 cx: &mut Context<Self>,
9751 ) {
9752 self.manipulate_text(window, cx, |text| text.to_lowercase())
9753 }
9754
9755 pub fn convert_to_title_case(
9756 &mut self,
9757 _: &ConvertToTitleCase,
9758 window: &mut Window,
9759 cx: &mut Context<Self>,
9760 ) {
9761 self.manipulate_text(window, cx, |text| {
9762 text.split('\n')
9763 .map(|line| line.to_case(Case::Title))
9764 .join("\n")
9765 })
9766 }
9767
9768 pub fn convert_to_snake_case(
9769 &mut self,
9770 _: &ConvertToSnakeCase,
9771 window: &mut Window,
9772 cx: &mut Context<Self>,
9773 ) {
9774 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9775 }
9776
9777 pub fn convert_to_kebab_case(
9778 &mut self,
9779 _: &ConvertToKebabCase,
9780 window: &mut Window,
9781 cx: &mut Context<Self>,
9782 ) {
9783 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9784 }
9785
9786 pub fn convert_to_upper_camel_case(
9787 &mut self,
9788 _: &ConvertToUpperCamelCase,
9789 window: &mut Window,
9790 cx: &mut Context<Self>,
9791 ) {
9792 self.manipulate_text(window, cx, |text| {
9793 text.split('\n')
9794 .map(|line| line.to_case(Case::UpperCamel))
9795 .join("\n")
9796 })
9797 }
9798
9799 pub fn convert_to_lower_camel_case(
9800 &mut self,
9801 _: &ConvertToLowerCamelCase,
9802 window: &mut Window,
9803 cx: &mut Context<Self>,
9804 ) {
9805 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9806 }
9807
9808 pub fn convert_to_opposite_case(
9809 &mut self,
9810 _: &ConvertToOppositeCase,
9811 window: &mut Window,
9812 cx: &mut Context<Self>,
9813 ) {
9814 self.manipulate_text(window, cx, |text| {
9815 text.chars()
9816 .fold(String::with_capacity(text.len()), |mut t, c| {
9817 if c.is_uppercase() {
9818 t.extend(c.to_lowercase());
9819 } else {
9820 t.extend(c.to_uppercase());
9821 }
9822 t
9823 })
9824 })
9825 }
9826
9827 pub fn convert_to_rot13(
9828 &mut self,
9829 _: &ConvertToRot13,
9830 window: &mut Window,
9831 cx: &mut Context<Self>,
9832 ) {
9833 self.manipulate_text(window, cx, |text| {
9834 text.chars()
9835 .map(|c| match c {
9836 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9837 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9838 _ => c,
9839 })
9840 .collect()
9841 })
9842 }
9843
9844 pub fn convert_to_rot47(
9845 &mut self,
9846 _: &ConvertToRot47,
9847 window: &mut Window,
9848 cx: &mut Context<Self>,
9849 ) {
9850 self.manipulate_text(window, cx, |text| {
9851 text.chars()
9852 .map(|c| {
9853 let code_point = c as u32;
9854 if code_point >= 33 && code_point <= 126 {
9855 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9856 }
9857 c
9858 })
9859 .collect()
9860 })
9861 }
9862
9863 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9864 where
9865 Fn: FnMut(&str) -> String,
9866 {
9867 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9868 let buffer = self.buffer.read(cx).snapshot(cx);
9869
9870 let mut new_selections = Vec::new();
9871 let mut edits = Vec::new();
9872 let mut selection_adjustment = 0i32;
9873
9874 for selection in self.selections.all::<usize>(cx) {
9875 let selection_is_empty = selection.is_empty();
9876
9877 let (start, end) = if selection_is_empty {
9878 let word_range = movement::surrounding_word(
9879 &display_map,
9880 selection.start.to_display_point(&display_map),
9881 );
9882 let start = word_range.start.to_offset(&display_map, Bias::Left);
9883 let end = word_range.end.to_offset(&display_map, Bias::Left);
9884 (start, end)
9885 } else {
9886 (selection.start, selection.end)
9887 };
9888
9889 let text = buffer.text_for_range(start..end).collect::<String>();
9890 let old_length = text.len() as i32;
9891 let text = callback(&text);
9892
9893 new_selections.push(Selection {
9894 start: (start as i32 - selection_adjustment) as usize,
9895 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9896 goal: SelectionGoal::None,
9897 ..selection
9898 });
9899
9900 selection_adjustment += old_length - text.len() as i32;
9901
9902 edits.push((start..end, text));
9903 }
9904
9905 self.transact(window, cx, |this, window, cx| {
9906 this.buffer.update(cx, |buffer, cx| {
9907 buffer.edit(edits, None, cx);
9908 });
9909
9910 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9911 s.select(new_selections);
9912 });
9913
9914 this.request_autoscroll(Autoscroll::fit(), cx);
9915 });
9916 }
9917
9918 pub fn duplicate(
9919 &mut self,
9920 upwards: bool,
9921 whole_lines: bool,
9922 window: &mut Window,
9923 cx: &mut Context<Self>,
9924 ) {
9925 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9926
9927 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9928 let buffer = &display_map.buffer_snapshot;
9929 let selections = self.selections.all::<Point>(cx);
9930
9931 let mut edits = Vec::new();
9932 let mut selections_iter = selections.iter().peekable();
9933 while let Some(selection) = selections_iter.next() {
9934 let mut rows = selection.spanned_rows(false, &display_map);
9935 // duplicate line-wise
9936 if whole_lines || selection.start == selection.end {
9937 // Avoid duplicating the same lines twice.
9938 while let Some(next_selection) = selections_iter.peek() {
9939 let next_rows = next_selection.spanned_rows(false, &display_map);
9940 if next_rows.start < rows.end {
9941 rows.end = next_rows.end;
9942 selections_iter.next().unwrap();
9943 } else {
9944 break;
9945 }
9946 }
9947
9948 // Copy the text from the selected row region and splice it either at the start
9949 // or end of the region.
9950 let start = Point::new(rows.start.0, 0);
9951 let end = Point::new(
9952 rows.end.previous_row().0,
9953 buffer.line_len(rows.end.previous_row()),
9954 );
9955 let text = buffer
9956 .text_for_range(start..end)
9957 .chain(Some("\n"))
9958 .collect::<String>();
9959 let insert_location = if upwards {
9960 Point::new(rows.end.0, 0)
9961 } else {
9962 start
9963 };
9964 edits.push((insert_location..insert_location, text));
9965 } else {
9966 // duplicate character-wise
9967 let start = selection.start;
9968 let end = selection.end;
9969 let text = buffer.text_for_range(start..end).collect::<String>();
9970 edits.push((selection.end..selection.end, text));
9971 }
9972 }
9973
9974 self.transact(window, cx, |this, _, cx| {
9975 this.buffer.update(cx, |buffer, cx| {
9976 buffer.edit(edits, None, cx);
9977 });
9978
9979 this.request_autoscroll(Autoscroll::fit(), cx);
9980 });
9981 }
9982
9983 pub fn duplicate_line_up(
9984 &mut self,
9985 _: &DuplicateLineUp,
9986 window: &mut Window,
9987 cx: &mut Context<Self>,
9988 ) {
9989 self.duplicate(true, true, window, cx);
9990 }
9991
9992 pub fn duplicate_line_down(
9993 &mut self,
9994 _: &DuplicateLineDown,
9995 window: &mut Window,
9996 cx: &mut Context<Self>,
9997 ) {
9998 self.duplicate(false, true, window, cx);
9999 }
10000
10001 pub fn duplicate_selection(
10002 &mut self,
10003 _: &DuplicateSelection,
10004 window: &mut Window,
10005 cx: &mut Context<Self>,
10006 ) {
10007 self.duplicate(false, false, window, cx);
10008 }
10009
10010 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10011 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10012
10013 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10014 let buffer = self.buffer.read(cx).snapshot(cx);
10015
10016 let mut edits = Vec::new();
10017 let mut unfold_ranges = Vec::new();
10018 let mut refold_creases = Vec::new();
10019
10020 let selections = self.selections.all::<Point>(cx);
10021 let mut selections = selections.iter().peekable();
10022 let mut contiguous_row_selections = Vec::new();
10023 let mut new_selections = Vec::new();
10024
10025 while let Some(selection) = selections.next() {
10026 // Find all the selections that span a contiguous row range
10027 let (start_row, end_row) = consume_contiguous_rows(
10028 &mut contiguous_row_selections,
10029 selection,
10030 &display_map,
10031 &mut selections,
10032 );
10033
10034 // Move the text spanned by the row range to be before the line preceding the row range
10035 if start_row.0 > 0 {
10036 let range_to_move = Point::new(
10037 start_row.previous_row().0,
10038 buffer.line_len(start_row.previous_row()),
10039 )
10040 ..Point::new(
10041 end_row.previous_row().0,
10042 buffer.line_len(end_row.previous_row()),
10043 );
10044 let insertion_point = display_map
10045 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10046 .0;
10047
10048 // Don't move lines across excerpts
10049 if buffer
10050 .excerpt_containing(insertion_point..range_to_move.end)
10051 .is_some()
10052 {
10053 let text = buffer
10054 .text_for_range(range_to_move.clone())
10055 .flat_map(|s| s.chars())
10056 .skip(1)
10057 .chain(['\n'])
10058 .collect::<String>();
10059
10060 edits.push((
10061 buffer.anchor_after(range_to_move.start)
10062 ..buffer.anchor_before(range_to_move.end),
10063 String::new(),
10064 ));
10065 let insertion_anchor = buffer.anchor_after(insertion_point);
10066 edits.push((insertion_anchor..insertion_anchor, text));
10067
10068 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10069
10070 // Move selections up
10071 new_selections.extend(contiguous_row_selections.drain(..).map(
10072 |mut selection| {
10073 selection.start.row -= row_delta;
10074 selection.end.row -= row_delta;
10075 selection
10076 },
10077 ));
10078
10079 // Move folds up
10080 unfold_ranges.push(range_to_move.clone());
10081 for fold in display_map.folds_in_range(
10082 buffer.anchor_before(range_to_move.start)
10083 ..buffer.anchor_after(range_to_move.end),
10084 ) {
10085 let mut start = fold.range.start.to_point(&buffer);
10086 let mut end = fold.range.end.to_point(&buffer);
10087 start.row -= row_delta;
10088 end.row -= row_delta;
10089 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10090 }
10091 }
10092 }
10093
10094 // If we didn't move line(s), preserve the existing selections
10095 new_selections.append(&mut contiguous_row_selections);
10096 }
10097
10098 self.transact(window, cx, |this, window, cx| {
10099 this.unfold_ranges(&unfold_ranges, true, true, cx);
10100 this.buffer.update(cx, |buffer, cx| {
10101 for (range, text) in edits {
10102 buffer.edit([(range, text)], None, cx);
10103 }
10104 });
10105 this.fold_creases(refold_creases, true, window, cx);
10106 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10107 s.select(new_selections);
10108 })
10109 });
10110 }
10111
10112 pub fn move_line_down(
10113 &mut self,
10114 _: &MoveLineDown,
10115 window: &mut Window,
10116 cx: &mut Context<Self>,
10117 ) {
10118 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10119
10120 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10121 let buffer = self.buffer.read(cx).snapshot(cx);
10122
10123 let mut edits = Vec::new();
10124 let mut unfold_ranges = Vec::new();
10125 let mut refold_creases = Vec::new();
10126
10127 let selections = self.selections.all::<Point>(cx);
10128 let mut selections = selections.iter().peekable();
10129 let mut contiguous_row_selections = Vec::new();
10130 let mut new_selections = Vec::new();
10131
10132 while let Some(selection) = selections.next() {
10133 // Find all the selections that span a contiguous row range
10134 let (start_row, end_row) = consume_contiguous_rows(
10135 &mut contiguous_row_selections,
10136 selection,
10137 &display_map,
10138 &mut selections,
10139 );
10140
10141 // Move the text spanned by the row range to be after the last line of the row range
10142 if end_row.0 <= buffer.max_point().row {
10143 let range_to_move =
10144 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10145 let insertion_point = display_map
10146 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10147 .0;
10148
10149 // Don't move lines across excerpt boundaries
10150 if buffer
10151 .excerpt_containing(range_to_move.start..insertion_point)
10152 .is_some()
10153 {
10154 let mut text = String::from("\n");
10155 text.extend(buffer.text_for_range(range_to_move.clone()));
10156 text.pop(); // Drop trailing newline
10157 edits.push((
10158 buffer.anchor_after(range_to_move.start)
10159 ..buffer.anchor_before(range_to_move.end),
10160 String::new(),
10161 ));
10162 let insertion_anchor = buffer.anchor_after(insertion_point);
10163 edits.push((insertion_anchor..insertion_anchor, text));
10164
10165 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10166
10167 // Move selections down
10168 new_selections.extend(contiguous_row_selections.drain(..).map(
10169 |mut selection| {
10170 selection.start.row += row_delta;
10171 selection.end.row += row_delta;
10172 selection
10173 },
10174 ));
10175
10176 // Move folds down
10177 unfold_ranges.push(range_to_move.clone());
10178 for fold in display_map.folds_in_range(
10179 buffer.anchor_before(range_to_move.start)
10180 ..buffer.anchor_after(range_to_move.end),
10181 ) {
10182 let mut start = fold.range.start.to_point(&buffer);
10183 let mut end = fold.range.end.to_point(&buffer);
10184 start.row += row_delta;
10185 end.row += row_delta;
10186 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10187 }
10188 }
10189 }
10190
10191 // If we didn't move line(s), preserve the existing selections
10192 new_selections.append(&mut contiguous_row_selections);
10193 }
10194
10195 self.transact(window, cx, |this, window, cx| {
10196 this.unfold_ranges(&unfold_ranges, true, true, cx);
10197 this.buffer.update(cx, |buffer, cx| {
10198 for (range, text) in edits {
10199 buffer.edit([(range, text)], None, cx);
10200 }
10201 });
10202 this.fold_creases(refold_creases, true, window, cx);
10203 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10204 s.select(new_selections)
10205 });
10206 });
10207 }
10208
10209 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10210 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10211 let text_layout_details = &self.text_layout_details(window);
10212 self.transact(window, cx, |this, window, cx| {
10213 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10214 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10215 s.move_with(|display_map, selection| {
10216 if !selection.is_empty() {
10217 return;
10218 }
10219
10220 let mut head = selection.head();
10221 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10222 if head.column() == display_map.line_len(head.row()) {
10223 transpose_offset = display_map
10224 .buffer_snapshot
10225 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10226 }
10227
10228 if transpose_offset == 0 {
10229 return;
10230 }
10231
10232 *head.column_mut() += 1;
10233 head = display_map.clip_point(head, Bias::Right);
10234 let goal = SelectionGoal::HorizontalPosition(
10235 display_map
10236 .x_for_display_point(head, text_layout_details)
10237 .into(),
10238 );
10239 selection.collapse_to(head, goal);
10240
10241 let transpose_start = display_map
10242 .buffer_snapshot
10243 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10244 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10245 let transpose_end = display_map
10246 .buffer_snapshot
10247 .clip_offset(transpose_offset + 1, Bias::Right);
10248 if let Some(ch) =
10249 display_map.buffer_snapshot.chars_at(transpose_start).next()
10250 {
10251 edits.push((transpose_start..transpose_offset, String::new()));
10252 edits.push((transpose_end..transpose_end, ch.to_string()));
10253 }
10254 }
10255 });
10256 edits
10257 });
10258 this.buffer
10259 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10260 let selections = this.selections.all::<usize>(cx);
10261 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10262 s.select(selections);
10263 });
10264 });
10265 }
10266
10267 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10268 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10269 self.rewrap_impl(RewrapOptions::default(), cx)
10270 }
10271
10272 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10273 let buffer = self.buffer.read(cx).snapshot(cx);
10274 let selections = self.selections.all::<Point>(cx);
10275 let mut selections = selections.iter().peekable();
10276
10277 let mut edits = Vec::new();
10278 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10279
10280 while let Some(selection) = selections.next() {
10281 let mut start_row = selection.start.row;
10282 let mut end_row = selection.end.row;
10283
10284 // Skip selections that overlap with a range that has already been rewrapped.
10285 let selection_range = start_row..end_row;
10286 if rewrapped_row_ranges
10287 .iter()
10288 .any(|range| range.overlaps(&selection_range))
10289 {
10290 continue;
10291 }
10292
10293 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10294
10295 // Since not all lines in the selection may be at the same indent
10296 // level, choose the indent size that is the most common between all
10297 // of the lines.
10298 //
10299 // If there is a tie, we use the deepest indent.
10300 let (indent_size, indent_end) = {
10301 let mut indent_size_occurrences = HashMap::default();
10302 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10303
10304 for row in start_row..=end_row {
10305 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10306 rows_by_indent_size.entry(indent).or_default().push(row);
10307 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10308 }
10309
10310 let indent_size = indent_size_occurrences
10311 .into_iter()
10312 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10313 .map(|(indent, _)| indent)
10314 .unwrap_or_default();
10315 let row = rows_by_indent_size[&indent_size][0];
10316 let indent_end = Point::new(row, indent_size.len);
10317
10318 (indent_size, indent_end)
10319 };
10320
10321 let mut line_prefix = indent_size.chars().collect::<String>();
10322
10323 let mut inside_comment = false;
10324 if let Some(comment_prefix) =
10325 buffer
10326 .language_scope_at(selection.head())
10327 .and_then(|language| {
10328 language
10329 .line_comment_prefixes()
10330 .iter()
10331 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10332 .cloned()
10333 })
10334 {
10335 line_prefix.push_str(&comment_prefix);
10336 inside_comment = true;
10337 }
10338
10339 let language_settings = buffer.language_settings_at(selection.head(), cx);
10340 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10341 RewrapBehavior::InComments => inside_comment,
10342 RewrapBehavior::InSelections => !selection.is_empty(),
10343 RewrapBehavior::Anywhere => true,
10344 };
10345
10346 let should_rewrap = options.override_language_settings
10347 || allow_rewrap_based_on_language
10348 || self.hard_wrap.is_some();
10349 if !should_rewrap {
10350 continue;
10351 }
10352
10353 if selection.is_empty() {
10354 'expand_upwards: while start_row > 0 {
10355 let prev_row = start_row - 1;
10356 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10357 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10358 {
10359 start_row = prev_row;
10360 } else {
10361 break 'expand_upwards;
10362 }
10363 }
10364
10365 'expand_downwards: while end_row < buffer.max_point().row {
10366 let next_row = end_row + 1;
10367 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10368 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10369 {
10370 end_row = next_row;
10371 } else {
10372 break 'expand_downwards;
10373 }
10374 }
10375 }
10376
10377 let start = Point::new(start_row, 0);
10378 let start_offset = start.to_offset(&buffer);
10379 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10380 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10381 let Some(lines_without_prefixes) = selection_text
10382 .lines()
10383 .map(|line| {
10384 line.strip_prefix(&line_prefix)
10385 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10386 .ok_or_else(|| {
10387 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10388 })
10389 })
10390 .collect::<Result<Vec<_>, _>>()
10391 .log_err()
10392 else {
10393 continue;
10394 };
10395
10396 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10397 buffer
10398 .language_settings_at(Point::new(start_row, 0), cx)
10399 .preferred_line_length as usize
10400 });
10401 let wrapped_text = wrap_with_prefix(
10402 line_prefix,
10403 lines_without_prefixes.join("\n"),
10404 wrap_column,
10405 tab_size,
10406 options.preserve_existing_whitespace,
10407 );
10408
10409 // TODO: should always use char-based diff while still supporting cursor behavior that
10410 // matches vim.
10411 let mut diff_options = DiffOptions::default();
10412 if options.override_language_settings {
10413 diff_options.max_word_diff_len = 0;
10414 diff_options.max_word_diff_line_count = 0;
10415 } else {
10416 diff_options.max_word_diff_len = usize::MAX;
10417 diff_options.max_word_diff_line_count = usize::MAX;
10418 }
10419
10420 for (old_range, new_text) in
10421 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10422 {
10423 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10424 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10425 edits.push((edit_start..edit_end, new_text));
10426 }
10427
10428 rewrapped_row_ranges.push(start_row..=end_row);
10429 }
10430
10431 self.buffer
10432 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10433 }
10434
10435 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10436 let mut text = String::new();
10437 let buffer = self.buffer.read(cx).snapshot(cx);
10438 let mut selections = self.selections.all::<Point>(cx);
10439 let mut clipboard_selections = Vec::with_capacity(selections.len());
10440 {
10441 let max_point = buffer.max_point();
10442 let mut is_first = true;
10443 for selection in &mut selections {
10444 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10445 if is_entire_line {
10446 selection.start = Point::new(selection.start.row, 0);
10447 if !selection.is_empty() && selection.end.column == 0 {
10448 selection.end = cmp::min(max_point, selection.end);
10449 } else {
10450 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10451 }
10452 selection.goal = SelectionGoal::None;
10453 }
10454 if is_first {
10455 is_first = false;
10456 } else {
10457 text += "\n";
10458 }
10459 let mut len = 0;
10460 for chunk in buffer.text_for_range(selection.start..selection.end) {
10461 text.push_str(chunk);
10462 len += chunk.len();
10463 }
10464 clipboard_selections.push(ClipboardSelection {
10465 len,
10466 is_entire_line,
10467 first_line_indent: buffer
10468 .indent_size_for_line(MultiBufferRow(selection.start.row))
10469 .len,
10470 });
10471 }
10472 }
10473
10474 self.transact(window, cx, |this, window, cx| {
10475 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10476 s.select(selections);
10477 });
10478 this.insert("", window, cx);
10479 });
10480 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10481 }
10482
10483 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10484 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10485 let item = self.cut_common(window, cx);
10486 cx.write_to_clipboard(item);
10487 }
10488
10489 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10490 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10491 self.change_selections(None, window, cx, |s| {
10492 s.move_with(|snapshot, sel| {
10493 if sel.is_empty() {
10494 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10495 }
10496 });
10497 });
10498 let item = self.cut_common(window, cx);
10499 cx.set_global(KillRing(item))
10500 }
10501
10502 pub fn kill_ring_yank(
10503 &mut self,
10504 _: &KillRingYank,
10505 window: &mut Window,
10506 cx: &mut Context<Self>,
10507 ) {
10508 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10509 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10510 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10511 (kill_ring.text().to_string(), kill_ring.metadata_json())
10512 } else {
10513 return;
10514 }
10515 } else {
10516 return;
10517 };
10518 self.do_paste(&text, metadata, false, window, cx);
10519 }
10520
10521 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10522 self.do_copy(true, cx);
10523 }
10524
10525 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10526 self.do_copy(false, cx);
10527 }
10528
10529 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10530 let selections = self.selections.all::<Point>(cx);
10531 let buffer = self.buffer.read(cx).read(cx);
10532 let mut text = String::new();
10533
10534 let mut clipboard_selections = Vec::with_capacity(selections.len());
10535 {
10536 let max_point = buffer.max_point();
10537 let mut is_first = true;
10538 for selection in &selections {
10539 let mut start = selection.start;
10540 let mut end = selection.end;
10541 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10542 if is_entire_line {
10543 start = Point::new(start.row, 0);
10544 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10545 }
10546
10547 let mut trimmed_selections = Vec::new();
10548 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10549 let row = MultiBufferRow(start.row);
10550 let first_indent = buffer.indent_size_for_line(row);
10551 if first_indent.len == 0 || start.column > first_indent.len {
10552 trimmed_selections.push(start..end);
10553 } else {
10554 trimmed_selections.push(
10555 Point::new(row.0, first_indent.len)
10556 ..Point::new(row.0, buffer.line_len(row)),
10557 );
10558 for row in start.row + 1..=end.row {
10559 let mut line_len = buffer.line_len(MultiBufferRow(row));
10560 if row == end.row {
10561 line_len = end.column;
10562 }
10563 if line_len == 0 {
10564 trimmed_selections
10565 .push(Point::new(row, 0)..Point::new(row, line_len));
10566 continue;
10567 }
10568 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10569 if row_indent_size.len >= first_indent.len {
10570 trimmed_selections.push(
10571 Point::new(row, first_indent.len)..Point::new(row, line_len),
10572 );
10573 } else {
10574 trimmed_selections.clear();
10575 trimmed_selections.push(start..end);
10576 break;
10577 }
10578 }
10579 }
10580 } else {
10581 trimmed_selections.push(start..end);
10582 }
10583
10584 for trimmed_range in trimmed_selections {
10585 if is_first {
10586 is_first = false;
10587 } else {
10588 text += "\n";
10589 }
10590 let mut len = 0;
10591 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10592 text.push_str(chunk);
10593 len += chunk.len();
10594 }
10595 clipboard_selections.push(ClipboardSelection {
10596 len,
10597 is_entire_line,
10598 first_line_indent: buffer
10599 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10600 .len,
10601 });
10602 }
10603 }
10604 }
10605
10606 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10607 text,
10608 clipboard_selections,
10609 ));
10610 }
10611
10612 pub fn do_paste(
10613 &mut self,
10614 text: &String,
10615 clipboard_selections: Option<Vec<ClipboardSelection>>,
10616 handle_entire_lines: bool,
10617 window: &mut Window,
10618 cx: &mut Context<Self>,
10619 ) {
10620 if self.read_only(cx) {
10621 return;
10622 }
10623
10624 let clipboard_text = Cow::Borrowed(text);
10625
10626 self.transact(window, cx, |this, window, cx| {
10627 if let Some(mut clipboard_selections) = clipboard_selections {
10628 let old_selections = this.selections.all::<usize>(cx);
10629 let all_selections_were_entire_line =
10630 clipboard_selections.iter().all(|s| s.is_entire_line);
10631 let first_selection_indent_column =
10632 clipboard_selections.first().map(|s| s.first_line_indent);
10633 if clipboard_selections.len() != old_selections.len() {
10634 clipboard_selections.drain(..);
10635 }
10636 let cursor_offset = this.selections.last::<usize>(cx).head();
10637 let mut auto_indent_on_paste = true;
10638
10639 this.buffer.update(cx, |buffer, cx| {
10640 let snapshot = buffer.read(cx);
10641 auto_indent_on_paste = snapshot
10642 .language_settings_at(cursor_offset, cx)
10643 .auto_indent_on_paste;
10644
10645 let mut start_offset = 0;
10646 let mut edits = Vec::new();
10647 let mut original_indent_columns = Vec::new();
10648 for (ix, selection) in old_selections.iter().enumerate() {
10649 let to_insert;
10650 let entire_line;
10651 let original_indent_column;
10652 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10653 let end_offset = start_offset + clipboard_selection.len;
10654 to_insert = &clipboard_text[start_offset..end_offset];
10655 entire_line = clipboard_selection.is_entire_line;
10656 start_offset = end_offset + 1;
10657 original_indent_column = Some(clipboard_selection.first_line_indent);
10658 } else {
10659 to_insert = clipboard_text.as_str();
10660 entire_line = all_selections_were_entire_line;
10661 original_indent_column = first_selection_indent_column
10662 }
10663
10664 // If the corresponding selection was empty when this slice of the
10665 // clipboard text was written, then the entire line containing the
10666 // selection was copied. If this selection is also currently empty,
10667 // then paste the line before the current line of the buffer.
10668 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10669 let column = selection.start.to_point(&snapshot).column as usize;
10670 let line_start = selection.start - column;
10671 line_start..line_start
10672 } else {
10673 selection.range()
10674 };
10675
10676 edits.push((range, to_insert));
10677 original_indent_columns.push(original_indent_column);
10678 }
10679 drop(snapshot);
10680
10681 buffer.edit(
10682 edits,
10683 if auto_indent_on_paste {
10684 Some(AutoindentMode::Block {
10685 original_indent_columns,
10686 })
10687 } else {
10688 None
10689 },
10690 cx,
10691 );
10692 });
10693
10694 let selections = this.selections.all::<usize>(cx);
10695 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10696 s.select(selections)
10697 });
10698 } else {
10699 this.insert(&clipboard_text, window, cx);
10700 }
10701 });
10702 }
10703
10704 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10705 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10706 if let Some(item) = cx.read_from_clipboard() {
10707 let entries = item.entries();
10708
10709 match entries.first() {
10710 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10711 // of all the pasted entries.
10712 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10713 .do_paste(
10714 clipboard_string.text(),
10715 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10716 true,
10717 window,
10718 cx,
10719 ),
10720 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10721 }
10722 }
10723 }
10724
10725 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10726 if self.read_only(cx) {
10727 return;
10728 }
10729
10730 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10731
10732 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10733 if let Some((selections, _)) =
10734 self.selection_history.transaction(transaction_id).cloned()
10735 {
10736 self.change_selections(None, window, cx, |s| {
10737 s.select_anchors(selections.to_vec());
10738 });
10739 } else {
10740 log::error!(
10741 "No entry in selection_history found for undo. \
10742 This may correspond to a bug where undo does not update the selection. \
10743 If this is occurring, please add details to \
10744 https://github.com/zed-industries/zed/issues/22692"
10745 );
10746 }
10747 self.request_autoscroll(Autoscroll::fit(), cx);
10748 self.unmark_text(window, cx);
10749 self.refresh_inline_completion(true, false, window, cx);
10750 cx.emit(EditorEvent::Edited { transaction_id });
10751 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10752 }
10753 }
10754
10755 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10756 if self.read_only(cx) {
10757 return;
10758 }
10759
10760 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10761
10762 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10763 if let Some((_, Some(selections))) =
10764 self.selection_history.transaction(transaction_id).cloned()
10765 {
10766 self.change_selections(None, window, cx, |s| {
10767 s.select_anchors(selections.to_vec());
10768 });
10769 } else {
10770 log::error!(
10771 "No entry in selection_history found for redo. \
10772 This may correspond to a bug where undo does not update the selection. \
10773 If this is occurring, please add details to \
10774 https://github.com/zed-industries/zed/issues/22692"
10775 );
10776 }
10777 self.request_autoscroll(Autoscroll::fit(), cx);
10778 self.unmark_text(window, cx);
10779 self.refresh_inline_completion(true, false, window, cx);
10780 cx.emit(EditorEvent::Edited { transaction_id });
10781 }
10782 }
10783
10784 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10785 self.buffer
10786 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10787 }
10788
10789 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10790 self.buffer
10791 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10792 }
10793
10794 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10795 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10796 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10797 s.move_with(|map, selection| {
10798 let cursor = if selection.is_empty() {
10799 movement::left(map, selection.start)
10800 } else {
10801 selection.start
10802 };
10803 selection.collapse_to(cursor, SelectionGoal::None);
10804 });
10805 })
10806 }
10807
10808 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10809 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10810 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10811 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10812 })
10813 }
10814
10815 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10816 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10817 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10818 s.move_with(|map, selection| {
10819 let cursor = if selection.is_empty() {
10820 movement::right(map, selection.end)
10821 } else {
10822 selection.end
10823 };
10824 selection.collapse_to(cursor, SelectionGoal::None)
10825 });
10826 })
10827 }
10828
10829 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10830 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10831 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10832 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10833 })
10834 }
10835
10836 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10837 if self.take_rename(true, window, cx).is_some() {
10838 return;
10839 }
10840
10841 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10842 cx.propagate();
10843 return;
10844 }
10845
10846 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10847
10848 let text_layout_details = &self.text_layout_details(window);
10849 let selection_count = self.selections.count();
10850 let first_selection = self.selections.first_anchor();
10851
10852 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10853 s.move_with(|map, selection| {
10854 if !selection.is_empty() {
10855 selection.goal = SelectionGoal::None;
10856 }
10857 let (cursor, goal) = movement::up(
10858 map,
10859 selection.start,
10860 selection.goal,
10861 false,
10862 text_layout_details,
10863 );
10864 selection.collapse_to(cursor, goal);
10865 });
10866 });
10867
10868 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10869 {
10870 cx.propagate();
10871 }
10872 }
10873
10874 pub fn move_up_by_lines(
10875 &mut self,
10876 action: &MoveUpByLines,
10877 window: &mut Window,
10878 cx: &mut Context<Self>,
10879 ) {
10880 if self.take_rename(true, window, cx).is_some() {
10881 return;
10882 }
10883
10884 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10885 cx.propagate();
10886 return;
10887 }
10888
10889 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10890
10891 let text_layout_details = &self.text_layout_details(window);
10892
10893 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10894 s.move_with(|map, selection| {
10895 if !selection.is_empty() {
10896 selection.goal = SelectionGoal::None;
10897 }
10898 let (cursor, goal) = movement::up_by_rows(
10899 map,
10900 selection.start,
10901 action.lines,
10902 selection.goal,
10903 false,
10904 text_layout_details,
10905 );
10906 selection.collapse_to(cursor, goal);
10907 });
10908 })
10909 }
10910
10911 pub fn move_down_by_lines(
10912 &mut self,
10913 action: &MoveDownByLines,
10914 window: &mut Window,
10915 cx: &mut Context<Self>,
10916 ) {
10917 if self.take_rename(true, window, cx).is_some() {
10918 return;
10919 }
10920
10921 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10922 cx.propagate();
10923 return;
10924 }
10925
10926 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10927
10928 let text_layout_details = &self.text_layout_details(window);
10929
10930 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10931 s.move_with(|map, selection| {
10932 if !selection.is_empty() {
10933 selection.goal = SelectionGoal::None;
10934 }
10935 let (cursor, goal) = movement::down_by_rows(
10936 map,
10937 selection.start,
10938 action.lines,
10939 selection.goal,
10940 false,
10941 text_layout_details,
10942 );
10943 selection.collapse_to(cursor, goal);
10944 });
10945 })
10946 }
10947
10948 pub fn select_down_by_lines(
10949 &mut self,
10950 action: &SelectDownByLines,
10951 window: &mut Window,
10952 cx: &mut Context<Self>,
10953 ) {
10954 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10955 let text_layout_details = &self.text_layout_details(window);
10956 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10957 s.move_heads_with(|map, head, goal| {
10958 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10959 })
10960 })
10961 }
10962
10963 pub fn select_up_by_lines(
10964 &mut self,
10965 action: &SelectUpByLines,
10966 window: &mut Window,
10967 cx: &mut Context<Self>,
10968 ) {
10969 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10970 let text_layout_details = &self.text_layout_details(window);
10971 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10972 s.move_heads_with(|map, head, goal| {
10973 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10974 })
10975 })
10976 }
10977
10978 pub fn select_page_up(
10979 &mut self,
10980 _: &SelectPageUp,
10981 window: &mut Window,
10982 cx: &mut Context<Self>,
10983 ) {
10984 let Some(row_count) = self.visible_row_count() else {
10985 return;
10986 };
10987
10988 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10989
10990 let text_layout_details = &self.text_layout_details(window);
10991
10992 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10993 s.move_heads_with(|map, head, goal| {
10994 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10995 })
10996 })
10997 }
10998
10999 pub fn move_page_up(
11000 &mut self,
11001 action: &MovePageUp,
11002 window: &mut Window,
11003 cx: &mut Context<Self>,
11004 ) {
11005 if self.take_rename(true, window, cx).is_some() {
11006 return;
11007 }
11008
11009 if self
11010 .context_menu
11011 .borrow_mut()
11012 .as_mut()
11013 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11014 .unwrap_or(false)
11015 {
11016 return;
11017 }
11018
11019 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11020 cx.propagate();
11021 return;
11022 }
11023
11024 let Some(row_count) = self.visible_row_count() else {
11025 return;
11026 };
11027
11028 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11029
11030 let autoscroll = if action.center_cursor {
11031 Autoscroll::center()
11032 } else {
11033 Autoscroll::fit()
11034 };
11035
11036 let text_layout_details = &self.text_layout_details(window);
11037
11038 self.change_selections(Some(autoscroll), window, cx, |s| {
11039 s.move_with(|map, selection| {
11040 if !selection.is_empty() {
11041 selection.goal = SelectionGoal::None;
11042 }
11043 let (cursor, goal) = movement::up_by_rows(
11044 map,
11045 selection.end,
11046 row_count,
11047 selection.goal,
11048 false,
11049 text_layout_details,
11050 );
11051 selection.collapse_to(cursor, goal);
11052 });
11053 });
11054 }
11055
11056 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11057 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11058 let text_layout_details = &self.text_layout_details(window);
11059 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11060 s.move_heads_with(|map, head, goal| {
11061 movement::up(map, head, goal, false, text_layout_details)
11062 })
11063 })
11064 }
11065
11066 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11067 self.take_rename(true, window, cx);
11068
11069 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11070 cx.propagate();
11071 return;
11072 }
11073
11074 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11075
11076 let text_layout_details = &self.text_layout_details(window);
11077 let selection_count = self.selections.count();
11078 let first_selection = self.selections.first_anchor();
11079
11080 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11081 s.move_with(|map, selection| {
11082 if !selection.is_empty() {
11083 selection.goal = SelectionGoal::None;
11084 }
11085 let (cursor, goal) = movement::down(
11086 map,
11087 selection.end,
11088 selection.goal,
11089 false,
11090 text_layout_details,
11091 );
11092 selection.collapse_to(cursor, goal);
11093 });
11094 });
11095
11096 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11097 {
11098 cx.propagate();
11099 }
11100 }
11101
11102 pub fn select_page_down(
11103 &mut self,
11104 _: &SelectPageDown,
11105 window: &mut Window,
11106 cx: &mut Context<Self>,
11107 ) {
11108 let Some(row_count) = self.visible_row_count() else {
11109 return;
11110 };
11111
11112 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11113
11114 let text_layout_details = &self.text_layout_details(window);
11115
11116 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11117 s.move_heads_with(|map, head, goal| {
11118 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11119 })
11120 })
11121 }
11122
11123 pub fn move_page_down(
11124 &mut self,
11125 action: &MovePageDown,
11126 window: &mut Window,
11127 cx: &mut Context<Self>,
11128 ) {
11129 if self.take_rename(true, window, cx).is_some() {
11130 return;
11131 }
11132
11133 if self
11134 .context_menu
11135 .borrow_mut()
11136 .as_mut()
11137 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11138 .unwrap_or(false)
11139 {
11140 return;
11141 }
11142
11143 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11144 cx.propagate();
11145 return;
11146 }
11147
11148 let Some(row_count) = self.visible_row_count() else {
11149 return;
11150 };
11151
11152 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11153
11154 let autoscroll = if action.center_cursor {
11155 Autoscroll::center()
11156 } else {
11157 Autoscroll::fit()
11158 };
11159
11160 let text_layout_details = &self.text_layout_details(window);
11161 self.change_selections(Some(autoscroll), window, cx, |s| {
11162 s.move_with(|map, selection| {
11163 if !selection.is_empty() {
11164 selection.goal = SelectionGoal::None;
11165 }
11166 let (cursor, goal) = movement::down_by_rows(
11167 map,
11168 selection.end,
11169 row_count,
11170 selection.goal,
11171 false,
11172 text_layout_details,
11173 );
11174 selection.collapse_to(cursor, goal);
11175 });
11176 });
11177 }
11178
11179 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11180 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11181 let text_layout_details = &self.text_layout_details(window);
11182 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11183 s.move_heads_with(|map, head, goal| {
11184 movement::down(map, head, goal, false, text_layout_details)
11185 })
11186 });
11187 }
11188
11189 pub fn context_menu_first(
11190 &mut self,
11191 _: &ContextMenuFirst,
11192 _window: &mut Window,
11193 cx: &mut Context<Self>,
11194 ) {
11195 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11196 context_menu.select_first(self.completion_provider.as_deref(), cx);
11197 }
11198 }
11199
11200 pub fn context_menu_prev(
11201 &mut self,
11202 _: &ContextMenuPrevious,
11203 _window: &mut Window,
11204 cx: &mut Context<Self>,
11205 ) {
11206 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11207 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11208 }
11209 }
11210
11211 pub fn context_menu_next(
11212 &mut self,
11213 _: &ContextMenuNext,
11214 _window: &mut Window,
11215 cx: &mut Context<Self>,
11216 ) {
11217 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11218 context_menu.select_next(self.completion_provider.as_deref(), cx);
11219 }
11220 }
11221
11222 pub fn context_menu_last(
11223 &mut self,
11224 _: &ContextMenuLast,
11225 _window: &mut Window,
11226 cx: &mut Context<Self>,
11227 ) {
11228 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11229 context_menu.select_last(self.completion_provider.as_deref(), cx);
11230 }
11231 }
11232
11233 pub fn move_to_previous_word_start(
11234 &mut self,
11235 _: &MoveToPreviousWordStart,
11236 window: &mut Window,
11237 cx: &mut Context<Self>,
11238 ) {
11239 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11240 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11241 s.move_cursors_with(|map, head, _| {
11242 (
11243 movement::previous_word_start(map, head),
11244 SelectionGoal::None,
11245 )
11246 });
11247 })
11248 }
11249
11250 pub fn move_to_previous_subword_start(
11251 &mut self,
11252 _: &MoveToPreviousSubwordStart,
11253 window: &mut Window,
11254 cx: &mut Context<Self>,
11255 ) {
11256 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11257 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11258 s.move_cursors_with(|map, head, _| {
11259 (
11260 movement::previous_subword_start(map, head),
11261 SelectionGoal::None,
11262 )
11263 });
11264 })
11265 }
11266
11267 pub fn select_to_previous_word_start(
11268 &mut self,
11269 _: &SelectToPreviousWordStart,
11270 window: &mut Window,
11271 cx: &mut Context<Self>,
11272 ) {
11273 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11274 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11275 s.move_heads_with(|map, head, _| {
11276 (
11277 movement::previous_word_start(map, head),
11278 SelectionGoal::None,
11279 )
11280 });
11281 })
11282 }
11283
11284 pub fn select_to_previous_subword_start(
11285 &mut self,
11286 _: &SelectToPreviousSubwordStart,
11287 window: &mut Window,
11288 cx: &mut Context<Self>,
11289 ) {
11290 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11291 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11292 s.move_heads_with(|map, head, _| {
11293 (
11294 movement::previous_subword_start(map, head),
11295 SelectionGoal::None,
11296 )
11297 });
11298 })
11299 }
11300
11301 pub fn delete_to_previous_word_start(
11302 &mut self,
11303 action: &DeleteToPreviousWordStart,
11304 window: &mut Window,
11305 cx: &mut Context<Self>,
11306 ) {
11307 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11308 self.transact(window, cx, |this, window, cx| {
11309 this.select_autoclose_pair(window, cx);
11310 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11311 s.move_with(|map, selection| {
11312 if selection.is_empty() {
11313 let cursor = if action.ignore_newlines {
11314 movement::previous_word_start(map, selection.head())
11315 } else {
11316 movement::previous_word_start_or_newline(map, selection.head())
11317 };
11318 selection.set_head(cursor, SelectionGoal::None);
11319 }
11320 });
11321 });
11322 this.insert("", window, cx);
11323 });
11324 }
11325
11326 pub fn delete_to_previous_subword_start(
11327 &mut self,
11328 _: &DeleteToPreviousSubwordStart,
11329 window: &mut Window,
11330 cx: &mut Context<Self>,
11331 ) {
11332 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11333 self.transact(window, cx, |this, window, cx| {
11334 this.select_autoclose_pair(window, cx);
11335 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11336 s.move_with(|map, selection| {
11337 if selection.is_empty() {
11338 let cursor = movement::previous_subword_start(map, selection.head());
11339 selection.set_head(cursor, SelectionGoal::None);
11340 }
11341 });
11342 });
11343 this.insert("", window, cx);
11344 });
11345 }
11346
11347 pub fn move_to_next_word_end(
11348 &mut self,
11349 _: &MoveToNextWordEnd,
11350 window: &mut Window,
11351 cx: &mut Context<Self>,
11352 ) {
11353 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11354 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11355 s.move_cursors_with(|map, head, _| {
11356 (movement::next_word_end(map, head), SelectionGoal::None)
11357 });
11358 })
11359 }
11360
11361 pub fn move_to_next_subword_end(
11362 &mut self,
11363 _: &MoveToNextSubwordEnd,
11364 window: &mut Window,
11365 cx: &mut Context<Self>,
11366 ) {
11367 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11368 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11369 s.move_cursors_with(|map, head, _| {
11370 (movement::next_subword_end(map, head), SelectionGoal::None)
11371 });
11372 })
11373 }
11374
11375 pub fn select_to_next_word_end(
11376 &mut self,
11377 _: &SelectToNextWordEnd,
11378 window: &mut Window,
11379 cx: &mut Context<Self>,
11380 ) {
11381 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11382 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11383 s.move_heads_with(|map, head, _| {
11384 (movement::next_word_end(map, head), SelectionGoal::None)
11385 });
11386 })
11387 }
11388
11389 pub fn select_to_next_subword_end(
11390 &mut self,
11391 _: &SelectToNextSubwordEnd,
11392 window: &mut Window,
11393 cx: &mut Context<Self>,
11394 ) {
11395 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11396 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11397 s.move_heads_with(|map, head, _| {
11398 (movement::next_subword_end(map, head), SelectionGoal::None)
11399 });
11400 })
11401 }
11402
11403 pub fn delete_to_next_word_end(
11404 &mut self,
11405 action: &DeleteToNextWordEnd,
11406 window: &mut Window,
11407 cx: &mut Context<Self>,
11408 ) {
11409 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11410 self.transact(window, cx, |this, window, cx| {
11411 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11412 s.move_with(|map, selection| {
11413 if selection.is_empty() {
11414 let cursor = if action.ignore_newlines {
11415 movement::next_word_end(map, selection.head())
11416 } else {
11417 movement::next_word_end_or_newline(map, selection.head())
11418 };
11419 selection.set_head(cursor, SelectionGoal::None);
11420 }
11421 });
11422 });
11423 this.insert("", window, cx);
11424 });
11425 }
11426
11427 pub fn delete_to_next_subword_end(
11428 &mut self,
11429 _: &DeleteToNextSubwordEnd,
11430 window: &mut Window,
11431 cx: &mut Context<Self>,
11432 ) {
11433 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11434 self.transact(window, cx, |this, window, cx| {
11435 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11436 s.move_with(|map, selection| {
11437 if selection.is_empty() {
11438 let cursor = movement::next_subword_end(map, selection.head());
11439 selection.set_head(cursor, SelectionGoal::None);
11440 }
11441 });
11442 });
11443 this.insert("", window, cx);
11444 });
11445 }
11446
11447 pub fn move_to_beginning_of_line(
11448 &mut self,
11449 action: &MoveToBeginningOfLine,
11450 window: &mut Window,
11451 cx: &mut Context<Self>,
11452 ) {
11453 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11454 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11455 s.move_cursors_with(|map, head, _| {
11456 (
11457 movement::indented_line_beginning(
11458 map,
11459 head,
11460 action.stop_at_soft_wraps,
11461 action.stop_at_indent,
11462 ),
11463 SelectionGoal::None,
11464 )
11465 });
11466 })
11467 }
11468
11469 pub fn select_to_beginning_of_line(
11470 &mut self,
11471 action: &SelectToBeginningOfLine,
11472 window: &mut Window,
11473 cx: &mut Context<Self>,
11474 ) {
11475 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11476 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11477 s.move_heads_with(|map, head, _| {
11478 (
11479 movement::indented_line_beginning(
11480 map,
11481 head,
11482 action.stop_at_soft_wraps,
11483 action.stop_at_indent,
11484 ),
11485 SelectionGoal::None,
11486 )
11487 });
11488 });
11489 }
11490
11491 pub fn delete_to_beginning_of_line(
11492 &mut self,
11493 action: &DeleteToBeginningOfLine,
11494 window: &mut Window,
11495 cx: &mut Context<Self>,
11496 ) {
11497 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11498 self.transact(window, cx, |this, window, cx| {
11499 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11500 s.move_with(|_, selection| {
11501 selection.reversed = true;
11502 });
11503 });
11504
11505 this.select_to_beginning_of_line(
11506 &SelectToBeginningOfLine {
11507 stop_at_soft_wraps: false,
11508 stop_at_indent: action.stop_at_indent,
11509 },
11510 window,
11511 cx,
11512 );
11513 this.backspace(&Backspace, window, cx);
11514 });
11515 }
11516
11517 pub fn move_to_end_of_line(
11518 &mut self,
11519 action: &MoveToEndOfLine,
11520 window: &mut Window,
11521 cx: &mut Context<Self>,
11522 ) {
11523 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11524 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11525 s.move_cursors_with(|map, head, _| {
11526 (
11527 movement::line_end(map, head, action.stop_at_soft_wraps),
11528 SelectionGoal::None,
11529 )
11530 });
11531 })
11532 }
11533
11534 pub fn select_to_end_of_line(
11535 &mut self,
11536 action: &SelectToEndOfLine,
11537 window: &mut Window,
11538 cx: &mut Context<Self>,
11539 ) {
11540 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11541 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11542 s.move_heads_with(|map, head, _| {
11543 (
11544 movement::line_end(map, head, action.stop_at_soft_wraps),
11545 SelectionGoal::None,
11546 )
11547 });
11548 })
11549 }
11550
11551 pub fn delete_to_end_of_line(
11552 &mut self,
11553 _: &DeleteToEndOfLine,
11554 window: &mut Window,
11555 cx: &mut Context<Self>,
11556 ) {
11557 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11558 self.transact(window, cx, |this, window, cx| {
11559 this.select_to_end_of_line(
11560 &SelectToEndOfLine {
11561 stop_at_soft_wraps: false,
11562 },
11563 window,
11564 cx,
11565 );
11566 this.delete(&Delete, window, cx);
11567 });
11568 }
11569
11570 pub fn cut_to_end_of_line(
11571 &mut self,
11572 _: &CutToEndOfLine,
11573 window: &mut Window,
11574 cx: &mut Context<Self>,
11575 ) {
11576 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11577 self.transact(window, cx, |this, window, cx| {
11578 this.select_to_end_of_line(
11579 &SelectToEndOfLine {
11580 stop_at_soft_wraps: false,
11581 },
11582 window,
11583 cx,
11584 );
11585 this.cut(&Cut, window, cx);
11586 });
11587 }
11588
11589 pub fn move_to_start_of_paragraph(
11590 &mut self,
11591 _: &MoveToStartOfParagraph,
11592 window: &mut Window,
11593 cx: &mut Context<Self>,
11594 ) {
11595 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11596 cx.propagate();
11597 return;
11598 }
11599 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11600 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11601 s.move_with(|map, selection| {
11602 selection.collapse_to(
11603 movement::start_of_paragraph(map, selection.head(), 1),
11604 SelectionGoal::None,
11605 )
11606 });
11607 })
11608 }
11609
11610 pub fn move_to_end_of_paragraph(
11611 &mut self,
11612 _: &MoveToEndOfParagraph,
11613 window: &mut Window,
11614 cx: &mut Context<Self>,
11615 ) {
11616 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11617 cx.propagate();
11618 return;
11619 }
11620 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11621 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11622 s.move_with(|map, selection| {
11623 selection.collapse_to(
11624 movement::end_of_paragraph(map, selection.head(), 1),
11625 SelectionGoal::None,
11626 )
11627 });
11628 })
11629 }
11630
11631 pub fn select_to_start_of_paragraph(
11632 &mut self,
11633 _: &SelectToStartOfParagraph,
11634 window: &mut Window,
11635 cx: &mut Context<Self>,
11636 ) {
11637 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11638 cx.propagate();
11639 return;
11640 }
11641 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11642 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11643 s.move_heads_with(|map, head, _| {
11644 (
11645 movement::start_of_paragraph(map, head, 1),
11646 SelectionGoal::None,
11647 )
11648 });
11649 })
11650 }
11651
11652 pub fn select_to_end_of_paragraph(
11653 &mut self,
11654 _: &SelectToEndOfParagraph,
11655 window: &mut Window,
11656 cx: &mut Context<Self>,
11657 ) {
11658 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11659 cx.propagate();
11660 return;
11661 }
11662 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11663 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11664 s.move_heads_with(|map, head, _| {
11665 (
11666 movement::end_of_paragraph(map, head, 1),
11667 SelectionGoal::None,
11668 )
11669 });
11670 })
11671 }
11672
11673 pub fn move_to_start_of_excerpt(
11674 &mut self,
11675 _: &MoveToStartOfExcerpt,
11676 window: &mut Window,
11677 cx: &mut Context<Self>,
11678 ) {
11679 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11680 cx.propagate();
11681 return;
11682 }
11683 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11684 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11685 s.move_with(|map, selection| {
11686 selection.collapse_to(
11687 movement::start_of_excerpt(
11688 map,
11689 selection.head(),
11690 workspace::searchable::Direction::Prev,
11691 ),
11692 SelectionGoal::None,
11693 )
11694 });
11695 })
11696 }
11697
11698 pub fn move_to_start_of_next_excerpt(
11699 &mut self,
11700 _: &MoveToStartOfNextExcerpt,
11701 window: &mut Window,
11702 cx: &mut Context<Self>,
11703 ) {
11704 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11705 cx.propagate();
11706 return;
11707 }
11708
11709 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11710 s.move_with(|map, selection| {
11711 selection.collapse_to(
11712 movement::start_of_excerpt(
11713 map,
11714 selection.head(),
11715 workspace::searchable::Direction::Next,
11716 ),
11717 SelectionGoal::None,
11718 )
11719 });
11720 })
11721 }
11722
11723 pub fn move_to_end_of_excerpt(
11724 &mut self,
11725 _: &MoveToEndOfExcerpt,
11726 window: &mut Window,
11727 cx: &mut Context<Self>,
11728 ) {
11729 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11730 cx.propagate();
11731 return;
11732 }
11733 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11734 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11735 s.move_with(|map, selection| {
11736 selection.collapse_to(
11737 movement::end_of_excerpt(
11738 map,
11739 selection.head(),
11740 workspace::searchable::Direction::Next,
11741 ),
11742 SelectionGoal::None,
11743 )
11744 });
11745 })
11746 }
11747
11748 pub fn move_to_end_of_previous_excerpt(
11749 &mut self,
11750 _: &MoveToEndOfPreviousExcerpt,
11751 window: &mut Window,
11752 cx: &mut Context<Self>,
11753 ) {
11754 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11755 cx.propagate();
11756 return;
11757 }
11758 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11759 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11760 s.move_with(|map, selection| {
11761 selection.collapse_to(
11762 movement::end_of_excerpt(
11763 map,
11764 selection.head(),
11765 workspace::searchable::Direction::Prev,
11766 ),
11767 SelectionGoal::None,
11768 )
11769 });
11770 })
11771 }
11772
11773 pub fn select_to_start_of_excerpt(
11774 &mut self,
11775 _: &SelectToStartOfExcerpt,
11776 window: &mut Window,
11777 cx: &mut Context<Self>,
11778 ) {
11779 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11780 cx.propagate();
11781 return;
11782 }
11783 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11784 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11785 s.move_heads_with(|map, head, _| {
11786 (
11787 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11788 SelectionGoal::None,
11789 )
11790 });
11791 })
11792 }
11793
11794 pub fn select_to_start_of_next_excerpt(
11795 &mut self,
11796 _: &SelectToStartOfNextExcerpt,
11797 window: &mut Window,
11798 cx: &mut Context<Self>,
11799 ) {
11800 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11801 cx.propagate();
11802 return;
11803 }
11804 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11805 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11806 s.move_heads_with(|map, head, _| {
11807 (
11808 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11809 SelectionGoal::None,
11810 )
11811 });
11812 })
11813 }
11814
11815 pub fn select_to_end_of_excerpt(
11816 &mut self,
11817 _: &SelectToEndOfExcerpt,
11818 window: &mut Window,
11819 cx: &mut Context<Self>,
11820 ) {
11821 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11822 cx.propagate();
11823 return;
11824 }
11825 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11826 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11827 s.move_heads_with(|map, head, _| {
11828 (
11829 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11830 SelectionGoal::None,
11831 )
11832 });
11833 })
11834 }
11835
11836 pub fn select_to_end_of_previous_excerpt(
11837 &mut self,
11838 _: &SelectToEndOfPreviousExcerpt,
11839 window: &mut Window,
11840 cx: &mut Context<Self>,
11841 ) {
11842 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11843 cx.propagate();
11844 return;
11845 }
11846 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11847 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11848 s.move_heads_with(|map, head, _| {
11849 (
11850 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11851 SelectionGoal::None,
11852 )
11853 });
11854 })
11855 }
11856
11857 pub fn move_to_beginning(
11858 &mut self,
11859 _: &MoveToBeginning,
11860 window: &mut Window,
11861 cx: &mut Context<Self>,
11862 ) {
11863 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11864 cx.propagate();
11865 return;
11866 }
11867 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11868 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11869 s.select_ranges(vec![0..0]);
11870 });
11871 }
11872
11873 pub fn select_to_beginning(
11874 &mut self,
11875 _: &SelectToBeginning,
11876 window: &mut Window,
11877 cx: &mut Context<Self>,
11878 ) {
11879 let mut selection = self.selections.last::<Point>(cx);
11880 selection.set_head(Point::zero(), SelectionGoal::None);
11881 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11882 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11883 s.select(vec![selection]);
11884 });
11885 }
11886
11887 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11888 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11889 cx.propagate();
11890 return;
11891 }
11892 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11893 let cursor = self.buffer.read(cx).read(cx).len();
11894 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11895 s.select_ranges(vec![cursor..cursor])
11896 });
11897 }
11898
11899 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11900 self.nav_history = nav_history;
11901 }
11902
11903 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11904 self.nav_history.as_ref()
11905 }
11906
11907 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11908 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11909 }
11910
11911 fn push_to_nav_history(
11912 &mut self,
11913 cursor_anchor: Anchor,
11914 new_position: Option<Point>,
11915 is_deactivate: bool,
11916 cx: &mut Context<Self>,
11917 ) {
11918 if let Some(nav_history) = self.nav_history.as_mut() {
11919 let buffer = self.buffer.read(cx).read(cx);
11920 let cursor_position = cursor_anchor.to_point(&buffer);
11921 let scroll_state = self.scroll_manager.anchor();
11922 let scroll_top_row = scroll_state.top_row(&buffer);
11923 drop(buffer);
11924
11925 if let Some(new_position) = new_position {
11926 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11927 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11928 return;
11929 }
11930 }
11931
11932 nav_history.push(
11933 Some(NavigationData {
11934 cursor_anchor,
11935 cursor_position,
11936 scroll_anchor: scroll_state,
11937 scroll_top_row,
11938 }),
11939 cx,
11940 );
11941 cx.emit(EditorEvent::PushedToNavHistory {
11942 anchor: cursor_anchor,
11943 is_deactivate,
11944 })
11945 }
11946 }
11947
11948 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11949 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11950 let buffer = self.buffer.read(cx).snapshot(cx);
11951 let mut selection = self.selections.first::<usize>(cx);
11952 selection.set_head(buffer.len(), SelectionGoal::None);
11953 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11954 s.select(vec![selection]);
11955 });
11956 }
11957
11958 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11959 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11960 let end = self.buffer.read(cx).read(cx).len();
11961 self.change_selections(None, window, cx, |s| {
11962 s.select_ranges(vec![0..end]);
11963 });
11964 }
11965
11966 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11967 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11968 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11969 let mut selections = self.selections.all::<Point>(cx);
11970 let max_point = display_map.buffer_snapshot.max_point();
11971 for selection in &mut selections {
11972 let rows = selection.spanned_rows(true, &display_map);
11973 selection.start = Point::new(rows.start.0, 0);
11974 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11975 selection.reversed = false;
11976 }
11977 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11978 s.select(selections);
11979 });
11980 }
11981
11982 pub fn split_selection_into_lines(
11983 &mut self,
11984 _: &SplitSelectionIntoLines,
11985 window: &mut Window,
11986 cx: &mut Context<Self>,
11987 ) {
11988 let selections = self
11989 .selections
11990 .all::<Point>(cx)
11991 .into_iter()
11992 .map(|selection| selection.start..selection.end)
11993 .collect::<Vec<_>>();
11994 self.unfold_ranges(&selections, true, true, cx);
11995
11996 let mut new_selection_ranges = Vec::new();
11997 {
11998 let buffer = self.buffer.read(cx).read(cx);
11999 for selection in selections {
12000 for row in selection.start.row..selection.end.row {
12001 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12002 new_selection_ranges.push(cursor..cursor);
12003 }
12004
12005 let is_multiline_selection = selection.start.row != selection.end.row;
12006 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12007 // so this action feels more ergonomic when paired with other selection operations
12008 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12009 if !should_skip_last {
12010 new_selection_ranges.push(selection.end..selection.end);
12011 }
12012 }
12013 }
12014 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12015 s.select_ranges(new_selection_ranges);
12016 });
12017 }
12018
12019 pub fn add_selection_above(
12020 &mut self,
12021 _: &AddSelectionAbove,
12022 window: &mut Window,
12023 cx: &mut Context<Self>,
12024 ) {
12025 self.add_selection(true, window, cx);
12026 }
12027
12028 pub fn add_selection_below(
12029 &mut self,
12030 _: &AddSelectionBelow,
12031 window: &mut Window,
12032 cx: &mut Context<Self>,
12033 ) {
12034 self.add_selection(false, window, cx);
12035 }
12036
12037 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12038 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12039
12040 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12041 let mut selections = self.selections.all::<Point>(cx);
12042 let text_layout_details = self.text_layout_details(window);
12043 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12044 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12045 let range = oldest_selection.display_range(&display_map).sorted();
12046
12047 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12048 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12049 let positions = start_x.min(end_x)..start_x.max(end_x);
12050
12051 selections.clear();
12052 let mut stack = Vec::new();
12053 for row in range.start.row().0..=range.end.row().0 {
12054 if let Some(selection) = self.selections.build_columnar_selection(
12055 &display_map,
12056 DisplayRow(row),
12057 &positions,
12058 oldest_selection.reversed,
12059 &text_layout_details,
12060 ) {
12061 stack.push(selection.id);
12062 selections.push(selection);
12063 }
12064 }
12065
12066 if above {
12067 stack.reverse();
12068 }
12069
12070 AddSelectionsState { above, stack }
12071 });
12072
12073 let last_added_selection = *state.stack.last().unwrap();
12074 let mut new_selections = Vec::new();
12075 if above == state.above {
12076 let end_row = if above {
12077 DisplayRow(0)
12078 } else {
12079 display_map.max_point().row()
12080 };
12081
12082 'outer: for selection in selections {
12083 if selection.id == last_added_selection {
12084 let range = selection.display_range(&display_map).sorted();
12085 debug_assert_eq!(range.start.row(), range.end.row());
12086 let mut row = range.start.row();
12087 let positions =
12088 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12089 px(start)..px(end)
12090 } else {
12091 let start_x =
12092 display_map.x_for_display_point(range.start, &text_layout_details);
12093 let end_x =
12094 display_map.x_for_display_point(range.end, &text_layout_details);
12095 start_x.min(end_x)..start_x.max(end_x)
12096 };
12097
12098 while row != end_row {
12099 if above {
12100 row.0 -= 1;
12101 } else {
12102 row.0 += 1;
12103 }
12104
12105 if let Some(new_selection) = self.selections.build_columnar_selection(
12106 &display_map,
12107 row,
12108 &positions,
12109 selection.reversed,
12110 &text_layout_details,
12111 ) {
12112 state.stack.push(new_selection.id);
12113 if above {
12114 new_selections.push(new_selection);
12115 new_selections.push(selection);
12116 } else {
12117 new_selections.push(selection);
12118 new_selections.push(new_selection);
12119 }
12120
12121 continue 'outer;
12122 }
12123 }
12124 }
12125
12126 new_selections.push(selection);
12127 }
12128 } else {
12129 new_selections = selections;
12130 new_selections.retain(|s| s.id != last_added_selection);
12131 state.stack.pop();
12132 }
12133
12134 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12135 s.select(new_selections);
12136 });
12137 if state.stack.len() > 1 {
12138 self.add_selections_state = Some(state);
12139 }
12140 }
12141
12142 pub fn select_next_match_internal(
12143 &mut self,
12144 display_map: &DisplaySnapshot,
12145 replace_newest: bool,
12146 autoscroll: Option<Autoscroll>,
12147 window: &mut Window,
12148 cx: &mut Context<Self>,
12149 ) -> Result<()> {
12150 fn select_next_match_ranges(
12151 this: &mut Editor,
12152 range: Range<usize>,
12153 reversed: bool,
12154 replace_newest: bool,
12155 auto_scroll: Option<Autoscroll>,
12156 window: &mut Window,
12157 cx: &mut Context<Editor>,
12158 ) {
12159 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12160 this.change_selections(auto_scroll, window, cx, |s| {
12161 if replace_newest {
12162 s.delete(s.newest_anchor().id);
12163 }
12164 if reversed {
12165 s.insert_range(range.end..range.start);
12166 } else {
12167 s.insert_range(range);
12168 }
12169 });
12170 }
12171
12172 let buffer = &display_map.buffer_snapshot;
12173 let mut selections = self.selections.all::<usize>(cx);
12174 if let Some(mut select_next_state) = self.select_next_state.take() {
12175 let query = &select_next_state.query;
12176 if !select_next_state.done {
12177 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12178 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12179 let mut next_selected_range = None;
12180
12181 let bytes_after_last_selection =
12182 buffer.bytes_in_range(last_selection.end..buffer.len());
12183 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12184 let query_matches = query
12185 .stream_find_iter(bytes_after_last_selection)
12186 .map(|result| (last_selection.end, result))
12187 .chain(
12188 query
12189 .stream_find_iter(bytes_before_first_selection)
12190 .map(|result| (0, result)),
12191 );
12192
12193 for (start_offset, query_match) in query_matches {
12194 let query_match = query_match.unwrap(); // can only fail due to I/O
12195 let offset_range =
12196 start_offset + query_match.start()..start_offset + query_match.end();
12197 let display_range = offset_range.start.to_display_point(display_map)
12198 ..offset_range.end.to_display_point(display_map);
12199
12200 if !select_next_state.wordwise
12201 || (!movement::is_inside_word(display_map, display_range.start)
12202 && !movement::is_inside_word(display_map, display_range.end))
12203 {
12204 // TODO: This is n^2, because we might check all the selections
12205 if !selections
12206 .iter()
12207 .any(|selection| selection.range().overlaps(&offset_range))
12208 {
12209 next_selected_range = Some(offset_range);
12210 break;
12211 }
12212 }
12213 }
12214
12215 if let Some(next_selected_range) = next_selected_range {
12216 select_next_match_ranges(
12217 self,
12218 next_selected_range,
12219 last_selection.reversed,
12220 replace_newest,
12221 autoscroll,
12222 window,
12223 cx,
12224 );
12225 } else {
12226 select_next_state.done = true;
12227 }
12228 }
12229
12230 self.select_next_state = Some(select_next_state);
12231 } else {
12232 let mut only_carets = true;
12233 let mut same_text_selected = true;
12234 let mut selected_text = None;
12235
12236 let mut selections_iter = selections.iter().peekable();
12237 while let Some(selection) = selections_iter.next() {
12238 if selection.start != selection.end {
12239 only_carets = false;
12240 }
12241
12242 if same_text_selected {
12243 if selected_text.is_none() {
12244 selected_text =
12245 Some(buffer.text_for_range(selection.range()).collect::<String>());
12246 }
12247
12248 if let Some(next_selection) = selections_iter.peek() {
12249 if next_selection.range().len() == selection.range().len() {
12250 let next_selected_text = buffer
12251 .text_for_range(next_selection.range())
12252 .collect::<String>();
12253 if Some(next_selected_text) != selected_text {
12254 same_text_selected = false;
12255 selected_text = None;
12256 }
12257 } else {
12258 same_text_selected = false;
12259 selected_text = None;
12260 }
12261 }
12262 }
12263 }
12264
12265 if only_carets {
12266 for selection in &mut selections {
12267 let word_range = movement::surrounding_word(
12268 display_map,
12269 selection.start.to_display_point(display_map),
12270 );
12271 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12272 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12273 selection.goal = SelectionGoal::None;
12274 selection.reversed = false;
12275 select_next_match_ranges(
12276 self,
12277 selection.start..selection.end,
12278 selection.reversed,
12279 replace_newest,
12280 autoscroll,
12281 window,
12282 cx,
12283 );
12284 }
12285
12286 if selections.len() == 1 {
12287 let selection = selections
12288 .last()
12289 .expect("ensured that there's only one selection");
12290 let query = buffer
12291 .text_for_range(selection.start..selection.end)
12292 .collect::<String>();
12293 let is_empty = query.is_empty();
12294 let select_state = SelectNextState {
12295 query: AhoCorasick::new(&[query])?,
12296 wordwise: true,
12297 done: is_empty,
12298 };
12299 self.select_next_state = Some(select_state);
12300 } else {
12301 self.select_next_state = None;
12302 }
12303 } else if let Some(selected_text) = selected_text {
12304 self.select_next_state = Some(SelectNextState {
12305 query: AhoCorasick::new(&[selected_text])?,
12306 wordwise: false,
12307 done: false,
12308 });
12309 self.select_next_match_internal(
12310 display_map,
12311 replace_newest,
12312 autoscroll,
12313 window,
12314 cx,
12315 )?;
12316 }
12317 }
12318 Ok(())
12319 }
12320
12321 pub fn select_all_matches(
12322 &mut self,
12323 _action: &SelectAllMatches,
12324 window: &mut Window,
12325 cx: &mut Context<Self>,
12326 ) -> Result<()> {
12327 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12328
12329 self.push_to_selection_history();
12330 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12331
12332 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12333 let Some(select_next_state) = self.select_next_state.as_mut() else {
12334 return Ok(());
12335 };
12336 if select_next_state.done {
12337 return Ok(());
12338 }
12339
12340 let mut new_selections = Vec::new();
12341
12342 let reversed = self.selections.oldest::<usize>(cx).reversed;
12343 let buffer = &display_map.buffer_snapshot;
12344 let query_matches = select_next_state
12345 .query
12346 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12347
12348 for query_match in query_matches.into_iter() {
12349 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12350 let offset_range = if reversed {
12351 query_match.end()..query_match.start()
12352 } else {
12353 query_match.start()..query_match.end()
12354 };
12355 let display_range = offset_range.start.to_display_point(&display_map)
12356 ..offset_range.end.to_display_point(&display_map);
12357
12358 if !select_next_state.wordwise
12359 || (!movement::is_inside_word(&display_map, display_range.start)
12360 && !movement::is_inside_word(&display_map, display_range.end))
12361 {
12362 new_selections.push(offset_range.start..offset_range.end);
12363 }
12364 }
12365
12366 select_next_state.done = true;
12367 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12368 self.change_selections(None, window, cx, |selections| {
12369 selections.select_ranges(new_selections)
12370 });
12371
12372 Ok(())
12373 }
12374
12375 pub fn select_next(
12376 &mut self,
12377 action: &SelectNext,
12378 window: &mut Window,
12379 cx: &mut Context<Self>,
12380 ) -> Result<()> {
12381 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12382 self.push_to_selection_history();
12383 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12384 self.select_next_match_internal(
12385 &display_map,
12386 action.replace_newest,
12387 Some(Autoscroll::newest()),
12388 window,
12389 cx,
12390 )?;
12391 Ok(())
12392 }
12393
12394 pub fn select_previous(
12395 &mut self,
12396 action: &SelectPrevious,
12397 window: &mut Window,
12398 cx: &mut Context<Self>,
12399 ) -> Result<()> {
12400 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12401 self.push_to_selection_history();
12402 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12403 let buffer = &display_map.buffer_snapshot;
12404 let mut selections = self.selections.all::<usize>(cx);
12405 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12406 let query = &select_prev_state.query;
12407 if !select_prev_state.done {
12408 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12409 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12410 let mut next_selected_range = None;
12411 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12412 let bytes_before_last_selection =
12413 buffer.reversed_bytes_in_range(0..last_selection.start);
12414 let bytes_after_first_selection =
12415 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12416 let query_matches = query
12417 .stream_find_iter(bytes_before_last_selection)
12418 .map(|result| (last_selection.start, result))
12419 .chain(
12420 query
12421 .stream_find_iter(bytes_after_first_selection)
12422 .map(|result| (buffer.len(), result)),
12423 );
12424 for (end_offset, query_match) in query_matches {
12425 let query_match = query_match.unwrap(); // can only fail due to I/O
12426 let offset_range =
12427 end_offset - query_match.end()..end_offset - query_match.start();
12428 let display_range = offset_range.start.to_display_point(&display_map)
12429 ..offset_range.end.to_display_point(&display_map);
12430
12431 if !select_prev_state.wordwise
12432 || (!movement::is_inside_word(&display_map, display_range.start)
12433 && !movement::is_inside_word(&display_map, display_range.end))
12434 {
12435 next_selected_range = Some(offset_range);
12436 break;
12437 }
12438 }
12439
12440 if let Some(next_selected_range) = next_selected_range {
12441 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
12442 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12443 if action.replace_newest {
12444 s.delete(s.newest_anchor().id);
12445 }
12446 if last_selection.reversed {
12447 s.insert_range(next_selected_range.end..next_selected_range.start);
12448 } else {
12449 s.insert_range(next_selected_range);
12450 }
12451 });
12452 } else {
12453 select_prev_state.done = true;
12454 }
12455 }
12456
12457 self.select_prev_state = Some(select_prev_state);
12458 } else {
12459 let mut only_carets = true;
12460 let mut same_text_selected = true;
12461 let mut selected_text = None;
12462
12463 let mut selections_iter = selections.iter().peekable();
12464 while let Some(selection) = selections_iter.next() {
12465 if selection.start != selection.end {
12466 only_carets = false;
12467 }
12468
12469 if same_text_selected {
12470 if selected_text.is_none() {
12471 selected_text =
12472 Some(buffer.text_for_range(selection.range()).collect::<String>());
12473 }
12474
12475 if let Some(next_selection) = selections_iter.peek() {
12476 if next_selection.range().len() == selection.range().len() {
12477 let next_selected_text = buffer
12478 .text_for_range(next_selection.range())
12479 .collect::<String>();
12480 if Some(next_selected_text) != selected_text {
12481 same_text_selected = false;
12482 selected_text = None;
12483 }
12484 } else {
12485 same_text_selected = false;
12486 selected_text = None;
12487 }
12488 }
12489 }
12490 }
12491
12492 if only_carets {
12493 for selection in &mut selections {
12494 let word_range = movement::surrounding_word(
12495 &display_map,
12496 selection.start.to_display_point(&display_map),
12497 );
12498 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12499 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12500 selection.goal = SelectionGoal::None;
12501 selection.reversed = false;
12502 }
12503 if selections.len() == 1 {
12504 let selection = selections
12505 .last()
12506 .expect("ensured that there's only one selection");
12507 let query = buffer
12508 .text_for_range(selection.start..selection.end)
12509 .collect::<String>();
12510 let is_empty = query.is_empty();
12511 let select_state = SelectNextState {
12512 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12513 wordwise: true,
12514 done: is_empty,
12515 };
12516 self.select_prev_state = Some(select_state);
12517 } else {
12518 self.select_prev_state = None;
12519 }
12520
12521 self.unfold_ranges(
12522 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
12523 false,
12524 true,
12525 cx,
12526 );
12527 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12528 s.select(selections);
12529 });
12530 } else if let Some(selected_text) = selected_text {
12531 self.select_prev_state = Some(SelectNextState {
12532 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12533 wordwise: false,
12534 done: false,
12535 });
12536 self.select_previous(action, window, cx)?;
12537 }
12538 }
12539 Ok(())
12540 }
12541
12542 pub fn find_next_match(
12543 &mut self,
12544 _: &FindNextMatch,
12545 window: &mut Window,
12546 cx: &mut Context<Self>,
12547 ) -> Result<()> {
12548 let selections = self.selections.disjoint_anchors();
12549 match selections.first() {
12550 Some(first) if selections.len() >= 2 => {
12551 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12552 s.select_ranges([first.range()]);
12553 });
12554 }
12555 _ => self.select_next(
12556 &SelectNext {
12557 replace_newest: true,
12558 },
12559 window,
12560 cx,
12561 )?,
12562 }
12563 Ok(())
12564 }
12565
12566 pub fn find_previous_match(
12567 &mut self,
12568 _: &FindPreviousMatch,
12569 window: &mut Window,
12570 cx: &mut Context<Self>,
12571 ) -> Result<()> {
12572 let selections = self.selections.disjoint_anchors();
12573 match selections.last() {
12574 Some(last) if selections.len() >= 2 => {
12575 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12576 s.select_ranges([last.range()]);
12577 });
12578 }
12579 _ => self.select_previous(
12580 &SelectPrevious {
12581 replace_newest: true,
12582 },
12583 window,
12584 cx,
12585 )?,
12586 }
12587 Ok(())
12588 }
12589
12590 pub fn toggle_comments(
12591 &mut self,
12592 action: &ToggleComments,
12593 window: &mut Window,
12594 cx: &mut Context<Self>,
12595 ) {
12596 if self.read_only(cx) {
12597 return;
12598 }
12599 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12600 let text_layout_details = &self.text_layout_details(window);
12601 self.transact(window, cx, |this, window, cx| {
12602 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12603 let mut edits = Vec::new();
12604 let mut selection_edit_ranges = Vec::new();
12605 let mut last_toggled_row = None;
12606 let snapshot = this.buffer.read(cx).read(cx);
12607 let empty_str: Arc<str> = Arc::default();
12608 let mut suffixes_inserted = Vec::new();
12609 let ignore_indent = action.ignore_indent;
12610
12611 fn comment_prefix_range(
12612 snapshot: &MultiBufferSnapshot,
12613 row: MultiBufferRow,
12614 comment_prefix: &str,
12615 comment_prefix_whitespace: &str,
12616 ignore_indent: bool,
12617 ) -> Range<Point> {
12618 let indent_size = if ignore_indent {
12619 0
12620 } else {
12621 snapshot.indent_size_for_line(row).len
12622 };
12623
12624 let start = Point::new(row.0, indent_size);
12625
12626 let mut line_bytes = snapshot
12627 .bytes_in_range(start..snapshot.max_point())
12628 .flatten()
12629 .copied();
12630
12631 // If this line currently begins with the line comment prefix, then record
12632 // the range containing the prefix.
12633 if line_bytes
12634 .by_ref()
12635 .take(comment_prefix.len())
12636 .eq(comment_prefix.bytes())
12637 {
12638 // Include any whitespace that matches the comment prefix.
12639 let matching_whitespace_len = line_bytes
12640 .zip(comment_prefix_whitespace.bytes())
12641 .take_while(|(a, b)| a == b)
12642 .count() as u32;
12643 let end = Point::new(
12644 start.row,
12645 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12646 );
12647 start..end
12648 } else {
12649 start..start
12650 }
12651 }
12652
12653 fn comment_suffix_range(
12654 snapshot: &MultiBufferSnapshot,
12655 row: MultiBufferRow,
12656 comment_suffix: &str,
12657 comment_suffix_has_leading_space: bool,
12658 ) -> Range<Point> {
12659 let end = Point::new(row.0, snapshot.line_len(row));
12660 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12661
12662 let mut line_end_bytes = snapshot
12663 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12664 .flatten()
12665 .copied();
12666
12667 let leading_space_len = if suffix_start_column > 0
12668 && line_end_bytes.next() == Some(b' ')
12669 && comment_suffix_has_leading_space
12670 {
12671 1
12672 } else {
12673 0
12674 };
12675
12676 // If this line currently begins with the line comment prefix, then record
12677 // the range containing the prefix.
12678 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12679 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12680 start..end
12681 } else {
12682 end..end
12683 }
12684 }
12685
12686 // TODO: Handle selections that cross excerpts
12687 for selection in &mut selections {
12688 let start_column = snapshot
12689 .indent_size_for_line(MultiBufferRow(selection.start.row))
12690 .len;
12691 let language = if let Some(language) =
12692 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12693 {
12694 language
12695 } else {
12696 continue;
12697 };
12698
12699 selection_edit_ranges.clear();
12700
12701 // If multiple selections contain a given row, avoid processing that
12702 // row more than once.
12703 let mut start_row = MultiBufferRow(selection.start.row);
12704 if last_toggled_row == Some(start_row) {
12705 start_row = start_row.next_row();
12706 }
12707 let end_row =
12708 if selection.end.row > selection.start.row && selection.end.column == 0 {
12709 MultiBufferRow(selection.end.row - 1)
12710 } else {
12711 MultiBufferRow(selection.end.row)
12712 };
12713 last_toggled_row = Some(end_row);
12714
12715 if start_row > end_row {
12716 continue;
12717 }
12718
12719 // If the language has line comments, toggle those.
12720 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12721
12722 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12723 if ignore_indent {
12724 full_comment_prefixes = full_comment_prefixes
12725 .into_iter()
12726 .map(|s| Arc::from(s.trim_end()))
12727 .collect();
12728 }
12729
12730 if !full_comment_prefixes.is_empty() {
12731 let first_prefix = full_comment_prefixes
12732 .first()
12733 .expect("prefixes is non-empty");
12734 let prefix_trimmed_lengths = full_comment_prefixes
12735 .iter()
12736 .map(|p| p.trim_end_matches(' ').len())
12737 .collect::<SmallVec<[usize; 4]>>();
12738
12739 let mut all_selection_lines_are_comments = true;
12740
12741 for row in start_row.0..=end_row.0 {
12742 let row = MultiBufferRow(row);
12743 if start_row < end_row && snapshot.is_line_blank(row) {
12744 continue;
12745 }
12746
12747 let prefix_range = full_comment_prefixes
12748 .iter()
12749 .zip(prefix_trimmed_lengths.iter().copied())
12750 .map(|(prefix, trimmed_prefix_len)| {
12751 comment_prefix_range(
12752 snapshot.deref(),
12753 row,
12754 &prefix[..trimmed_prefix_len],
12755 &prefix[trimmed_prefix_len..],
12756 ignore_indent,
12757 )
12758 })
12759 .max_by_key(|range| range.end.column - range.start.column)
12760 .expect("prefixes is non-empty");
12761
12762 if prefix_range.is_empty() {
12763 all_selection_lines_are_comments = false;
12764 }
12765
12766 selection_edit_ranges.push(prefix_range);
12767 }
12768
12769 if all_selection_lines_are_comments {
12770 edits.extend(
12771 selection_edit_ranges
12772 .iter()
12773 .cloned()
12774 .map(|range| (range, empty_str.clone())),
12775 );
12776 } else {
12777 let min_column = selection_edit_ranges
12778 .iter()
12779 .map(|range| range.start.column)
12780 .min()
12781 .unwrap_or(0);
12782 edits.extend(selection_edit_ranges.iter().map(|range| {
12783 let position = Point::new(range.start.row, min_column);
12784 (position..position, first_prefix.clone())
12785 }));
12786 }
12787 } else if let Some((full_comment_prefix, comment_suffix)) =
12788 language.block_comment_delimiters()
12789 {
12790 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12791 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12792 let prefix_range = comment_prefix_range(
12793 snapshot.deref(),
12794 start_row,
12795 comment_prefix,
12796 comment_prefix_whitespace,
12797 ignore_indent,
12798 );
12799 let suffix_range = comment_suffix_range(
12800 snapshot.deref(),
12801 end_row,
12802 comment_suffix.trim_start_matches(' '),
12803 comment_suffix.starts_with(' '),
12804 );
12805
12806 if prefix_range.is_empty() || suffix_range.is_empty() {
12807 edits.push((
12808 prefix_range.start..prefix_range.start,
12809 full_comment_prefix.clone(),
12810 ));
12811 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12812 suffixes_inserted.push((end_row, comment_suffix.len()));
12813 } else {
12814 edits.push((prefix_range, empty_str.clone()));
12815 edits.push((suffix_range, empty_str.clone()));
12816 }
12817 } else {
12818 continue;
12819 }
12820 }
12821
12822 drop(snapshot);
12823 this.buffer.update(cx, |buffer, cx| {
12824 buffer.edit(edits, None, cx);
12825 });
12826
12827 // Adjust selections so that they end before any comment suffixes that
12828 // were inserted.
12829 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12830 let mut selections = this.selections.all::<Point>(cx);
12831 let snapshot = this.buffer.read(cx).read(cx);
12832 for selection in &mut selections {
12833 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12834 match row.cmp(&MultiBufferRow(selection.end.row)) {
12835 Ordering::Less => {
12836 suffixes_inserted.next();
12837 continue;
12838 }
12839 Ordering::Greater => break,
12840 Ordering::Equal => {
12841 if selection.end.column == snapshot.line_len(row) {
12842 if selection.is_empty() {
12843 selection.start.column -= suffix_len as u32;
12844 }
12845 selection.end.column -= suffix_len as u32;
12846 }
12847 break;
12848 }
12849 }
12850 }
12851 }
12852
12853 drop(snapshot);
12854 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12855 s.select(selections)
12856 });
12857
12858 let selections = this.selections.all::<Point>(cx);
12859 let selections_on_single_row = selections.windows(2).all(|selections| {
12860 selections[0].start.row == selections[1].start.row
12861 && selections[0].end.row == selections[1].end.row
12862 && selections[0].start.row == selections[0].end.row
12863 });
12864 let selections_selecting = selections
12865 .iter()
12866 .any(|selection| selection.start != selection.end);
12867 let advance_downwards = action.advance_downwards
12868 && selections_on_single_row
12869 && !selections_selecting
12870 && !matches!(this.mode, EditorMode::SingleLine { .. });
12871
12872 if advance_downwards {
12873 let snapshot = this.buffer.read(cx).snapshot(cx);
12874
12875 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12876 s.move_cursors_with(|display_snapshot, display_point, _| {
12877 let mut point = display_point.to_point(display_snapshot);
12878 point.row += 1;
12879 point = snapshot.clip_point(point, Bias::Left);
12880 let display_point = point.to_display_point(display_snapshot);
12881 let goal = SelectionGoal::HorizontalPosition(
12882 display_snapshot
12883 .x_for_display_point(display_point, text_layout_details)
12884 .into(),
12885 );
12886 (display_point, goal)
12887 })
12888 });
12889 }
12890 });
12891 }
12892
12893 pub fn select_enclosing_symbol(
12894 &mut self,
12895 _: &SelectEnclosingSymbol,
12896 window: &mut Window,
12897 cx: &mut Context<Self>,
12898 ) {
12899 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12900
12901 let buffer = self.buffer.read(cx).snapshot(cx);
12902 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12903
12904 fn update_selection(
12905 selection: &Selection<usize>,
12906 buffer_snap: &MultiBufferSnapshot,
12907 ) -> Option<Selection<usize>> {
12908 let cursor = selection.head();
12909 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12910 for symbol in symbols.iter().rev() {
12911 let start = symbol.range.start.to_offset(buffer_snap);
12912 let end = symbol.range.end.to_offset(buffer_snap);
12913 let new_range = start..end;
12914 if start < selection.start || end > selection.end {
12915 return Some(Selection {
12916 id: selection.id,
12917 start: new_range.start,
12918 end: new_range.end,
12919 goal: SelectionGoal::None,
12920 reversed: selection.reversed,
12921 });
12922 }
12923 }
12924 None
12925 }
12926
12927 let mut selected_larger_symbol = false;
12928 let new_selections = old_selections
12929 .iter()
12930 .map(|selection| match update_selection(selection, &buffer) {
12931 Some(new_selection) => {
12932 if new_selection.range() != selection.range() {
12933 selected_larger_symbol = true;
12934 }
12935 new_selection
12936 }
12937 None => selection.clone(),
12938 })
12939 .collect::<Vec<_>>();
12940
12941 if selected_larger_symbol {
12942 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12943 s.select(new_selections);
12944 });
12945 }
12946 }
12947
12948 pub fn select_larger_syntax_node(
12949 &mut self,
12950 _: &SelectLargerSyntaxNode,
12951 window: &mut Window,
12952 cx: &mut Context<Self>,
12953 ) {
12954 let Some(visible_row_count) = self.visible_row_count() else {
12955 return;
12956 };
12957 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12958 if old_selections.is_empty() {
12959 return;
12960 }
12961
12962 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12963
12964 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12965 let buffer = self.buffer.read(cx).snapshot(cx);
12966
12967 let mut selected_larger_node = false;
12968 let mut new_selections = old_selections
12969 .iter()
12970 .map(|selection| {
12971 let old_range = selection.start..selection.end;
12972
12973 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
12974 // manually select word at selection
12975 if ["string_content", "inline"].contains(&node.kind()) {
12976 let word_range = {
12977 let display_point = buffer
12978 .offset_to_point(old_range.start)
12979 .to_display_point(&display_map);
12980 let Range { start, end } =
12981 movement::surrounding_word(&display_map, display_point);
12982 start.to_point(&display_map).to_offset(&buffer)
12983 ..end.to_point(&display_map).to_offset(&buffer)
12984 };
12985 // ignore if word is already selected
12986 if !word_range.is_empty() && old_range != word_range {
12987 let last_word_range = {
12988 let display_point = buffer
12989 .offset_to_point(old_range.end)
12990 .to_display_point(&display_map);
12991 let Range { start, end } =
12992 movement::surrounding_word(&display_map, display_point);
12993 start.to_point(&display_map).to_offset(&buffer)
12994 ..end.to_point(&display_map).to_offset(&buffer)
12995 };
12996 // only select word if start and end point belongs to same word
12997 if word_range == last_word_range {
12998 selected_larger_node = true;
12999 return Selection {
13000 id: selection.id,
13001 start: word_range.start,
13002 end: word_range.end,
13003 goal: SelectionGoal::None,
13004 reversed: selection.reversed,
13005 };
13006 }
13007 }
13008 }
13009 }
13010
13011 let mut new_range = old_range.clone();
13012 let mut new_node = None;
13013 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
13014 {
13015 new_node = Some(node);
13016 new_range = match containing_range {
13017 MultiOrSingleBufferOffsetRange::Single(_) => break,
13018 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13019 };
13020 if !display_map.intersects_fold(new_range.start)
13021 && !display_map.intersects_fold(new_range.end)
13022 {
13023 break;
13024 }
13025 }
13026
13027 if let Some(node) = new_node {
13028 // Log the ancestor, to support using this action as a way to explore TreeSitter
13029 // nodes. Parent and grandparent are also logged because this operation will not
13030 // visit nodes that have the same range as their parent.
13031 log::info!("Node: {node:?}");
13032 let parent = node.parent();
13033 log::info!("Parent: {parent:?}");
13034 let grandparent = parent.and_then(|x| x.parent());
13035 log::info!("Grandparent: {grandparent:?}");
13036 }
13037
13038 selected_larger_node |= new_range != old_range;
13039 Selection {
13040 id: selection.id,
13041 start: new_range.start,
13042 end: new_range.end,
13043 goal: SelectionGoal::None,
13044 reversed: selection.reversed,
13045 }
13046 })
13047 .collect::<Vec<_>>();
13048
13049 if !selected_larger_node {
13050 return; // don't put this call in the history
13051 }
13052
13053 // scroll based on transformation done to the last selection created by the user
13054 let (last_old, last_new) = old_selections
13055 .last()
13056 .zip(new_selections.last().cloned())
13057 .expect("old_selections isn't empty");
13058
13059 // revert selection
13060 let is_selection_reversed = {
13061 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13062 new_selections.last_mut().expect("checked above").reversed =
13063 should_newest_selection_be_reversed;
13064 should_newest_selection_be_reversed
13065 };
13066
13067 if selected_larger_node {
13068 self.select_syntax_node_history.disable_clearing = true;
13069 self.change_selections(None, window, cx, |s| {
13070 s.select(new_selections.clone());
13071 });
13072 self.select_syntax_node_history.disable_clearing = false;
13073 }
13074
13075 let start_row = last_new.start.to_display_point(&display_map).row().0;
13076 let end_row = last_new.end.to_display_point(&display_map).row().0;
13077 let selection_height = end_row - start_row + 1;
13078 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13079
13080 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13081 let scroll_behavior = if fits_on_the_screen {
13082 self.request_autoscroll(Autoscroll::fit(), cx);
13083 SelectSyntaxNodeScrollBehavior::FitSelection
13084 } else if is_selection_reversed {
13085 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13086 SelectSyntaxNodeScrollBehavior::CursorTop
13087 } else {
13088 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13089 SelectSyntaxNodeScrollBehavior::CursorBottom
13090 };
13091
13092 self.select_syntax_node_history.push((
13093 old_selections,
13094 scroll_behavior,
13095 is_selection_reversed,
13096 ));
13097 }
13098
13099 pub fn select_smaller_syntax_node(
13100 &mut self,
13101 _: &SelectSmallerSyntaxNode,
13102 window: &mut Window,
13103 cx: &mut Context<Self>,
13104 ) {
13105 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13106
13107 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13108 self.select_syntax_node_history.pop()
13109 {
13110 if let Some(selection) = selections.last_mut() {
13111 selection.reversed = is_selection_reversed;
13112 }
13113
13114 self.select_syntax_node_history.disable_clearing = true;
13115 self.change_selections(None, window, cx, |s| {
13116 s.select(selections.to_vec());
13117 });
13118 self.select_syntax_node_history.disable_clearing = false;
13119
13120 match scroll_behavior {
13121 SelectSyntaxNodeScrollBehavior::CursorTop => {
13122 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13123 }
13124 SelectSyntaxNodeScrollBehavior::FitSelection => {
13125 self.request_autoscroll(Autoscroll::fit(), cx);
13126 }
13127 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13128 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13129 }
13130 }
13131 }
13132 }
13133
13134 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13135 if !EditorSettings::get_global(cx).gutter.runnables {
13136 self.clear_tasks();
13137 return Task::ready(());
13138 }
13139 let project = self.project.as_ref().map(Entity::downgrade);
13140 let task_sources = self.lsp_task_sources(cx);
13141 cx.spawn_in(window, async move |editor, cx| {
13142 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13143 let Some(project) = project.and_then(|p| p.upgrade()) else {
13144 return;
13145 };
13146 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13147 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13148 }) else {
13149 return;
13150 };
13151
13152 let hide_runnables = project
13153 .update(cx, |project, cx| {
13154 // Do not display any test indicators in non-dev server remote projects.
13155 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13156 })
13157 .unwrap_or(true);
13158 if hide_runnables {
13159 return;
13160 }
13161 let new_rows =
13162 cx.background_spawn({
13163 let snapshot = display_snapshot.clone();
13164 async move {
13165 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13166 }
13167 })
13168 .await;
13169 let Ok(lsp_tasks) =
13170 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13171 else {
13172 return;
13173 };
13174 let lsp_tasks = lsp_tasks.await;
13175
13176 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13177 lsp_tasks
13178 .into_iter()
13179 .flat_map(|(kind, tasks)| {
13180 tasks.into_iter().filter_map(move |(location, task)| {
13181 Some((kind.clone(), location?, task))
13182 })
13183 })
13184 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13185 let buffer = location.target.buffer;
13186 let buffer_snapshot = buffer.read(cx).snapshot();
13187 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13188 |(excerpt_id, snapshot, _)| {
13189 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13190 display_snapshot
13191 .buffer_snapshot
13192 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13193 } else {
13194 None
13195 }
13196 },
13197 );
13198 if let Some(offset) = offset {
13199 let task_buffer_range =
13200 location.target.range.to_point(&buffer_snapshot);
13201 let context_buffer_range =
13202 task_buffer_range.to_offset(&buffer_snapshot);
13203 let context_range = BufferOffset(context_buffer_range.start)
13204 ..BufferOffset(context_buffer_range.end);
13205
13206 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13207 .or_insert_with(|| RunnableTasks {
13208 templates: Vec::new(),
13209 offset,
13210 column: task_buffer_range.start.column,
13211 extra_variables: HashMap::default(),
13212 context_range,
13213 })
13214 .templates
13215 .push((kind, task.original_task().clone()));
13216 }
13217
13218 acc
13219 })
13220 }) else {
13221 return;
13222 };
13223
13224 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13225 editor
13226 .update(cx, |editor, _| {
13227 editor.clear_tasks();
13228 for (key, mut value) in rows {
13229 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13230 value.templates.extend(lsp_tasks.templates);
13231 }
13232
13233 editor.insert_tasks(key, value);
13234 }
13235 for (key, value) in lsp_tasks_by_rows {
13236 editor.insert_tasks(key, value);
13237 }
13238 })
13239 .ok();
13240 })
13241 }
13242 fn fetch_runnable_ranges(
13243 snapshot: &DisplaySnapshot,
13244 range: Range<Anchor>,
13245 ) -> Vec<language::RunnableRange> {
13246 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13247 }
13248
13249 fn runnable_rows(
13250 project: Entity<Project>,
13251 snapshot: DisplaySnapshot,
13252 runnable_ranges: Vec<RunnableRange>,
13253 mut cx: AsyncWindowContext,
13254 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13255 runnable_ranges
13256 .into_iter()
13257 .filter_map(|mut runnable| {
13258 let tasks = cx
13259 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13260 .ok()?;
13261 if tasks.is_empty() {
13262 return None;
13263 }
13264
13265 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13266
13267 let row = snapshot
13268 .buffer_snapshot
13269 .buffer_line_for_row(MultiBufferRow(point.row))?
13270 .1
13271 .start
13272 .row;
13273
13274 let context_range =
13275 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13276 Some((
13277 (runnable.buffer_id, row),
13278 RunnableTasks {
13279 templates: tasks,
13280 offset: snapshot
13281 .buffer_snapshot
13282 .anchor_before(runnable.run_range.start),
13283 context_range,
13284 column: point.column,
13285 extra_variables: runnable.extra_captures,
13286 },
13287 ))
13288 })
13289 .collect()
13290 }
13291
13292 fn templates_with_tags(
13293 project: &Entity<Project>,
13294 runnable: &mut Runnable,
13295 cx: &mut App,
13296 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13297 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13298 let (worktree_id, file) = project
13299 .buffer_for_id(runnable.buffer, cx)
13300 .and_then(|buffer| buffer.read(cx).file())
13301 .map(|file| (file.worktree_id(cx), file.clone()))
13302 .unzip();
13303
13304 (
13305 project.task_store().read(cx).task_inventory().cloned(),
13306 worktree_id,
13307 file,
13308 )
13309 });
13310
13311 let mut templates_with_tags = mem::take(&mut runnable.tags)
13312 .into_iter()
13313 .flat_map(|RunnableTag(tag)| {
13314 inventory
13315 .as_ref()
13316 .into_iter()
13317 .flat_map(|inventory| {
13318 inventory.read(cx).list_tasks(
13319 file.clone(),
13320 Some(runnable.language.clone()),
13321 worktree_id,
13322 cx,
13323 )
13324 })
13325 .filter(move |(_, template)| {
13326 template.tags.iter().any(|source_tag| source_tag == &tag)
13327 })
13328 })
13329 .sorted_by_key(|(kind, _)| kind.to_owned())
13330 .collect::<Vec<_>>();
13331 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13332 // Strongest source wins; if we have worktree tag binding, prefer that to
13333 // global and language bindings;
13334 // if we have a global binding, prefer that to language binding.
13335 let first_mismatch = templates_with_tags
13336 .iter()
13337 .position(|(tag_source, _)| tag_source != leading_tag_source);
13338 if let Some(index) = first_mismatch {
13339 templates_with_tags.truncate(index);
13340 }
13341 }
13342
13343 templates_with_tags
13344 }
13345
13346 pub fn move_to_enclosing_bracket(
13347 &mut self,
13348 _: &MoveToEnclosingBracket,
13349 window: &mut Window,
13350 cx: &mut Context<Self>,
13351 ) {
13352 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13353 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13354 s.move_offsets_with(|snapshot, selection| {
13355 let Some(enclosing_bracket_ranges) =
13356 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13357 else {
13358 return;
13359 };
13360
13361 let mut best_length = usize::MAX;
13362 let mut best_inside = false;
13363 let mut best_in_bracket_range = false;
13364 let mut best_destination = None;
13365 for (open, close) in enclosing_bracket_ranges {
13366 let close = close.to_inclusive();
13367 let length = close.end() - open.start;
13368 let inside = selection.start >= open.end && selection.end <= *close.start();
13369 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13370 || close.contains(&selection.head());
13371
13372 // If best is next to a bracket and current isn't, skip
13373 if !in_bracket_range && best_in_bracket_range {
13374 continue;
13375 }
13376
13377 // Prefer smaller lengths unless best is inside and current isn't
13378 if length > best_length && (best_inside || !inside) {
13379 continue;
13380 }
13381
13382 best_length = length;
13383 best_inside = inside;
13384 best_in_bracket_range = in_bracket_range;
13385 best_destination = Some(
13386 if close.contains(&selection.start) && close.contains(&selection.end) {
13387 if inside { open.end } else { open.start }
13388 } else if inside {
13389 *close.start()
13390 } else {
13391 *close.end()
13392 },
13393 );
13394 }
13395
13396 if let Some(destination) = best_destination {
13397 selection.collapse_to(destination, SelectionGoal::None);
13398 }
13399 })
13400 });
13401 }
13402
13403 pub fn undo_selection(
13404 &mut self,
13405 _: &UndoSelection,
13406 window: &mut Window,
13407 cx: &mut Context<Self>,
13408 ) {
13409 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13410 self.end_selection(window, cx);
13411 self.selection_history.mode = SelectionHistoryMode::Undoing;
13412 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13413 self.change_selections(None, window, cx, |s| {
13414 s.select_anchors(entry.selections.to_vec())
13415 });
13416 self.select_next_state = entry.select_next_state;
13417 self.select_prev_state = entry.select_prev_state;
13418 self.add_selections_state = entry.add_selections_state;
13419 self.request_autoscroll(Autoscroll::newest(), cx);
13420 }
13421 self.selection_history.mode = SelectionHistoryMode::Normal;
13422 }
13423
13424 pub fn redo_selection(
13425 &mut self,
13426 _: &RedoSelection,
13427 window: &mut Window,
13428 cx: &mut Context<Self>,
13429 ) {
13430 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13431 self.end_selection(window, cx);
13432 self.selection_history.mode = SelectionHistoryMode::Redoing;
13433 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13434 self.change_selections(None, window, cx, |s| {
13435 s.select_anchors(entry.selections.to_vec())
13436 });
13437 self.select_next_state = entry.select_next_state;
13438 self.select_prev_state = entry.select_prev_state;
13439 self.add_selections_state = entry.add_selections_state;
13440 self.request_autoscroll(Autoscroll::newest(), cx);
13441 }
13442 self.selection_history.mode = SelectionHistoryMode::Normal;
13443 }
13444
13445 pub fn expand_excerpts(
13446 &mut self,
13447 action: &ExpandExcerpts,
13448 _: &mut Window,
13449 cx: &mut Context<Self>,
13450 ) {
13451 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13452 }
13453
13454 pub fn expand_excerpts_down(
13455 &mut self,
13456 action: &ExpandExcerptsDown,
13457 _: &mut Window,
13458 cx: &mut Context<Self>,
13459 ) {
13460 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13461 }
13462
13463 pub fn expand_excerpts_up(
13464 &mut self,
13465 action: &ExpandExcerptsUp,
13466 _: &mut Window,
13467 cx: &mut Context<Self>,
13468 ) {
13469 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13470 }
13471
13472 pub fn expand_excerpts_for_direction(
13473 &mut self,
13474 lines: u32,
13475 direction: ExpandExcerptDirection,
13476
13477 cx: &mut Context<Self>,
13478 ) {
13479 let selections = self.selections.disjoint_anchors();
13480
13481 let lines = if lines == 0 {
13482 EditorSettings::get_global(cx).expand_excerpt_lines
13483 } else {
13484 lines
13485 };
13486
13487 self.buffer.update(cx, |buffer, cx| {
13488 let snapshot = buffer.snapshot(cx);
13489 let mut excerpt_ids = selections
13490 .iter()
13491 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13492 .collect::<Vec<_>>();
13493 excerpt_ids.sort();
13494 excerpt_ids.dedup();
13495 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13496 })
13497 }
13498
13499 pub fn expand_excerpt(
13500 &mut self,
13501 excerpt: ExcerptId,
13502 direction: ExpandExcerptDirection,
13503 window: &mut Window,
13504 cx: &mut Context<Self>,
13505 ) {
13506 let current_scroll_position = self.scroll_position(cx);
13507 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13508 let mut should_scroll_up = false;
13509
13510 if direction == ExpandExcerptDirection::Down {
13511 let multi_buffer = self.buffer.read(cx);
13512 let snapshot = multi_buffer.snapshot(cx);
13513 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13514 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13515 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13516 let buffer_snapshot = buffer.read(cx).snapshot();
13517 let excerpt_end_row =
13518 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13519 let last_row = buffer_snapshot.max_point().row;
13520 let lines_below = last_row.saturating_sub(excerpt_end_row);
13521 should_scroll_up = lines_below >= lines_to_expand;
13522 }
13523 }
13524 }
13525 }
13526
13527 self.buffer.update(cx, |buffer, cx| {
13528 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13529 });
13530
13531 if should_scroll_up {
13532 let new_scroll_position =
13533 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13534 self.set_scroll_position(new_scroll_position, window, cx);
13535 }
13536 }
13537
13538 pub fn go_to_singleton_buffer_point(
13539 &mut self,
13540 point: Point,
13541 window: &mut Window,
13542 cx: &mut Context<Self>,
13543 ) {
13544 self.go_to_singleton_buffer_range(point..point, window, cx);
13545 }
13546
13547 pub fn go_to_singleton_buffer_range(
13548 &mut self,
13549 range: Range<Point>,
13550 window: &mut Window,
13551 cx: &mut Context<Self>,
13552 ) {
13553 let multibuffer = self.buffer().read(cx);
13554 let Some(buffer) = multibuffer.as_singleton() else {
13555 return;
13556 };
13557 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13558 return;
13559 };
13560 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13561 return;
13562 };
13563 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13564 s.select_anchor_ranges([start..end])
13565 });
13566 }
13567
13568 pub fn go_to_diagnostic(
13569 &mut self,
13570 _: &GoToDiagnostic,
13571 window: &mut Window,
13572 cx: &mut Context<Self>,
13573 ) {
13574 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13575 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13576 }
13577
13578 pub fn go_to_prev_diagnostic(
13579 &mut self,
13580 _: &GoToPreviousDiagnostic,
13581 window: &mut Window,
13582 cx: &mut Context<Self>,
13583 ) {
13584 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13585 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13586 }
13587
13588 pub fn go_to_diagnostic_impl(
13589 &mut self,
13590 direction: Direction,
13591 window: &mut Window,
13592 cx: &mut Context<Self>,
13593 ) {
13594 let buffer = self.buffer.read(cx).snapshot(cx);
13595 let selection = self.selections.newest::<usize>(cx);
13596
13597 let mut active_group_id = None;
13598 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13599 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13600 active_group_id = Some(active_group.group_id);
13601 }
13602 }
13603
13604 fn filtered(
13605 snapshot: EditorSnapshot,
13606 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13607 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13608 diagnostics
13609 .filter(|entry| entry.range.start != entry.range.end)
13610 .filter(|entry| !entry.diagnostic.is_unnecessary)
13611 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13612 }
13613
13614 let snapshot = self.snapshot(window, cx);
13615 let before = filtered(
13616 snapshot.clone(),
13617 buffer
13618 .diagnostics_in_range(0..selection.start)
13619 .filter(|entry| entry.range.start <= selection.start),
13620 );
13621 let after = filtered(
13622 snapshot,
13623 buffer
13624 .diagnostics_in_range(selection.start..buffer.len())
13625 .filter(|entry| entry.range.start >= selection.start),
13626 );
13627
13628 let mut found: Option<DiagnosticEntry<usize>> = None;
13629 if direction == Direction::Prev {
13630 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13631 {
13632 for diagnostic in prev_diagnostics.into_iter().rev() {
13633 if diagnostic.range.start != selection.start
13634 || active_group_id
13635 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13636 {
13637 found = Some(diagnostic);
13638 break 'outer;
13639 }
13640 }
13641 }
13642 } else {
13643 for diagnostic in after.chain(before) {
13644 if diagnostic.range.start != selection.start
13645 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13646 {
13647 found = Some(diagnostic);
13648 break;
13649 }
13650 }
13651 }
13652 let Some(next_diagnostic) = found else {
13653 return;
13654 };
13655
13656 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13657 return;
13658 };
13659 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13660 s.select_ranges(vec![
13661 next_diagnostic.range.start..next_diagnostic.range.start,
13662 ])
13663 });
13664 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13665 self.refresh_inline_completion(false, true, window, cx);
13666 }
13667
13668 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13669 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13670 let snapshot = self.snapshot(window, cx);
13671 let selection = self.selections.newest::<Point>(cx);
13672 self.go_to_hunk_before_or_after_position(
13673 &snapshot,
13674 selection.head(),
13675 Direction::Next,
13676 window,
13677 cx,
13678 );
13679 }
13680
13681 pub fn go_to_hunk_before_or_after_position(
13682 &mut self,
13683 snapshot: &EditorSnapshot,
13684 position: Point,
13685 direction: Direction,
13686 window: &mut Window,
13687 cx: &mut Context<Editor>,
13688 ) {
13689 let row = if direction == Direction::Next {
13690 self.hunk_after_position(snapshot, position)
13691 .map(|hunk| hunk.row_range.start)
13692 } else {
13693 self.hunk_before_position(snapshot, position)
13694 };
13695
13696 if let Some(row) = row {
13697 let destination = Point::new(row.0, 0);
13698 let autoscroll = Autoscroll::center();
13699
13700 self.unfold_ranges(&[destination..destination], false, false, cx);
13701 self.change_selections(Some(autoscroll), window, cx, |s| {
13702 s.select_ranges([destination..destination]);
13703 });
13704 }
13705 }
13706
13707 fn hunk_after_position(
13708 &mut self,
13709 snapshot: &EditorSnapshot,
13710 position: Point,
13711 ) -> Option<MultiBufferDiffHunk> {
13712 snapshot
13713 .buffer_snapshot
13714 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13715 .find(|hunk| hunk.row_range.start.0 > position.row)
13716 .or_else(|| {
13717 snapshot
13718 .buffer_snapshot
13719 .diff_hunks_in_range(Point::zero()..position)
13720 .find(|hunk| hunk.row_range.end.0 < position.row)
13721 })
13722 }
13723
13724 fn go_to_prev_hunk(
13725 &mut self,
13726 _: &GoToPreviousHunk,
13727 window: &mut Window,
13728 cx: &mut Context<Self>,
13729 ) {
13730 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13731 let snapshot = self.snapshot(window, cx);
13732 let selection = self.selections.newest::<Point>(cx);
13733 self.go_to_hunk_before_or_after_position(
13734 &snapshot,
13735 selection.head(),
13736 Direction::Prev,
13737 window,
13738 cx,
13739 );
13740 }
13741
13742 fn hunk_before_position(
13743 &mut self,
13744 snapshot: &EditorSnapshot,
13745 position: Point,
13746 ) -> Option<MultiBufferRow> {
13747 snapshot
13748 .buffer_snapshot
13749 .diff_hunk_before(position)
13750 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13751 }
13752
13753 fn go_to_next_change(
13754 &mut self,
13755 _: &GoToNextChange,
13756 window: &mut Window,
13757 cx: &mut Context<Self>,
13758 ) {
13759 if let Some(selections) = self
13760 .change_list
13761 .next_change(1, Direction::Next)
13762 .map(|s| s.to_vec())
13763 {
13764 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13765 let map = s.display_map();
13766 s.select_display_ranges(selections.iter().map(|a| {
13767 let point = a.to_display_point(&map);
13768 point..point
13769 }))
13770 })
13771 }
13772 }
13773
13774 fn go_to_previous_change(
13775 &mut self,
13776 _: &GoToPreviousChange,
13777 window: &mut Window,
13778 cx: &mut Context<Self>,
13779 ) {
13780 if let Some(selections) = self
13781 .change_list
13782 .next_change(1, Direction::Prev)
13783 .map(|s| s.to_vec())
13784 {
13785 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13786 let map = s.display_map();
13787 s.select_display_ranges(selections.iter().map(|a| {
13788 let point = a.to_display_point(&map);
13789 point..point
13790 }))
13791 })
13792 }
13793 }
13794
13795 fn go_to_line<T: 'static>(
13796 &mut self,
13797 position: Anchor,
13798 highlight_color: Option<Hsla>,
13799 window: &mut Window,
13800 cx: &mut Context<Self>,
13801 ) {
13802 let snapshot = self.snapshot(window, cx).display_snapshot;
13803 let position = position.to_point(&snapshot.buffer_snapshot);
13804 let start = snapshot
13805 .buffer_snapshot
13806 .clip_point(Point::new(position.row, 0), Bias::Left);
13807 let end = start + Point::new(1, 0);
13808 let start = snapshot.buffer_snapshot.anchor_before(start);
13809 let end = snapshot.buffer_snapshot.anchor_before(end);
13810
13811 self.highlight_rows::<T>(
13812 start..end,
13813 highlight_color
13814 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13815 Default::default(),
13816 cx,
13817 );
13818 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13819 }
13820
13821 pub fn go_to_definition(
13822 &mut self,
13823 _: &GoToDefinition,
13824 window: &mut Window,
13825 cx: &mut Context<Self>,
13826 ) -> Task<Result<Navigated>> {
13827 let definition =
13828 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13829 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13830 cx.spawn_in(window, async move |editor, cx| {
13831 if definition.await? == Navigated::Yes {
13832 return Ok(Navigated::Yes);
13833 }
13834 match fallback_strategy {
13835 GoToDefinitionFallback::None => Ok(Navigated::No),
13836 GoToDefinitionFallback::FindAllReferences => {
13837 match editor.update_in(cx, |editor, window, cx| {
13838 editor.find_all_references(&FindAllReferences, window, cx)
13839 })? {
13840 Some(references) => references.await,
13841 None => Ok(Navigated::No),
13842 }
13843 }
13844 }
13845 })
13846 }
13847
13848 pub fn go_to_declaration(
13849 &mut self,
13850 _: &GoToDeclaration,
13851 window: &mut Window,
13852 cx: &mut Context<Self>,
13853 ) -> Task<Result<Navigated>> {
13854 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13855 }
13856
13857 pub fn go_to_declaration_split(
13858 &mut self,
13859 _: &GoToDeclaration,
13860 window: &mut Window,
13861 cx: &mut Context<Self>,
13862 ) -> Task<Result<Navigated>> {
13863 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13864 }
13865
13866 pub fn go_to_implementation(
13867 &mut self,
13868 _: &GoToImplementation,
13869 window: &mut Window,
13870 cx: &mut Context<Self>,
13871 ) -> Task<Result<Navigated>> {
13872 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13873 }
13874
13875 pub fn go_to_implementation_split(
13876 &mut self,
13877 _: &GoToImplementationSplit,
13878 window: &mut Window,
13879 cx: &mut Context<Self>,
13880 ) -> Task<Result<Navigated>> {
13881 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13882 }
13883
13884 pub fn go_to_type_definition(
13885 &mut self,
13886 _: &GoToTypeDefinition,
13887 window: &mut Window,
13888 cx: &mut Context<Self>,
13889 ) -> Task<Result<Navigated>> {
13890 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13891 }
13892
13893 pub fn go_to_definition_split(
13894 &mut self,
13895 _: &GoToDefinitionSplit,
13896 window: &mut Window,
13897 cx: &mut Context<Self>,
13898 ) -> Task<Result<Navigated>> {
13899 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13900 }
13901
13902 pub fn go_to_type_definition_split(
13903 &mut self,
13904 _: &GoToTypeDefinitionSplit,
13905 window: &mut Window,
13906 cx: &mut Context<Self>,
13907 ) -> Task<Result<Navigated>> {
13908 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13909 }
13910
13911 fn go_to_definition_of_kind(
13912 &mut self,
13913 kind: GotoDefinitionKind,
13914 split: bool,
13915 window: &mut Window,
13916 cx: &mut Context<Self>,
13917 ) -> Task<Result<Navigated>> {
13918 let Some(provider) = self.semantics_provider.clone() else {
13919 return Task::ready(Ok(Navigated::No));
13920 };
13921 let head = self.selections.newest::<usize>(cx).head();
13922 let buffer = self.buffer.read(cx);
13923 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13924 text_anchor
13925 } else {
13926 return Task::ready(Ok(Navigated::No));
13927 };
13928
13929 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13930 return Task::ready(Ok(Navigated::No));
13931 };
13932
13933 cx.spawn_in(window, async move |editor, cx| {
13934 let definitions = definitions.await?;
13935 let navigated = editor
13936 .update_in(cx, |editor, window, cx| {
13937 editor.navigate_to_hover_links(
13938 Some(kind),
13939 definitions
13940 .into_iter()
13941 .filter(|location| {
13942 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13943 })
13944 .map(HoverLink::Text)
13945 .collect::<Vec<_>>(),
13946 split,
13947 window,
13948 cx,
13949 )
13950 })?
13951 .await?;
13952 anyhow::Ok(navigated)
13953 })
13954 }
13955
13956 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13957 let selection = self.selections.newest_anchor();
13958 let head = selection.head();
13959 let tail = selection.tail();
13960
13961 let Some((buffer, start_position)) =
13962 self.buffer.read(cx).text_anchor_for_position(head, cx)
13963 else {
13964 return;
13965 };
13966
13967 let end_position = if head != tail {
13968 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13969 return;
13970 };
13971 Some(pos)
13972 } else {
13973 None
13974 };
13975
13976 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13977 let url = if let Some(end_pos) = end_position {
13978 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13979 } else {
13980 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13981 };
13982
13983 if let Some(url) = url {
13984 editor.update(cx, |_, cx| {
13985 cx.open_url(&url);
13986 })
13987 } else {
13988 Ok(())
13989 }
13990 });
13991
13992 url_finder.detach();
13993 }
13994
13995 pub fn open_selected_filename(
13996 &mut self,
13997 _: &OpenSelectedFilename,
13998 window: &mut Window,
13999 cx: &mut Context<Self>,
14000 ) {
14001 let Some(workspace) = self.workspace() else {
14002 return;
14003 };
14004
14005 let position = self.selections.newest_anchor().head();
14006
14007 let Some((buffer, buffer_position)) =
14008 self.buffer.read(cx).text_anchor_for_position(position, cx)
14009 else {
14010 return;
14011 };
14012
14013 let project = self.project.clone();
14014
14015 cx.spawn_in(window, async move |_, cx| {
14016 let result = find_file(&buffer, project, buffer_position, cx).await;
14017
14018 if let Some((_, path)) = result {
14019 workspace
14020 .update_in(cx, |workspace, window, cx| {
14021 workspace.open_resolved_path(path, window, cx)
14022 })?
14023 .await?;
14024 }
14025 anyhow::Ok(())
14026 })
14027 .detach();
14028 }
14029
14030 pub(crate) fn navigate_to_hover_links(
14031 &mut self,
14032 kind: Option<GotoDefinitionKind>,
14033 mut definitions: Vec<HoverLink>,
14034 split: bool,
14035 window: &mut Window,
14036 cx: &mut Context<Editor>,
14037 ) -> Task<Result<Navigated>> {
14038 // If there is one definition, just open it directly
14039 if definitions.len() == 1 {
14040 let definition = definitions.pop().unwrap();
14041
14042 enum TargetTaskResult {
14043 Location(Option<Location>),
14044 AlreadyNavigated,
14045 }
14046
14047 let target_task = match definition {
14048 HoverLink::Text(link) => {
14049 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14050 }
14051 HoverLink::InlayHint(lsp_location, server_id) => {
14052 let computation =
14053 self.compute_target_location(lsp_location, server_id, window, cx);
14054 cx.background_spawn(async move {
14055 let location = computation.await?;
14056 Ok(TargetTaskResult::Location(location))
14057 })
14058 }
14059 HoverLink::Url(url) => {
14060 cx.open_url(&url);
14061 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14062 }
14063 HoverLink::File(path) => {
14064 if let Some(workspace) = self.workspace() {
14065 cx.spawn_in(window, async move |_, cx| {
14066 workspace
14067 .update_in(cx, |workspace, window, cx| {
14068 workspace.open_resolved_path(path, window, cx)
14069 })?
14070 .await
14071 .map(|_| TargetTaskResult::AlreadyNavigated)
14072 })
14073 } else {
14074 Task::ready(Ok(TargetTaskResult::Location(None)))
14075 }
14076 }
14077 };
14078 cx.spawn_in(window, async move |editor, cx| {
14079 let target = match target_task.await.context("target resolution task")? {
14080 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14081 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14082 TargetTaskResult::Location(Some(target)) => target,
14083 };
14084
14085 editor.update_in(cx, |editor, window, cx| {
14086 let Some(workspace) = editor.workspace() else {
14087 return Navigated::No;
14088 };
14089 let pane = workspace.read(cx).active_pane().clone();
14090
14091 let range = target.range.to_point(target.buffer.read(cx));
14092 let range = editor.range_for_match(&range);
14093 let range = collapse_multiline_range(range);
14094
14095 if !split
14096 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14097 {
14098 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14099 } else {
14100 window.defer(cx, move |window, cx| {
14101 let target_editor: Entity<Self> =
14102 workspace.update(cx, |workspace, cx| {
14103 let pane = if split {
14104 workspace.adjacent_pane(window, cx)
14105 } else {
14106 workspace.active_pane().clone()
14107 };
14108
14109 workspace.open_project_item(
14110 pane,
14111 target.buffer.clone(),
14112 true,
14113 true,
14114 window,
14115 cx,
14116 )
14117 });
14118 target_editor.update(cx, |target_editor, cx| {
14119 // When selecting a definition in a different buffer, disable the nav history
14120 // to avoid creating a history entry at the previous cursor location.
14121 pane.update(cx, |pane, _| pane.disable_history());
14122 target_editor.go_to_singleton_buffer_range(range, window, cx);
14123 pane.update(cx, |pane, _| pane.enable_history());
14124 });
14125 });
14126 }
14127 Navigated::Yes
14128 })
14129 })
14130 } else if !definitions.is_empty() {
14131 cx.spawn_in(window, async move |editor, cx| {
14132 let (title, location_tasks, workspace) = editor
14133 .update_in(cx, |editor, window, cx| {
14134 let tab_kind = match kind {
14135 Some(GotoDefinitionKind::Implementation) => "Implementations",
14136 _ => "Definitions",
14137 };
14138 let title = definitions
14139 .iter()
14140 .find_map(|definition| match definition {
14141 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14142 let buffer = origin.buffer.read(cx);
14143 format!(
14144 "{} for {}",
14145 tab_kind,
14146 buffer
14147 .text_for_range(origin.range.clone())
14148 .collect::<String>()
14149 )
14150 }),
14151 HoverLink::InlayHint(_, _) => None,
14152 HoverLink::Url(_) => None,
14153 HoverLink::File(_) => None,
14154 })
14155 .unwrap_or(tab_kind.to_string());
14156 let location_tasks = definitions
14157 .into_iter()
14158 .map(|definition| match definition {
14159 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14160 HoverLink::InlayHint(lsp_location, server_id) => editor
14161 .compute_target_location(lsp_location, server_id, window, cx),
14162 HoverLink::Url(_) => Task::ready(Ok(None)),
14163 HoverLink::File(_) => Task::ready(Ok(None)),
14164 })
14165 .collect::<Vec<_>>();
14166 (title, location_tasks, editor.workspace().clone())
14167 })
14168 .context("location tasks preparation")?;
14169
14170 let locations = future::join_all(location_tasks)
14171 .await
14172 .into_iter()
14173 .filter_map(|location| location.transpose())
14174 .collect::<Result<_>>()
14175 .context("location tasks")?;
14176
14177 let Some(workspace) = workspace else {
14178 return Ok(Navigated::No);
14179 };
14180 let opened = workspace
14181 .update_in(cx, |workspace, window, cx| {
14182 Self::open_locations_in_multibuffer(
14183 workspace,
14184 locations,
14185 title,
14186 split,
14187 MultibufferSelectionMode::First,
14188 window,
14189 cx,
14190 )
14191 })
14192 .ok();
14193
14194 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14195 })
14196 } else {
14197 Task::ready(Ok(Navigated::No))
14198 }
14199 }
14200
14201 fn compute_target_location(
14202 &self,
14203 lsp_location: lsp::Location,
14204 server_id: LanguageServerId,
14205 window: &mut Window,
14206 cx: &mut Context<Self>,
14207 ) -> Task<anyhow::Result<Option<Location>>> {
14208 let Some(project) = self.project.clone() else {
14209 return Task::ready(Ok(None));
14210 };
14211
14212 cx.spawn_in(window, async move |editor, cx| {
14213 let location_task = editor.update(cx, |_, cx| {
14214 project.update(cx, |project, cx| {
14215 let language_server_name = project
14216 .language_server_statuses(cx)
14217 .find(|(id, _)| server_id == *id)
14218 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14219 language_server_name.map(|language_server_name| {
14220 project.open_local_buffer_via_lsp(
14221 lsp_location.uri.clone(),
14222 server_id,
14223 language_server_name,
14224 cx,
14225 )
14226 })
14227 })
14228 })?;
14229 let location = match location_task {
14230 Some(task) => Some({
14231 let target_buffer_handle = task.await.context("open local buffer")?;
14232 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14233 let target_start = target_buffer
14234 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14235 let target_end = target_buffer
14236 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14237 target_buffer.anchor_after(target_start)
14238 ..target_buffer.anchor_before(target_end)
14239 })?;
14240 Location {
14241 buffer: target_buffer_handle,
14242 range,
14243 }
14244 }),
14245 None => None,
14246 };
14247 Ok(location)
14248 })
14249 }
14250
14251 pub fn find_all_references(
14252 &mut self,
14253 _: &FindAllReferences,
14254 window: &mut Window,
14255 cx: &mut Context<Self>,
14256 ) -> Option<Task<Result<Navigated>>> {
14257 let selection = self.selections.newest::<usize>(cx);
14258 let multi_buffer = self.buffer.read(cx);
14259 let head = selection.head();
14260
14261 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14262 let head_anchor = multi_buffer_snapshot.anchor_at(
14263 head,
14264 if head < selection.tail() {
14265 Bias::Right
14266 } else {
14267 Bias::Left
14268 },
14269 );
14270
14271 match self
14272 .find_all_references_task_sources
14273 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14274 {
14275 Ok(_) => {
14276 log::info!(
14277 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14278 );
14279 return None;
14280 }
14281 Err(i) => {
14282 self.find_all_references_task_sources.insert(i, head_anchor);
14283 }
14284 }
14285
14286 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14287 let workspace = self.workspace()?;
14288 let project = workspace.read(cx).project().clone();
14289 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14290 Some(cx.spawn_in(window, async move |editor, cx| {
14291 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14292 if let Ok(i) = editor
14293 .find_all_references_task_sources
14294 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14295 {
14296 editor.find_all_references_task_sources.remove(i);
14297 }
14298 });
14299
14300 let locations = references.await?;
14301 if locations.is_empty() {
14302 return anyhow::Ok(Navigated::No);
14303 }
14304
14305 workspace.update_in(cx, |workspace, window, cx| {
14306 let title = locations
14307 .first()
14308 .as_ref()
14309 .map(|location| {
14310 let buffer = location.buffer.read(cx);
14311 format!(
14312 "References to `{}`",
14313 buffer
14314 .text_for_range(location.range.clone())
14315 .collect::<String>()
14316 )
14317 })
14318 .unwrap();
14319 Self::open_locations_in_multibuffer(
14320 workspace,
14321 locations,
14322 title,
14323 false,
14324 MultibufferSelectionMode::First,
14325 window,
14326 cx,
14327 );
14328 Navigated::Yes
14329 })
14330 }))
14331 }
14332
14333 /// Opens a multibuffer with the given project locations in it
14334 pub fn open_locations_in_multibuffer(
14335 workspace: &mut Workspace,
14336 mut locations: Vec<Location>,
14337 title: String,
14338 split: bool,
14339 multibuffer_selection_mode: MultibufferSelectionMode,
14340 window: &mut Window,
14341 cx: &mut Context<Workspace>,
14342 ) {
14343 // If there are multiple definitions, open them in a multibuffer
14344 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14345 let mut locations = locations.into_iter().peekable();
14346 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14347 let capability = workspace.project().read(cx).capability();
14348
14349 let excerpt_buffer = cx.new(|cx| {
14350 let mut multibuffer = MultiBuffer::new(capability);
14351 while let Some(location) = locations.next() {
14352 let buffer = location.buffer.read(cx);
14353 let mut ranges_for_buffer = Vec::new();
14354 let range = location.range.to_point(buffer);
14355 ranges_for_buffer.push(range.clone());
14356
14357 while let Some(next_location) = locations.peek() {
14358 if next_location.buffer == location.buffer {
14359 ranges_for_buffer.push(next_location.range.to_point(buffer));
14360 locations.next();
14361 } else {
14362 break;
14363 }
14364 }
14365
14366 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14367 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14368 PathKey::for_buffer(&location.buffer, cx),
14369 location.buffer.clone(),
14370 ranges_for_buffer,
14371 DEFAULT_MULTIBUFFER_CONTEXT,
14372 cx,
14373 );
14374 ranges.extend(new_ranges)
14375 }
14376
14377 multibuffer.with_title(title)
14378 });
14379
14380 let editor = cx.new(|cx| {
14381 Editor::for_multibuffer(
14382 excerpt_buffer,
14383 Some(workspace.project().clone()),
14384 window,
14385 cx,
14386 )
14387 });
14388 editor.update(cx, |editor, cx| {
14389 match multibuffer_selection_mode {
14390 MultibufferSelectionMode::First => {
14391 if let Some(first_range) = ranges.first() {
14392 editor.change_selections(None, window, cx, |selections| {
14393 selections.clear_disjoint();
14394 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14395 });
14396 }
14397 editor.highlight_background::<Self>(
14398 &ranges,
14399 |theme| theme.editor_highlighted_line_background,
14400 cx,
14401 );
14402 }
14403 MultibufferSelectionMode::All => {
14404 editor.change_selections(None, window, cx, |selections| {
14405 selections.clear_disjoint();
14406 selections.select_anchor_ranges(ranges);
14407 });
14408 }
14409 }
14410 editor.register_buffers_with_language_servers(cx);
14411 });
14412
14413 let item = Box::new(editor);
14414 let item_id = item.item_id();
14415
14416 if split {
14417 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14418 } else {
14419 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14420 let (preview_item_id, preview_item_idx) =
14421 workspace.active_pane().update(cx, |pane, _| {
14422 (pane.preview_item_id(), pane.preview_item_idx())
14423 });
14424
14425 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14426
14427 if let Some(preview_item_id) = preview_item_id {
14428 workspace.active_pane().update(cx, |pane, cx| {
14429 pane.remove_item(preview_item_id, false, false, window, cx);
14430 });
14431 }
14432 } else {
14433 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14434 }
14435 }
14436 workspace.active_pane().update(cx, |pane, cx| {
14437 pane.set_preview_item_id(Some(item_id), cx);
14438 });
14439 }
14440
14441 pub fn rename(
14442 &mut self,
14443 _: &Rename,
14444 window: &mut Window,
14445 cx: &mut Context<Self>,
14446 ) -> Option<Task<Result<()>>> {
14447 use language::ToOffset as _;
14448
14449 let provider = self.semantics_provider.clone()?;
14450 let selection = self.selections.newest_anchor().clone();
14451 let (cursor_buffer, cursor_buffer_position) = self
14452 .buffer
14453 .read(cx)
14454 .text_anchor_for_position(selection.head(), cx)?;
14455 let (tail_buffer, cursor_buffer_position_end) = self
14456 .buffer
14457 .read(cx)
14458 .text_anchor_for_position(selection.tail(), cx)?;
14459 if tail_buffer != cursor_buffer {
14460 return None;
14461 }
14462
14463 let snapshot = cursor_buffer.read(cx).snapshot();
14464 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14465 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14466 let prepare_rename = provider
14467 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14468 .unwrap_or_else(|| Task::ready(Ok(None)));
14469 drop(snapshot);
14470
14471 Some(cx.spawn_in(window, async move |this, cx| {
14472 let rename_range = if let Some(range) = prepare_rename.await? {
14473 Some(range)
14474 } else {
14475 this.update(cx, |this, cx| {
14476 let buffer = this.buffer.read(cx).snapshot(cx);
14477 let mut buffer_highlights = this
14478 .document_highlights_for_position(selection.head(), &buffer)
14479 .filter(|highlight| {
14480 highlight.start.excerpt_id == selection.head().excerpt_id
14481 && highlight.end.excerpt_id == selection.head().excerpt_id
14482 });
14483 buffer_highlights
14484 .next()
14485 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14486 })?
14487 };
14488 if let Some(rename_range) = rename_range {
14489 this.update_in(cx, |this, window, cx| {
14490 let snapshot = cursor_buffer.read(cx).snapshot();
14491 let rename_buffer_range = rename_range.to_offset(&snapshot);
14492 let cursor_offset_in_rename_range =
14493 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14494 let cursor_offset_in_rename_range_end =
14495 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14496
14497 this.take_rename(false, window, cx);
14498 let buffer = this.buffer.read(cx).read(cx);
14499 let cursor_offset = selection.head().to_offset(&buffer);
14500 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14501 let rename_end = rename_start + rename_buffer_range.len();
14502 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14503 let mut old_highlight_id = None;
14504 let old_name: Arc<str> = buffer
14505 .chunks(rename_start..rename_end, true)
14506 .map(|chunk| {
14507 if old_highlight_id.is_none() {
14508 old_highlight_id = chunk.syntax_highlight_id;
14509 }
14510 chunk.text
14511 })
14512 .collect::<String>()
14513 .into();
14514
14515 drop(buffer);
14516
14517 // Position the selection in the rename editor so that it matches the current selection.
14518 this.show_local_selections = false;
14519 let rename_editor = cx.new(|cx| {
14520 let mut editor = Editor::single_line(window, cx);
14521 editor.buffer.update(cx, |buffer, cx| {
14522 buffer.edit([(0..0, old_name.clone())], None, cx)
14523 });
14524 let rename_selection_range = match cursor_offset_in_rename_range
14525 .cmp(&cursor_offset_in_rename_range_end)
14526 {
14527 Ordering::Equal => {
14528 editor.select_all(&SelectAll, window, cx);
14529 return editor;
14530 }
14531 Ordering::Less => {
14532 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14533 }
14534 Ordering::Greater => {
14535 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14536 }
14537 };
14538 if rename_selection_range.end > old_name.len() {
14539 editor.select_all(&SelectAll, window, cx);
14540 } else {
14541 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14542 s.select_ranges([rename_selection_range]);
14543 });
14544 }
14545 editor
14546 });
14547 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14548 if e == &EditorEvent::Focused {
14549 cx.emit(EditorEvent::FocusedIn)
14550 }
14551 })
14552 .detach();
14553
14554 let write_highlights =
14555 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14556 let read_highlights =
14557 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14558 let ranges = write_highlights
14559 .iter()
14560 .flat_map(|(_, ranges)| ranges.iter())
14561 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14562 .cloned()
14563 .collect();
14564
14565 this.highlight_text::<Rename>(
14566 ranges,
14567 HighlightStyle {
14568 fade_out: Some(0.6),
14569 ..Default::default()
14570 },
14571 cx,
14572 );
14573 let rename_focus_handle = rename_editor.focus_handle(cx);
14574 window.focus(&rename_focus_handle);
14575 let block_id = this.insert_blocks(
14576 [BlockProperties {
14577 style: BlockStyle::Flex,
14578 placement: BlockPlacement::Below(range.start),
14579 height: Some(1),
14580 render: Arc::new({
14581 let rename_editor = rename_editor.clone();
14582 move |cx: &mut BlockContext| {
14583 let mut text_style = cx.editor_style.text.clone();
14584 if let Some(highlight_style) = old_highlight_id
14585 .and_then(|h| h.style(&cx.editor_style.syntax))
14586 {
14587 text_style = text_style.highlight(highlight_style);
14588 }
14589 div()
14590 .block_mouse_down()
14591 .pl(cx.anchor_x)
14592 .child(EditorElement::new(
14593 &rename_editor,
14594 EditorStyle {
14595 background: cx.theme().system().transparent,
14596 local_player: cx.editor_style.local_player,
14597 text: text_style,
14598 scrollbar_width: cx.editor_style.scrollbar_width,
14599 syntax: cx.editor_style.syntax.clone(),
14600 status: cx.editor_style.status.clone(),
14601 inlay_hints_style: HighlightStyle {
14602 font_weight: Some(FontWeight::BOLD),
14603 ..make_inlay_hints_style(cx.app)
14604 },
14605 inline_completion_styles: make_suggestion_styles(
14606 cx.app,
14607 ),
14608 ..EditorStyle::default()
14609 },
14610 ))
14611 .into_any_element()
14612 }
14613 }),
14614 priority: 0,
14615 }],
14616 Some(Autoscroll::fit()),
14617 cx,
14618 )[0];
14619 this.pending_rename = Some(RenameState {
14620 range,
14621 old_name,
14622 editor: rename_editor,
14623 block_id,
14624 });
14625 })?;
14626 }
14627
14628 Ok(())
14629 }))
14630 }
14631
14632 pub fn confirm_rename(
14633 &mut self,
14634 _: &ConfirmRename,
14635 window: &mut Window,
14636 cx: &mut Context<Self>,
14637 ) -> Option<Task<Result<()>>> {
14638 let rename = self.take_rename(false, window, cx)?;
14639 let workspace = self.workspace()?.downgrade();
14640 let (buffer, start) = self
14641 .buffer
14642 .read(cx)
14643 .text_anchor_for_position(rename.range.start, cx)?;
14644 let (end_buffer, _) = self
14645 .buffer
14646 .read(cx)
14647 .text_anchor_for_position(rename.range.end, cx)?;
14648 if buffer != end_buffer {
14649 return None;
14650 }
14651
14652 let old_name = rename.old_name;
14653 let new_name = rename.editor.read(cx).text(cx);
14654
14655 let rename = self.semantics_provider.as_ref()?.perform_rename(
14656 &buffer,
14657 start,
14658 new_name.clone(),
14659 cx,
14660 )?;
14661
14662 Some(cx.spawn_in(window, async move |editor, cx| {
14663 let project_transaction = rename.await?;
14664 Self::open_project_transaction(
14665 &editor,
14666 workspace,
14667 project_transaction,
14668 format!("Rename: {} → {}", old_name, new_name),
14669 cx,
14670 )
14671 .await?;
14672
14673 editor.update(cx, |editor, cx| {
14674 editor.refresh_document_highlights(cx);
14675 })?;
14676 Ok(())
14677 }))
14678 }
14679
14680 fn take_rename(
14681 &mut self,
14682 moving_cursor: bool,
14683 window: &mut Window,
14684 cx: &mut Context<Self>,
14685 ) -> Option<RenameState> {
14686 let rename = self.pending_rename.take()?;
14687 if rename.editor.focus_handle(cx).is_focused(window) {
14688 window.focus(&self.focus_handle);
14689 }
14690
14691 self.remove_blocks(
14692 [rename.block_id].into_iter().collect(),
14693 Some(Autoscroll::fit()),
14694 cx,
14695 );
14696 self.clear_highlights::<Rename>(cx);
14697 self.show_local_selections = true;
14698
14699 if moving_cursor {
14700 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14701 editor.selections.newest::<usize>(cx).head()
14702 });
14703
14704 // Update the selection to match the position of the selection inside
14705 // the rename editor.
14706 let snapshot = self.buffer.read(cx).read(cx);
14707 let rename_range = rename.range.to_offset(&snapshot);
14708 let cursor_in_editor = snapshot
14709 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14710 .min(rename_range.end);
14711 drop(snapshot);
14712
14713 self.change_selections(None, window, cx, |s| {
14714 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14715 });
14716 } else {
14717 self.refresh_document_highlights(cx);
14718 }
14719
14720 Some(rename)
14721 }
14722
14723 pub fn pending_rename(&self) -> Option<&RenameState> {
14724 self.pending_rename.as_ref()
14725 }
14726
14727 fn format(
14728 &mut self,
14729 _: &Format,
14730 window: &mut Window,
14731 cx: &mut Context<Self>,
14732 ) -> Option<Task<Result<()>>> {
14733 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14734
14735 let project = match &self.project {
14736 Some(project) => project.clone(),
14737 None => return None,
14738 };
14739
14740 Some(self.perform_format(
14741 project,
14742 FormatTrigger::Manual,
14743 FormatTarget::Buffers,
14744 window,
14745 cx,
14746 ))
14747 }
14748
14749 fn format_selections(
14750 &mut self,
14751 _: &FormatSelections,
14752 window: &mut Window,
14753 cx: &mut Context<Self>,
14754 ) -> Option<Task<Result<()>>> {
14755 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14756
14757 let project = match &self.project {
14758 Some(project) => project.clone(),
14759 None => return None,
14760 };
14761
14762 let ranges = self
14763 .selections
14764 .all_adjusted(cx)
14765 .into_iter()
14766 .map(|selection| selection.range())
14767 .collect_vec();
14768
14769 Some(self.perform_format(
14770 project,
14771 FormatTrigger::Manual,
14772 FormatTarget::Ranges(ranges),
14773 window,
14774 cx,
14775 ))
14776 }
14777
14778 fn perform_format(
14779 &mut self,
14780 project: Entity<Project>,
14781 trigger: FormatTrigger,
14782 target: FormatTarget,
14783 window: &mut Window,
14784 cx: &mut Context<Self>,
14785 ) -> Task<Result<()>> {
14786 let buffer = self.buffer.clone();
14787 let (buffers, target) = match target {
14788 FormatTarget::Buffers => {
14789 let mut buffers = buffer.read(cx).all_buffers();
14790 if trigger == FormatTrigger::Save {
14791 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14792 }
14793 (buffers, LspFormatTarget::Buffers)
14794 }
14795 FormatTarget::Ranges(selection_ranges) => {
14796 let multi_buffer = buffer.read(cx);
14797 let snapshot = multi_buffer.read(cx);
14798 let mut buffers = HashSet::default();
14799 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14800 BTreeMap::new();
14801 for selection_range in selection_ranges {
14802 for (buffer, buffer_range, _) in
14803 snapshot.range_to_buffer_ranges(selection_range)
14804 {
14805 let buffer_id = buffer.remote_id();
14806 let start = buffer.anchor_before(buffer_range.start);
14807 let end = buffer.anchor_after(buffer_range.end);
14808 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14809 buffer_id_to_ranges
14810 .entry(buffer_id)
14811 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14812 .or_insert_with(|| vec![start..end]);
14813 }
14814 }
14815 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14816 }
14817 };
14818
14819 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14820 let selections_prev = transaction_id_prev
14821 .and_then(|transaction_id_prev| {
14822 // default to selections as they were after the last edit, if we have them,
14823 // instead of how they are now.
14824 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14825 // will take you back to where you made the last edit, instead of staying where you scrolled
14826 self.selection_history
14827 .transaction(transaction_id_prev)
14828 .map(|t| t.0.clone())
14829 })
14830 .unwrap_or_else(|| {
14831 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14832 self.selections.disjoint_anchors()
14833 });
14834
14835 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14836 let format = project.update(cx, |project, cx| {
14837 project.format(buffers, target, true, trigger, cx)
14838 });
14839
14840 cx.spawn_in(window, async move |editor, cx| {
14841 let transaction = futures::select_biased! {
14842 transaction = format.log_err().fuse() => transaction,
14843 () = timeout => {
14844 log::warn!("timed out waiting for formatting");
14845 None
14846 }
14847 };
14848
14849 buffer
14850 .update(cx, |buffer, cx| {
14851 if let Some(transaction) = transaction {
14852 if !buffer.is_singleton() {
14853 buffer.push_transaction(&transaction.0, cx);
14854 }
14855 }
14856 cx.notify();
14857 })
14858 .ok();
14859
14860 if let Some(transaction_id_now) =
14861 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14862 {
14863 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14864 if has_new_transaction {
14865 _ = editor.update(cx, |editor, _| {
14866 editor
14867 .selection_history
14868 .insert_transaction(transaction_id_now, selections_prev);
14869 });
14870 }
14871 }
14872
14873 Ok(())
14874 })
14875 }
14876
14877 fn organize_imports(
14878 &mut self,
14879 _: &OrganizeImports,
14880 window: &mut Window,
14881 cx: &mut Context<Self>,
14882 ) -> Option<Task<Result<()>>> {
14883 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14884 let project = match &self.project {
14885 Some(project) => project.clone(),
14886 None => return None,
14887 };
14888 Some(self.perform_code_action_kind(
14889 project,
14890 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14891 window,
14892 cx,
14893 ))
14894 }
14895
14896 fn perform_code_action_kind(
14897 &mut self,
14898 project: Entity<Project>,
14899 kind: CodeActionKind,
14900 window: &mut Window,
14901 cx: &mut Context<Self>,
14902 ) -> Task<Result<()>> {
14903 let buffer = self.buffer.clone();
14904 let buffers = buffer.read(cx).all_buffers();
14905 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14906 let apply_action = project.update(cx, |project, cx| {
14907 project.apply_code_action_kind(buffers, kind, true, cx)
14908 });
14909 cx.spawn_in(window, async move |_, cx| {
14910 let transaction = futures::select_biased! {
14911 () = timeout => {
14912 log::warn!("timed out waiting for executing code action");
14913 None
14914 }
14915 transaction = apply_action.log_err().fuse() => transaction,
14916 };
14917 buffer
14918 .update(cx, |buffer, cx| {
14919 // check if we need this
14920 if let Some(transaction) = transaction {
14921 if !buffer.is_singleton() {
14922 buffer.push_transaction(&transaction.0, cx);
14923 }
14924 }
14925 cx.notify();
14926 })
14927 .ok();
14928 Ok(())
14929 })
14930 }
14931
14932 fn restart_language_server(
14933 &mut self,
14934 _: &RestartLanguageServer,
14935 _: &mut Window,
14936 cx: &mut Context<Self>,
14937 ) {
14938 if let Some(project) = self.project.clone() {
14939 self.buffer.update(cx, |multi_buffer, cx| {
14940 project.update(cx, |project, cx| {
14941 project.restart_language_servers_for_buffers(
14942 multi_buffer.all_buffers().into_iter().collect(),
14943 cx,
14944 );
14945 });
14946 })
14947 }
14948 }
14949
14950 fn stop_language_server(
14951 &mut self,
14952 _: &StopLanguageServer,
14953 _: &mut Window,
14954 cx: &mut Context<Self>,
14955 ) {
14956 if let Some(project) = self.project.clone() {
14957 self.buffer.update(cx, |multi_buffer, cx| {
14958 project.update(cx, |project, cx| {
14959 project.stop_language_servers_for_buffers(
14960 multi_buffer.all_buffers().into_iter().collect(),
14961 cx,
14962 );
14963 cx.emit(project::Event::RefreshInlayHints);
14964 });
14965 });
14966 }
14967 }
14968
14969 fn cancel_language_server_work(
14970 workspace: &mut Workspace,
14971 _: &actions::CancelLanguageServerWork,
14972 _: &mut Window,
14973 cx: &mut Context<Workspace>,
14974 ) {
14975 let project = workspace.project();
14976 let buffers = workspace
14977 .active_item(cx)
14978 .and_then(|item| item.act_as::<Editor>(cx))
14979 .map_or(HashSet::default(), |editor| {
14980 editor.read(cx).buffer.read(cx).all_buffers()
14981 });
14982 project.update(cx, |project, cx| {
14983 project.cancel_language_server_work_for_buffers(buffers, cx);
14984 });
14985 }
14986
14987 fn show_character_palette(
14988 &mut self,
14989 _: &ShowCharacterPalette,
14990 window: &mut Window,
14991 _: &mut Context<Self>,
14992 ) {
14993 window.show_character_palette();
14994 }
14995
14996 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14997 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14998 let buffer = self.buffer.read(cx).snapshot(cx);
14999 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15000 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15001 let is_valid = buffer
15002 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15003 .any(|entry| {
15004 entry.diagnostic.is_primary
15005 && !entry.range.is_empty()
15006 && entry.range.start == primary_range_start
15007 && entry.diagnostic.message == active_diagnostics.active_message
15008 });
15009
15010 if !is_valid {
15011 self.dismiss_diagnostics(cx);
15012 }
15013 }
15014 }
15015
15016 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15017 match &self.active_diagnostics {
15018 ActiveDiagnostic::Group(group) => Some(group),
15019 _ => None,
15020 }
15021 }
15022
15023 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15024 self.dismiss_diagnostics(cx);
15025 self.active_diagnostics = ActiveDiagnostic::All;
15026 }
15027
15028 fn activate_diagnostics(
15029 &mut self,
15030 buffer_id: BufferId,
15031 diagnostic: DiagnosticEntry<usize>,
15032 window: &mut Window,
15033 cx: &mut Context<Self>,
15034 ) {
15035 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15036 return;
15037 }
15038 self.dismiss_diagnostics(cx);
15039 let snapshot = self.snapshot(window, cx);
15040 let buffer = self.buffer.read(cx).snapshot(cx);
15041 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15042 return;
15043 };
15044
15045 let diagnostic_group = buffer
15046 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15047 .collect::<Vec<_>>();
15048
15049 let blocks =
15050 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15051
15052 let blocks = self.display_map.update(cx, |display_map, cx| {
15053 display_map.insert_blocks(blocks, cx).into_iter().collect()
15054 });
15055 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15056 active_range: buffer.anchor_before(diagnostic.range.start)
15057 ..buffer.anchor_after(diagnostic.range.end),
15058 active_message: diagnostic.diagnostic.message.clone(),
15059 group_id: diagnostic.diagnostic.group_id,
15060 blocks,
15061 });
15062 cx.notify();
15063 }
15064
15065 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15066 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15067 return;
15068 };
15069
15070 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15071 if let ActiveDiagnostic::Group(group) = prev {
15072 self.display_map.update(cx, |display_map, cx| {
15073 display_map.remove_blocks(group.blocks, cx);
15074 });
15075 cx.notify();
15076 }
15077 }
15078
15079 /// Disable inline diagnostics rendering for this editor.
15080 pub fn disable_inline_diagnostics(&mut self) {
15081 self.inline_diagnostics_enabled = false;
15082 self.inline_diagnostics_update = Task::ready(());
15083 self.inline_diagnostics.clear();
15084 }
15085
15086 pub fn inline_diagnostics_enabled(&self) -> bool {
15087 self.inline_diagnostics_enabled
15088 }
15089
15090 pub fn show_inline_diagnostics(&self) -> bool {
15091 self.show_inline_diagnostics
15092 }
15093
15094 pub fn toggle_inline_diagnostics(
15095 &mut self,
15096 _: &ToggleInlineDiagnostics,
15097 window: &mut Window,
15098 cx: &mut Context<Editor>,
15099 ) {
15100 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15101 self.refresh_inline_diagnostics(false, window, cx);
15102 }
15103
15104 fn refresh_inline_diagnostics(
15105 &mut self,
15106 debounce: bool,
15107 window: &mut Window,
15108 cx: &mut Context<Self>,
15109 ) {
15110 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
15111 self.inline_diagnostics_update = Task::ready(());
15112 self.inline_diagnostics.clear();
15113 return;
15114 }
15115
15116 let debounce_ms = ProjectSettings::get_global(cx)
15117 .diagnostics
15118 .inline
15119 .update_debounce_ms;
15120 let debounce = if debounce && debounce_ms > 0 {
15121 Some(Duration::from_millis(debounce_ms))
15122 } else {
15123 None
15124 };
15125 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15126 let editor = editor.upgrade().unwrap();
15127
15128 if let Some(debounce) = debounce {
15129 cx.background_executor().timer(debounce).await;
15130 }
15131 let Some(snapshot) = editor
15132 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15133 .ok()
15134 else {
15135 return;
15136 };
15137
15138 let new_inline_diagnostics = cx
15139 .background_spawn(async move {
15140 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15141 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15142 let message = diagnostic_entry
15143 .diagnostic
15144 .message
15145 .split_once('\n')
15146 .map(|(line, _)| line)
15147 .map(SharedString::new)
15148 .unwrap_or_else(|| {
15149 SharedString::from(diagnostic_entry.diagnostic.message)
15150 });
15151 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15152 let (Ok(i) | Err(i)) = inline_diagnostics
15153 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15154 inline_diagnostics.insert(
15155 i,
15156 (
15157 start_anchor,
15158 InlineDiagnostic {
15159 message,
15160 group_id: diagnostic_entry.diagnostic.group_id,
15161 start: diagnostic_entry.range.start.to_point(&snapshot),
15162 is_primary: diagnostic_entry.diagnostic.is_primary,
15163 severity: diagnostic_entry.diagnostic.severity,
15164 },
15165 ),
15166 );
15167 }
15168 inline_diagnostics
15169 })
15170 .await;
15171
15172 editor
15173 .update(cx, |editor, cx| {
15174 editor.inline_diagnostics = new_inline_diagnostics;
15175 cx.notify();
15176 })
15177 .ok();
15178 });
15179 }
15180
15181 pub fn set_selections_from_remote(
15182 &mut self,
15183 selections: Vec<Selection<Anchor>>,
15184 pending_selection: Option<Selection<Anchor>>,
15185 window: &mut Window,
15186 cx: &mut Context<Self>,
15187 ) {
15188 let old_cursor_position = self.selections.newest_anchor().head();
15189 self.selections.change_with(cx, |s| {
15190 s.select_anchors(selections);
15191 if let Some(pending_selection) = pending_selection {
15192 s.set_pending(pending_selection, SelectMode::Character);
15193 } else {
15194 s.clear_pending();
15195 }
15196 });
15197 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15198 }
15199
15200 fn push_to_selection_history(&mut self) {
15201 self.selection_history.push(SelectionHistoryEntry {
15202 selections: self.selections.disjoint_anchors(),
15203 select_next_state: self.select_next_state.clone(),
15204 select_prev_state: self.select_prev_state.clone(),
15205 add_selections_state: self.add_selections_state.clone(),
15206 });
15207 }
15208
15209 pub fn transact(
15210 &mut self,
15211 window: &mut Window,
15212 cx: &mut Context<Self>,
15213 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15214 ) -> Option<TransactionId> {
15215 self.start_transaction_at(Instant::now(), window, cx);
15216 update(self, window, cx);
15217 self.end_transaction_at(Instant::now(), cx)
15218 }
15219
15220 pub fn start_transaction_at(
15221 &mut self,
15222 now: Instant,
15223 window: &mut Window,
15224 cx: &mut Context<Self>,
15225 ) {
15226 self.end_selection(window, cx);
15227 if let Some(tx_id) = self
15228 .buffer
15229 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15230 {
15231 self.selection_history
15232 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15233 cx.emit(EditorEvent::TransactionBegun {
15234 transaction_id: tx_id,
15235 })
15236 }
15237 }
15238
15239 pub fn end_transaction_at(
15240 &mut self,
15241 now: Instant,
15242 cx: &mut Context<Self>,
15243 ) -> Option<TransactionId> {
15244 if let Some(transaction_id) = self
15245 .buffer
15246 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15247 {
15248 if let Some((_, end_selections)) =
15249 self.selection_history.transaction_mut(transaction_id)
15250 {
15251 *end_selections = Some(self.selections.disjoint_anchors());
15252 } else {
15253 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15254 }
15255
15256 cx.emit(EditorEvent::Edited { transaction_id });
15257 Some(transaction_id)
15258 } else {
15259 None
15260 }
15261 }
15262
15263 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15264 if self.selection_mark_mode {
15265 self.change_selections(None, window, cx, |s| {
15266 s.move_with(|_, sel| {
15267 sel.collapse_to(sel.head(), SelectionGoal::None);
15268 });
15269 })
15270 }
15271 self.selection_mark_mode = true;
15272 cx.notify();
15273 }
15274
15275 pub fn swap_selection_ends(
15276 &mut self,
15277 _: &actions::SwapSelectionEnds,
15278 window: &mut Window,
15279 cx: &mut Context<Self>,
15280 ) {
15281 self.change_selections(None, window, cx, |s| {
15282 s.move_with(|_, sel| {
15283 if sel.start != sel.end {
15284 sel.reversed = !sel.reversed
15285 }
15286 });
15287 });
15288 self.request_autoscroll(Autoscroll::newest(), cx);
15289 cx.notify();
15290 }
15291
15292 pub fn toggle_fold(
15293 &mut self,
15294 _: &actions::ToggleFold,
15295 window: &mut Window,
15296 cx: &mut Context<Self>,
15297 ) {
15298 if self.is_singleton(cx) {
15299 let selection = self.selections.newest::<Point>(cx);
15300
15301 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15302 let range = if selection.is_empty() {
15303 let point = selection.head().to_display_point(&display_map);
15304 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15305 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15306 .to_point(&display_map);
15307 start..end
15308 } else {
15309 selection.range()
15310 };
15311 if display_map.folds_in_range(range).next().is_some() {
15312 self.unfold_lines(&Default::default(), window, cx)
15313 } else {
15314 self.fold(&Default::default(), window, cx)
15315 }
15316 } else {
15317 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15318 let buffer_ids: HashSet<_> = self
15319 .selections
15320 .disjoint_anchor_ranges()
15321 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15322 .collect();
15323
15324 let should_unfold = buffer_ids
15325 .iter()
15326 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15327
15328 for buffer_id in buffer_ids {
15329 if should_unfold {
15330 self.unfold_buffer(buffer_id, cx);
15331 } else {
15332 self.fold_buffer(buffer_id, cx);
15333 }
15334 }
15335 }
15336 }
15337
15338 pub fn toggle_fold_recursive(
15339 &mut self,
15340 _: &actions::ToggleFoldRecursive,
15341 window: &mut Window,
15342 cx: &mut Context<Self>,
15343 ) {
15344 let selection = self.selections.newest::<Point>(cx);
15345
15346 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15347 let range = if selection.is_empty() {
15348 let point = selection.head().to_display_point(&display_map);
15349 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15350 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15351 .to_point(&display_map);
15352 start..end
15353 } else {
15354 selection.range()
15355 };
15356 if display_map.folds_in_range(range).next().is_some() {
15357 self.unfold_recursive(&Default::default(), window, cx)
15358 } else {
15359 self.fold_recursive(&Default::default(), window, cx)
15360 }
15361 }
15362
15363 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15364 if self.is_singleton(cx) {
15365 let mut to_fold = Vec::new();
15366 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15367 let selections = self.selections.all_adjusted(cx);
15368
15369 for selection in selections {
15370 let range = selection.range().sorted();
15371 let buffer_start_row = range.start.row;
15372
15373 if range.start.row != range.end.row {
15374 let mut found = false;
15375 let mut row = range.start.row;
15376 while row <= range.end.row {
15377 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15378 {
15379 found = true;
15380 row = crease.range().end.row + 1;
15381 to_fold.push(crease);
15382 } else {
15383 row += 1
15384 }
15385 }
15386 if found {
15387 continue;
15388 }
15389 }
15390
15391 for row in (0..=range.start.row).rev() {
15392 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15393 if crease.range().end.row >= buffer_start_row {
15394 to_fold.push(crease);
15395 if row <= range.start.row {
15396 break;
15397 }
15398 }
15399 }
15400 }
15401 }
15402
15403 self.fold_creases(to_fold, true, window, cx);
15404 } else {
15405 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15406 let buffer_ids = self
15407 .selections
15408 .disjoint_anchor_ranges()
15409 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15410 .collect::<HashSet<_>>();
15411 for buffer_id in buffer_ids {
15412 self.fold_buffer(buffer_id, cx);
15413 }
15414 }
15415 }
15416
15417 fn fold_at_level(
15418 &mut self,
15419 fold_at: &FoldAtLevel,
15420 window: &mut Window,
15421 cx: &mut Context<Self>,
15422 ) {
15423 if !self.buffer.read(cx).is_singleton() {
15424 return;
15425 }
15426
15427 let fold_at_level = fold_at.0;
15428 let snapshot = self.buffer.read(cx).snapshot(cx);
15429 let mut to_fold = Vec::new();
15430 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15431
15432 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15433 while start_row < end_row {
15434 match self
15435 .snapshot(window, cx)
15436 .crease_for_buffer_row(MultiBufferRow(start_row))
15437 {
15438 Some(crease) => {
15439 let nested_start_row = crease.range().start.row + 1;
15440 let nested_end_row = crease.range().end.row;
15441
15442 if current_level < fold_at_level {
15443 stack.push((nested_start_row, nested_end_row, current_level + 1));
15444 } else if current_level == fold_at_level {
15445 to_fold.push(crease);
15446 }
15447
15448 start_row = nested_end_row + 1;
15449 }
15450 None => start_row += 1,
15451 }
15452 }
15453 }
15454
15455 self.fold_creases(to_fold, true, window, cx);
15456 }
15457
15458 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15459 if self.buffer.read(cx).is_singleton() {
15460 let mut fold_ranges = Vec::new();
15461 let snapshot = self.buffer.read(cx).snapshot(cx);
15462
15463 for row in 0..snapshot.max_row().0 {
15464 if let Some(foldable_range) = self
15465 .snapshot(window, cx)
15466 .crease_for_buffer_row(MultiBufferRow(row))
15467 {
15468 fold_ranges.push(foldable_range);
15469 }
15470 }
15471
15472 self.fold_creases(fold_ranges, true, window, cx);
15473 } else {
15474 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15475 editor
15476 .update_in(cx, |editor, _, cx| {
15477 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15478 editor.fold_buffer(buffer_id, cx);
15479 }
15480 })
15481 .ok();
15482 });
15483 }
15484 }
15485
15486 pub fn fold_function_bodies(
15487 &mut self,
15488 _: &actions::FoldFunctionBodies,
15489 window: &mut Window,
15490 cx: &mut Context<Self>,
15491 ) {
15492 let snapshot = self.buffer.read(cx).snapshot(cx);
15493
15494 let ranges = snapshot
15495 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15496 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15497 .collect::<Vec<_>>();
15498
15499 let creases = ranges
15500 .into_iter()
15501 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15502 .collect();
15503
15504 self.fold_creases(creases, true, window, cx);
15505 }
15506
15507 pub fn fold_recursive(
15508 &mut self,
15509 _: &actions::FoldRecursive,
15510 window: &mut Window,
15511 cx: &mut Context<Self>,
15512 ) {
15513 let mut to_fold = Vec::new();
15514 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15515 let selections = self.selections.all_adjusted(cx);
15516
15517 for selection in selections {
15518 let range = selection.range().sorted();
15519 let buffer_start_row = range.start.row;
15520
15521 if range.start.row != range.end.row {
15522 let mut found = false;
15523 for row in range.start.row..=range.end.row {
15524 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15525 found = true;
15526 to_fold.push(crease);
15527 }
15528 }
15529 if found {
15530 continue;
15531 }
15532 }
15533
15534 for row in (0..=range.start.row).rev() {
15535 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15536 if crease.range().end.row >= buffer_start_row {
15537 to_fold.push(crease);
15538 } else {
15539 break;
15540 }
15541 }
15542 }
15543 }
15544
15545 self.fold_creases(to_fold, true, window, cx);
15546 }
15547
15548 pub fn fold_at(
15549 &mut self,
15550 buffer_row: MultiBufferRow,
15551 window: &mut Window,
15552 cx: &mut Context<Self>,
15553 ) {
15554 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15555
15556 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15557 let autoscroll = self
15558 .selections
15559 .all::<Point>(cx)
15560 .iter()
15561 .any(|selection| crease.range().overlaps(&selection.range()));
15562
15563 self.fold_creases(vec![crease], autoscroll, window, cx);
15564 }
15565 }
15566
15567 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15568 if self.is_singleton(cx) {
15569 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15570 let buffer = &display_map.buffer_snapshot;
15571 let selections = self.selections.all::<Point>(cx);
15572 let ranges = selections
15573 .iter()
15574 .map(|s| {
15575 let range = s.display_range(&display_map).sorted();
15576 let mut start = range.start.to_point(&display_map);
15577 let mut end = range.end.to_point(&display_map);
15578 start.column = 0;
15579 end.column = buffer.line_len(MultiBufferRow(end.row));
15580 start..end
15581 })
15582 .collect::<Vec<_>>();
15583
15584 self.unfold_ranges(&ranges, true, true, cx);
15585 } else {
15586 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15587 let buffer_ids = self
15588 .selections
15589 .disjoint_anchor_ranges()
15590 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15591 .collect::<HashSet<_>>();
15592 for buffer_id in buffer_ids {
15593 self.unfold_buffer(buffer_id, cx);
15594 }
15595 }
15596 }
15597
15598 pub fn unfold_recursive(
15599 &mut self,
15600 _: &UnfoldRecursive,
15601 _window: &mut Window,
15602 cx: &mut Context<Self>,
15603 ) {
15604 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15605 let selections = self.selections.all::<Point>(cx);
15606 let ranges = selections
15607 .iter()
15608 .map(|s| {
15609 let mut range = s.display_range(&display_map).sorted();
15610 *range.start.column_mut() = 0;
15611 *range.end.column_mut() = display_map.line_len(range.end.row());
15612 let start = range.start.to_point(&display_map);
15613 let end = range.end.to_point(&display_map);
15614 start..end
15615 })
15616 .collect::<Vec<_>>();
15617
15618 self.unfold_ranges(&ranges, true, true, cx);
15619 }
15620
15621 pub fn unfold_at(
15622 &mut self,
15623 buffer_row: MultiBufferRow,
15624 _window: &mut Window,
15625 cx: &mut Context<Self>,
15626 ) {
15627 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15628
15629 let intersection_range = Point::new(buffer_row.0, 0)
15630 ..Point::new(
15631 buffer_row.0,
15632 display_map.buffer_snapshot.line_len(buffer_row),
15633 );
15634
15635 let autoscroll = self
15636 .selections
15637 .all::<Point>(cx)
15638 .iter()
15639 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15640
15641 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15642 }
15643
15644 pub fn unfold_all(
15645 &mut self,
15646 _: &actions::UnfoldAll,
15647 _window: &mut Window,
15648 cx: &mut Context<Self>,
15649 ) {
15650 if self.buffer.read(cx).is_singleton() {
15651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15652 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15653 } else {
15654 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15655 editor
15656 .update(cx, |editor, cx| {
15657 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15658 editor.unfold_buffer(buffer_id, cx);
15659 }
15660 })
15661 .ok();
15662 });
15663 }
15664 }
15665
15666 pub fn fold_selected_ranges(
15667 &mut self,
15668 _: &FoldSelectedRanges,
15669 window: &mut Window,
15670 cx: &mut Context<Self>,
15671 ) {
15672 let selections = self.selections.all_adjusted(cx);
15673 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15674 let ranges = selections
15675 .into_iter()
15676 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15677 .collect::<Vec<_>>();
15678 self.fold_creases(ranges, true, window, cx);
15679 }
15680
15681 pub fn fold_ranges<T: ToOffset + Clone>(
15682 &mut self,
15683 ranges: Vec<Range<T>>,
15684 auto_scroll: bool,
15685 window: &mut Window,
15686 cx: &mut Context<Self>,
15687 ) {
15688 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15689 let ranges = ranges
15690 .into_iter()
15691 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15692 .collect::<Vec<_>>();
15693 self.fold_creases(ranges, auto_scroll, window, cx);
15694 }
15695
15696 pub fn fold_creases<T: ToOffset + Clone>(
15697 &mut self,
15698 creases: Vec<Crease<T>>,
15699 auto_scroll: bool,
15700 _window: &mut Window,
15701 cx: &mut Context<Self>,
15702 ) {
15703 if creases.is_empty() {
15704 return;
15705 }
15706
15707 let mut buffers_affected = HashSet::default();
15708 let multi_buffer = self.buffer().read(cx);
15709 for crease in &creases {
15710 if let Some((_, buffer, _)) =
15711 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15712 {
15713 buffers_affected.insert(buffer.read(cx).remote_id());
15714 };
15715 }
15716
15717 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15718
15719 if auto_scroll {
15720 self.request_autoscroll(Autoscroll::fit(), cx);
15721 }
15722
15723 cx.notify();
15724
15725 self.scrollbar_marker_state.dirty = true;
15726 self.folds_did_change(cx);
15727 }
15728
15729 /// Removes any folds whose ranges intersect any of the given ranges.
15730 pub fn unfold_ranges<T: ToOffset + Clone>(
15731 &mut self,
15732 ranges: &[Range<T>],
15733 inclusive: bool,
15734 auto_scroll: bool,
15735 cx: &mut Context<Self>,
15736 ) {
15737 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15738 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15739 });
15740 self.folds_did_change(cx);
15741 }
15742
15743 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15744 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15745 return;
15746 }
15747 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15748 self.display_map.update(cx, |display_map, cx| {
15749 display_map.fold_buffers([buffer_id], cx)
15750 });
15751 cx.emit(EditorEvent::BufferFoldToggled {
15752 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15753 folded: true,
15754 });
15755 cx.notify();
15756 }
15757
15758 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15759 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15760 return;
15761 }
15762 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15763 self.display_map.update(cx, |display_map, cx| {
15764 display_map.unfold_buffers([buffer_id], cx);
15765 });
15766 cx.emit(EditorEvent::BufferFoldToggled {
15767 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15768 folded: false,
15769 });
15770 cx.notify();
15771 }
15772
15773 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15774 self.display_map.read(cx).is_buffer_folded(buffer)
15775 }
15776
15777 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15778 self.display_map.read(cx).folded_buffers()
15779 }
15780
15781 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15782 self.display_map.update(cx, |display_map, cx| {
15783 display_map.disable_header_for_buffer(buffer_id, cx);
15784 });
15785 cx.notify();
15786 }
15787
15788 /// Removes any folds with the given ranges.
15789 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15790 &mut self,
15791 ranges: &[Range<T>],
15792 type_id: TypeId,
15793 auto_scroll: bool,
15794 cx: &mut Context<Self>,
15795 ) {
15796 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15797 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15798 });
15799 self.folds_did_change(cx);
15800 }
15801
15802 fn remove_folds_with<T: ToOffset + Clone>(
15803 &mut self,
15804 ranges: &[Range<T>],
15805 auto_scroll: bool,
15806 cx: &mut Context<Self>,
15807 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15808 ) {
15809 if ranges.is_empty() {
15810 return;
15811 }
15812
15813 let mut buffers_affected = HashSet::default();
15814 let multi_buffer = self.buffer().read(cx);
15815 for range in ranges {
15816 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15817 buffers_affected.insert(buffer.read(cx).remote_id());
15818 };
15819 }
15820
15821 self.display_map.update(cx, update);
15822
15823 if auto_scroll {
15824 self.request_autoscroll(Autoscroll::fit(), cx);
15825 }
15826
15827 cx.notify();
15828 self.scrollbar_marker_state.dirty = true;
15829 self.active_indent_guides_state.dirty = true;
15830 }
15831
15832 pub fn update_fold_widths(
15833 &mut self,
15834 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15835 cx: &mut Context<Self>,
15836 ) -> bool {
15837 self.display_map
15838 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15839 }
15840
15841 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15842 self.display_map.read(cx).fold_placeholder.clone()
15843 }
15844
15845 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15846 self.buffer.update(cx, |buffer, cx| {
15847 buffer.set_all_diff_hunks_expanded(cx);
15848 });
15849 }
15850
15851 pub fn expand_all_diff_hunks(
15852 &mut self,
15853 _: &ExpandAllDiffHunks,
15854 _window: &mut Window,
15855 cx: &mut Context<Self>,
15856 ) {
15857 self.buffer.update(cx, |buffer, cx| {
15858 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15859 });
15860 }
15861
15862 pub fn toggle_selected_diff_hunks(
15863 &mut self,
15864 _: &ToggleSelectedDiffHunks,
15865 _window: &mut Window,
15866 cx: &mut Context<Self>,
15867 ) {
15868 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15869 self.toggle_diff_hunks_in_ranges(ranges, cx);
15870 }
15871
15872 pub fn diff_hunks_in_ranges<'a>(
15873 &'a self,
15874 ranges: &'a [Range<Anchor>],
15875 buffer: &'a MultiBufferSnapshot,
15876 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15877 ranges.iter().flat_map(move |range| {
15878 let end_excerpt_id = range.end.excerpt_id;
15879 let range = range.to_point(buffer);
15880 let mut peek_end = range.end;
15881 if range.end.row < buffer.max_row().0 {
15882 peek_end = Point::new(range.end.row + 1, 0);
15883 }
15884 buffer
15885 .diff_hunks_in_range(range.start..peek_end)
15886 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15887 })
15888 }
15889
15890 pub fn has_stageable_diff_hunks_in_ranges(
15891 &self,
15892 ranges: &[Range<Anchor>],
15893 snapshot: &MultiBufferSnapshot,
15894 ) -> bool {
15895 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15896 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15897 }
15898
15899 pub fn toggle_staged_selected_diff_hunks(
15900 &mut self,
15901 _: &::git::ToggleStaged,
15902 _: &mut Window,
15903 cx: &mut Context<Self>,
15904 ) {
15905 let snapshot = self.buffer.read(cx).snapshot(cx);
15906 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15907 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15908 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15909 }
15910
15911 pub fn set_render_diff_hunk_controls(
15912 &mut self,
15913 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15914 cx: &mut Context<Self>,
15915 ) {
15916 self.render_diff_hunk_controls = render_diff_hunk_controls;
15917 cx.notify();
15918 }
15919
15920 pub fn stage_and_next(
15921 &mut self,
15922 _: &::git::StageAndNext,
15923 window: &mut Window,
15924 cx: &mut Context<Self>,
15925 ) {
15926 self.do_stage_or_unstage_and_next(true, window, cx);
15927 }
15928
15929 pub fn unstage_and_next(
15930 &mut self,
15931 _: &::git::UnstageAndNext,
15932 window: &mut Window,
15933 cx: &mut Context<Self>,
15934 ) {
15935 self.do_stage_or_unstage_and_next(false, window, cx);
15936 }
15937
15938 pub fn stage_or_unstage_diff_hunks(
15939 &mut self,
15940 stage: bool,
15941 ranges: Vec<Range<Anchor>>,
15942 cx: &mut Context<Self>,
15943 ) {
15944 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15945 cx.spawn(async move |this, cx| {
15946 task.await?;
15947 this.update(cx, |this, cx| {
15948 let snapshot = this.buffer.read(cx).snapshot(cx);
15949 let chunk_by = this
15950 .diff_hunks_in_ranges(&ranges, &snapshot)
15951 .chunk_by(|hunk| hunk.buffer_id);
15952 for (buffer_id, hunks) in &chunk_by {
15953 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15954 }
15955 })
15956 })
15957 .detach_and_log_err(cx);
15958 }
15959
15960 fn save_buffers_for_ranges_if_needed(
15961 &mut self,
15962 ranges: &[Range<Anchor>],
15963 cx: &mut Context<Editor>,
15964 ) -> Task<Result<()>> {
15965 let multibuffer = self.buffer.read(cx);
15966 let snapshot = multibuffer.read(cx);
15967 let buffer_ids: HashSet<_> = ranges
15968 .iter()
15969 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15970 .collect();
15971 drop(snapshot);
15972
15973 let mut buffers = HashSet::default();
15974 for buffer_id in buffer_ids {
15975 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15976 let buffer = buffer_entity.read(cx);
15977 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15978 {
15979 buffers.insert(buffer_entity);
15980 }
15981 }
15982 }
15983
15984 if let Some(project) = &self.project {
15985 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15986 } else {
15987 Task::ready(Ok(()))
15988 }
15989 }
15990
15991 fn do_stage_or_unstage_and_next(
15992 &mut self,
15993 stage: bool,
15994 window: &mut Window,
15995 cx: &mut Context<Self>,
15996 ) {
15997 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15998
15999 if ranges.iter().any(|range| range.start != range.end) {
16000 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16001 return;
16002 }
16003
16004 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16005 let snapshot = self.snapshot(window, cx);
16006 let position = self.selections.newest::<Point>(cx).head();
16007 let mut row = snapshot
16008 .buffer_snapshot
16009 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16010 .find(|hunk| hunk.row_range.start.0 > position.row)
16011 .map(|hunk| hunk.row_range.start);
16012
16013 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16014 // Outside of the project diff editor, wrap around to the beginning.
16015 if !all_diff_hunks_expanded {
16016 row = row.or_else(|| {
16017 snapshot
16018 .buffer_snapshot
16019 .diff_hunks_in_range(Point::zero()..position)
16020 .find(|hunk| hunk.row_range.end.0 < position.row)
16021 .map(|hunk| hunk.row_range.start)
16022 });
16023 }
16024
16025 if let Some(row) = row {
16026 let destination = Point::new(row.0, 0);
16027 let autoscroll = Autoscroll::center();
16028
16029 self.unfold_ranges(&[destination..destination], false, false, cx);
16030 self.change_selections(Some(autoscroll), window, cx, |s| {
16031 s.select_ranges([destination..destination]);
16032 });
16033 }
16034 }
16035
16036 fn do_stage_or_unstage(
16037 &self,
16038 stage: bool,
16039 buffer_id: BufferId,
16040 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16041 cx: &mut App,
16042 ) -> Option<()> {
16043 let project = self.project.as_ref()?;
16044 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16045 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16046 let buffer_snapshot = buffer.read(cx).snapshot();
16047 let file_exists = buffer_snapshot
16048 .file()
16049 .is_some_and(|file| file.disk_state().exists());
16050 diff.update(cx, |diff, cx| {
16051 diff.stage_or_unstage_hunks(
16052 stage,
16053 &hunks
16054 .map(|hunk| buffer_diff::DiffHunk {
16055 buffer_range: hunk.buffer_range,
16056 diff_base_byte_range: hunk.diff_base_byte_range,
16057 secondary_status: hunk.secondary_status,
16058 range: Point::zero()..Point::zero(), // unused
16059 })
16060 .collect::<Vec<_>>(),
16061 &buffer_snapshot,
16062 file_exists,
16063 cx,
16064 )
16065 });
16066 None
16067 }
16068
16069 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16070 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16071 self.buffer
16072 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16073 }
16074
16075 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16076 self.buffer.update(cx, |buffer, cx| {
16077 let ranges = vec![Anchor::min()..Anchor::max()];
16078 if !buffer.all_diff_hunks_expanded()
16079 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16080 {
16081 buffer.collapse_diff_hunks(ranges, cx);
16082 true
16083 } else {
16084 false
16085 }
16086 })
16087 }
16088
16089 fn toggle_diff_hunks_in_ranges(
16090 &mut self,
16091 ranges: Vec<Range<Anchor>>,
16092 cx: &mut Context<Editor>,
16093 ) {
16094 self.buffer.update(cx, |buffer, cx| {
16095 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16096 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16097 })
16098 }
16099
16100 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16101 self.buffer.update(cx, |buffer, cx| {
16102 let snapshot = buffer.snapshot(cx);
16103 let excerpt_id = range.end.excerpt_id;
16104 let point_range = range.to_point(&snapshot);
16105 let expand = !buffer.single_hunk_is_expanded(range, cx);
16106 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16107 })
16108 }
16109
16110 pub(crate) fn apply_all_diff_hunks(
16111 &mut self,
16112 _: &ApplyAllDiffHunks,
16113 window: &mut Window,
16114 cx: &mut Context<Self>,
16115 ) {
16116 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16117
16118 let buffers = self.buffer.read(cx).all_buffers();
16119 for branch_buffer in buffers {
16120 branch_buffer.update(cx, |branch_buffer, cx| {
16121 branch_buffer.merge_into_base(Vec::new(), cx);
16122 });
16123 }
16124
16125 if let Some(project) = self.project.clone() {
16126 self.save(true, project, window, cx).detach_and_log_err(cx);
16127 }
16128 }
16129
16130 pub(crate) fn apply_selected_diff_hunks(
16131 &mut self,
16132 _: &ApplyDiffHunk,
16133 window: &mut Window,
16134 cx: &mut Context<Self>,
16135 ) {
16136 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16137 let snapshot = self.snapshot(window, cx);
16138 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16139 let mut ranges_by_buffer = HashMap::default();
16140 self.transact(window, cx, |editor, _window, cx| {
16141 for hunk in hunks {
16142 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16143 ranges_by_buffer
16144 .entry(buffer.clone())
16145 .or_insert_with(Vec::new)
16146 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16147 }
16148 }
16149
16150 for (buffer, ranges) in ranges_by_buffer {
16151 buffer.update(cx, |buffer, cx| {
16152 buffer.merge_into_base(ranges, cx);
16153 });
16154 }
16155 });
16156
16157 if let Some(project) = self.project.clone() {
16158 self.save(true, project, window, cx).detach_and_log_err(cx);
16159 }
16160 }
16161
16162 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16163 if hovered != self.gutter_hovered {
16164 self.gutter_hovered = hovered;
16165 cx.notify();
16166 }
16167 }
16168
16169 pub fn insert_blocks(
16170 &mut self,
16171 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16172 autoscroll: Option<Autoscroll>,
16173 cx: &mut Context<Self>,
16174 ) -> Vec<CustomBlockId> {
16175 let blocks = self
16176 .display_map
16177 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16178 if let Some(autoscroll) = autoscroll {
16179 self.request_autoscroll(autoscroll, cx);
16180 }
16181 cx.notify();
16182 blocks
16183 }
16184
16185 pub fn resize_blocks(
16186 &mut self,
16187 heights: HashMap<CustomBlockId, u32>,
16188 autoscroll: Option<Autoscroll>,
16189 cx: &mut Context<Self>,
16190 ) {
16191 self.display_map
16192 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16193 if let Some(autoscroll) = autoscroll {
16194 self.request_autoscroll(autoscroll, cx);
16195 }
16196 cx.notify();
16197 }
16198
16199 pub fn replace_blocks(
16200 &mut self,
16201 renderers: HashMap<CustomBlockId, RenderBlock>,
16202 autoscroll: Option<Autoscroll>,
16203 cx: &mut Context<Self>,
16204 ) {
16205 self.display_map
16206 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16207 if let Some(autoscroll) = autoscroll {
16208 self.request_autoscroll(autoscroll, cx);
16209 }
16210 cx.notify();
16211 }
16212
16213 pub fn remove_blocks(
16214 &mut self,
16215 block_ids: HashSet<CustomBlockId>,
16216 autoscroll: Option<Autoscroll>,
16217 cx: &mut Context<Self>,
16218 ) {
16219 self.display_map.update(cx, |display_map, cx| {
16220 display_map.remove_blocks(block_ids, cx)
16221 });
16222 if let Some(autoscroll) = autoscroll {
16223 self.request_autoscroll(autoscroll, cx);
16224 }
16225 cx.notify();
16226 }
16227
16228 pub fn row_for_block(
16229 &self,
16230 block_id: CustomBlockId,
16231 cx: &mut Context<Self>,
16232 ) -> Option<DisplayRow> {
16233 self.display_map
16234 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16235 }
16236
16237 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16238 self.focused_block = Some(focused_block);
16239 }
16240
16241 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16242 self.focused_block.take()
16243 }
16244
16245 pub fn insert_creases(
16246 &mut self,
16247 creases: impl IntoIterator<Item = Crease<Anchor>>,
16248 cx: &mut Context<Self>,
16249 ) -> Vec<CreaseId> {
16250 self.display_map
16251 .update(cx, |map, cx| map.insert_creases(creases, cx))
16252 }
16253
16254 pub fn remove_creases(
16255 &mut self,
16256 ids: impl IntoIterator<Item = CreaseId>,
16257 cx: &mut Context<Self>,
16258 ) -> Vec<(CreaseId, Range<Anchor>)> {
16259 self.display_map
16260 .update(cx, |map, cx| map.remove_creases(ids, cx))
16261 }
16262
16263 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16264 self.display_map
16265 .update(cx, |map, cx| map.snapshot(cx))
16266 .longest_row()
16267 }
16268
16269 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16270 self.display_map
16271 .update(cx, |map, cx| map.snapshot(cx))
16272 .max_point()
16273 }
16274
16275 pub fn text(&self, cx: &App) -> String {
16276 self.buffer.read(cx).read(cx).text()
16277 }
16278
16279 pub fn is_empty(&self, cx: &App) -> bool {
16280 self.buffer.read(cx).read(cx).is_empty()
16281 }
16282
16283 pub fn text_option(&self, cx: &App) -> Option<String> {
16284 let text = self.text(cx);
16285 let text = text.trim();
16286
16287 if text.is_empty() {
16288 return None;
16289 }
16290
16291 Some(text.to_string())
16292 }
16293
16294 pub fn set_text(
16295 &mut self,
16296 text: impl Into<Arc<str>>,
16297 window: &mut Window,
16298 cx: &mut Context<Self>,
16299 ) {
16300 self.transact(window, cx, |this, _, cx| {
16301 this.buffer
16302 .read(cx)
16303 .as_singleton()
16304 .expect("you can only call set_text on editors for singleton buffers")
16305 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16306 });
16307 }
16308
16309 pub fn display_text(&self, cx: &mut App) -> String {
16310 self.display_map
16311 .update(cx, |map, cx| map.snapshot(cx))
16312 .text()
16313 }
16314
16315 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16316 let mut wrap_guides = smallvec::smallvec![];
16317
16318 if self.show_wrap_guides == Some(false) {
16319 return wrap_guides;
16320 }
16321
16322 let settings = self.buffer.read(cx).language_settings(cx);
16323 if settings.show_wrap_guides {
16324 match self.soft_wrap_mode(cx) {
16325 SoftWrap::Column(soft_wrap) => {
16326 wrap_guides.push((soft_wrap as usize, true));
16327 }
16328 SoftWrap::Bounded(soft_wrap) => {
16329 wrap_guides.push((soft_wrap as usize, true));
16330 }
16331 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16332 }
16333 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16334 }
16335
16336 wrap_guides
16337 }
16338
16339 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16340 let settings = self.buffer.read(cx).language_settings(cx);
16341 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16342 match mode {
16343 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16344 SoftWrap::None
16345 }
16346 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16347 language_settings::SoftWrap::PreferredLineLength => {
16348 SoftWrap::Column(settings.preferred_line_length)
16349 }
16350 language_settings::SoftWrap::Bounded => {
16351 SoftWrap::Bounded(settings.preferred_line_length)
16352 }
16353 }
16354 }
16355
16356 pub fn set_soft_wrap_mode(
16357 &mut self,
16358 mode: language_settings::SoftWrap,
16359
16360 cx: &mut Context<Self>,
16361 ) {
16362 self.soft_wrap_mode_override = Some(mode);
16363 cx.notify();
16364 }
16365
16366 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16367 self.hard_wrap = hard_wrap;
16368 cx.notify();
16369 }
16370
16371 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16372 self.text_style_refinement = Some(style);
16373 }
16374
16375 /// called by the Element so we know what style we were most recently rendered with.
16376 pub(crate) fn set_style(
16377 &mut self,
16378 style: EditorStyle,
16379 window: &mut Window,
16380 cx: &mut Context<Self>,
16381 ) {
16382 let rem_size = window.rem_size();
16383 self.display_map.update(cx, |map, cx| {
16384 map.set_font(
16385 style.text.font(),
16386 style.text.font_size.to_pixels(rem_size),
16387 cx,
16388 )
16389 });
16390 self.style = Some(style);
16391 }
16392
16393 pub fn style(&self) -> Option<&EditorStyle> {
16394 self.style.as_ref()
16395 }
16396
16397 // Called by the element. This method is not designed to be called outside of the editor
16398 // element's layout code because it does not notify when rewrapping is computed synchronously.
16399 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16400 self.display_map
16401 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16402 }
16403
16404 pub fn set_soft_wrap(&mut self) {
16405 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16406 }
16407
16408 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16409 if self.soft_wrap_mode_override.is_some() {
16410 self.soft_wrap_mode_override.take();
16411 } else {
16412 let soft_wrap = match self.soft_wrap_mode(cx) {
16413 SoftWrap::GitDiff => return,
16414 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16415 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16416 language_settings::SoftWrap::None
16417 }
16418 };
16419 self.soft_wrap_mode_override = Some(soft_wrap);
16420 }
16421 cx.notify();
16422 }
16423
16424 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16425 let Some(workspace) = self.workspace() else {
16426 return;
16427 };
16428 let fs = workspace.read(cx).app_state().fs.clone();
16429 let current_show = TabBarSettings::get_global(cx).show;
16430 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16431 setting.show = Some(!current_show);
16432 });
16433 }
16434
16435 pub fn toggle_indent_guides(
16436 &mut self,
16437 _: &ToggleIndentGuides,
16438 _: &mut Window,
16439 cx: &mut Context<Self>,
16440 ) {
16441 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16442 self.buffer
16443 .read(cx)
16444 .language_settings(cx)
16445 .indent_guides
16446 .enabled
16447 });
16448 self.show_indent_guides = Some(!currently_enabled);
16449 cx.notify();
16450 }
16451
16452 fn should_show_indent_guides(&self) -> Option<bool> {
16453 self.show_indent_guides
16454 }
16455
16456 pub fn toggle_line_numbers(
16457 &mut self,
16458 _: &ToggleLineNumbers,
16459 _: &mut Window,
16460 cx: &mut Context<Self>,
16461 ) {
16462 let mut editor_settings = EditorSettings::get_global(cx).clone();
16463 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16464 EditorSettings::override_global(editor_settings, cx);
16465 }
16466
16467 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16468 if let Some(show_line_numbers) = self.show_line_numbers {
16469 return show_line_numbers;
16470 }
16471 EditorSettings::get_global(cx).gutter.line_numbers
16472 }
16473
16474 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16475 self.use_relative_line_numbers
16476 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16477 }
16478
16479 pub fn toggle_relative_line_numbers(
16480 &mut self,
16481 _: &ToggleRelativeLineNumbers,
16482 _: &mut Window,
16483 cx: &mut Context<Self>,
16484 ) {
16485 let is_relative = self.should_use_relative_line_numbers(cx);
16486 self.set_relative_line_number(Some(!is_relative), cx)
16487 }
16488
16489 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16490 self.use_relative_line_numbers = is_relative;
16491 cx.notify();
16492 }
16493
16494 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16495 self.show_gutter = show_gutter;
16496 cx.notify();
16497 }
16498
16499 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16500 self.show_scrollbars = show_scrollbars;
16501 cx.notify();
16502 }
16503
16504 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16505 self.show_line_numbers = Some(show_line_numbers);
16506 cx.notify();
16507 }
16508
16509 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16510 self.disable_expand_excerpt_buttons = true;
16511 cx.notify();
16512 }
16513
16514 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16515 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16516 cx.notify();
16517 }
16518
16519 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16520 self.show_code_actions = Some(show_code_actions);
16521 cx.notify();
16522 }
16523
16524 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16525 self.show_runnables = Some(show_runnables);
16526 cx.notify();
16527 }
16528
16529 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16530 self.show_breakpoints = Some(show_breakpoints);
16531 cx.notify();
16532 }
16533
16534 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16535 if self.display_map.read(cx).masked != masked {
16536 self.display_map.update(cx, |map, _| map.masked = masked);
16537 }
16538 cx.notify()
16539 }
16540
16541 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16542 self.show_wrap_guides = Some(show_wrap_guides);
16543 cx.notify();
16544 }
16545
16546 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16547 self.show_indent_guides = Some(show_indent_guides);
16548 cx.notify();
16549 }
16550
16551 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16552 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16553 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16554 if let Some(dir) = file.abs_path(cx).parent() {
16555 return Some(dir.to_owned());
16556 }
16557 }
16558
16559 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16560 return Some(project_path.path.to_path_buf());
16561 }
16562 }
16563
16564 None
16565 }
16566
16567 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16568 self.active_excerpt(cx)?
16569 .1
16570 .read(cx)
16571 .file()
16572 .and_then(|f| f.as_local())
16573 }
16574
16575 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16576 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16577 let buffer = buffer.read(cx);
16578 if let Some(project_path) = buffer.project_path(cx) {
16579 let project = self.project.as_ref()?.read(cx);
16580 project.absolute_path(&project_path, cx)
16581 } else {
16582 buffer
16583 .file()
16584 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16585 }
16586 })
16587 }
16588
16589 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16590 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16591 let project_path = buffer.read(cx).project_path(cx)?;
16592 let project = self.project.as_ref()?.read(cx);
16593 let entry = project.entry_for_path(&project_path, cx)?;
16594 let path = entry.path.to_path_buf();
16595 Some(path)
16596 })
16597 }
16598
16599 pub fn reveal_in_finder(
16600 &mut self,
16601 _: &RevealInFileManager,
16602 _window: &mut Window,
16603 cx: &mut Context<Self>,
16604 ) {
16605 if let Some(target) = self.target_file(cx) {
16606 cx.reveal_path(&target.abs_path(cx));
16607 }
16608 }
16609
16610 pub fn copy_path(
16611 &mut self,
16612 _: &zed_actions::workspace::CopyPath,
16613 _window: &mut Window,
16614 cx: &mut Context<Self>,
16615 ) {
16616 if let Some(path) = self.target_file_abs_path(cx) {
16617 if let Some(path) = path.to_str() {
16618 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16619 }
16620 }
16621 }
16622
16623 pub fn copy_relative_path(
16624 &mut self,
16625 _: &zed_actions::workspace::CopyRelativePath,
16626 _window: &mut Window,
16627 cx: &mut Context<Self>,
16628 ) {
16629 if let Some(path) = self.target_file_path(cx) {
16630 if let Some(path) = path.to_str() {
16631 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16632 }
16633 }
16634 }
16635
16636 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16637 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16638 buffer.read(cx).project_path(cx)
16639 } else {
16640 None
16641 }
16642 }
16643
16644 // Returns true if the editor handled a go-to-line request
16645 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16646 maybe!({
16647 let breakpoint_store = self.breakpoint_store.as_ref()?;
16648
16649 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
16650 else {
16651 self.clear_row_highlights::<ActiveDebugLine>();
16652 return None;
16653 };
16654
16655 let position = active_stack_frame.position;
16656 let buffer_id = position.buffer_id?;
16657 let snapshot = self
16658 .project
16659 .as_ref()?
16660 .read(cx)
16661 .buffer_for_id(buffer_id, cx)?
16662 .read(cx)
16663 .snapshot();
16664
16665 let mut handled = false;
16666 for (id, ExcerptRange { context, .. }) in
16667 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
16668 {
16669 if context.start.cmp(&position, &snapshot).is_ge()
16670 || context.end.cmp(&position, &snapshot).is_lt()
16671 {
16672 continue;
16673 }
16674 let snapshot = self.buffer.read(cx).snapshot(cx);
16675 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
16676
16677 handled = true;
16678 self.clear_row_highlights::<ActiveDebugLine>();
16679 self.go_to_line::<ActiveDebugLine>(
16680 multibuffer_anchor,
16681 Some(cx.theme().colors().editor_debugger_active_line_background),
16682 window,
16683 cx,
16684 );
16685
16686 cx.notify();
16687 }
16688
16689 handled.then_some(())
16690 })
16691 .is_some()
16692 }
16693
16694 pub fn copy_file_name_without_extension(
16695 &mut self,
16696 _: &CopyFileNameWithoutExtension,
16697 _: &mut Window,
16698 cx: &mut Context<Self>,
16699 ) {
16700 if let Some(file) = self.target_file(cx) {
16701 if let Some(file_stem) = file.path().file_stem() {
16702 if let Some(name) = file_stem.to_str() {
16703 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16704 }
16705 }
16706 }
16707 }
16708
16709 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16710 if let Some(file) = self.target_file(cx) {
16711 if let Some(file_name) = file.path().file_name() {
16712 if let Some(name) = file_name.to_str() {
16713 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16714 }
16715 }
16716 }
16717 }
16718
16719 pub fn toggle_git_blame(
16720 &mut self,
16721 _: &::git::Blame,
16722 window: &mut Window,
16723 cx: &mut Context<Self>,
16724 ) {
16725 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16726
16727 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16728 self.start_git_blame(true, window, cx);
16729 }
16730
16731 cx.notify();
16732 }
16733
16734 pub fn toggle_git_blame_inline(
16735 &mut self,
16736 _: &ToggleGitBlameInline,
16737 window: &mut Window,
16738 cx: &mut Context<Self>,
16739 ) {
16740 self.toggle_git_blame_inline_internal(true, window, cx);
16741 cx.notify();
16742 }
16743
16744 pub fn open_git_blame_commit(
16745 &mut self,
16746 _: &OpenGitBlameCommit,
16747 window: &mut Window,
16748 cx: &mut Context<Self>,
16749 ) {
16750 self.open_git_blame_commit_internal(window, cx);
16751 }
16752
16753 fn open_git_blame_commit_internal(
16754 &mut self,
16755 window: &mut Window,
16756 cx: &mut Context<Self>,
16757 ) -> Option<()> {
16758 let blame = self.blame.as_ref()?;
16759 let snapshot = self.snapshot(window, cx);
16760 let cursor = self.selections.newest::<Point>(cx).head();
16761 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16762 let blame_entry = blame
16763 .update(cx, |blame, cx| {
16764 blame
16765 .blame_for_rows(
16766 &[RowInfo {
16767 buffer_id: Some(buffer.remote_id()),
16768 buffer_row: Some(point.row),
16769 ..Default::default()
16770 }],
16771 cx,
16772 )
16773 .next()
16774 })
16775 .flatten()?;
16776 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16777 let repo = blame.read(cx).repository(cx)?;
16778 let workspace = self.workspace()?.downgrade();
16779 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16780 None
16781 }
16782
16783 pub fn git_blame_inline_enabled(&self) -> bool {
16784 self.git_blame_inline_enabled
16785 }
16786
16787 pub fn toggle_selection_menu(
16788 &mut self,
16789 _: &ToggleSelectionMenu,
16790 _: &mut Window,
16791 cx: &mut Context<Self>,
16792 ) {
16793 self.show_selection_menu = self
16794 .show_selection_menu
16795 .map(|show_selections_menu| !show_selections_menu)
16796 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16797
16798 cx.notify();
16799 }
16800
16801 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16802 self.show_selection_menu
16803 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16804 }
16805
16806 fn start_git_blame(
16807 &mut self,
16808 user_triggered: bool,
16809 window: &mut Window,
16810 cx: &mut Context<Self>,
16811 ) {
16812 if let Some(project) = self.project.as_ref() {
16813 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16814 return;
16815 };
16816
16817 if buffer.read(cx).file().is_none() {
16818 return;
16819 }
16820
16821 let focused = self.focus_handle(cx).contains_focused(window, cx);
16822
16823 let project = project.clone();
16824 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16825 self.blame_subscription =
16826 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16827 self.blame = Some(blame);
16828 }
16829 }
16830
16831 fn toggle_git_blame_inline_internal(
16832 &mut self,
16833 user_triggered: bool,
16834 window: &mut Window,
16835 cx: &mut Context<Self>,
16836 ) {
16837 if self.git_blame_inline_enabled {
16838 self.git_blame_inline_enabled = false;
16839 self.show_git_blame_inline = false;
16840 self.show_git_blame_inline_delay_task.take();
16841 } else {
16842 self.git_blame_inline_enabled = true;
16843 self.start_git_blame_inline(user_triggered, window, cx);
16844 }
16845
16846 cx.notify();
16847 }
16848
16849 fn start_git_blame_inline(
16850 &mut self,
16851 user_triggered: bool,
16852 window: &mut Window,
16853 cx: &mut Context<Self>,
16854 ) {
16855 self.start_git_blame(user_triggered, window, cx);
16856
16857 if ProjectSettings::get_global(cx)
16858 .git
16859 .inline_blame_delay()
16860 .is_some()
16861 {
16862 self.start_inline_blame_timer(window, cx);
16863 } else {
16864 self.show_git_blame_inline = true
16865 }
16866 }
16867
16868 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16869 self.blame.as_ref()
16870 }
16871
16872 pub fn show_git_blame_gutter(&self) -> bool {
16873 self.show_git_blame_gutter
16874 }
16875
16876 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16877 self.show_git_blame_gutter && self.has_blame_entries(cx)
16878 }
16879
16880 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16881 self.show_git_blame_inline
16882 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
16883 && !self.newest_selection_head_on_empty_line(cx)
16884 && self.has_blame_entries(cx)
16885 }
16886
16887 fn has_blame_entries(&self, cx: &App) -> bool {
16888 self.blame()
16889 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16890 }
16891
16892 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16893 let cursor_anchor = self.selections.newest_anchor().head();
16894
16895 let snapshot = self.buffer.read(cx).snapshot(cx);
16896 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16897
16898 snapshot.line_len(buffer_row) == 0
16899 }
16900
16901 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16902 let buffer_and_selection = maybe!({
16903 let selection = self.selections.newest::<Point>(cx);
16904 let selection_range = selection.range();
16905
16906 let multi_buffer = self.buffer().read(cx);
16907 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16908 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16909
16910 let (buffer, range, _) = if selection.reversed {
16911 buffer_ranges.first()
16912 } else {
16913 buffer_ranges.last()
16914 }?;
16915
16916 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16917 ..text::ToPoint::to_point(&range.end, &buffer).row;
16918 Some((
16919 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16920 selection,
16921 ))
16922 });
16923
16924 let Some((buffer, selection)) = buffer_and_selection else {
16925 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16926 };
16927
16928 let Some(project) = self.project.as_ref() else {
16929 return Task::ready(Err(anyhow!("editor does not have project")));
16930 };
16931
16932 project.update(cx, |project, cx| {
16933 project.get_permalink_to_line(&buffer, selection, cx)
16934 })
16935 }
16936
16937 pub fn copy_permalink_to_line(
16938 &mut self,
16939 _: &CopyPermalinkToLine,
16940 window: &mut Window,
16941 cx: &mut Context<Self>,
16942 ) {
16943 let permalink_task = self.get_permalink_to_line(cx);
16944 let workspace = self.workspace();
16945
16946 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16947 Ok(permalink) => {
16948 cx.update(|_, cx| {
16949 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16950 })
16951 .ok();
16952 }
16953 Err(err) => {
16954 let message = format!("Failed to copy permalink: {err}");
16955
16956 Err::<(), anyhow::Error>(err).log_err();
16957
16958 if let Some(workspace) = workspace {
16959 workspace
16960 .update_in(cx, |workspace, _, cx| {
16961 struct CopyPermalinkToLine;
16962
16963 workspace.show_toast(
16964 Toast::new(
16965 NotificationId::unique::<CopyPermalinkToLine>(),
16966 message,
16967 ),
16968 cx,
16969 )
16970 })
16971 .ok();
16972 }
16973 }
16974 })
16975 .detach();
16976 }
16977
16978 pub fn copy_file_location(
16979 &mut self,
16980 _: &CopyFileLocation,
16981 _: &mut Window,
16982 cx: &mut Context<Self>,
16983 ) {
16984 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16985 if let Some(file) = self.target_file(cx) {
16986 if let Some(path) = file.path().to_str() {
16987 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16988 }
16989 }
16990 }
16991
16992 pub fn open_permalink_to_line(
16993 &mut self,
16994 _: &OpenPermalinkToLine,
16995 window: &mut Window,
16996 cx: &mut Context<Self>,
16997 ) {
16998 let permalink_task = self.get_permalink_to_line(cx);
16999 let workspace = self.workspace();
17000
17001 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17002 Ok(permalink) => {
17003 cx.update(|_, cx| {
17004 cx.open_url(permalink.as_ref());
17005 })
17006 .ok();
17007 }
17008 Err(err) => {
17009 let message = format!("Failed to open permalink: {err}");
17010
17011 Err::<(), anyhow::Error>(err).log_err();
17012
17013 if let Some(workspace) = workspace {
17014 workspace
17015 .update(cx, |workspace, cx| {
17016 struct OpenPermalinkToLine;
17017
17018 workspace.show_toast(
17019 Toast::new(
17020 NotificationId::unique::<OpenPermalinkToLine>(),
17021 message,
17022 ),
17023 cx,
17024 )
17025 })
17026 .ok();
17027 }
17028 }
17029 })
17030 .detach();
17031 }
17032
17033 pub fn insert_uuid_v4(
17034 &mut self,
17035 _: &InsertUuidV4,
17036 window: &mut Window,
17037 cx: &mut Context<Self>,
17038 ) {
17039 self.insert_uuid(UuidVersion::V4, window, cx);
17040 }
17041
17042 pub fn insert_uuid_v7(
17043 &mut self,
17044 _: &InsertUuidV7,
17045 window: &mut Window,
17046 cx: &mut Context<Self>,
17047 ) {
17048 self.insert_uuid(UuidVersion::V7, window, cx);
17049 }
17050
17051 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17052 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17053 self.transact(window, cx, |this, window, cx| {
17054 let edits = this
17055 .selections
17056 .all::<Point>(cx)
17057 .into_iter()
17058 .map(|selection| {
17059 let uuid = match version {
17060 UuidVersion::V4 => uuid::Uuid::new_v4(),
17061 UuidVersion::V7 => uuid::Uuid::now_v7(),
17062 };
17063
17064 (selection.range(), uuid.to_string())
17065 });
17066 this.edit(edits, cx);
17067 this.refresh_inline_completion(true, false, window, cx);
17068 });
17069 }
17070
17071 pub fn open_selections_in_multibuffer(
17072 &mut self,
17073 _: &OpenSelectionsInMultibuffer,
17074 window: &mut Window,
17075 cx: &mut Context<Self>,
17076 ) {
17077 let multibuffer = self.buffer.read(cx);
17078
17079 let Some(buffer) = multibuffer.as_singleton() else {
17080 return;
17081 };
17082
17083 let Some(workspace) = self.workspace() else {
17084 return;
17085 };
17086
17087 let locations = self
17088 .selections
17089 .disjoint_anchors()
17090 .iter()
17091 .map(|range| Location {
17092 buffer: buffer.clone(),
17093 range: range.start.text_anchor..range.end.text_anchor,
17094 })
17095 .collect::<Vec<_>>();
17096
17097 let title = multibuffer.title(cx).to_string();
17098
17099 cx.spawn_in(window, async move |_, cx| {
17100 workspace.update_in(cx, |workspace, window, cx| {
17101 Self::open_locations_in_multibuffer(
17102 workspace,
17103 locations,
17104 format!("Selections for '{title}'"),
17105 false,
17106 MultibufferSelectionMode::All,
17107 window,
17108 cx,
17109 );
17110 })
17111 })
17112 .detach();
17113 }
17114
17115 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17116 /// last highlight added will be used.
17117 ///
17118 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17119 pub fn highlight_rows<T: 'static>(
17120 &mut self,
17121 range: Range<Anchor>,
17122 color: Hsla,
17123 options: RowHighlightOptions,
17124 cx: &mut Context<Self>,
17125 ) {
17126 let snapshot = self.buffer().read(cx).snapshot(cx);
17127 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17128 let ix = row_highlights.binary_search_by(|highlight| {
17129 Ordering::Equal
17130 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17131 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17132 });
17133
17134 if let Err(mut ix) = ix {
17135 let index = post_inc(&mut self.highlight_order);
17136
17137 // If this range intersects with the preceding highlight, then merge it with
17138 // the preceding highlight. Otherwise insert a new highlight.
17139 let mut merged = false;
17140 if ix > 0 {
17141 let prev_highlight = &mut row_highlights[ix - 1];
17142 if prev_highlight
17143 .range
17144 .end
17145 .cmp(&range.start, &snapshot)
17146 .is_ge()
17147 {
17148 ix -= 1;
17149 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17150 prev_highlight.range.end = range.end;
17151 }
17152 merged = true;
17153 prev_highlight.index = index;
17154 prev_highlight.color = color;
17155 prev_highlight.options = options;
17156 }
17157 }
17158
17159 if !merged {
17160 row_highlights.insert(
17161 ix,
17162 RowHighlight {
17163 range: range.clone(),
17164 index,
17165 color,
17166 options,
17167 type_id: TypeId::of::<T>(),
17168 },
17169 );
17170 }
17171
17172 // If any of the following highlights intersect with this one, merge them.
17173 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17174 let highlight = &row_highlights[ix];
17175 if next_highlight
17176 .range
17177 .start
17178 .cmp(&highlight.range.end, &snapshot)
17179 .is_le()
17180 {
17181 if next_highlight
17182 .range
17183 .end
17184 .cmp(&highlight.range.end, &snapshot)
17185 .is_gt()
17186 {
17187 row_highlights[ix].range.end = next_highlight.range.end;
17188 }
17189 row_highlights.remove(ix + 1);
17190 } else {
17191 break;
17192 }
17193 }
17194 }
17195 }
17196
17197 /// Remove any highlighted row ranges of the given type that intersect the
17198 /// given ranges.
17199 pub fn remove_highlighted_rows<T: 'static>(
17200 &mut self,
17201 ranges_to_remove: Vec<Range<Anchor>>,
17202 cx: &mut Context<Self>,
17203 ) {
17204 let snapshot = self.buffer().read(cx).snapshot(cx);
17205 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17206 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17207 row_highlights.retain(|highlight| {
17208 while let Some(range_to_remove) = ranges_to_remove.peek() {
17209 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17210 Ordering::Less | Ordering::Equal => {
17211 ranges_to_remove.next();
17212 }
17213 Ordering::Greater => {
17214 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17215 Ordering::Less | Ordering::Equal => {
17216 return false;
17217 }
17218 Ordering::Greater => break,
17219 }
17220 }
17221 }
17222 }
17223
17224 true
17225 })
17226 }
17227
17228 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17229 pub fn clear_row_highlights<T: 'static>(&mut self) {
17230 self.highlighted_rows.remove(&TypeId::of::<T>());
17231 }
17232
17233 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17234 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17235 self.highlighted_rows
17236 .get(&TypeId::of::<T>())
17237 .map_or(&[] as &[_], |vec| vec.as_slice())
17238 .iter()
17239 .map(|highlight| (highlight.range.clone(), highlight.color))
17240 }
17241
17242 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17243 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17244 /// Allows to ignore certain kinds of highlights.
17245 pub fn highlighted_display_rows(
17246 &self,
17247 window: &mut Window,
17248 cx: &mut App,
17249 ) -> BTreeMap<DisplayRow, LineHighlight> {
17250 let snapshot = self.snapshot(window, cx);
17251 let mut used_highlight_orders = HashMap::default();
17252 self.highlighted_rows
17253 .iter()
17254 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17255 .fold(
17256 BTreeMap::<DisplayRow, LineHighlight>::new(),
17257 |mut unique_rows, highlight| {
17258 let start = highlight.range.start.to_display_point(&snapshot);
17259 let end = highlight.range.end.to_display_point(&snapshot);
17260 let start_row = start.row().0;
17261 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17262 && end.column() == 0
17263 {
17264 end.row().0.saturating_sub(1)
17265 } else {
17266 end.row().0
17267 };
17268 for row in start_row..=end_row {
17269 let used_index =
17270 used_highlight_orders.entry(row).or_insert(highlight.index);
17271 if highlight.index >= *used_index {
17272 *used_index = highlight.index;
17273 unique_rows.insert(
17274 DisplayRow(row),
17275 LineHighlight {
17276 include_gutter: highlight.options.include_gutter,
17277 border: None,
17278 background: highlight.color.into(),
17279 type_id: Some(highlight.type_id),
17280 },
17281 );
17282 }
17283 }
17284 unique_rows
17285 },
17286 )
17287 }
17288
17289 pub fn highlighted_display_row_for_autoscroll(
17290 &self,
17291 snapshot: &DisplaySnapshot,
17292 ) -> Option<DisplayRow> {
17293 self.highlighted_rows
17294 .values()
17295 .flat_map(|highlighted_rows| highlighted_rows.iter())
17296 .filter_map(|highlight| {
17297 if highlight.options.autoscroll {
17298 Some(highlight.range.start.to_display_point(snapshot).row())
17299 } else {
17300 None
17301 }
17302 })
17303 .min()
17304 }
17305
17306 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17307 self.highlight_background::<SearchWithinRange>(
17308 ranges,
17309 |colors| colors.editor_document_highlight_read_background,
17310 cx,
17311 )
17312 }
17313
17314 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17315 self.breadcrumb_header = Some(new_header);
17316 }
17317
17318 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17319 self.clear_background_highlights::<SearchWithinRange>(cx);
17320 }
17321
17322 pub fn highlight_background<T: 'static>(
17323 &mut self,
17324 ranges: &[Range<Anchor>],
17325 color_fetcher: fn(&ThemeColors) -> Hsla,
17326 cx: &mut Context<Self>,
17327 ) {
17328 self.background_highlights
17329 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17330 self.scrollbar_marker_state.dirty = true;
17331 cx.notify();
17332 }
17333
17334 pub fn clear_background_highlights<T: 'static>(
17335 &mut self,
17336 cx: &mut Context<Self>,
17337 ) -> Option<BackgroundHighlight> {
17338 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17339 if !text_highlights.1.is_empty() {
17340 self.scrollbar_marker_state.dirty = true;
17341 cx.notify();
17342 }
17343 Some(text_highlights)
17344 }
17345
17346 pub fn highlight_gutter<T: 'static>(
17347 &mut self,
17348 ranges: &[Range<Anchor>],
17349 color_fetcher: fn(&App) -> Hsla,
17350 cx: &mut Context<Self>,
17351 ) {
17352 self.gutter_highlights
17353 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17354 cx.notify();
17355 }
17356
17357 pub fn clear_gutter_highlights<T: 'static>(
17358 &mut self,
17359 cx: &mut Context<Self>,
17360 ) -> Option<GutterHighlight> {
17361 cx.notify();
17362 self.gutter_highlights.remove(&TypeId::of::<T>())
17363 }
17364
17365 #[cfg(feature = "test-support")]
17366 pub fn all_text_background_highlights(
17367 &self,
17368 window: &mut Window,
17369 cx: &mut Context<Self>,
17370 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17371 let snapshot = self.snapshot(window, cx);
17372 let buffer = &snapshot.buffer_snapshot;
17373 let start = buffer.anchor_before(0);
17374 let end = buffer.anchor_after(buffer.len());
17375 let theme = cx.theme().colors();
17376 self.background_highlights_in_range(start..end, &snapshot, theme)
17377 }
17378
17379 #[cfg(feature = "test-support")]
17380 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17381 let snapshot = self.buffer().read(cx).snapshot(cx);
17382
17383 let highlights = self
17384 .background_highlights
17385 .get(&TypeId::of::<items::BufferSearchHighlights>());
17386
17387 if let Some((_color, ranges)) = highlights {
17388 ranges
17389 .iter()
17390 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17391 .collect_vec()
17392 } else {
17393 vec![]
17394 }
17395 }
17396
17397 fn document_highlights_for_position<'a>(
17398 &'a self,
17399 position: Anchor,
17400 buffer: &'a MultiBufferSnapshot,
17401 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17402 let read_highlights = self
17403 .background_highlights
17404 .get(&TypeId::of::<DocumentHighlightRead>())
17405 .map(|h| &h.1);
17406 let write_highlights = self
17407 .background_highlights
17408 .get(&TypeId::of::<DocumentHighlightWrite>())
17409 .map(|h| &h.1);
17410 let left_position = position.bias_left(buffer);
17411 let right_position = position.bias_right(buffer);
17412 read_highlights
17413 .into_iter()
17414 .chain(write_highlights)
17415 .flat_map(move |ranges| {
17416 let start_ix = match ranges.binary_search_by(|probe| {
17417 let cmp = probe.end.cmp(&left_position, buffer);
17418 if cmp.is_ge() {
17419 Ordering::Greater
17420 } else {
17421 Ordering::Less
17422 }
17423 }) {
17424 Ok(i) | Err(i) => i,
17425 };
17426
17427 ranges[start_ix..]
17428 .iter()
17429 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17430 })
17431 }
17432
17433 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17434 self.background_highlights
17435 .get(&TypeId::of::<T>())
17436 .map_or(false, |(_, highlights)| !highlights.is_empty())
17437 }
17438
17439 pub fn background_highlights_in_range(
17440 &self,
17441 search_range: Range<Anchor>,
17442 display_snapshot: &DisplaySnapshot,
17443 theme: &ThemeColors,
17444 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17445 let mut results = Vec::new();
17446 for (color_fetcher, ranges) in self.background_highlights.values() {
17447 let color = color_fetcher(theme);
17448 let start_ix = match ranges.binary_search_by(|probe| {
17449 let cmp = probe
17450 .end
17451 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17452 if cmp.is_gt() {
17453 Ordering::Greater
17454 } else {
17455 Ordering::Less
17456 }
17457 }) {
17458 Ok(i) | Err(i) => i,
17459 };
17460 for range in &ranges[start_ix..] {
17461 if range
17462 .start
17463 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17464 .is_ge()
17465 {
17466 break;
17467 }
17468
17469 let start = range.start.to_display_point(display_snapshot);
17470 let end = range.end.to_display_point(display_snapshot);
17471 results.push((start..end, color))
17472 }
17473 }
17474 results
17475 }
17476
17477 pub fn background_highlight_row_ranges<T: 'static>(
17478 &self,
17479 search_range: Range<Anchor>,
17480 display_snapshot: &DisplaySnapshot,
17481 count: usize,
17482 ) -> Vec<RangeInclusive<DisplayPoint>> {
17483 let mut results = Vec::new();
17484 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17485 return vec![];
17486 };
17487
17488 let start_ix = match ranges.binary_search_by(|probe| {
17489 let cmp = probe
17490 .end
17491 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17492 if cmp.is_gt() {
17493 Ordering::Greater
17494 } else {
17495 Ordering::Less
17496 }
17497 }) {
17498 Ok(i) | Err(i) => i,
17499 };
17500 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17501 if let (Some(start_display), Some(end_display)) = (start, end) {
17502 results.push(
17503 start_display.to_display_point(display_snapshot)
17504 ..=end_display.to_display_point(display_snapshot),
17505 );
17506 }
17507 };
17508 let mut start_row: Option<Point> = None;
17509 let mut end_row: Option<Point> = None;
17510 if ranges.len() > count {
17511 return Vec::new();
17512 }
17513 for range in &ranges[start_ix..] {
17514 if range
17515 .start
17516 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17517 .is_ge()
17518 {
17519 break;
17520 }
17521 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17522 if let Some(current_row) = &end_row {
17523 if end.row == current_row.row {
17524 continue;
17525 }
17526 }
17527 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17528 if start_row.is_none() {
17529 assert_eq!(end_row, None);
17530 start_row = Some(start);
17531 end_row = Some(end);
17532 continue;
17533 }
17534 if let Some(current_end) = end_row.as_mut() {
17535 if start.row > current_end.row + 1 {
17536 push_region(start_row, end_row);
17537 start_row = Some(start);
17538 end_row = Some(end);
17539 } else {
17540 // Merge two hunks.
17541 *current_end = end;
17542 }
17543 } else {
17544 unreachable!();
17545 }
17546 }
17547 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17548 push_region(start_row, end_row);
17549 results
17550 }
17551
17552 pub fn gutter_highlights_in_range(
17553 &self,
17554 search_range: Range<Anchor>,
17555 display_snapshot: &DisplaySnapshot,
17556 cx: &App,
17557 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17558 let mut results = Vec::new();
17559 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17560 let color = color_fetcher(cx);
17561 let start_ix = match ranges.binary_search_by(|probe| {
17562 let cmp = probe
17563 .end
17564 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17565 if cmp.is_gt() {
17566 Ordering::Greater
17567 } else {
17568 Ordering::Less
17569 }
17570 }) {
17571 Ok(i) | Err(i) => i,
17572 };
17573 for range in &ranges[start_ix..] {
17574 if range
17575 .start
17576 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17577 .is_ge()
17578 {
17579 break;
17580 }
17581
17582 let start = range.start.to_display_point(display_snapshot);
17583 let end = range.end.to_display_point(display_snapshot);
17584 results.push((start..end, color))
17585 }
17586 }
17587 results
17588 }
17589
17590 /// Get the text ranges corresponding to the redaction query
17591 pub fn redacted_ranges(
17592 &self,
17593 search_range: Range<Anchor>,
17594 display_snapshot: &DisplaySnapshot,
17595 cx: &App,
17596 ) -> Vec<Range<DisplayPoint>> {
17597 display_snapshot
17598 .buffer_snapshot
17599 .redacted_ranges(search_range, |file| {
17600 if let Some(file) = file {
17601 file.is_private()
17602 && EditorSettings::get(
17603 Some(SettingsLocation {
17604 worktree_id: file.worktree_id(cx),
17605 path: file.path().as_ref(),
17606 }),
17607 cx,
17608 )
17609 .redact_private_values
17610 } else {
17611 false
17612 }
17613 })
17614 .map(|range| {
17615 range.start.to_display_point(display_snapshot)
17616 ..range.end.to_display_point(display_snapshot)
17617 })
17618 .collect()
17619 }
17620
17621 pub fn highlight_text<T: 'static>(
17622 &mut self,
17623 ranges: Vec<Range<Anchor>>,
17624 style: HighlightStyle,
17625 cx: &mut Context<Self>,
17626 ) {
17627 self.display_map.update(cx, |map, _| {
17628 map.highlight_text(TypeId::of::<T>(), ranges, style)
17629 });
17630 cx.notify();
17631 }
17632
17633 pub(crate) fn highlight_inlays<T: 'static>(
17634 &mut self,
17635 highlights: Vec<InlayHighlight>,
17636 style: HighlightStyle,
17637 cx: &mut Context<Self>,
17638 ) {
17639 self.display_map.update(cx, |map, _| {
17640 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17641 });
17642 cx.notify();
17643 }
17644
17645 pub fn text_highlights<'a, T: 'static>(
17646 &'a self,
17647 cx: &'a App,
17648 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17649 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17650 }
17651
17652 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17653 let cleared = self
17654 .display_map
17655 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17656 if cleared {
17657 cx.notify();
17658 }
17659 }
17660
17661 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17662 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17663 && self.focus_handle.is_focused(window)
17664 }
17665
17666 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17667 self.show_cursor_when_unfocused = is_enabled;
17668 cx.notify();
17669 }
17670
17671 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17672 cx.notify();
17673 }
17674
17675 fn on_debug_session_event(
17676 &mut self,
17677 _session: Entity<Session>,
17678 event: &SessionEvent,
17679 cx: &mut Context<Self>,
17680 ) {
17681 match event {
17682 SessionEvent::InvalidateInlineValue => {
17683 self.refresh_inline_values(cx);
17684 }
17685 _ => {}
17686 }
17687 }
17688
17689 fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
17690 let Some(project) = self.project.clone() else {
17691 return;
17692 };
17693 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
17694 return;
17695 };
17696 if !self.inline_value_cache.enabled {
17697 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
17698 self.splice_inlays(&inlays, Vec::new(), cx);
17699 return;
17700 }
17701
17702 let current_execution_position = self
17703 .highlighted_rows
17704 .get(&TypeId::of::<ActiveDebugLine>())
17705 .and_then(|lines| lines.last().map(|line| line.range.start));
17706
17707 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
17708 let snapshot = editor
17709 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17710 .ok()?;
17711
17712 let inline_values = editor
17713 .update(cx, |_, cx| {
17714 let Some(current_execution_position) = current_execution_position else {
17715 return Some(Task::ready(Ok(Vec::new())));
17716 };
17717
17718 // todo(debugger) when introducing multi buffer inline values check execution position's buffer id to make sure the text
17719 // anchor is in the same buffer
17720 let range =
17721 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
17722 project.inline_values(buffer, range, cx)
17723 })
17724 .ok()
17725 .flatten()?
17726 .await
17727 .context("refreshing debugger inlays")
17728 .log_err()?;
17729
17730 let (excerpt_id, buffer_id) = snapshot
17731 .excerpts()
17732 .next()
17733 .map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?;
17734 editor
17735 .update(cx, |editor, cx| {
17736 let new_inlays = inline_values
17737 .into_iter()
17738 .map(|debugger_value| {
17739 Inlay::debugger_hint(
17740 post_inc(&mut editor.next_inlay_id),
17741 Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position),
17742 debugger_value.text(),
17743 )
17744 })
17745 .collect::<Vec<_>>();
17746 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
17747 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
17748
17749 editor.splice_inlays(&inlay_ids, new_inlays, cx);
17750 })
17751 .ok()?;
17752 Some(())
17753 });
17754 }
17755
17756 fn on_buffer_event(
17757 &mut self,
17758 multibuffer: &Entity<MultiBuffer>,
17759 event: &multi_buffer::Event,
17760 window: &mut Window,
17761 cx: &mut Context<Self>,
17762 ) {
17763 match event {
17764 multi_buffer::Event::Edited {
17765 singleton_buffer_edited,
17766 edited_buffer: buffer_edited,
17767 } => {
17768 self.scrollbar_marker_state.dirty = true;
17769 self.active_indent_guides_state.dirty = true;
17770 self.refresh_active_diagnostics(cx);
17771 self.refresh_code_actions(window, cx);
17772 self.refresh_selected_text_highlights(true, window, cx);
17773 refresh_matching_bracket_highlights(self, window, cx);
17774 if self.has_active_inline_completion() {
17775 self.update_visible_inline_completion(window, cx);
17776 }
17777 if let Some(buffer) = buffer_edited {
17778 let buffer_id = buffer.read(cx).remote_id();
17779 if !self.registered_buffers.contains_key(&buffer_id) {
17780 if let Some(project) = self.project.as_ref() {
17781 project.update(cx, |project, cx| {
17782 self.registered_buffers.insert(
17783 buffer_id,
17784 project.register_buffer_with_language_servers(&buffer, cx),
17785 );
17786 })
17787 }
17788 }
17789 }
17790 cx.emit(EditorEvent::BufferEdited);
17791 cx.emit(SearchEvent::MatchesInvalidated);
17792 if *singleton_buffer_edited {
17793 if let Some(project) = &self.project {
17794 #[allow(clippy::mutable_key_type)]
17795 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17796 multibuffer
17797 .all_buffers()
17798 .into_iter()
17799 .filter_map(|buffer| {
17800 buffer.update(cx, |buffer, cx| {
17801 let language = buffer.language()?;
17802 let should_discard = project.update(cx, |project, cx| {
17803 project.is_local()
17804 && !project.has_language_servers_for(buffer, cx)
17805 });
17806 should_discard.not().then_some(language.clone())
17807 })
17808 })
17809 .collect::<HashSet<_>>()
17810 });
17811 if !languages_affected.is_empty() {
17812 self.refresh_inlay_hints(
17813 InlayHintRefreshReason::BufferEdited(languages_affected),
17814 cx,
17815 );
17816 }
17817 }
17818 }
17819
17820 let Some(project) = &self.project else { return };
17821 let (telemetry, is_via_ssh) = {
17822 let project = project.read(cx);
17823 let telemetry = project.client().telemetry().clone();
17824 let is_via_ssh = project.is_via_ssh();
17825 (telemetry, is_via_ssh)
17826 };
17827 refresh_linked_ranges(self, window, cx);
17828 telemetry.log_edit_event("editor", is_via_ssh);
17829 }
17830 multi_buffer::Event::ExcerptsAdded {
17831 buffer,
17832 predecessor,
17833 excerpts,
17834 } => {
17835 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17836 let buffer_id = buffer.read(cx).remote_id();
17837 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17838 if let Some(project) = &self.project {
17839 update_uncommitted_diff_for_buffer(
17840 cx.entity(),
17841 project,
17842 [buffer.clone()],
17843 self.buffer.clone(),
17844 cx,
17845 )
17846 .detach();
17847 }
17848 }
17849 cx.emit(EditorEvent::ExcerptsAdded {
17850 buffer: buffer.clone(),
17851 predecessor: *predecessor,
17852 excerpts: excerpts.clone(),
17853 });
17854 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17855 }
17856 multi_buffer::Event::ExcerptsRemoved {
17857 ids,
17858 removed_buffer_ids,
17859 } => {
17860 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17861 let buffer = self.buffer.read(cx);
17862 self.registered_buffers
17863 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17864 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17865 cx.emit(EditorEvent::ExcerptsRemoved {
17866 ids: ids.clone(),
17867 removed_buffer_ids: removed_buffer_ids.clone(),
17868 })
17869 }
17870 multi_buffer::Event::ExcerptsEdited {
17871 excerpt_ids,
17872 buffer_ids,
17873 } => {
17874 self.display_map.update(cx, |map, cx| {
17875 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17876 });
17877 cx.emit(EditorEvent::ExcerptsEdited {
17878 ids: excerpt_ids.clone(),
17879 })
17880 }
17881 multi_buffer::Event::ExcerptsExpanded { ids } => {
17882 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17883 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17884 }
17885 multi_buffer::Event::Reparsed(buffer_id) => {
17886 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17887 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17888
17889 cx.emit(EditorEvent::Reparsed(*buffer_id));
17890 }
17891 multi_buffer::Event::DiffHunksToggled => {
17892 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17893 }
17894 multi_buffer::Event::LanguageChanged(buffer_id) => {
17895 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17896 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17897 cx.emit(EditorEvent::Reparsed(*buffer_id));
17898 cx.notify();
17899 }
17900 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17901 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17902 multi_buffer::Event::FileHandleChanged
17903 | multi_buffer::Event::Reloaded
17904 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17905 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17906 multi_buffer::Event::DiagnosticsUpdated => {
17907 self.refresh_active_diagnostics(cx);
17908 self.refresh_inline_diagnostics(true, window, cx);
17909 self.scrollbar_marker_state.dirty = true;
17910 cx.notify();
17911 }
17912 _ => {}
17913 };
17914 }
17915
17916 pub fn start_temporary_diff_override(&mut self) {
17917 self.load_diff_task.take();
17918 self.temporary_diff_override = true;
17919 }
17920
17921 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
17922 self.temporary_diff_override = false;
17923 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
17924 self.buffer.update(cx, |buffer, cx| {
17925 buffer.set_all_diff_hunks_collapsed(cx);
17926 });
17927
17928 if let Some(project) = self.project.clone() {
17929 self.load_diff_task = Some(
17930 update_uncommitted_diff_for_buffer(
17931 cx.entity(),
17932 &project,
17933 self.buffer.read(cx).all_buffers(),
17934 self.buffer.clone(),
17935 cx,
17936 )
17937 .shared(),
17938 );
17939 }
17940 }
17941
17942 fn on_display_map_changed(
17943 &mut self,
17944 _: Entity<DisplayMap>,
17945 _: &mut Window,
17946 cx: &mut Context<Self>,
17947 ) {
17948 cx.notify();
17949 }
17950
17951 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17952 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17953 self.update_edit_prediction_settings(cx);
17954 self.refresh_inline_completion(true, false, window, cx);
17955 self.refresh_inlay_hints(
17956 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17957 self.selections.newest_anchor().head(),
17958 &self.buffer.read(cx).snapshot(cx),
17959 cx,
17960 )),
17961 cx,
17962 );
17963
17964 let old_cursor_shape = self.cursor_shape;
17965
17966 {
17967 let editor_settings = EditorSettings::get_global(cx);
17968 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17969 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17970 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17971 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17972 }
17973
17974 if old_cursor_shape != self.cursor_shape {
17975 cx.emit(EditorEvent::CursorShapeChanged);
17976 }
17977
17978 let project_settings = ProjectSettings::get_global(cx);
17979 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17980
17981 if self.mode.is_full() {
17982 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17983 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17984 if self.show_inline_diagnostics != show_inline_diagnostics {
17985 self.show_inline_diagnostics = show_inline_diagnostics;
17986 self.refresh_inline_diagnostics(false, window, cx);
17987 }
17988
17989 if self.git_blame_inline_enabled != inline_blame_enabled {
17990 self.toggle_git_blame_inline_internal(false, window, cx);
17991 }
17992 }
17993
17994 cx.notify();
17995 }
17996
17997 pub fn set_searchable(&mut self, searchable: bool) {
17998 self.searchable = searchable;
17999 }
18000
18001 pub fn searchable(&self) -> bool {
18002 self.searchable
18003 }
18004
18005 fn open_proposed_changes_editor(
18006 &mut self,
18007 _: &OpenProposedChangesEditor,
18008 window: &mut Window,
18009 cx: &mut Context<Self>,
18010 ) {
18011 let Some(workspace) = self.workspace() else {
18012 cx.propagate();
18013 return;
18014 };
18015
18016 let selections = self.selections.all::<usize>(cx);
18017 let multi_buffer = self.buffer.read(cx);
18018 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18019 let mut new_selections_by_buffer = HashMap::default();
18020 for selection in selections {
18021 for (buffer, range, _) in
18022 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18023 {
18024 let mut range = range.to_point(buffer);
18025 range.start.column = 0;
18026 range.end.column = buffer.line_len(range.end.row);
18027 new_selections_by_buffer
18028 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18029 .or_insert(Vec::new())
18030 .push(range)
18031 }
18032 }
18033
18034 let proposed_changes_buffers = new_selections_by_buffer
18035 .into_iter()
18036 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18037 .collect::<Vec<_>>();
18038 let proposed_changes_editor = cx.new(|cx| {
18039 ProposedChangesEditor::new(
18040 "Proposed changes",
18041 proposed_changes_buffers,
18042 self.project.clone(),
18043 window,
18044 cx,
18045 )
18046 });
18047
18048 window.defer(cx, move |window, cx| {
18049 workspace.update(cx, |workspace, cx| {
18050 workspace.active_pane().update(cx, |pane, cx| {
18051 pane.add_item(
18052 Box::new(proposed_changes_editor),
18053 true,
18054 true,
18055 None,
18056 window,
18057 cx,
18058 );
18059 });
18060 });
18061 });
18062 }
18063
18064 pub fn open_excerpts_in_split(
18065 &mut self,
18066 _: &OpenExcerptsSplit,
18067 window: &mut Window,
18068 cx: &mut Context<Self>,
18069 ) {
18070 self.open_excerpts_common(None, true, window, cx)
18071 }
18072
18073 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18074 self.open_excerpts_common(None, false, window, cx)
18075 }
18076
18077 fn open_excerpts_common(
18078 &mut self,
18079 jump_data: Option<JumpData>,
18080 split: bool,
18081 window: &mut Window,
18082 cx: &mut Context<Self>,
18083 ) {
18084 let Some(workspace) = self.workspace() else {
18085 cx.propagate();
18086 return;
18087 };
18088
18089 if self.buffer.read(cx).is_singleton() {
18090 cx.propagate();
18091 return;
18092 }
18093
18094 let mut new_selections_by_buffer = HashMap::default();
18095 match &jump_data {
18096 Some(JumpData::MultiBufferPoint {
18097 excerpt_id,
18098 position,
18099 anchor,
18100 line_offset_from_top,
18101 }) => {
18102 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18103 if let Some(buffer) = multi_buffer_snapshot
18104 .buffer_id_for_excerpt(*excerpt_id)
18105 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18106 {
18107 let buffer_snapshot = buffer.read(cx).snapshot();
18108 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18109 language::ToPoint::to_point(anchor, &buffer_snapshot)
18110 } else {
18111 buffer_snapshot.clip_point(*position, Bias::Left)
18112 };
18113 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18114 new_selections_by_buffer.insert(
18115 buffer,
18116 (
18117 vec![jump_to_offset..jump_to_offset],
18118 Some(*line_offset_from_top),
18119 ),
18120 );
18121 }
18122 }
18123 Some(JumpData::MultiBufferRow {
18124 row,
18125 line_offset_from_top,
18126 }) => {
18127 let point = MultiBufferPoint::new(row.0, 0);
18128 if let Some((buffer, buffer_point, _)) =
18129 self.buffer.read(cx).point_to_buffer_point(point, cx)
18130 {
18131 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18132 new_selections_by_buffer
18133 .entry(buffer)
18134 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18135 .0
18136 .push(buffer_offset..buffer_offset)
18137 }
18138 }
18139 None => {
18140 let selections = self.selections.all::<usize>(cx);
18141 let multi_buffer = self.buffer.read(cx);
18142 for selection in selections {
18143 for (snapshot, range, _, anchor) in multi_buffer
18144 .snapshot(cx)
18145 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18146 {
18147 if let Some(anchor) = anchor {
18148 // selection is in a deleted hunk
18149 let Some(buffer_id) = anchor.buffer_id else {
18150 continue;
18151 };
18152 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18153 continue;
18154 };
18155 let offset = text::ToOffset::to_offset(
18156 &anchor.text_anchor,
18157 &buffer_handle.read(cx).snapshot(),
18158 );
18159 let range = offset..offset;
18160 new_selections_by_buffer
18161 .entry(buffer_handle)
18162 .or_insert((Vec::new(), None))
18163 .0
18164 .push(range)
18165 } else {
18166 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18167 else {
18168 continue;
18169 };
18170 new_selections_by_buffer
18171 .entry(buffer_handle)
18172 .or_insert((Vec::new(), None))
18173 .0
18174 .push(range)
18175 }
18176 }
18177 }
18178 }
18179 }
18180
18181 new_selections_by_buffer
18182 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18183
18184 if new_selections_by_buffer.is_empty() {
18185 return;
18186 }
18187
18188 // We defer the pane interaction because we ourselves are a workspace item
18189 // and activating a new item causes the pane to call a method on us reentrantly,
18190 // which panics if we're on the stack.
18191 window.defer(cx, move |window, cx| {
18192 workspace.update(cx, |workspace, cx| {
18193 let pane = if split {
18194 workspace.adjacent_pane(window, cx)
18195 } else {
18196 workspace.active_pane().clone()
18197 };
18198
18199 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18200 let editor = buffer
18201 .read(cx)
18202 .file()
18203 .is_none()
18204 .then(|| {
18205 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18206 // so `workspace.open_project_item` will never find them, always opening a new editor.
18207 // Instead, we try to activate the existing editor in the pane first.
18208 let (editor, pane_item_index) =
18209 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18210 let editor = item.downcast::<Editor>()?;
18211 let singleton_buffer =
18212 editor.read(cx).buffer().read(cx).as_singleton()?;
18213 if singleton_buffer == buffer {
18214 Some((editor, i))
18215 } else {
18216 None
18217 }
18218 })?;
18219 pane.update(cx, |pane, cx| {
18220 pane.activate_item(pane_item_index, true, true, window, cx)
18221 });
18222 Some(editor)
18223 })
18224 .flatten()
18225 .unwrap_or_else(|| {
18226 workspace.open_project_item::<Self>(
18227 pane.clone(),
18228 buffer,
18229 true,
18230 true,
18231 window,
18232 cx,
18233 )
18234 });
18235
18236 editor.update(cx, |editor, cx| {
18237 let autoscroll = match scroll_offset {
18238 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18239 None => Autoscroll::newest(),
18240 };
18241 let nav_history = editor.nav_history.take();
18242 editor.change_selections(Some(autoscroll), window, cx, |s| {
18243 s.select_ranges(ranges);
18244 });
18245 editor.nav_history = nav_history;
18246 });
18247 }
18248 })
18249 });
18250 }
18251
18252 // For now, don't allow opening excerpts in buffers that aren't backed by
18253 // regular project files.
18254 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18255 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18256 }
18257
18258 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18259 let snapshot = self.buffer.read(cx).read(cx);
18260 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18261 Some(
18262 ranges
18263 .iter()
18264 .map(move |range| {
18265 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18266 })
18267 .collect(),
18268 )
18269 }
18270
18271 fn selection_replacement_ranges(
18272 &self,
18273 range: Range<OffsetUtf16>,
18274 cx: &mut App,
18275 ) -> Vec<Range<OffsetUtf16>> {
18276 let selections = self.selections.all::<OffsetUtf16>(cx);
18277 let newest_selection = selections
18278 .iter()
18279 .max_by_key(|selection| selection.id)
18280 .unwrap();
18281 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18282 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18283 let snapshot = self.buffer.read(cx).read(cx);
18284 selections
18285 .into_iter()
18286 .map(|mut selection| {
18287 selection.start.0 =
18288 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18289 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18290 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18291 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18292 })
18293 .collect()
18294 }
18295
18296 fn report_editor_event(
18297 &self,
18298 event_type: &'static str,
18299 file_extension: Option<String>,
18300 cx: &App,
18301 ) {
18302 if cfg!(any(test, feature = "test-support")) {
18303 return;
18304 }
18305
18306 let Some(project) = &self.project else { return };
18307
18308 // If None, we are in a file without an extension
18309 let file = self
18310 .buffer
18311 .read(cx)
18312 .as_singleton()
18313 .and_then(|b| b.read(cx).file());
18314 let file_extension = file_extension.or(file
18315 .as_ref()
18316 .and_then(|file| Path::new(file.file_name(cx)).extension())
18317 .and_then(|e| e.to_str())
18318 .map(|a| a.to_string()));
18319
18320 let vim_mode = vim_enabled(cx);
18321
18322 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18323 let copilot_enabled = edit_predictions_provider
18324 == language::language_settings::EditPredictionProvider::Copilot;
18325 let copilot_enabled_for_language = self
18326 .buffer
18327 .read(cx)
18328 .language_settings(cx)
18329 .show_edit_predictions;
18330
18331 let project = project.read(cx);
18332 telemetry::event!(
18333 event_type,
18334 file_extension,
18335 vim_mode,
18336 copilot_enabled,
18337 copilot_enabled_for_language,
18338 edit_predictions_provider,
18339 is_via_ssh = project.is_via_ssh(),
18340 );
18341 }
18342
18343 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18344 /// with each line being an array of {text, highlight} objects.
18345 fn copy_highlight_json(
18346 &mut self,
18347 _: &CopyHighlightJson,
18348 window: &mut Window,
18349 cx: &mut Context<Self>,
18350 ) {
18351 #[derive(Serialize)]
18352 struct Chunk<'a> {
18353 text: String,
18354 highlight: Option<&'a str>,
18355 }
18356
18357 let snapshot = self.buffer.read(cx).snapshot(cx);
18358 let range = self
18359 .selected_text_range(false, window, cx)
18360 .and_then(|selection| {
18361 if selection.range.is_empty() {
18362 None
18363 } else {
18364 Some(selection.range)
18365 }
18366 })
18367 .unwrap_or_else(|| 0..snapshot.len());
18368
18369 let chunks = snapshot.chunks(range, true);
18370 let mut lines = Vec::new();
18371 let mut line: VecDeque<Chunk> = VecDeque::new();
18372
18373 let Some(style) = self.style.as_ref() else {
18374 return;
18375 };
18376
18377 for chunk in chunks {
18378 let highlight = chunk
18379 .syntax_highlight_id
18380 .and_then(|id| id.name(&style.syntax));
18381 let mut chunk_lines = chunk.text.split('\n').peekable();
18382 while let Some(text) = chunk_lines.next() {
18383 let mut merged_with_last_token = false;
18384 if let Some(last_token) = line.back_mut() {
18385 if last_token.highlight == highlight {
18386 last_token.text.push_str(text);
18387 merged_with_last_token = true;
18388 }
18389 }
18390
18391 if !merged_with_last_token {
18392 line.push_back(Chunk {
18393 text: text.into(),
18394 highlight,
18395 });
18396 }
18397
18398 if chunk_lines.peek().is_some() {
18399 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18400 line.pop_front();
18401 }
18402 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18403 line.pop_back();
18404 }
18405
18406 lines.push(mem::take(&mut line));
18407 }
18408 }
18409 }
18410
18411 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18412 return;
18413 };
18414 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18415 }
18416
18417 pub fn open_context_menu(
18418 &mut self,
18419 _: &OpenContextMenu,
18420 window: &mut Window,
18421 cx: &mut Context<Self>,
18422 ) {
18423 self.request_autoscroll(Autoscroll::newest(), cx);
18424 let position = self.selections.newest_display(cx).start;
18425 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18426 }
18427
18428 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18429 &self.inlay_hint_cache
18430 }
18431
18432 pub fn replay_insert_event(
18433 &mut self,
18434 text: &str,
18435 relative_utf16_range: Option<Range<isize>>,
18436 window: &mut Window,
18437 cx: &mut Context<Self>,
18438 ) {
18439 if !self.input_enabled {
18440 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18441 return;
18442 }
18443 if let Some(relative_utf16_range) = relative_utf16_range {
18444 let selections = self.selections.all::<OffsetUtf16>(cx);
18445 self.change_selections(None, window, cx, |s| {
18446 let new_ranges = selections.into_iter().map(|range| {
18447 let start = OffsetUtf16(
18448 range
18449 .head()
18450 .0
18451 .saturating_add_signed(relative_utf16_range.start),
18452 );
18453 let end = OffsetUtf16(
18454 range
18455 .head()
18456 .0
18457 .saturating_add_signed(relative_utf16_range.end),
18458 );
18459 start..end
18460 });
18461 s.select_ranges(new_ranges);
18462 });
18463 }
18464
18465 self.handle_input(text, window, cx);
18466 }
18467
18468 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18469 let Some(provider) = self.semantics_provider.as_ref() else {
18470 return false;
18471 };
18472
18473 let mut supports = false;
18474 self.buffer().update(cx, |this, cx| {
18475 this.for_each_buffer(|buffer| {
18476 supports |= provider.supports_inlay_hints(buffer, cx);
18477 });
18478 });
18479
18480 supports
18481 }
18482
18483 pub fn is_focused(&self, window: &Window) -> bool {
18484 self.focus_handle.is_focused(window)
18485 }
18486
18487 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18488 cx.emit(EditorEvent::Focused);
18489
18490 if let Some(descendant) = self
18491 .last_focused_descendant
18492 .take()
18493 .and_then(|descendant| descendant.upgrade())
18494 {
18495 window.focus(&descendant);
18496 } else {
18497 if let Some(blame) = self.blame.as_ref() {
18498 blame.update(cx, GitBlame::focus)
18499 }
18500
18501 self.blink_manager.update(cx, BlinkManager::enable);
18502 self.show_cursor_names(window, cx);
18503 self.buffer.update(cx, |buffer, cx| {
18504 buffer.finalize_last_transaction(cx);
18505 if self.leader_id.is_none() {
18506 buffer.set_active_selections(
18507 &self.selections.disjoint_anchors(),
18508 self.selections.line_mode,
18509 self.cursor_shape,
18510 cx,
18511 );
18512 }
18513 });
18514 }
18515 }
18516
18517 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18518 cx.emit(EditorEvent::FocusedIn)
18519 }
18520
18521 fn handle_focus_out(
18522 &mut self,
18523 event: FocusOutEvent,
18524 _window: &mut Window,
18525 cx: &mut Context<Self>,
18526 ) {
18527 if event.blurred != self.focus_handle {
18528 self.last_focused_descendant = Some(event.blurred);
18529 }
18530 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18531 }
18532
18533 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18534 self.blink_manager.update(cx, BlinkManager::disable);
18535 self.buffer
18536 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18537
18538 if let Some(blame) = self.blame.as_ref() {
18539 blame.update(cx, GitBlame::blur)
18540 }
18541 if !self.hover_state.focused(window, cx) {
18542 hide_hover(self, cx);
18543 }
18544 if !self
18545 .context_menu
18546 .borrow()
18547 .as_ref()
18548 .is_some_and(|context_menu| context_menu.focused(window, cx))
18549 {
18550 self.hide_context_menu(window, cx);
18551 }
18552 self.discard_inline_completion(false, cx);
18553 cx.emit(EditorEvent::Blurred);
18554 cx.notify();
18555 }
18556
18557 pub fn register_action<A: Action>(
18558 &mut self,
18559 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18560 ) -> Subscription {
18561 let id = self.next_editor_action_id.post_inc();
18562 let listener = Arc::new(listener);
18563 self.editor_actions.borrow_mut().insert(
18564 id,
18565 Box::new(move |window, _| {
18566 let listener = listener.clone();
18567 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18568 let action = action.downcast_ref().unwrap();
18569 if phase == DispatchPhase::Bubble {
18570 listener(action, window, cx)
18571 }
18572 })
18573 }),
18574 );
18575
18576 let editor_actions = self.editor_actions.clone();
18577 Subscription::new(move || {
18578 editor_actions.borrow_mut().remove(&id);
18579 })
18580 }
18581
18582 pub fn file_header_size(&self) -> u32 {
18583 FILE_HEADER_HEIGHT
18584 }
18585
18586 pub fn restore(
18587 &mut self,
18588 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18589 window: &mut Window,
18590 cx: &mut Context<Self>,
18591 ) {
18592 let workspace = self.workspace();
18593 let project = self.project.as_ref();
18594 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18595 let mut tasks = Vec::new();
18596 for (buffer_id, changes) in revert_changes {
18597 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18598 buffer.update(cx, |buffer, cx| {
18599 buffer.edit(
18600 changes
18601 .into_iter()
18602 .map(|(range, text)| (range, text.to_string())),
18603 None,
18604 cx,
18605 );
18606 });
18607
18608 if let Some(project) =
18609 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18610 {
18611 project.update(cx, |project, cx| {
18612 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18613 })
18614 }
18615 }
18616 }
18617 tasks
18618 });
18619 cx.spawn_in(window, async move |_, cx| {
18620 for (buffer, task) in save_tasks {
18621 let result = task.await;
18622 if result.is_err() {
18623 let Some(path) = buffer
18624 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18625 .ok()
18626 else {
18627 continue;
18628 };
18629 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18630 let Some(task) = cx
18631 .update_window_entity(&workspace, |workspace, window, cx| {
18632 workspace
18633 .open_path_preview(path, None, false, false, false, window, cx)
18634 })
18635 .ok()
18636 else {
18637 continue;
18638 };
18639 task.await.log_err();
18640 }
18641 }
18642 }
18643 })
18644 .detach();
18645 self.change_selections(None, window, cx, |selections| selections.refresh());
18646 }
18647
18648 pub fn to_pixel_point(
18649 &self,
18650 source: multi_buffer::Anchor,
18651 editor_snapshot: &EditorSnapshot,
18652 window: &mut Window,
18653 ) -> Option<gpui::Point<Pixels>> {
18654 let source_point = source.to_display_point(editor_snapshot);
18655 self.display_to_pixel_point(source_point, editor_snapshot, window)
18656 }
18657
18658 pub fn display_to_pixel_point(
18659 &self,
18660 source: DisplayPoint,
18661 editor_snapshot: &EditorSnapshot,
18662 window: &mut Window,
18663 ) -> Option<gpui::Point<Pixels>> {
18664 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18665 let text_layout_details = self.text_layout_details(window);
18666 let scroll_top = text_layout_details
18667 .scroll_anchor
18668 .scroll_position(editor_snapshot)
18669 .y;
18670
18671 if source.row().as_f32() < scroll_top.floor() {
18672 return None;
18673 }
18674 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18675 let source_y = line_height * (source.row().as_f32() - scroll_top);
18676 Some(gpui::Point::new(source_x, source_y))
18677 }
18678
18679 pub fn has_visible_completions_menu(&self) -> bool {
18680 !self.edit_prediction_preview_is_active()
18681 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18682 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18683 })
18684 }
18685
18686 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18687 self.addons
18688 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18689 }
18690
18691 pub fn unregister_addon<T: Addon>(&mut self) {
18692 self.addons.remove(&std::any::TypeId::of::<T>());
18693 }
18694
18695 pub fn addon<T: Addon>(&self) -> Option<&T> {
18696 let type_id = std::any::TypeId::of::<T>();
18697 self.addons
18698 .get(&type_id)
18699 .and_then(|item| item.to_any().downcast_ref::<T>())
18700 }
18701
18702 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
18703 let type_id = std::any::TypeId::of::<T>();
18704 self.addons
18705 .get_mut(&type_id)
18706 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
18707 }
18708
18709 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18710 let text_layout_details = self.text_layout_details(window);
18711 let style = &text_layout_details.editor_style;
18712 let font_id = window.text_system().resolve_font(&style.text.font());
18713 let font_size = style.text.font_size.to_pixels(window.rem_size());
18714 let line_height = style.text.line_height_in_pixels(window.rem_size());
18715 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18716
18717 gpui::Size::new(em_width, line_height)
18718 }
18719
18720 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18721 self.load_diff_task.clone()
18722 }
18723
18724 fn read_metadata_from_db(
18725 &mut self,
18726 item_id: u64,
18727 workspace_id: WorkspaceId,
18728 window: &mut Window,
18729 cx: &mut Context<Editor>,
18730 ) {
18731 if self.is_singleton(cx)
18732 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18733 {
18734 let buffer_snapshot = OnceCell::new();
18735
18736 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18737 if !folds.is_empty() {
18738 let snapshot =
18739 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18740 self.fold_ranges(
18741 folds
18742 .into_iter()
18743 .map(|(start, end)| {
18744 snapshot.clip_offset(start, Bias::Left)
18745 ..snapshot.clip_offset(end, Bias::Right)
18746 })
18747 .collect(),
18748 false,
18749 window,
18750 cx,
18751 );
18752 }
18753 }
18754
18755 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18756 if !selections.is_empty() {
18757 let snapshot =
18758 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18759 self.change_selections(None, window, cx, |s| {
18760 s.select_ranges(selections.into_iter().map(|(start, end)| {
18761 snapshot.clip_offset(start, Bias::Left)
18762 ..snapshot.clip_offset(end, Bias::Right)
18763 }));
18764 });
18765 }
18766 };
18767 }
18768
18769 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18770 }
18771}
18772
18773fn vim_enabled(cx: &App) -> bool {
18774 cx.global::<SettingsStore>()
18775 .raw_user_settings()
18776 .get("vim_mode")
18777 == Some(&serde_json::Value::Bool(true))
18778}
18779
18780// Consider user intent and default settings
18781fn choose_completion_range(
18782 completion: &Completion,
18783 intent: CompletionIntent,
18784 buffer: &Entity<Buffer>,
18785 cx: &mut Context<Editor>,
18786) -> Range<usize> {
18787 fn should_replace(
18788 completion: &Completion,
18789 insert_range: &Range<text::Anchor>,
18790 intent: CompletionIntent,
18791 completion_mode_setting: LspInsertMode,
18792 buffer: &Buffer,
18793 ) -> bool {
18794 // specific actions take precedence over settings
18795 match intent {
18796 CompletionIntent::CompleteWithInsert => return false,
18797 CompletionIntent::CompleteWithReplace => return true,
18798 CompletionIntent::Complete | CompletionIntent::Compose => {}
18799 }
18800
18801 match completion_mode_setting {
18802 LspInsertMode::Insert => false,
18803 LspInsertMode::Replace => true,
18804 LspInsertMode::ReplaceSubsequence => {
18805 let mut text_to_replace = buffer.chars_for_range(
18806 buffer.anchor_before(completion.replace_range.start)
18807 ..buffer.anchor_after(completion.replace_range.end),
18808 );
18809 let mut completion_text = completion.new_text.chars();
18810
18811 // is `text_to_replace` a subsequence of `completion_text`
18812 text_to_replace
18813 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18814 }
18815 LspInsertMode::ReplaceSuffix => {
18816 let range_after_cursor = insert_range.end..completion.replace_range.end;
18817
18818 let text_after_cursor = buffer
18819 .text_for_range(
18820 buffer.anchor_before(range_after_cursor.start)
18821 ..buffer.anchor_after(range_after_cursor.end),
18822 )
18823 .collect::<String>();
18824 completion.new_text.ends_with(&text_after_cursor)
18825 }
18826 }
18827 }
18828
18829 let buffer = buffer.read(cx);
18830
18831 if let CompletionSource::Lsp {
18832 insert_range: Some(insert_range),
18833 ..
18834 } = &completion.source
18835 {
18836 let completion_mode_setting =
18837 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18838 .completions
18839 .lsp_insert_mode;
18840
18841 if !should_replace(
18842 completion,
18843 &insert_range,
18844 intent,
18845 completion_mode_setting,
18846 buffer,
18847 ) {
18848 return insert_range.to_offset(buffer);
18849 }
18850 }
18851
18852 completion.replace_range.to_offset(buffer)
18853}
18854
18855fn insert_extra_newline_brackets(
18856 buffer: &MultiBufferSnapshot,
18857 range: Range<usize>,
18858 language: &language::LanguageScope,
18859) -> bool {
18860 let leading_whitespace_len = buffer
18861 .reversed_chars_at(range.start)
18862 .take_while(|c| c.is_whitespace() && *c != '\n')
18863 .map(|c| c.len_utf8())
18864 .sum::<usize>();
18865 let trailing_whitespace_len = buffer
18866 .chars_at(range.end)
18867 .take_while(|c| c.is_whitespace() && *c != '\n')
18868 .map(|c| c.len_utf8())
18869 .sum::<usize>();
18870 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18871
18872 language.brackets().any(|(pair, enabled)| {
18873 let pair_start = pair.start.trim_end();
18874 let pair_end = pair.end.trim_start();
18875
18876 enabled
18877 && pair.newline
18878 && buffer.contains_str_at(range.end, pair_end)
18879 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18880 })
18881}
18882
18883fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18884 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18885 [(buffer, range, _)] => (*buffer, range.clone()),
18886 _ => return false,
18887 };
18888 let pair = {
18889 let mut result: Option<BracketMatch> = None;
18890
18891 for pair in buffer
18892 .all_bracket_ranges(range.clone())
18893 .filter(move |pair| {
18894 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18895 })
18896 {
18897 let len = pair.close_range.end - pair.open_range.start;
18898
18899 if let Some(existing) = &result {
18900 let existing_len = existing.close_range.end - existing.open_range.start;
18901 if len > existing_len {
18902 continue;
18903 }
18904 }
18905
18906 result = Some(pair);
18907 }
18908
18909 result
18910 };
18911 let Some(pair) = pair else {
18912 return false;
18913 };
18914 pair.newline_only
18915 && buffer
18916 .chars_for_range(pair.open_range.end..range.start)
18917 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18918 .all(|c| c.is_whitespace() && c != '\n')
18919}
18920
18921fn update_uncommitted_diff_for_buffer(
18922 editor: Entity<Editor>,
18923 project: &Entity<Project>,
18924 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18925 buffer: Entity<MultiBuffer>,
18926 cx: &mut App,
18927) -> Task<()> {
18928 let mut tasks = Vec::new();
18929 project.update(cx, |project, cx| {
18930 for buffer in buffers {
18931 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18932 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18933 }
18934 }
18935 });
18936 cx.spawn(async move |cx| {
18937 let diffs = future::join_all(tasks).await;
18938 if editor
18939 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
18940 .unwrap_or(false)
18941 {
18942 return;
18943 }
18944
18945 buffer
18946 .update(cx, |buffer, cx| {
18947 for diff in diffs.into_iter().flatten() {
18948 buffer.add_diff(diff, cx);
18949 }
18950 })
18951 .ok();
18952 })
18953}
18954
18955fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18956 let tab_size = tab_size.get() as usize;
18957 let mut width = offset;
18958
18959 for ch in text.chars() {
18960 width += if ch == '\t' {
18961 tab_size - (width % tab_size)
18962 } else {
18963 1
18964 };
18965 }
18966
18967 width - offset
18968}
18969
18970#[cfg(test)]
18971mod tests {
18972 use super::*;
18973
18974 #[test]
18975 fn test_string_size_with_expanded_tabs() {
18976 let nz = |val| NonZeroU32::new(val).unwrap();
18977 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18978 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18979 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18980 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18981 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18982 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18983 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18984 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18985 }
18986}
18987
18988/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18989struct WordBreakingTokenizer<'a> {
18990 input: &'a str,
18991}
18992
18993impl<'a> WordBreakingTokenizer<'a> {
18994 fn new(input: &'a str) -> Self {
18995 Self { input }
18996 }
18997}
18998
18999fn is_char_ideographic(ch: char) -> bool {
19000 use unicode_script::Script::*;
19001 use unicode_script::UnicodeScript;
19002 matches!(ch.script(), Han | Tangut | Yi)
19003}
19004
19005fn is_grapheme_ideographic(text: &str) -> bool {
19006 text.chars().any(is_char_ideographic)
19007}
19008
19009fn is_grapheme_whitespace(text: &str) -> bool {
19010 text.chars().any(|x| x.is_whitespace())
19011}
19012
19013fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19014 text.chars().next().map_or(false, |ch| {
19015 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19016 })
19017}
19018
19019#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19020enum WordBreakToken<'a> {
19021 Word { token: &'a str, grapheme_len: usize },
19022 InlineWhitespace { token: &'a str, grapheme_len: usize },
19023 Newline,
19024}
19025
19026impl<'a> Iterator for WordBreakingTokenizer<'a> {
19027 /// Yields a span, the count of graphemes in the token, and whether it was
19028 /// whitespace. Note that it also breaks at word boundaries.
19029 type Item = WordBreakToken<'a>;
19030
19031 fn next(&mut self) -> Option<Self::Item> {
19032 use unicode_segmentation::UnicodeSegmentation;
19033 if self.input.is_empty() {
19034 return None;
19035 }
19036
19037 let mut iter = self.input.graphemes(true).peekable();
19038 let mut offset = 0;
19039 let mut grapheme_len = 0;
19040 if let Some(first_grapheme) = iter.next() {
19041 let is_newline = first_grapheme == "\n";
19042 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19043 offset += first_grapheme.len();
19044 grapheme_len += 1;
19045 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19046 if let Some(grapheme) = iter.peek().copied() {
19047 if should_stay_with_preceding_ideograph(grapheme) {
19048 offset += grapheme.len();
19049 grapheme_len += 1;
19050 }
19051 }
19052 } else {
19053 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19054 let mut next_word_bound = words.peek().copied();
19055 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19056 next_word_bound = words.next();
19057 }
19058 while let Some(grapheme) = iter.peek().copied() {
19059 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19060 break;
19061 };
19062 if is_grapheme_whitespace(grapheme) != is_whitespace
19063 || (grapheme == "\n") != is_newline
19064 {
19065 break;
19066 };
19067 offset += grapheme.len();
19068 grapheme_len += 1;
19069 iter.next();
19070 }
19071 }
19072 let token = &self.input[..offset];
19073 self.input = &self.input[offset..];
19074 if token == "\n" {
19075 Some(WordBreakToken::Newline)
19076 } else if is_whitespace {
19077 Some(WordBreakToken::InlineWhitespace {
19078 token,
19079 grapheme_len,
19080 })
19081 } else {
19082 Some(WordBreakToken::Word {
19083 token,
19084 grapheme_len,
19085 })
19086 }
19087 } else {
19088 None
19089 }
19090 }
19091}
19092
19093#[test]
19094fn test_word_breaking_tokenizer() {
19095 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19096 ("", &[]),
19097 (" ", &[whitespace(" ", 2)]),
19098 ("Ʒ", &[word("Ʒ", 1)]),
19099 ("Ǽ", &[word("Ǽ", 1)]),
19100 ("⋑", &[word("⋑", 1)]),
19101 ("⋑⋑", &[word("⋑⋑", 2)]),
19102 (
19103 "原理,进而",
19104 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19105 ),
19106 (
19107 "hello world",
19108 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19109 ),
19110 (
19111 "hello, world",
19112 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19113 ),
19114 (
19115 " hello world",
19116 &[
19117 whitespace(" ", 2),
19118 word("hello", 5),
19119 whitespace(" ", 1),
19120 word("world", 5),
19121 ],
19122 ),
19123 (
19124 "这是什么 \n 钢笔",
19125 &[
19126 word("这", 1),
19127 word("是", 1),
19128 word("什", 1),
19129 word("么", 1),
19130 whitespace(" ", 1),
19131 newline(),
19132 whitespace(" ", 1),
19133 word("钢", 1),
19134 word("笔", 1),
19135 ],
19136 ),
19137 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19138 ];
19139
19140 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19141 WordBreakToken::Word {
19142 token,
19143 grapheme_len,
19144 }
19145 }
19146
19147 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19148 WordBreakToken::InlineWhitespace {
19149 token,
19150 grapheme_len,
19151 }
19152 }
19153
19154 fn newline() -> WordBreakToken<'static> {
19155 WordBreakToken::Newline
19156 }
19157
19158 for (input, result) in tests {
19159 assert_eq!(
19160 WordBreakingTokenizer::new(input)
19161 .collect::<Vec<_>>()
19162 .as_slice(),
19163 *result,
19164 );
19165 }
19166}
19167
19168fn wrap_with_prefix(
19169 line_prefix: String,
19170 unwrapped_text: String,
19171 wrap_column: usize,
19172 tab_size: NonZeroU32,
19173 preserve_existing_whitespace: bool,
19174) -> String {
19175 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19176 let mut wrapped_text = String::new();
19177 let mut current_line = line_prefix.clone();
19178
19179 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19180 let mut current_line_len = line_prefix_len;
19181 let mut in_whitespace = false;
19182 for token in tokenizer {
19183 let have_preceding_whitespace = in_whitespace;
19184 match token {
19185 WordBreakToken::Word {
19186 token,
19187 grapheme_len,
19188 } => {
19189 in_whitespace = false;
19190 if current_line_len + grapheme_len > wrap_column
19191 && current_line_len != line_prefix_len
19192 {
19193 wrapped_text.push_str(current_line.trim_end());
19194 wrapped_text.push('\n');
19195 current_line.truncate(line_prefix.len());
19196 current_line_len = line_prefix_len;
19197 }
19198 current_line.push_str(token);
19199 current_line_len += grapheme_len;
19200 }
19201 WordBreakToken::InlineWhitespace {
19202 mut token,
19203 mut grapheme_len,
19204 } => {
19205 in_whitespace = true;
19206 if have_preceding_whitespace && !preserve_existing_whitespace {
19207 continue;
19208 }
19209 if !preserve_existing_whitespace {
19210 token = " ";
19211 grapheme_len = 1;
19212 }
19213 if current_line_len + grapheme_len > wrap_column {
19214 wrapped_text.push_str(current_line.trim_end());
19215 wrapped_text.push('\n');
19216 current_line.truncate(line_prefix.len());
19217 current_line_len = line_prefix_len;
19218 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19219 current_line.push_str(token);
19220 current_line_len += grapheme_len;
19221 }
19222 }
19223 WordBreakToken::Newline => {
19224 in_whitespace = true;
19225 if preserve_existing_whitespace {
19226 wrapped_text.push_str(current_line.trim_end());
19227 wrapped_text.push('\n');
19228 current_line.truncate(line_prefix.len());
19229 current_line_len = line_prefix_len;
19230 } else if have_preceding_whitespace {
19231 continue;
19232 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19233 {
19234 wrapped_text.push_str(current_line.trim_end());
19235 wrapped_text.push('\n');
19236 current_line.truncate(line_prefix.len());
19237 current_line_len = line_prefix_len;
19238 } else if current_line_len != line_prefix_len {
19239 current_line.push(' ');
19240 current_line_len += 1;
19241 }
19242 }
19243 }
19244 }
19245
19246 if !current_line.is_empty() {
19247 wrapped_text.push_str(¤t_line);
19248 }
19249 wrapped_text
19250}
19251
19252#[test]
19253fn test_wrap_with_prefix() {
19254 assert_eq!(
19255 wrap_with_prefix(
19256 "# ".to_string(),
19257 "abcdefg".to_string(),
19258 4,
19259 NonZeroU32::new(4).unwrap(),
19260 false,
19261 ),
19262 "# abcdefg"
19263 );
19264 assert_eq!(
19265 wrap_with_prefix(
19266 "".to_string(),
19267 "\thello world".to_string(),
19268 8,
19269 NonZeroU32::new(4).unwrap(),
19270 false,
19271 ),
19272 "hello\nworld"
19273 );
19274 assert_eq!(
19275 wrap_with_prefix(
19276 "// ".to_string(),
19277 "xx \nyy zz aa bb cc".to_string(),
19278 12,
19279 NonZeroU32::new(4).unwrap(),
19280 false,
19281 ),
19282 "// xx yy zz\n// aa bb cc"
19283 );
19284 assert_eq!(
19285 wrap_with_prefix(
19286 String::new(),
19287 "这是什么 \n 钢笔".to_string(),
19288 3,
19289 NonZeroU32::new(4).unwrap(),
19290 false,
19291 ),
19292 "这是什\n么 钢\n笔"
19293 );
19294}
19295
19296pub trait CollaborationHub {
19297 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19298 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19299 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19300}
19301
19302impl CollaborationHub for Entity<Project> {
19303 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19304 self.read(cx).collaborators()
19305 }
19306
19307 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19308 self.read(cx).user_store().read(cx).participant_indices()
19309 }
19310
19311 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19312 let this = self.read(cx);
19313 let user_ids = this.collaborators().values().map(|c| c.user_id);
19314 this.user_store().read_with(cx, |user_store, cx| {
19315 user_store.participant_names(user_ids, cx)
19316 })
19317 }
19318}
19319
19320pub trait SemanticsProvider {
19321 fn hover(
19322 &self,
19323 buffer: &Entity<Buffer>,
19324 position: text::Anchor,
19325 cx: &mut App,
19326 ) -> Option<Task<Vec<project::Hover>>>;
19327
19328 fn inline_values(
19329 &self,
19330 buffer_handle: Entity<Buffer>,
19331 range: Range<text::Anchor>,
19332 cx: &mut App,
19333 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19334
19335 fn inlay_hints(
19336 &self,
19337 buffer_handle: Entity<Buffer>,
19338 range: Range<text::Anchor>,
19339 cx: &mut App,
19340 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19341
19342 fn resolve_inlay_hint(
19343 &self,
19344 hint: InlayHint,
19345 buffer_handle: Entity<Buffer>,
19346 server_id: LanguageServerId,
19347 cx: &mut App,
19348 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19349
19350 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19351
19352 fn document_highlights(
19353 &self,
19354 buffer: &Entity<Buffer>,
19355 position: text::Anchor,
19356 cx: &mut App,
19357 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19358
19359 fn definitions(
19360 &self,
19361 buffer: &Entity<Buffer>,
19362 position: text::Anchor,
19363 kind: GotoDefinitionKind,
19364 cx: &mut App,
19365 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19366
19367 fn range_for_rename(
19368 &self,
19369 buffer: &Entity<Buffer>,
19370 position: text::Anchor,
19371 cx: &mut App,
19372 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19373
19374 fn perform_rename(
19375 &self,
19376 buffer: &Entity<Buffer>,
19377 position: text::Anchor,
19378 new_name: String,
19379 cx: &mut App,
19380 ) -> Option<Task<Result<ProjectTransaction>>>;
19381}
19382
19383pub trait CompletionProvider {
19384 fn completions(
19385 &self,
19386 excerpt_id: ExcerptId,
19387 buffer: &Entity<Buffer>,
19388 buffer_position: text::Anchor,
19389 trigger: CompletionContext,
19390 window: &mut Window,
19391 cx: &mut Context<Editor>,
19392 ) -> Task<Result<Option<Vec<Completion>>>>;
19393
19394 fn resolve_completions(
19395 &self,
19396 buffer: Entity<Buffer>,
19397 completion_indices: Vec<usize>,
19398 completions: Rc<RefCell<Box<[Completion]>>>,
19399 cx: &mut Context<Editor>,
19400 ) -> Task<Result<bool>>;
19401
19402 fn apply_additional_edits_for_completion(
19403 &self,
19404 _buffer: Entity<Buffer>,
19405 _completions: Rc<RefCell<Box<[Completion]>>>,
19406 _completion_index: usize,
19407 _push_to_history: bool,
19408 _cx: &mut Context<Editor>,
19409 ) -> Task<Result<Option<language::Transaction>>> {
19410 Task::ready(Ok(None))
19411 }
19412
19413 fn is_completion_trigger(
19414 &self,
19415 buffer: &Entity<Buffer>,
19416 position: language::Anchor,
19417 text: &str,
19418 trigger_in_words: bool,
19419 cx: &mut Context<Editor>,
19420 ) -> bool;
19421
19422 fn sort_completions(&self) -> bool {
19423 true
19424 }
19425
19426 fn filter_completions(&self) -> bool {
19427 true
19428 }
19429}
19430
19431pub trait CodeActionProvider {
19432 fn id(&self) -> Arc<str>;
19433
19434 fn code_actions(
19435 &self,
19436 buffer: &Entity<Buffer>,
19437 range: Range<text::Anchor>,
19438 window: &mut Window,
19439 cx: &mut App,
19440 ) -> Task<Result<Vec<CodeAction>>>;
19441
19442 fn apply_code_action(
19443 &self,
19444 buffer_handle: Entity<Buffer>,
19445 action: CodeAction,
19446 excerpt_id: ExcerptId,
19447 push_to_history: bool,
19448 window: &mut Window,
19449 cx: &mut App,
19450 ) -> Task<Result<ProjectTransaction>>;
19451}
19452
19453impl CodeActionProvider for Entity<Project> {
19454 fn id(&self) -> Arc<str> {
19455 "project".into()
19456 }
19457
19458 fn code_actions(
19459 &self,
19460 buffer: &Entity<Buffer>,
19461 range: Range<text::Anchor>,
19462 _window: &mut Window,
19463 cx: &mut App,
19464 ) -> Task<Result<Vec<CodeAction>>> {
19465 self.update(cx, |project, cx| {
19466 let code_lens = project.code_lens(buffer, range.clone(), cx);
19467 let code_actions = project.code_actions(buffer, range, None, cx);
19468 cx.background_spawn(async move {
19469 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19470 Ok(code_lens
19471 .context("code lens fetch")?
19472 .into_iter()
19473 .chain(code_actions.context("code action fetch")?)
19474 .collect())
19475 })
19476 })
19477 }
19478
19479 fn apply_code_action(
19480 &self,
19481 buffer_handle: Entity<Buffer>,
19482 action: CodeAction,
19483 _excerpt_id: ExcerptId,
19484 push_to_history: bool,
19485 _window: &mut Window,
19486 cx: &mut App,
19487 ) -> Task<Result<ProjectTransaction>> {
19488 self.update(cx, |project, cx| {
19489 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19490 })
19491 }
19492}
19493
19494fn snippet_completions(
19495 project: &Project,
19496 buffer: &Entity<Buffer>,
19497 buffer_position: text::Anchor,
19498 cx: &mut App,
19499) -> Task<Result<Vec<Completion>>> {
19500 let languages = buffer.read(cx).languages_at(buffer_position);
19501 let snippet_store = project.snippets().read(cx);
19502
19503 let scopes: Vec<_> = languages
19504 .iter()
19505 .filter_map(|language| {
19506 let language_name = language.lsp_id();
19507 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19508
19509 if snippets.is_empty() {
19510 None
19511 } else {
19512 Some((language.default_scope(), snippets))
19513 }
19514 })
19515 .collect();
19516
19517 if scopes.is_empty() {
19518 return Task::ready(Ok(vec![]));
19519 }
19520
19521 let snapshot = buffer.read(cx).text_snapshot();
19522 let chars: String = snapshot
19523 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19524 .collect();
19525 let executor = cx.background_executor().clone();
19526
19527 cx.background_spawn(async move {
19528 let mut all_results: Vec<Completion> = Vec::new();
19529 for (scope, snippets) in scopes.into_iter() {
19530 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19531 let mut last_word = chars
19532 .chars()
19533 .take_while(|c| classifier.is_word(*c))
19534 .collect::<String>();
19535 last_word = last_word.chars().rev().collect();
19536
19537 if last_word.is_empty() {
19538 return Ok(vec![]);
19539 }
19540
19541 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19542 let to_lsp = |point: &text::Anchor| {
19543 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19544 point_to_lsp(end)
19545 };
19546 let lsp_end = to_lsp(&buffer_position);
19547
19548 let candidates = snippets
19549 .iter()
19550 .enumerate()
19551 .flat_map(|(ix, snippet)| {
19552 snippet
19553 .prefix
19554 .iter()
19555 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19556 })
19557 .collect::<Vec<StringMatchCandidate>>();
19558
19559 let mut matches = fuzzy::match_strings(
19560 &candidates,
19561 &last_word,
19562 last_word.chars().any(|c| c.is_uppercase()),
19563 100,
19564 &Default::default(),
19565 executor.clone(),
19566 )
19567 .await;
19568
19569 // Remove all candidates where the query's start does not match the start of any word in the candidate
19570 if let Some(query_start) = last_word.chars().next() {
19571 matches.retain(|string_match| {
19572 split_words(&string_match.string).any(|word| {
19573 // Check that the first codepoint of the word as lowercase matches the first
19574 // codepoint of the query as lowercase
19575 word.chars()
19576 .flat_map(|codepoint| codepoint.to_lowercase())
19577 .zip(query_start.to_lowercase())
19578 .all(|(word_cp, query_cp)| word_cp == query_cp)
19579 })
19580 });
19581 }
19582
19583 let matched_strings = matches
19584 .into_iter()
19585 .map(|m| m.string)
19586 .collect::<HashSet<_>>();
19587
19588 let mut result: Vec<Completion> = snippets
19589 .iter()
19590 .filter_map(|snippet| {
19591 let matching_prefix = snippet
19592 .prefix
19593 .iter()
19594 .find(|prefix| matched_strings.contains(*prefix))?;
19595 let start = as_offset - last_word.len();
19596 let start = snapshot.anchor_before(start);
19597 let range = start..buffer_position;
19598 let lsp_start = to_lsp(&start);
19599 let lsp_range = lsp::Range {
19600 start: lsp_start,
19601 end: lsp_end,
19602 };
19603 Some(Completion {
19604 replace_range: range,
19605 new_text: snippet.body.clone(),
19606 source: CompletionSource::Lsp {
19607 insert_range: None,
19608 server_id: LanguageServerId(usize::MAX),
19609 resolved: true,
19610 lsp_completion: Box::new(lsp::CompletionItem {
19611 label: snippet.prefix.first().unwrap().clone(),
19612 kind: Some(CompletionItemKind::SNIPPET),
19613 label_details: snippet.description.as_ref().map(|description| {
19614 lsp::CompletionItemLabelDetails {
19615 detail: Some(description.clone()),
19616 description: None,
19617 }
19618 }),
19619 insert_text_format: Some(InsertTextFormat::SNIPPET),
19620 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19621 lsp::InsertReplaceEdit {
19622 new_text: snippet.body.clone(),
19623 insert: lsp_range,
19624 replace: lsp_range,
19625 },
19626 )),
19627 filter_text: Some(snippet.body.clone()),
19628 sort_text: Some(char::MAX.to_string()),
19629 ..lsp::CompletionItem::default()
19630 }),
19631 lsp_defaults: None,
19632 },
19633 label: CodeLabel {
19634 text: matching_prefix.clone(),
19635 runs: Vec::new(),
19636 filter_range: 0..matching_prefix.len(),
19637 },
19638 icon_path: None,
19639 documentation: snippet.description.clone().map(|description| {
19640 CompletionDocumentation::SingleLine(description.into())
19641 }),
19642 insert_text_mode: None,
19643 confirm: None,
19644 })
19645 })
19646 .collect();
19647
19648 all_results.append(&mut result);
19649 }
19650
19651 Ok(all_results)
19652 })
19653}
19654
19655impl CompletionProvider for Entity<Project> {
19656 fn completions(
19657 &self,
19658 _excerpt_id: ExcerptId,
19659 buffer: &Entity<Buffer>,
19660 buffer_position: text::Anchor,
19661 options: CompletionContext,
19662 _window: &mut Window,
19663 cx: &mut Context<Editor>,
19664 ) -> Task<Result<Option<Vec<Completion>>>> {
19665 self.update(cx, |project, cx| {
19666 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19667 let project_completions = project.completions(buffer, buffer_position, options, cx);
19668 cx.background_spawn(async move {
19669 let snippets_completions = snippets.await?;
19670 match project_completions.await? {
19671 Some(mut completions) => {
19672 completions.extend(snippets_completions);
19673 Ok(Some(completions))
19674 }
19675 None => {
19676 if snippets_completions.is_empty() {
19677 Ok(None)
19678 } else {
19679 Ok(Some(snippets_completions))
19680 }
19681 }
19682 }
19683 })
19684 })
19685 }
19686
19687 fn resolve_completions(
19688 &self,
19689 buffer: Entity<Buffer>,
19690 completion_indices: Vec<usize>,
19691 completions: Rc<RefCell<Box<[Completion]>>>,
19692 cx: &mut Context<Editor>,
19693 ) -> Task<Result<bool>> {
19694 self.update(cx, |project, cx| {
19695 project.lsp_store().update(cx, |lsp_store, cx| {
19696 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19697 })
19698 })
19699 }
19700
19701 fn apply_additional_edits_for_completion(
19702 &self,
19703 buffer: Entity<Buffer>,
19704 completions: Rc<RefCell<Box<[Completion]>>>,
19705 completion_index: usize,
19706 push_to_history: bool,
19707 cx: &mut Context<Editor>,
19708 ) -> Task<Result<Option<language::Transaction>>> {
19709 self.update(cx, |project, cx| {
19710 project.lsp_store().update(cx, |lsp_store, cx| {
19711 lsp_store.apply_additional_edits_for_completion(
19712 buffer,
19713 completions,
19714 completion_index,
19715 push_to_history,
19716 cx,
19717 )
19718 })
19719 })
19720 }
19721
19722 fn is_completion_trigger(
19723 &self,
19724 buffer: &Entity<Buffer>,
19725 position: language::Anchor,
19726 text: &str,
19727 trigger_in_words: bool,
19728 cx: &mut Context<Editor>,
19729 ) -> bool {
19730 let mut chars = text.chars();
19731 let char = if let Some(char) = chars.next() {
19732 char
19733 } else {
19734 return false;
19735 };
19736 if chars.next().is_some() {
19737 return false;
19738 }
19739
19740 let buffer = buffer.read(cx);
19741 let snapshot = buffer.snapshot();
19742 if !snapshot.settings_at(position, cx).show_completions_on_input {
19743 return false;
19744 }
19745 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19746 if trigger_in_words && classifier.is_word(char) {
19747 return true;
19748 }
19749
19750 buffer.completion_triggers().contains(text)
19751 }
19752}
19753
19754impl SemanticsProvider for Entity<Project> {
19755 fn hover(
19756 &self,
19757 buffer: &Entity<Buffer>,
19758 position: text::Anchor,
19759 cx: &mut App,
19760 ) -> Option<Task<Vec<project::Hover>>> {
19761 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19762 }
19763
19764 fn document_highlights(
19765 &self,
19766 buffer: &Entity<Buffer>,
19767 position: text::Anchor,
19768 cx: &mut App,
19769 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19770 Some(self.update(cx, |project, cx| {
19771 project.document_highlights(buffer, position, cx)
19772 }))
19773 }
19774
19775 fn definitions(
19776 &self,
19777 buffer: &Entity<Buffer>,
19778 position: text::Anchor,
19779 kind: GotoDefinitionKind,
19780 cx: &mut App,
19781 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19782 Some(self.update(cx, |project, cx| match kind {
19783 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19784 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19785 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19786 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19787 }))
19788 }
19789
19790 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19791 // TODO: make this work for remote projects
19792 self.update(cx, |project, cx| {
19793 if project
19794 .active_debug_session(cx)
19795 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
19796 {
19797 return true;
19798 }
19799
19800 buffer.update(cx, |buffer, cx| {
19801 project.any_language_server_supports_inlay_hints(buffer, cx)
19802 })
19803 })
19804 }
19805
19806 fn inline_values(
19807 &self,
19808 buffer_handle: Entity<Buffer>,
19809 range: Range<text::Anchor>,
19810 cx: &mut App,
19811 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19812 self.update(cx, |project, cx| {
19813 let (session, active_stack_frame) = project.active_debug_session(cx)?;
19814
19815 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
19816 })
19817 }
19818
19819 fn inlay_hints(
19820 &self,
19821 buffer_handle: Entity<Buffer>,
19822 range: Range<text::Anchor>,
19823 cx: &mut App,
19824 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19825 Some(self.update(cx, |project, cx| {
19826 project.inlay_hints(buffer_handle, range, cx)
19827 }))
19828 }
19829
19830 fn resolve_inlay_hint(
19831 &self,
19832 hint: InlayHint,
19833 buffer_handle: Entity<Buffer>,
19834 server_id: LanguageServerId,
19835 cx: &mut App,
19836 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19837 Some(self.update(cx, |project, cx| {
19838 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19839 }))
19840 }
19841
19842 fn range_for_rename(
19843 &self,
19844 buffer: &Entity<Buffer>,
19845 position: text::Anchor,
19846 cx: &mut App,
19847 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19848 Some(self.update(cx, |project, cx| {
19849 let buffer = buffer.clone();
19850 let task = project.prepare_rename(buffer.clone(), position, cx);
19851 cx.spawn(async move |_, cx| {
19852 Ok(match task.await? {
19853 PrepareRenameResponse::Success(range) => Some(range),
19854 PrepareRenameResponse::InvalidPosition => None,
19855 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19856 // Fallback on using TreeSitter info to determine identifier range
19857 buffer.update(cx, |buffer, _| {
19858 let snapshot = buffer.snapshot();
19859 let (range, kind) = snapshot.surrounding_word(position);
19860 if kind != Some(CharKind::Word) {
19861 return None;
19862 }
19863 Some(
19864 snapshot.anchor_before(range.start)
19865 ..snapshot.anchor_after(range.end),
19866 )
19867 })?
19868 }
19869 })
19870 })
19871 }))
19872 }
19873
19874 fn perform_rename(
19875 &self,
19876 buffer: &Entity<Buffer>,
19877 position: text::Anchor,
19878 new_name: String,
19879 cx: &mut App,
19880 ) -> Option<Task<Result<ProjectTransaction>>> {
19881 Some(self.update(cx, |project, cx| {
19882 project.perform_rename(buffer.clone(), position, new_name, cx)
19883 }))
19884 }
19885}
19886
19887fn inlay_hint_settings(
19888 location: Anchor,
19889 snapshot: &MultiBufferSnapshot,
19890 cx: &mut Context<Editor>,
19891) -> InlayHintSettings {
19892 let file = snapshot.file_at(location);
19893 let language = snapshot.language_at(location).map(|l| l.name());
19894 language_settings(language, file, cx).inlay_hints
19895}
19896
19897fn consume_contiguous_rows(
19898 contiguous_row_selections: &mut Vec<Selection<Point>>,
19899 selection: &Selection<Point>,
19900 display_map: &DisplaySnapshot,
19901 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19902) -> (MultiBufferRow, MultiBufferRow) {
19903 contiguous_row_selections.push(selection.clone());
19904 let start_row = MultiBufferRow(selection.start.row);
19905 let mut end_row = ending_row(selection, display_map);
19906
19907 while let Some(next_selection) = selections.peek() {
19908 if next_selection.start.row <= end_row.0 {
19909 end_row = ending_row(next_selection, display_map);
19910 contiguous_row_selections.push(selections.next().unwrap().clone());
19911 } else {
19912 break;
19913 }
19914 }
19915 (start_row, end_row)
19916}
19917
19918fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19919 if next_selection.end.column > 0 || next_selection.is_empty() {
19920 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19921 } else {
19922 MultiBufferRow(next_selection.end.row)
19923 }
19924}
19925
19926impl EditorSnapshot {
19927 pub fn remote_selections_in_range<'a>(
19928 &'a self,
19929 range: &'a Range<Anchor>,
19930 collaboration_hub: &dyn CollaborationHub,
19931 cx: &'a App,
19932 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19933 let participant_names = collaboration_hub.user_names(cx);
19934 let participant_indices = collaboration_hub.user_participant_indices(cx);
19935 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19936 let collaborators_by_replica_id = collaborators_by_peer_id
19937 .iter()
19938 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19939 .collect::<HashMap<_, _>>();
19940 self.buffer_snapshot
19941 .selections_in_range(range, false)
19942 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19943 if replica_id == AGENT_REPLICA_ID {
19944 Some(RemoteSelection {
19945 replica_id,
19946 selection,
19947 cursor_shape,
19948 line_mode,
19949 collaborator_id: CollaboratorId::Agent,
19950 user_name: Some("Agent".into()),
19951 color: cx.theme().players().agent(),
19952 })
19953 } else {
19954 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19955 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19956 let user_name = participant_names.get(&collaborator.user_id).cloned();
19957 Some(RemoteSelection {
19958 replica_id,
19959 selection,
19960 cursor_shape,
19961 line_mode,
19962 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
19963 user_name,
19964 color: if let Some(index) = participant_index {
19965 cx.theme().players().color_for_participant(index.0)
19966 } else {
19967 cx.theme().players().absent()
19968 },
19969 })
19970 }
19971 })
19972 }
19973
19974 pub fn hunks_for_ranges(
19975 &self,
19976 ranges: impl IntoIterator<Item = Range<Point>>,
19977 ) -> Vec<MultiBufferDiffHunk> {
19978 let mut hunks = Vec::new();
19979 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19980 HashMap::default();
19981 for query_range in ranges {
19982 let query_rows =
19983 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19984 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19985 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19986 ) {
19987 // Include deleted hunks that are adjacent to the query range, because
19988 // otherwise they would be missed.
19989 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19990 if hunk.status().is_deleted() {
19991 intersects_range |= hunk.row_range.start == query_rows.end;
19992 intersects_range |= hunk.row_range.end == query_rows.start;
19993 }
19994 if intersects_range {
19995 if !processed_buffer_rows
19996 .entry(hunk.buffer_id)
19997 .or_default()
19998 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19999 {
20000 continue;
20001 }
20002 hunks.push(hunk);
20003 }
20004 }
20005 }
20006
20007 hunks
20008 }
20009
20010 fn display_diff_hunks_for_rows<'a>(
20011 &'a self,
20012 display_rows: Range<DisplayRow>,
20013 folded_buffers: &'a HashSet<BufferId>,
20014 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20015 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20016 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20017
20018 self.buffer_snapshot
20019 .diff_hunks_in_range(buffer_start..buffer_end)
20020 .filter_map(|hunk| {
20021 if folded_buffers.contains(&hunk.buffer_id) {
20022 return None;
20023 }
20024
20025 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20026 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20027
20028 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20029 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20030
20031 let display_hunk = if hunk_display_start.column() != 0 {
20032 DisplayDiffHunk::Folded {
20033 display_row: hunk_display_start.row(),
20034 }
20035 } else {
20036 let mut end_row = hunk_display_end.row();
20037 if hunk_display_end.column() > 0 {
20038 end_row.0 += 1;
20039 }
20040 let is_created_file = hunk.is_created_file();
20041 DisplayDiffHunk::Unfolded {
20042 status: hunk.status(),
20043 diff_base_byte_range: hunk.diff_base_byte_range,
20044 display_row_range: hunk_display_start.row()..end_row,
20045 multi_buffer_range: Anchor::range_in_buffer(
20046 hunk.excerpt_id,
20047 hunk.buffer_id,
20048 hunk.buffer_range,
20049 ),
20050 is_created_file,
20051 }
20052 };
20053
20054 Some(display_hunk)
20055 })
20056 }
20057
20058 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20059 self.display_snapshot.buffer_snapshot.language_at(position)
20060 }
20061
20062 pub fn is_focused(&self) -> bool {
20063 self.is_focused
20064 }
20065
20066 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20067 self.placeholder_text.as_ref()
20068 }
20069
20070 pub fn scroll_position(&self) -> gpui::Point<f32> {
20071 self.scroll_anchor.scroll_position(&self.display_snapshot)
20072 }
20073
20074 fn gutter_dimensions(
20075 &self,
20076 font_id: FontId,
20077 font_size: Pixels,
20078 max_line_number_width: Pixels,
20079 cx: &App,
20080 ) -> Option<GutterDimensions> {
20081 if !self.show_gutter {
20082 return None;
20083 }
20084
20085 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20086 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20087
20088 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20089 matches!(
20090 ProjectSettings::get_global(cx).git.git_gutter,
20091 Some(GitGutterSetting::TrackedFiles)
20092 )
20093 });
20094 let gutter_settings = EditorSettings::get_global(cx).gutter;
20095 let show_line_numbers = self
20096 .show_line_numbers
20097 .unwrap_or(gutter_settings.line_numbers);
20098 let line_gutter_width = if show_line_numbers {
20099 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20100 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20101 max_line_number_width.max(min_width_for_number_on_gutter)
20102 } else {
20103 0.0.into()
20104 };
20105
20106 let show_code_actions = self
20107 .show_code_actions
20108 .unwrap_or(gutter_settings.code_actions);
20109
20110 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20111 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20112
20113 let git_blame_entries_width =
20114 self.git_blame_gutter_max_author_length
20115 .map(|max_author_length| {
20116 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20117 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20118
20119 /// The number of characters to dedicate to gaps and margins.
20120 const SPACING_WIDTH: usize = 4;
20121
20122 let max_char_count = max_author_length.min(renderer.max_author_length())
20123 + ::git::SHORT_SHA_LENGTH
20124 + MAX_RELATIVE_TIMESTAMP.len()
20125 + SPACING_WIDTH;
20126
20127 em_advance * max_char_count
20128 });
20129
20130 let is_singleton = self.buffer_snapshot.is_singleton();
20131
20132 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20133 left_padding += if !is_singleton {
20134 em_width * 4.0
20135 } else if show_code_actions || show_runnables || show_breakpoints {
20136 em_width * 3.0
20137 } else if show_git_gutter && show_line_numbers {
20138 em_width * 2.0
20139 } else if show_git_gutter || show_line_numbers {
20140 em_width
20141 } else {
20142 px(0.)
20143 };
20144
20145 let shows_folds = is_singleton && gutter_settings.folds;
20146
20147 let right_padding = if shows_folds && show_line_numbers {
20148 em_width * 4.0
20149 } else if shows_folds || (!is_singleton && show_line_numbers) {
20150 em_width * 3.0
20151 } else if show_line_numbers {
20152 em_width
20153 } else {
20154 px(0.)
20155 };
20156
20157 Some(GutterDimensions {
20158 left_padding,
20159 right_padding,
20160 width: line_gutter_width + left_padding + right_padding,
20161 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20162 git_blame_entries_width,
20163 })
20164 }
20165
20166 pub fn render_crease_toggle(
20167 &self,
20168 buffer_row: MultiBufferRow,
20169 row_contains_cursor: bool,
20170 editor: Entity<Editor>,
20171 window: &mut Window,
20172 cx: &mut App,
20173 ) -> Option<AnyElement> {
20174 let folded = self.is_line_folded(buffer_row);
20175 let mut is_foldable = false;
20176
20177 if let Some(crease) = self
20178 .crease_snapshot
20179 .query_row(buffer_row, &self.buffer_snapshot)
20180 {
20181 is_foldable = true;
20182 match crease {
20183 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20184 if let Some(render_toggle) = render_toggle {
20185 let toggle_callback =
20186 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20187 if folded {
20188 editor.update(cx, |editor, cx| {
20189 editor.fold_at(buffer_row, window, cx)
20190 });
20191 } else {
20192 editor.update(cx, |editor, cx| {
20193 editor.unfold_at(buffer_row, window, cx)
20194 });
20195 }
20196 });
20197 return Some((render_toggle)(
20198 buffer_row,
20199 folded,
20200 toggle_callback,
20201 window,
20202 cx,
20203 ));
20204 }
20205 }
20206 }
20207 }
20208
20209 is_foldable |= self.starts_indent(buffer_row);
20210
20211 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20212 Some(
20213 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20214 .toggle_state(folded)
20215 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20216 if folded {
20217 this.unfold_at(buffer_row, window, cx);
20218 } else {
20219 this.fold_at(buffer_row, window, cx);
20220 }
20221 }))
20222 .into_any_element(),
20223 )
20224 } else {
20225 None
20226 }
20227 }
20228
20229 pub fn render_crease_trailer(
20230 &self,
20231 buffer_row: MultiBufferRow,
20232 window: &mut Window,
20233 cx: &mut App,
20234 ) -> Option<AnyElement> {
20235 let folded = self.is_line_folded(buffer_row);
20236 if let Crease::Inline { render_trailer, .. } = self
20237 .crease_snapshot
20238 .query_row(buffer_row, &self.buffer_snapshot)?
20239 {
20240 let render_trailer = render_trailer.as_ref()?;
20241 Some(render_trailer(buffer_row, folded, window, cx))
20242 } else {
20243 None
20244 }
20245 }
20246}
20247
20248impl Deref for EditorSnapshot {
20249 type Target = DisplaySnapshot;
20250
20251 fn deref(&self) -> &Self::Target {
20252 &self.display_snapshot
20253 }
20254}
20255
20256#[derive(Clone, Debug, PartialEq, Eq)]
20257pub enum EditorEvent {
20258 InputIgnored {
20259 text: Arc<str>,
20260 },
20261 InputHandled {
20262 utf16_range_to_replace: Option<Range<isize>>,
20263 text: Arc<str>,
20264 },
20265 ExcerptsAdded {
20266 buffer: Entity<Buffer>,
20267 predecessor: ExcerptId,
20268 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20269 },
20270 ExcerptsRemoved {
20271 ids: Vec<ExcerptId>,
20272 removed_buffer_ids: Vec<BufferId>,
20273 },
20274 BufferFoldToggled {
20275 ids: Vec<ExcerptId>,
20276 folded: bool,
20277 },
20278 ExcerptsEdited {
20279 ids: Vec<ExcerptId>,
20280 },
20281 ExcerptsExpanded {
20282 ids: Vec<ExcerptId>,
20283 },
20284 BufferEdited,
20285 Edited {
20286 transaction_id: clock::Lamport,
20287 },
20288 Reparsed(BufferId),
20289 Focused,
20290 FocusedIn,
20291 Blurred,
20292 DirtyChanged,
20293 Saved,
20294 TitleChanged,
20295 DiffBaseChanged,
20296 SelectionsChanged {
20297 local: bool,
20298 },
20299 ScrollPositionChanged {
20300 local: bool,
20301 autoscroll: bool,
20302 },
20303 Closed,
20304 TransactionUndone {
20305 transaction_id: clock::Lamport,
20306 },
20307 TransactionBegun {
20308 transaction_id: clock::Lamport,
20309 },
20310 Reloaded,
20311 CursorShapeChanged,
20312 PushedToNavHistory {
20313 anchor: Anchor,
20314 is_deactivate: bool,
20315 },
20316}
20317
20318impl EventEmitter<EditorEvent> for Editor {}
20319
20320impl Focusable for Editor {
20321 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20322 self.focus_handle.clone()
20323 }
20324}
20325
20326impl Render for Editor {
20327 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20328 let settings = ThemeSettings::get_global(cx);
20329
20330 let mut text_style = match self.mode {
20331 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20332 color: cx.theme().colors().editor_foreground,
20333 font_family: settings.ui_font.family.clone(),
20334 font_features: settings.ui_font.features.clone(),
20335 font_fallbacks: settings.ui_font.fallbacks.clone(),
20336 font_size: rems(0.875).into(),
20337 font_weight: settings.ui_font.weight,
20338 line_height: relative(settings.buffer_line_height.value()),
20339 ..Default::default()
20340 },
20341 EditorMode::Full { .. } => TextStyle {
20342 color: cx.theme().colors().editor_foreground,
20343 font_family: settings.buffer_font.family.clone(),
20344 font_features: settings.buffer_font.features.clone(),
20345 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20346 font_size: settings.buffer_font_size(cx).into(),
20347 font_weight: settings.buffer_font.weight,
20348 line_height: relative(settings.buffer_line_height.value()),
20349 ..Default::default()
20350 },
20351 };
20352 if let Some(text_style_refinement) = &self.text_style_refinement {
20353 text_style.refine(text_style_refinement)
20354 }
20355
20356 let background = match self.mode {
20357 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20358 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20359 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20360 };
20361
20362 EditorElement::new(
20363 &cx.entity(),
20364 EditorStyle {
20365 background,
20366 local_player: cx.theme().players().local(),
20367 text: text_style,
20368 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20369 syntax: cx.theme().syntax().clone(),
20370 status: cx.theme().status().clone(),
20371 inlay_hints_style: make_inlay_hints_style(cx),
20372 inline_completion_styles: make_suggestion_styles(cx),
20373 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20374 },
20375 )
20376 }
20377}
20378
20379impl EntityInputHandler for Editor {
20380 fn text_for_range(
20381 &mut self,
20382 range_utf16: Range<usize>,
20383 adjusted_range: &mut Option<Range<usize>>,
20384 _: &mut Window,
20385 cx: &mut Context<Self>,
20386 ) -> Option<String> {
20387 let snapshot = self.buffer.read(cx).read(cx);
20388 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20389 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20390 if (start.0..end.0) != range_utf16 {
20391 adjusted_range.replace(start.0..end.0);
20392 }
20393 Some(snapshot.text_for_range(start..end).collect())
20394 }
20395
20396 fn selected_text_range(
20397 &mut self,
20398 ignore_disabled_input: bool,
20399 _: &mut Window,
20400 cx: &mut Context<Self>,
20401 ) -> Option<UTF16Selection> {
20402 // Prevent the IME menu from appearing when holding down an alphabetic key
20403 // while input is disabled.
20404 if !ignore_disabled_input && !self.input_enabled {
20405 return None;
20406 }
20407
20408 let selection = self.selections.newest::<OffsetUtf16>(cx);
20409 let range = selection.range();
20410
20411 Some(UTF16Selection {
20412 range: range.start.0..range.end.0,
20413 reversed: selection.reversed,
20414 })
20415 }
20416
20417 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20418 let snapshot = self.buffer.read(cx).read(cx);
20419 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20420 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20421 }
20422
20423 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20424 self.clear_highlights::<InputComposition>(cx);
20425 self.ime_transaction.take();
20426 }
20427
20428 fn replace_text_in_range(
20429 &mut self,
20430 range_utf16: Option<Range<usize>>,
20431 text: &str,
20432 window: &mut Window,
20433 cx: &mut Context<Self>,
20434 ) {
20435 if !self.input_enabled {
20436 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20437 return;
20438 }
20439
20440 self.transact(window, cx, |this, window, cx| {
20441 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20442 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20443 Some(this.selection_replacement_ranges(range_utf16, cx))
20444 } else {
20445 this.marked_text_ranges(cx)
20446 };
20447
20448 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20449 let newest_selection_id = this.selections.newest_anchor().id;
20450 this.selections
20451 .all::<OffsetUtf16>(cx)
20452 .iter()
20453 .zip(ranges_to_replace.iter())
20454 .find_map(|(selection, range)| {
20455 if selection.id == newest_selection_id {
20456 Some(
20457 (range.start.0 as isize - selection.head().0 as isize)
20458 ..(range.end.0 as isize - selection.head().0 as isize),
20459 )
20460 } else {
20461 None
20462 }
20463 })
20464 });
20465
20466 cx.emit(EditorEvent::InputHandled {
20467 utf16_range_to_replace: range_to_replace,
20468 text: text.into(),
20469 });
20470
20471 if let Some(new_selected_ranges) = new_selected_ranges {
20472 this.change_selections(None, window, cx, |selections| {
20473 selections.select_ranges(new_selected_ranges)
20474 });
20475 this.backspace(&Default::default(), window, cx);
20476 }
20477
20478 this.handle_input(text, window, cx);
20479 });
20480
20481 if let Some(transaction) = self.ime_transaction {
20482 self.buffer.update(cx, |buffer, cx| {
20483 buffer.group_until_transaction(transaction, cx);
20484 });
20485 }
20486
20487 self.unmark_text(window, cx);
20488 }
20489
20490 fn replace_and_mark_text_in_range(
20491 &mut self,
20492 range_utf16: Option<Range<usize>>,
20493 text: &str,
20494 new_selected_range_utf16: Option<Range<usize>>,
20495 window: &mut Window,
20496 cx: &mut Context<Self>,
20497 ) {
20498 if !self.input_enabled {
20499 return;
20500 }
20501
20502 let transaction = self.transact(window, cx, |this, window, cx| {
20503 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20504 let snapshot = this.buffer.read(cx).read(cx);
20505 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20506 for marked_range in &mut marked_ranges {
20507 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20508 marked_range.start.0 += relative_range_utf16.start;
20509 marked_range.start =
20510 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20511 marked_range.end =
20512 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20513 }
20514 }
20515 Some(marked_ranges)
20516 } else if let Some(range_utf16) = range_utf16 {
20517 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20518 Some(this.selection_replacement_ranges(range_utf16, cx))
20519 } else {
20520 None
20521 };
20522
20523 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20524 let newest_selection_id = this.selections.newest_anchor().id;
20525 this.selections
20526 .all::<OffsetUtf16>(cx)
20527 .iter()
20528 .zip(ranges_to_replace.iter())
20529 .find_map(|(selection, range)| {
20530 if selection.id == newest_selection_id {
20531 Some(
20532 (range.start.0 as isize - selection.head().0 as isize)
20533 ..(range.end.0 as isize - selection.head().0 as isize),
20534 )
20535 } else {
20536 None
20537 }
20538 })
20539 });
20540
20541 cx.emit(EditorEvent::InputHandled {
20542 utf16_range_to_replace: range_to_replace,
20543 text: text.into(),
20544 });
20545
20546 if let Some(ranges) = ranges_to_replace {
20547 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20548 }
20549
20550 let marked_ranges = {
20551 let snapshot = this.buffer.read(cx).read(cx);
20552 this.selections
20553 .disjoint_anchors()
20554 .iter()
20555 .map(|selection| {
20556 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20557 })
20558 .collect::<Vec<_>>()
20559 };
20560
20561 if text.is_empty() {
20562 this.unmark_text(window, cx);
20563 } else {
20564 this.highlight_text::<InputComposition>(
20565 marked_ranges.clone(),
20566 HighlightStyle {
20567 underline: Some(UnderlineStyle {
20568 thickness: px(1.),
20569 color: None,
20570 wavy: false,
20571 }),
20572 ..Default::default()
20573 },
20574 cx,
20575 );
20576 }
20577
20578 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20579 let use_autoclose = this.use_autoclose;
20580 let use_auto_surround = this.use_auto_surround;
20581 this.set_use_autoclose(false);
20582 this.set_use_auto_surround(false);
20583 this.handle_input(text, window, cx);
20584 this.set_use_autoclose(use_autoclose);
20585 this.set_use_auto_surround(use_auto_surround);
20586
20587 if let Some(new_selected_range) = new_selected_range_utf16 {
20588 let snapshot = this.buffer.read(cx).read(cx);
20589 let new_selected_ranges = marked_ranges
20590 .into_iter()
20591 .map(|marked_range| {
20592 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20593 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20594 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20595 snapshot.clip_offset_utf16(new_start, Bias::Left)
20596 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20597 })
20598 .collect::<Vec<_>>();
20599
20600 drop(snapshot);
20601 this.change_selections(None, window, cx, |selections| {
20602 selections.select_ranges(new_selected_ranges)
20603 });
20604 }
20605 });
20606
20607 self.ime_transaction = self.ime_transaction.or(transaction);
20608 if let Some(transaction) = self.ime_transaction {
20609 self.buffer.update(cx, |buffer, cx| {
20610 buffer.group_until_transaction(transaction, cx);
20611 });
20612 }
20613
20614 if self.text_highlights::<InputComposition>(cx).is_none() {
20615 self.ime_transaction.take();
20616 }
20617 }
20618
20619 fn bounds_for_range(
20620 &mut self,
20621 range_utf16: Range<usize>,
20622 element_bounds: gpui::Bounds<Pixels>,
20623 window: &mut Window,
20624 cx: &mut Context<Self>,
20625 ) -> Option<gpui::Bounds<Pixels>> {
20626 let text_layout_details = self.text_layout_details(window);
20627 let gpui::Size {
20628 width: em_width,
20629 height: line_height,
20630 } = self.character_size(window);
20631
20632 let snapshot = self.snapshot(window, cx);
20633 let scroll_position = snapshot.scroll_position();
20634 let scroll_left = scroll_position.x * em_width;
20635
20636 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20637 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20638 + self.gutter_dimensions.width
20639 + self.gutter_dimensions.margin;
20640 let y = line_height * (start.row().as_f32() - scroll_position.y);
20641
20642 Some(Bounds {
20643 origin: element_bounds.origin + point(x, y),
20644 size: size(em_width, line_height),
20645 })
20646 }
20647
20648 fn character_index_for_point(
20649 &mut self,
20650 point: gpui::Point<Pixels>,
20651 _window: &mut Window,
20652 _cx: &mut Context<Self>,
20653 ) -> Option<usize> {
20654 let position_map = self.last_position_map.as_ref()?;
20655 if !position_map.text_hitbox.contains(&point) {
20656 return None;
20657 }
20658 let display_point = position_map.point_for_position(point).previous_valid;
20659 let anchor = position_map
20660 .snapshot
20661 .display_point_to_anchor(display_point, Bias::Left);
20662 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20663 Some(utf16_offset.0)
20664 }
20665}
20666
20667trait SelectionExt {
20668 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20669 fn spanned_rows(
20670 &self,
20671 include_end_if_at_line_start: bool,
20672 map: &DisplaySnapshot,
20673 ) -> Range<MultiBufferRow>;
20674}
20675
20676impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20677 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20678 let start = self
20679 .start
20680 .to_point(&map.buffer_snapshot)
20681 .to_display_point(map);
20682 let end = self
20683 .end
20684 .to_point(&map.buffer_snapshot)
20685 .to_display_point(map);
20686 if self.reversed {
20687 end..start
20688 } else {
20689 start..end
20690 }
20691 }
20692
20693 fn spanned_rows(
20694 &self,
20695 include_end_if_at_line_start: bool,
20696 map: &DisplaySnapshot,
20697 ) -> Range<MultiBufferRow> {
20698 let start = self.start.to_point(&map.buffer_snapshot);
20699 let mut end = self.end.to_point(&map.buffer_snapshot);
20700 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20701 end.row -= 1;
20702 }
20703
20704 let buffer_start = map.prev_line_boundary(start).0;
20705 let buffer_end = map.next_line_boundary(end).0;
20706 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20707 }
20708}
20709
20710impl<T: InvalidationRegion> InvalidationStack<T> {
20711 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20712 where
20713 S: Clone + ToOffset,
20714 {
20715 while let Some(region) = self.last() {
20716 let all_selections_inside_invalidation_ranges =
20717 if selections.len() == region.ranges().len() {
20718 selections
20719 .iter()
20720 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20721 .all(|(selection, invalidation_range)| {
20722 let head = selection.head().to_offset(buffer);
20723 invalidation_range.start <= head && invalidation_range.end >= head
20724 })
20725 } else {
20726 false
20727 };
20728
20729 if all_selections_inside_invalidation_ranges {
20730 break;
20731 } else {
20732 self.pop();
20733 }
20734 }
20735 }
20736}
20737
20738impl<T> Default for InvalidationStack<T> {
20739 fn default() -> Self {
20740 Self(Default::default())
20741 }
20742}
20743
20744impl<T> Deref for InvalidationStack<T> {
20745 type Target = Vec<T>;
20746
20747 fn deref(&self) -> &Self::Target {
20748 &self.0
20749 }
20750}
20751
20752impl<T> DerefMut for InvalidationStack<T> {
20753 fn deref_mut(&mut self) -> &mut Self::Target {
20754 &mut self.0
20755 }
20756}
20757
20758impl InvalidationRegion for SnippetState {
20759 fn ranges(&self) -> &[Range<Anchor>] {
20760 &self.ranges[self.active_index]
20761 }
20762}
20763
20764fn inline_completion_edit_text(
20765 current_snapshot: &BufferSnapshot,
20766 edits: &[(Range<Anchor>, String)],
20767 edit_preview: &EditPreview,
20768 include_deletions: bool,
20769 cx: &App,
20770) -> HighlightedText {
20771 let edits = edits
20772 .iter()
20773 .map(|(anchor, text)| {
20774 (
20775 anchor.start.text_anchor..anchor.end.text_anchor,
20776 text.clone(),
20777 )
20778 })
20779 .collect::<Vec<_>>();
20780
20781 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20782}
20783
20784pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20785 match severity {
20786 DiagnosticSeverity::ERROR => colors.error,
20787 DiagnosticSeverity::WARNING => colors.warning,
20788 DiagnosticSeverity::INFORMATION => colors.info,
20789 DiagnosticSeverity::HINT => colors.info,
20790 _ => colors.ignored,
20791 }
20792}
20793
20794pub fn styled_runs_for_code_label<'a>(
20795 label: &'a CodeLabel,
20796 syntax_theme: &'a theme::SyntaxTheme,
20797) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20798 let fade_out = HighlightStyle {
20799 fade_out: Some(0.35),
20800 ..Default::default()
20801 };
20802
20803 let mut prev_end = label.filter_range.end;
20804 label
20805 .runs
20806 .iter()
20807 .enumerate()
20808 .flat_map(move |(ix, (range, highlight_id))| {
20809 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20810 style
20811 } else {
20812 return Default::default();
20813 };
20814 let mut muted_style = style;
20815 muted_style.highlight(fade_out);
20816
20817 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20818 if range.start >= label.filter_range.end {
20819 if range.start > prev_end {
20820 runs.push((prev_end..range.start, fade_out));
20821 }
20822 runs.push((range.clone(), muted_style));
20823 } else if range.end <= label.filter_range.end {
20824 runs.push((range.clone(), style));
20825 } else {
20826 runs.push((range.start..label.filter_range.end, style));
20827 runs.push((label.filter_range.end..range.end, muted_style));
20828 }
20829 prev_end = cmp::max(prev_end, range.end);
20830
20831 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20832 runs.push((prev_end..label.text.len(), fade_out));
20833 }
20834
20835 runs
20836 })
20837}
20838
20839pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20840 let mut prev_index = 0;
20841 let mut prev_codepoint: Option<char> = None;
20842 text.char_indices()
20843 .chain([(text.len(), '\0')])
20844 .filter_map(move |(index, codepoint)| {
20845 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20846 let is_boundary = index == text.len()
20847 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20848 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20849 if is_boundary {
20850 let chunk = &text[prev_index..index];
20851 prev_index = index;
20852 Some(chunk)
20853 } else {
20854 None
20855 }
20856 })
20857}
20858
20859pub trait RangeToAnchorExt: Sized {
20860 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20861
20862 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20863 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20864 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20865 }
20866}
20867
20868impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20869 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20870 let start_offset = self.start.to_offset(snapshot);
20871 let end_offset = self.end.to_offset(snapshot);
20872 if start_offset == end_offset {
20873 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20874 } else {
20875 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20876 }
20877 }
20878}
20879
20880pub trait RowExt {
20881 fn as_f32(&self) -> f32;
20882
20883 fn next_row(&self) -> Self;
20884
20885 fn previous_row(&self) -> Self;
20886
20887 fn minus(&self, other: Self) -> u32;
20888}
20889
20890impl RowExt for DisplayRow {
20891 fn as_f32(&self) -> f32 {
20892 self.0 as f32
20893 }
20894
20895 fn next_row(&self) -> Self {
20896 Self(self.0 + 1)
20897 }
20898
20899 fn previous_row(&self) -> Self {
20900 Self(self.0.saturating_sub(1))
20901 }
20902
20903 fn minus(&self, other: Self) -> u32 {
20904 self.0 - other.0
20905 }
20906}
20907
20908impl RowExt for MultiBufferRow {
20909 fn as_f32(&self) -> f32 {
20910 self.0 as f32
20911 }
20912
20913 fn next_row(&self) -> Self {
20914 Self(self.0 + 1)
20915 }
20916
20917 fn previous_row(&self) -> Self {
20918 Self(self.0.saturating_sub(1))
20919 }
20920
20921 fn minus(&self, other: Self) -> u32 {
20922 self.0 - other.0
20923 }
20924}
20925
20926trait RowRangeExt {
20927 type Row;
20928
20929 fn len(&self) -> usize;
20930
20931 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20932}
20933
20934impl RowRangeExt for Range<MultiBufferRow> {
20935 type Row = MultiBufferRow;
20936
20937 fn len(&self) -> usize {
20938 (self.end.0 - self.start.0) as usize
20939 }
20940
20941 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20942 (self.start.0..self.end.0).map(MultiBufferRow)
20943 }
20944}
20945
20946impl RowRangeExt for Range<DisplayRow> {
20947 type Row = DisplayRow;
20948
20949 fn len(&self) -> usize {
20950 (self.end.0 - self.start.0) as usize
20951 }
20952
20953 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20954 (self.start.0..self.end.0).map(DisplayRow)
20955 }
20956}
20957
20958/// If select range has more than one line, we
20959/// just point the cursor to range.start.
20960fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20961 if range.start.row == range.end.row {
20962 range
20963 } else {
20964 range.start..range.start
20965 }
20966}
20967pub struct KillRing(ClipboardItem);
20968impl Global for KillRing {}
20969
20970const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20971
20972enum BreakpointPromptEditAction {
20973 Log,
20974 Condition,
20975 HitCondition,
20976}
20977
20978struct BreakpointPromptEditor {
20979 pub(crate) prompt: Entity<Editor>,
20980 editor: WeakEntity<Editor>,
20981 breakpoint_anchor: Anchor,
20982 breakpoint: Breakpoint,
20983 edit_action: BreakpointPromptEditAction,
20984 block_ids: HashSet<CustomBlockId>,
20985 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20986 _subscriptions: Vec<Subscription>,
20987}
20988
20989impl BreakpointPromptEditor {
20990 const MAX_LINES: u8 = 4;
20991
20992 fn new(
20993 editor: WeakEntity<Editor>,
20994 breakpoint_anchor: Anchor,
20995 breakpoint: Breakpoint,
20996 edit_action: BreakpointPromptEditAction,
20997 window: &mut Window,
20998 cx: &mut Context<Self>,
20999 ) -> Self {
21000 let base_text = match edit_action {
21001 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21002 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21003 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21004 }
21005 .map(|msg| msg.to_string())
21006 .unwrap_or_default();
21007
21008 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21009 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21010
21011 let prompt = cx.new(|cx| {
21012 let mut prompt = Editor::new(
21013 EditorMode::AutoHeight {
21014 max_lines: Self::MAX_LINES as usize,
21015 },
21016 buffer,
21017 None,
21018 window,
21019 cx,
21020 );
21021 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21022 prompt.set_show_cursor_when_unfocused(false, cx);
21023 prompt.set_placeholder_text(
21024 match edit_action {
21025 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21026 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21027 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21028 },
21029 cx,
21030 );
21031
21032 prompt
21033 });
21034
21035 Self {
21036 prompt,
21037 editor,
21038 breakpoint_anchor,
21039 breakpoint,
21040 edit_action,
21041 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
21042 block_ids: Default::default(),
21043 _subscriptions: vec![],
21044 }
21045 }
21046
21047 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21048 self.block_ids.extend(block_ids)
21049 }
21050
21051 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21052 if let Some(editor) = self.editor.upgrade() {
21053 let message = self
21054 .prompt
21055 .read(cx)
21056 .buffer
21057 .read(cx)
21058 .as_singleton()
21059 .expect("A multi buffer in breakpoint prompt isn't possible")
21060 .read(cx)
21061 .as_rope()
21062 .to_string();
21063
21064 editor.update(cx, |editor, cx| {
21065 editor.edit_breakpoint_at_anchor(
21066 self.breakpoint_anchor,
21067 self.breakpoint.clone(),
21068 match self.edit_action {
21069 BreakpointPromptEditAction::Log => {
21070 BreakpointEditAction::EditLogMessage(message.into())
21071 }
21072 BreakpointPromptEditAction::Condition => {
21073 BreakpointEditAction::EditCondition(message.into())
21074 }
21075 BreakpointPromptEditAction::HitCondition => {
21076 BreakpointEditAction::EditHitCondition(message.into())
21077 }
21078 },
21079 cx,
21080 );
21081
21082 editor.remove_blocks(self.block_ids.clone(), None, cx);
21083 cx.focus_self(window);
21084 });
21085 }
21086 }
21087
21088 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21089 self.editor
21090 .update(cx, |editor, cx| {
21091 editor.remove_blocks(self.block_ids.clone(), None, cx);
21092 window.focus(&editor.focus_handle);
21093 })
21094 .log_err();
21095 }
21096
21097 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21098 let settings = ThemeSettings::get_global(cx);
21099 let text_style = TextStyle {
21100 color: if self.prompt.read(cx).read_only(cx) {
21101 cx.theme().colors().text_disabled
21102 } else {
21103 cx.theme().colors().text
21104 },
21105 font_family: settings.buffer_font.family.clone(),
21106 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21107 font_size: settings.buffer_font_size(cx).into(),
21108 font_weight: settings.buffer_font.weight,
21109 line_height: relative(settings.buffer_line_height.value()),
21110 ..Default::default()
21111 };
21112 EditorElement::new(
21113 &self.prompt,
21114 EditorStyle {
21115 background: cx.theme().colors().editor_background,
21116 local_player: cx.theme().players().local(),
21117 text: text_style,
21118 ..Default::default()
21119 },
21120 )
21121 }
21122}
21123
21124impl Render for BreakpointPromptEditor {
21125 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21126 let gutter_dimensions = *self.gutter_dimensions.lock();
21127 h_flex()
21128 .key_context("Editor")
21129 .bg(cx.theme().colors().editor_background)
21130 .border_y_1()
21131 .border_color(cx.theme().status().info_border)
21132 .size_full()
21133 .py(window.line_height() / 2.5)
21134 .on_action(cx.listener(Self::confirm))
21135 .on_action(cx.listener(Self::cancel))
21136 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21137 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21138 }
21139}
21140
21141impl Focusable for BreakpointPromptEditor {
21142 fn focus_handle(&self, cx: &App) -> FocusHandle {
21143 self.prompt.focus_handle(cx)
21144 }
21145}
21146
21147fn all_edits_insertions_or_deletions(
21148 edits: &Vec<(Range<Anchor>, String)>,
21149 snapshot: &MultiBufferSnapshot,
21150) -> bool {
21151 let mut all_insertions = true;
21152 let mut all_deletions = true;
21153
21154 for (range, new_text) in edits.iter() {
21155 let range_is_empty = range.to_offset(&snapshot).is_empty();
21156 let text_is_empty = new_text.is_empty();
21157
21158 if range_is_empty != text_is_empty {
21159 if range_is_empty {
21160 all_deletions = false;
21161 } else {
21162 all_insertions = false;
21163 }
21164 } else {
21165 return false;
21166 }
21167
21168 if !all_insertions && !all_deletions {
21169 return false;
21170 }
21171 }
21172 all_insertions || all_deletions
21173}
21174
21175struct MissingEditPredictionKeybindingTooltip;
21176
21177impl Render for MissingEditPredictionKeybindingTooltip {
21178 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21179 ui::tooltip_container(window, cx, |container, _, cx| {
21180 container
21181 .flex_shrink_0()
21182 .max_w_80()
21183 .min_h(rems_from_px(124.))
21184 .justify_between()
21185 .child(
21186 v_flex()
21187 .flex_1()
21188 .text_ui_sm(cx)
21189 .child(Label::new("Conflict with Accept Keybinding"))
21190 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21191 )
21192 .child(
21193 h_flex()
21194 .pb_1()
21195 .gap_1()
21196 .items_end()
21197 .w_full()
21198 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21199 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21200 }))
21201 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21202 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21203 })),
21204 )
21205 })
21206 }
21207}
21208
21209#[derive(Debug, Clone, Copy, PartialEq)]
21210pub struct LineHighlight {
21211 pub background: Background,
21212 pub border: Option<gpui::Hsla>,
21213 pub include_gutter: bool,
21214 pub type_id: Option<TypeId>,
21215}
21216
21217fn render_diff_hunk_controls(
21218 row: u32,
21219 status: &DiffHunkStatus,
21220 hunk_range: Range<Anchor>,
21221 is_created_file: bool,
21222 line_height: Pixels,
21223 editor: &Entity<Editor>,
21224 _window: &mut Window,
21225 cx: &mut App,
21226) -> AnyElement {
21227 h_flex()
21228 .h(line_height)
21229 .mr_1()
21230 .gap_1()
21231 .px_0p5()
21232 .pb_1()
21233 .border_x_1()
21234 .border_b_1()
21235 .border_color(cx.theme().colors().border_variant)
21236 .rounded_b_lg()
21237 .bg(cx.theme().colors().editor_background)
21238 .gap_1()
21239 .occlude()
21240 .shadow_md()
21241 .child(if status.has_secondary_hunk() {
21242 Button::new(("stage", row as u64), "Stage")
21243 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21244 .tooltip({
21245 let focus_handle = editor.focus_handle(cx);
21246 move |window, cx| {
21247 Tooltip::for_action_in(
21248 "Stage Hunk",
21249 &::git::ToggleStaged,
21250 &focus_handle,
21251 window,
21252 cx,
21253 )
21254 }
21255 })
21256 .on_click({
21257 let editor = editor.clone();
21258 move |_event, _window, cx| {
21259 editor.update(cx, |editor, cx| {
21260 editor.stage_or_unstage_diff_hunks(
21261 true,
21262 vec![hunk_range.start..hunk_range.start],
21263 cx,
21264 );
21265 });
21266 }
21267 })
21268 } else {
21269 Button::new(("unstage", row as u64), "Unstage")
21270 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21271 .tooltip({
21272 let focus_handle = editor.focus_handle(cx);
21273 move |window, cx| {
21274 Tooltip::for_action_in(
21275 "Unstage Hunk",
21276 &::git::ToggleStaged,
21277 &focus_handle,
21278 window,
21279 cx,
21280 )
21281 }
21282 })
21283 .on_click({
21284 let editor = editor.clone();
21285 move |_event, _window, cx| {
21286 editor.update(cx, |editor, cx| {
21287 editor.stage_or_unstage_diff_hunks(
21288 false,
21289 vec![hunk_range.start..hunk_range.start],
21290 cx,
21291 );
21292 });
21293 }
21294 })
21295 })
21296 .child(
21297 Button::new(("restore", row as u64), "Restore")
21298 .tooltip({
21299 let focus_handle = editor.focus_handle(cx);
21300 move |window, cx| {
21301 Tooltip::for_action_in(
21302 "Restore Hunk",
21303 &::git::Restore,
21304 &focus_handle,
21305 window,
21306 cx,
21307 )
21308 }
21309 })
21310 .on_click({
21311 let editor = editor.clone();
21312 move |_event, window, cx| {
21313 editor.update(cx, |editor, cx| {
21314 let snapshot = editor.snapshot(window, cx);
21315 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21316 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21317 });
21318 }
21319 })
21320 .disabled(is_created_file),
21321 )
21322 .when(
21323 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21324 |el| {
21325 el.child(
21326 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21327 .shape(IconButtonShape::Square)
21328 .icon_size(IconSize::Small)
21329 // .disabled(!has_multiple_hunks)
21330 .tooltip({
21331 let focus_handle = editor.focus_handle(cx);
21332 move |window, cx| {
21333 Tooltip::for_action_in(
21334 "Next Hunk",
21335 &GoToHunk,
21336 &focus_handle,
21337 window,
21338 cx,
21339 )
21340 }
21341 })
21342 .on_click({
21343 let editor = editor.clone();
21344 move |_event, window, cx| {
21345 editor.update(cx, |editor, cx| {
21346 let snapshot = editor.snapshot(window, cx);
21347 let position =
21348 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21349 editor.go_to_hunk_before_or_after_position(
21350 &snapshot,
21351 position,
21352 Direction::Next,
21353 window,
21354 cx,
21355 );
21356 editor.expand_selected_diff_hunks(cx);
21357 });
21358 }
21359 }),
21360 )
21361 .child(
21362 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21363 .shape(IconButtonShape::Square)
21364 .icon_size(IconSize::Small)
21365 // .disabled(!has_multiple_hunks)
21366 .tooltip({
21367 let focus_handle = editor.focus_handle(cx);
21368 move |window, cx| {
21369 Tooltip::for_action_in(
21370 "Previous Hunk",
21371 &GoToPreviousHunk,
21372 &focus_handle,
21373 window,
21374 cx,
21375 )
21376 }
21377 })
21378 .on_click({
21379 let editor = editor.clone();
21380 move |_event, window, cx| {
21381 editor.update(cx, |editor, cx| {
21382 let snapshot = editor.snapshot(window, cx);
21383 let point =
21384 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21385 editor.go_to_hunk_before_or_after_position(
21386 &snapshot,
21387 point,
21388 Direction::Prev,
21389 window,
21390 cx,
21391 );
21392 editor.expand_selected_diff_hunks(cx);
21393 });
21394 }
21395 }),
21396 )
21397 },
21398 )
21399 .into_any_element()
21400}