1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::ReplicaId;
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use display_map::*;
63pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
64use editor_settings::GoToDefinitionFallback;
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
67 ShowScrollbar,
68};
69pub use editor_settings_controls::*;
70use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
71pub use element::{
72 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
73};
74use feature_flags::{Debugger, FeatureFlagAppExt};
75use futures::{
76 FutureExt,
77 future::{self, Shared, join},
78};
79use fuzzy::StringMatchCandidate;
80
81use ::git::Restore;
82use code_context_menus::{
83 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
84 CompletionsMenu, ContextMenuOrigin,
85};
86use git::blame::{GitBlame, GlobalBlameRenderer};
87use gpui::{
88 Action, Animation, AnimationExt, AnyElement, AnyWeakEntity, App, AppContext,
89 AsyncWindowContext, AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry,
90 ClipboardItem, Context, DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter,
91 FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla,
92 KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render,
93 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
94 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
95 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
96};
97use highlight_matching_bracket::refresh_matching_bracket_highlights;
98use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
99pub use hover_popover::hover_markdown_style;
100use hover_popover::{HoverState, hide_hover};
101use indent_guides::ActiveIndentGuidesState;
102use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
103pub use inline_completion::Direction;
104use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
105pub use items::MAX_TAB_TITLE_LEN;
106use itertools::Itertools;
107use language::{
108 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
109 CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
110 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
111 TransactionId, TreeSitterOptions, WordsQuery,
112 language_settings::{
113 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
114 all_language_settings, language_settings,
115 },
116 point_from_lsp, text_diff_with_options,
117};
118use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
119use linked_editing_ranges::refresh_linked_ranges;
120use mouse_context_menu::MouseContextMenu;
121use persistence::DB;
122use project::{
123 ProjectPath,
124 debugger::{
125 breakpoint_store::{
126 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
127 },
128 session::{Session, SessionEvent},
129 },
130};
131
132pub use git::blame::BlameRenderer;
133pub use proposed_changes_editor::{
134 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
135};
136use smallvec::smallvec;
137use std::{cell::OnceCell, iter::Peekable};
138use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
139
140pub use lsp::CompletionContext;
141use lsp::{
142 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
143 InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
144};
145
146use language::BufferSnapshot;
147pub use lsp_ext::lsp_tasks;
148use movement::TextLayoutDetails;
149pub use multi_buffer::{
150 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
151 RowInfo, ToOffset, ToPoint,
152};
153use multi_buffer::{
154 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
155 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
156};
157use parking_lot::Mutex;
158use project::{
159 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
160 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
161 TaskSourceKind,
162 debugger::breakpoint_store::Breakpoint,
163 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
164 project_settings::{GitGutterSetting, ProjectSettings},
165};
166use rand::prelude::*;
167use rpc::{ErrorExt, proto::*};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
169use selections_collection::{
170 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
171};
172use serde::{Deserialize, Serialize};
173use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
174use smallvec::SmallVec;
175use snippet::Snippet;
176use std::sync::Arc;
177use std::{
178 any::TypeId,
179 borrow::Cow,
180 cell::RefCell,
181 cmp::{self, Ordering, Reverse},
182 mem,
183 num::NonZeroU32,
184 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
185 path::{Path, PathBuf},
186 rc::Rc,
187 time::{Duration, Instant},
188};
189pub use sum_tree::Bias;
190use sum_tree::TreeMap;
191use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
192use theme::{
193 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
194 observe_buffer_font_size_adjustment,
195};
196use ui::{
197 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
198 IconSize, Key, Tooltip, h_flex, prelude::*,
199};
200use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
201use workspace::{
202 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
203 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
204 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
205 item::{ItemHandle, PreviewTabsSettings},
206 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
207 searchable::SearchEvent,
208};
209
210use crate::hover_links::{find_url, find_url_from_range};
211use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
212
213pub const FILE_HEADER_HEIGHT: u32 = 2;
214pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
215pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
216const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
217const MAX_LINE_LEN: usize = 1024;
218const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
219const MAX_SELECTION_HISTORY_LEN: usize = 1024;
220pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
221#[doc(hidden)]
222pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
223const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
224
225pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
226pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
227pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
228
229pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
230pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
231pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
232
233pub type RenderDiffHunkControlsFn = Arc<
234 dyn Fn(
235 u32,
236 &DiffHunkStatus,
237 Range<Anchor>,
238 bool,
239 Pixels,
240 &Entity<Editor>,
241 &mut Window,
242 &mut App,
243 ) -> AnyElement,
244>;
245
246const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
247 alt: true,
248 shift: true,
249 control: false,
250 platform: false,
251 function: false,
252};
253
254struct InlineValueCache {
255 enabled: bool,
256 inlays: Vec<InlayId>,
257 refresh_task: Task<Option<()>>,
258}
259
260impl InlineValueCache {
261 fn new(enabled: bool) -> Self {
262 Self {
263 enabled,
264 inlays: Vec::new(),
265 refresh_task: Task::ready(None),
266 }
267 }
268}
269
270#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
271pub enum InlayId {
272 InlineCompletion(usize),
273 Hint(usize),
274 DebuggerValue(usize),
275}
276
277impl InlayId {
278 fn id(&self) -> usize {
279 match self {
280 Self::InlineCompletion(id) => *id,
281 Self::Hint(id) => *id,
282 Self::DebuggerValue(id) => *id,
283 }
284 }
285}
286
287pub enum DebugCurrentRowHighlight {}
288enum DocumentHighlightRead {}
289enum DocumentHighlightWrite {}
290enum InputComposition {}
291enum SelectedTextHighlight {}
292
293pub enum ConflictsOuter {}
294pub enum ConflictsOurs {}
295pub enum ConflictsTheirs {}
296pub enum ConflictsOursMarker {}
297pub enum ConflictsTheirsMarker {}
298
299#[derive(Debug, Copy, Clone, PartialEq, Eq)]
300pub enum Navigated {
301 Yes,
302 No,
303}
304
305impl Navigated {
306 pub fn from_bool(yes: bool) -> Navigated {
307 if yes { Navigated::Yes } else { Navigated::No }
308 }
309}
310
311#[derive(Debug, Clone, PartialEq, Eq)]
312enum DisplayDiffHunk {
313 Folded {
314 display_row: DisplayRow,
315 },
316 Unfolded {
317 is_created_file: bool,
318 diff_base_byte_range: Range<usize>,
319 display_row_range: Range<DisplayRow>,
320 multi_buffer_range: Range<Anchor>,
321 status: DiffHunkStatus,
322 },
323}
324
325pub enum HideMouseCursorOrigin {
326 TypingAction,
327 MovementAction,
328}
329
330pub fn init_settings(cx: &mut App) {
331 EditorSettings::register(cx);
332}
333
334pub fn init(cx: &mut App) {
335 init_settings(cx);
336
337 cx.set_global(GlobalBlameRenderer(Arc::new(())));
338
339 workspace::register_project_item::<Editor>(cx);
340 workspace::FollowableViewRegistry::register::<Editor>(cx);
341 workspace::register_serializable_item::<Editor>(cx);
342
343 cx.observe_new(
344 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
345 workspace.register_action(Editor::new_file);
346 workspace.register_action(Editor::new_file_vertical);
347 workspace.register_action(Editor::new_file_horizontal);
348 workspace.register_action(Editor::cancel_language_server_work);
349 },
350 )
351 .detach();
352
353 cx.on_action(move |_: &workspace::NewFile, cx| {
354 let app_state = workspace::AppState::global(cx);
355 if let Some(app_state) = app_state.upgrade() {
356 workspace::open_new(
357 Default::default(),
358 app_state,
359 cx,
360 |workspace, window, cx| {
361 Editor::new_file(workspace, &Default::default(), window, cx)
362 },
363 )
364 .detach();
365 }
366 });
367 cx.on_action(move |_: &workspace::NewWindow, cx| {
368 let app_state = workspace::AppState::global(cx);
369 if let Some(app_state) = app_state.upgrade() {
370 workspace::open_new(
371 Default::default(),
372 app_state,
373 cx,
374 |workspace, window, cx| {
375 cx.activate(true);
376 Editor::new_file(workspace, &Default::default(), window, cx)
377 },
378 )
379 .detach();
380 }
381 });
382}
383
384pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
385 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
386}
387
388pub trait DiagnosticRenderer {
389 fn render_group(
390 &self,
391 diagnostic_group: Vec<DiagnosticEntry<Point>>,
392 buffer_id: BufferId,
393 snapshot: EditorSnapshot,
394 editor: WeakEntity<Editor>,
395 cx: &mut App,
396 ) -> Vec<BlockProperties<Anchor>>;
397
398 fn render_hover(
399 &self,
400 diagnostic_group: Vec<DiagnosticEntry<Point>>,
401 range: Range<Point>,
402 buffer_id: BufferId,
403 cx: &mut App,
404 ) -> Option<Entity<markdown::Markdown>>;
405
406 fn open_link(
407 &self,
408 editor: &mut Editor,
409 link: SharedString,
410 window: &mut Window,
411 cx: &mut Context<Editor>,
412 );
413}
414
415pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
416
417impl GlobalDiagnosticRenderer {
418 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
419 cx.try_global::<Self>().map(|g| g.0.clone())
420 }
421}
422
423impl gpui::Global for GlobalDiagnosticRenderer {}
424pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
425 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
426}
427
428pub struct SearchWithinRange;
429
430trait InvalidationRegion {
431 fn ranges(&self) -> &[Range<Anchor>];
432}
433
434#[derive(Clone, Debug, PartialEq)]
435pub enum SelectPhase {
436 Begin {
437 position: DisplayPoint,
438 add: bool,
439 click_count: usize,
440 },
441 BeginColumnar {
442 position: DisplayPoint,
443 reset: bool,
444 goal_column: u32,
445 },
446 Extend {
447 position: DisplayPoint,
448 click_count: usize,
449 },
450 Update {
451 position: DisplayPoint,
452 goal_column: u32,
453 scroll_delta: gpui::Point<f32>,
454 },
455 End,
456}
457
458#[derive(Clone, Debug)]
459pub enum SelectMode {
460 Character,
461 Word(Range<Anchor>),
462 Line(Range<Anchor>),
463 All,
464}
465
466#[derive(Copy, Clone, PartialEq, Eq, Debug)]
467pub enum EditorMode {
468 SingleLine {
469 auto_width: bool,
470 },
471 AutoHeight {
472 max_lines: usize,
473 },
474 Full {
475 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
476 scale_ui_elements_with_buffer_font_size: bool,
477 /// When set to `true`, the editor will render a background for the active line.
478 show_active_line_background: bool,
479 /// When set to `true`, the editor's height will be determined by its content.
480 sized_by_content: bool,
481 },
482}
483
484impl EditorMode {
485 pub fn full() -> Self {
486 Self::Full {
487 scale_ui_elements_with_buffer_font_size: true,
488 show_active_line_background: true,
489 sized_by_content: false,
490 }
491 }
492
493 pub fn is_full(&self) -> bool {
494 matches!(self, Self::Full { .. })
495 }
496}
497
498#[derive(Copy, Clone, Debug)]
499pub enum SoftWrap {
500 /// Prefer not to wrap at all.
501 ///
502 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
503 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
504 GitDiff,
505 /// Prefer a single line generally, unless an overly long line is encountered.
506 None,
507 /// Soft wrap lines that exceed the editor width.
508 EditorWidth,
509 /// Soft wrap lines at the preferred line length.
510 Column(u32),
511 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
512 Bounded(u32),
513}
514
515#[derive(Clone)]
516pub struct EditorStyle {
517 pub background: Hsla,
518 pub local_player: PlayerColor,
519 pub text: TextStyle,
520 pub scrollbar_width: Pixels,
521 pub syntax: Arc<SyntaxTheme>,
522 pub status: StatusColors,
523 pub inlay_hints_style: HighlightStyle,
524 pub inline_completion_styles: InlineCompletionStyles,
525 pub unnecessary_code_fade: f32,
526}
527
528impl Default for EditorStyle {
529 fn default() -> Self {
530 Self {
531 background: Hsla::default(),
532 local_player: PlayerColor::default(),
533 text: TextStyle::default(),
534 scrollbar_width: Pixels::default(),
535 syntax: Default::default(),
536 // HACK: Status colors don't have a real default.
537 // We should look into removing the status colors from the editor
538 // style and retrieve them directly from the theme.
539 status: StatusColors::dark(),
540 inlay_hints_style: HighlightStyle::default(),
541 inline_completion_styles: InlineCompletionStyles {
542 insertion: HighlightStyle::default(),
543 whitespace: HighlightStyle::default(),
544 },
545 unnecessary_code_fade: Default::default(),
546 }
547 }
548}
549
550pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
551 let show_background = language_settings::language_settings(None, None, cx)
552 .inlay_hints
553 .show_background;
554
555 HighlightStyle {
556 color: Some(cx.theme().status().hint),
557 background_color: show_background.then(|| cx.theme().status().hint_background),
558 ..HighlightStyle::default()
559 }
560}
561
562pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
563 InlineCompletionStyles {
564 insertion: HighlightStyle {
565 color: Some(cx.theme().status().predictive),
566 ..HighlightStyle::default()
567 },
568 whitespace: HighlightStyle {
569 background_color: Some(cx.theme().status().created_background),
570 ..HighlightStyle::default()
571 },
572 }
573}
574
575type CompletionId = usize;
576
577pub(crate) enum EditDisplayMode {
578 TabAccept,
579 DiffPopover,
580 Inline,
581}
582
583enum InlineCompletion {
584 Edit {
585 edits: Vec<(Range<Anchor>, String)>,
586 edit_preview: Option<EditPreview>,
587 display_mode: EditDisplayMode,
588 snapshot: BufferSnapshot,
589 },
590 Move {
591 target: Anchor,
592 snapshot: BufferSnapshot,
593 },
594}
595
596struct InlineCompletionState {
597 inlay_ids: Vec<InlayId>,
598 completion: InlineCompletion,
599 completion_id: Option<SharedString>,
600 invalidation_range: Range<Anchor>,
601}
602
603enum EditPredictionSettings {
604 Disabled,
605 Enabled {
606 show_in_menu: bool,
607 preview_requires_modifier: bool,
608 },
609}
610
611enum InlineCompletionHighlight {}
612
613#[derive(Debug, Clone)]
614struct InlineDiagnostic {
615 message: SharedString,
616 group_id: usize,
617 is_primary: bool,
618 start: Point,
619 severity: DiagnosticSeverity,
620}
621
622pub enum MenuInlineCompletionsPolicy {
623 Never,
624 ByProvider,
625}
626
627pub enum EditPredictionPreview {
628 /// Modifier is not pressed
629 Inactive { released_too_fast: bool },
630 /// Modifier pressed
631 Active {
632 since: Instant,
633 previous_scroll_position: Option<ScrollAnchor>,
634 },
635}
636
637impl EditPredictionPreview {
638 pub fn released_too_fast(&self) -> bool {
639 match self {
640 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
641 EditPredictionPreview::Active { .. } => false,
642 }
643 }
644
645 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
646 if let EditPredictionPreview::Active {
647 previous_scroll_position,
648 ..
649 } = self
650 {
651 *previous_scroll_position = scroll_position;
652 }
653 }
654}
655
656pub struct ContextMenuOptions {
657 pub min_entries_visible: usize,
658 pub max_entries_visible: usize,
659 pub placement: Option<ContextMenuPlacement>,
660}
661
662#[derive(Debug, Clone, PartialEq, Eq)]
663pub enum ContextMenuPlacement {
664 Above,
665 Below,
666}
667
668#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
669struct EditorActionId(usize);
670
671impl EditorActionId {
672 pub fn post_inc(&mut self) -> Self {
673 let answer = self.0;
674
675 *self = Self(answer + 1);
676
677 Self(answer)
678 }
679}
680
681// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
682// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
683
684type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
685type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
686
687#[derive(Default)]
688struct ScrollbarMarkerState {
689 scrollbar_size: Size<Pixels>,
690 dirty: bool,
691 markers: Arc<[PaintQuad]>,
692 pending_refresh: Option<Task<Result<()>>>,
693}
694
695impl ScrollbarMarkerState {
696 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
697 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
698 }
699}
700
701#[derive(Clone, Debug)]
702struct RunnableTasks {
703 templates: Vec<(TaskSourceKind, TaskTemplate)>,
704 offset: multi_buffer::Anchor,
705 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
706 column: u32,
707 // Values of all named captures, including those starting with '_'
708 extra_variables: HashMap<String, String>,
709 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
710 context_range: Range<BufferOffset>,
711}
712
713impl RunnableTasks {
714 fn resolve<'a>(
715 &'a self,
716 cx: &'a task::TaskContext,
717 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
718 self.templates.iter().filter_map(|(kind, template)| {
719 template
720 .resolve_task(&kind.to_id_base(), cx)
721 .map(|task| (kind.clone(), task))
722 })
723 }
724}
725
726#[derive(Clone)]
727struct ResolvedTasks {
728 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
729 position: Anchor,
730}
731
732#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
733struct BufferOffset(usize);
734
735// Addons allow storing per-editor state in other crates (e.g. Vim)
736pub trait Addon: 'static {
737 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
738
739 fn render_buffer_header_controls(
740 &self,
741 _: &ExcerptInfo,
742 _: &Window,
743 _: &App,
744 ) -> Option<AnyElement> {
745 None
746 }
747
748 fn to_any(&self) -> &dyn std::any::Any;
749
750 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
751 None
752 }
753}
754
755/// A set of caret positions, registered when the editor was edited.
756pub struct ChangeList {
757 changes: Vec<Vec<Anchor>>,
758 /// Currently "selected" change.
759 position: Option<usize>,
760}
761
762impl ChangeList {
763 pub fn new() -> Self {
764 Self {
765 changes: Vec::new(),
766 position: None,
767 }
768 }
769
770 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
771 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
772 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
773 if self.changes.is_empty() {
774 return None;
775 }
776
777 let prev = self.position.unwrap_or(self.changes.len());
778 let next = if direction == Direction::Prev {
779 prev.saturating_sub(count)
780 } else {
781 (prev + count).min(self.changes.len() - 1)
782 };
783 self.position = Some(next);
784 self.changes.get(next).map(|anchors| anchors.as_slice())
785 }
786
787 /// Adds a new change to the list, resetting the change list position.
788 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
789 self.position.take();
790 if pop_state {
791 self.changes.pop();
792 }
793 self.changes.push(new_positions.clone());
794 }
795
796 pub fn last(&self) -> Option<&[Anchor]> {
797 self.changes.last().map(|anchors| anchors.as_slice())
798 }
799}
800
801/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
802///
803/// See the [module level documentation](self) for more information.
804pub struct Editor {
805 focus_handle: FocusHandle,
806 last_focused_descendant: Option<WeakFocusHandle>,
807 /// The text buffer being edited
808 buffer: Entity<MultiBuffer>,
809 /// Map of how text in the buffer should be displayed.
810 /// Handles soft wraps, folds, fake inlay text insertions, etc.
811 pub display_map: Entity<DisplayMap>,
812 pub selections: SelectionsCollection,
813 pub scroll_manager: ScrollManager,
814 /// When inline assist editors are linked, they all render cursors because
815 /// typing enters text into each of them, even the ones that aren't focused.
816 pub(crate) show_cursor_when_unfocused: bool,
817 columnar_selection_tail: Option<Anchor>,
818 add_selections_state: Option<AddSelectionsState>,
819 select_next_state: Option<SelectNextState>,
820 select_prev_state: Option<SelectNextState>,
821 selection_history: SelectionHistory,
822 autoclose_regions: Vec<AutocloseRegion>,
823 snippet_stack: InvalidationStack<SnippetState>,
824 select_syntax_node_history: SelectSyntaxNodeHistory,
825 ime_transaction: Option<TransactionId>,
826 active_diagnostics: ActiveDiagnostic,
827 show_inline_diagnostics: bool,
828 inline_diagnostics_update: Task<()>,
829 inline_diagnostics_enabled: bool,
830 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
831 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
832 hard_wrap: Option<usize>,
833
834 // TODO: make this a access method
835 pub project: Option<Entity<Project>>,
836 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
837 completion_provider: Option<Box<dyn CompletionProvider>>,
838 collaboration_hub: Option<Box<dyn CollaborationHub>>,
839 blink_manager: Entity<BlinkManager>,
840 show_cursor_names: bool,
841 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
842 pub show_local_selections: bool,
843 mode: EditorMode,
844 show_breadcrumbs: bool,
845 show_gutter: bool,
846 show_scrollbars: bool,
847 disable_scrolling: bool,
848 disable_expand_excerpt_buttons: bool,
849 show_line_numbers: Option<bool>,
850 use_relative_line_numbers: Option<bool>,
851 show_git_diff_gutter: Option<bool>,
852 show_code_actions: Option<bool>,
853 show_runnables: Option<bool>,
854 show_breakpoints: Option<bool>,
855 show_wrap_guides: Option<bool>,
856 show_indent_guides: Option<bool>,
857 placeholder_text: Option<Arc<str>>,
858 highlight_order: usize,
859 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
860 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
861 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
862 scrollbar_marker_state: ScrollbarMarkerState,
863 active_indent_guides_state: ActiveIndentGuidesState,
864 nav_history: Option<ItemNavHistory>,
865 context_menu: RefCell<Option<CodeContextMenu>>,
866 context_menu_options: Option<ContextMenuOptions>,
867 mouse_context_menu: Option<MouseContextMenu>,
868 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
869 signature_help_state: SignatureHelpState,
870 auto_signature_help: Option<bool>,
871 find_all_references_task_sources: Vec<Anchor>,
872 next_completion_id: CompletionId,
873 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
874 code_actions_task: Option<Task<Result<()>>>,
875 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
876 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
877 document_highlights_task: Option<Task<()>>,
878 linked_editing_range_task: Option<Task<Option<()>>>,
879 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
880 pending_rename: Option<RenameState>,
881 searchable: bool,
882 cursor_shape: CursorShape,
883 current_line_highlight: Option<CurrentLineHighlight>,
884 collapse_matches: bool,
885 autoindent_mode: Option<AutoindentMode>,
886 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
887 input_enabled: bool,
888 use_modal_editing: bool,
889 read_only: bool,
890 leader_peer_id: Option<PeerId>,
891 remote_id: Option<ViewId>,
892 pub hover_state: HoverState,
893 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
894 gutter_hovered: bool,
895 hovered_link_state: Option<HoveredLinkState>,
896 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
897 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
898 active_inline_completion: Option<InlineCompletionState>,
899 /// Used to prevent flickering as the user types while the menu is open
900 stale_inline_completion_in_menu: Option<InlineCompletionState>,
901 edit_prediction_settings: EditPredictionSettings,
902 inline_completions_hidden_for_vim_mode: bool,
903 show_inline_completions_override: Option<bool>,
904 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
905 edit_prediction_preview: EditPredictionPreview,
906 edit_prediction_indent_conflict: bool,
907 edit_prediction_requires_modifier_in_indent_conflict: bool,
908 inlay_hint_cache: InlayHintCache,
909 next_inlay_id: usize,
910 _subscriptions: Vec<Subscription>,
911 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
912 gutter_dimensions: GutterDimensions,
913 style: Option<EditorStyle>,
914 text_style_refinement: Option<TextStyleRefinement>,
915 next_editor_action_id: EditorActionId,
916 editor_actions:
917 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
918 use_autoclose: bool,
919 use_auto_surround: bool,
920 auto_replace_emoji_shortcode: bool,
921 jsx_tag_auto_close_enabled_in_any_buffer: bool,
922 show_git_blame_gutter: bool,
923 show_git_blame_inline: bool,
924 show_git_blame_inline_delay_task: Option<Task<()>>,
925 pub git_blame_inline_tooltip: Option<AnyWeakEntity>,
926 git_blame_inline_enabled: bool,
927 render_diff_hunk_controls: RenderDiffHunkControlsFn,
928 serialize_dirty_buffers: bool,
929 show_selection_menu: Option<bool>,
930 blame: Option<Entity<GitBlame>>,
931 blame_subscription: Option<Subscription>,
932 custom_context_menu: Option<
933 Box<
934 dyn 'static
935 + Fn(
936 &mut Self,
937 DisplayPoint,
938 &mut Window,
939 &mut Context<Self>,
940 ) -> Option<Entity<ui::ContextMenu>>,
941 >,
942 >,
943 last_bounds: Option<Bounds<Pixels>>,
944 last_position_map: Option<Rc<PositionMap>>,
945 expect_bounds_change: Option<Bounds<Pixels>>,
946 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
947 tasks_update_task: Option<Task<()>>,
948 breakpoint_store: Option<Entity<BreakpointStore>>,
949 /// Allow's a user to create a breakpoint by selecting this indicator
950 /// It should be None while a user is not hovering over the gutter
951 /// Otherwise it represents the point that the breakpoint will be shown
952 gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
953 in_project_search: bool,
954 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
955 breadcrumb_header: Option<String>,
956 focused_block: Option<FocusedBlock>,
957 next_scroll_position: NextScrollCursorCenterTopBottom,
958 addons: HashMap<TypeId, Box<dyn Addon>>,
959 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
960 load_diff_task: Option<Shared<Task<()>>>,
961 selection_mark_mode: bool,
962 toggle_fold_multiple_buffers: Task<()>,
963 _scroll_cursor_center_top_bottom_task: Task<()>,
964 serialize_selections: Task<()>,
965 serialize_folds: Task<()>,
966 mouse_cursor_hidden: bool,
967 hide_mouse_mode: HideMouseMode,
968 pub change_list: ChangeList,
969 inline_value_cache: InlineValueCache,
970}
971
972#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
973enum NextScrollCursorCenterTopBottom {
974 #[default]
975 Center,
976 Top,
977 Bottom,
978}
979
980impl NextScrollCursorCenterTopBottom {
981 fn next(&self) -> Self {
982 match self {
983 Self::Center => Self::Top,
984 Self::Top => Self::Bottom,
985 Self::Bottom => Self::Center,
986 }
987 }
988}
989
990#[derive(Clone)]
991pub struct EditorSnapshot {
992 pub mode: EditorMode,
993 show_gutter: bool,
994 show_line_numbers: Option<bool>,
995 show_git_diff_gutter: Option<bool>,
996 show_code_actions: Option<bool>,
997 show_runnables: Option<bool>,
998 show_breakpoints: Option<bool>,
999 git_blame_gutter_max_author_length: Option<usize>,
1000 pub display_snapshot: DisplaySnapshot,
1001 pub placeholder_text: Option<Arc<str>>,
1002 is_focused: bool,
1003 scroll_anchor: ScrollAnchor,
1004 ongoing_scroll: OngoingScroll,
1005 current_line_highlight: CurrentLineHighlight,
1006 gutter_hovered: bool,
1007}
1008
1009#[derive(Default, Debug, Clone, Copy)]
1010pub struct GutterDimensions {
1011 pub left_padding: Pixels,
1012 pub right_padding: Pixels,
1013 pub width: Pixels,
1014 pub margin: Pixels,
1015 pub git_blame_entries_width: Option<Pixels>,
1016}
1017
1018impl GutterDimensions {
1019 /// The full width of the space taken up by the gutter.
1020 pub fn full_width(&self) -> Pixels {
1021 self.margin + self.width
1022 }
1023
1024 /// The width of the space reserved for the fold indicators,
1025 /// use alongside 'justify_end' and `gutter_width` to
1026 /// right align content with the line numbers
1027 pub fn fold_area_width(&self) -> Pixels {
1028 self.margin + self.right_padding
1029 }
1030}
1031
1032#[derive(Debug)]
1033pub struct RemoteSelection {
1034 pub replica_id: ReplicaId,
1035 pub selection: Selection<Anchor>,
1036 pub cursor_shape: CursorShape,
1037 pub peer_id: PeerId,
1038 pub line_mode: bool,
1039 pub participant_index: Option<ParticipantIndex>,
1040 pub user_name: Option<SharedString>,
1041}
1042
1043#[derive(Clone, Debug)]
1044struct SelectionHistoryEntry {
1045 selections: Arc<[Selection<Anchor>]>,
1046 select_next_state: Option<SelectNextState>,
1047 select_prev_state: Option<SelectNextState>,
1048 add_selections_state: Option<AddSelectionsState>,
1049}
1050
1051enum SelectionHistoryMode {
1052 Normal,
1053 Undoing,
1054 Redoing,
1055}
1056
1057#[derive(Clone, PartialEq, Eq, Hash)]
1058struct HoveredCursor {
1059 replica_id: u16,
1060 selection_id: usize,
1061}
1062
1063impl Default for SelectionHistoryMode {
1064 fn default() -> Self {
1065 Self::Normal
1066 }
1067}
1068
1069#[derive(Default)]
1070struct SelectionHistory {
1071 #[allow(clippy::type_complexity)]
1072 selections_by_transaction:
1073 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1074 mode: SelectionHistoryMode,
1075 undo_stack: VecDeque<SelectionHistoryEntry>,
1076 redo_stack: VecDeque<SelectionHistoryEntry>,
1077}
1078
1079impl SelectionHistory {
1080 fn insert_transaction(
1081 &mut self,
1082 transaction_id: TransactionId,
1083 selections: Arc<[Selection<Anchor>]>,
1084 ) {
1085 self.selections_by_transaction
1086 .insert(transaction_id, (selections, None));
1087 }
1088
1089 #[allow(clippy::type_complexity)]
1090 fn transaction(
1091 &self,
1092 transaction_id: TransactionId,
1093 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1094 self.selections_by_transaction.get(&transaction_id)
1095 }
1096
1097 #[allow(clippy::type_complexity)]
1098 fn transaction_mut(
1099 &mut self,
1100 transaction_id: TransactionId,
1101 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1102 self.selections_by_transaction.get_mut(&transaction_id)
1103 }
1104
1105 fn push(&mut self, entry: SelectionHistoryEntry) {
1106 if !entry.selections.is_empty() {
1107 match self.mode {
1108 SelectionHistoryMode::Normal => {
1109 self.push_undo(entry);
1110 self.redo_stack.clear();
1111 }
1112 SelectionHistoryMode::Undoing => self.push_redo(entry),
1113 SelectionHistoryMode::Redoing => self.push_undo(entry),
1114 }
1115 }
1116 }
1117
1118 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1119 if self
1120 .undo_stack
1121 .back()
1122 .map_or(true, |e| e.selections != entry.selections)
1123 {
1124 self.undo_stack.push_back(entry);
1125 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1126 self.undo_stack.pop_front();
1127 }
1128 }
1129 }
1130
1131 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1132 if self
1133 .redo_stack
1134 .back()
1135 .map_or(true, |e| e.selections != entry.selections)
1136 {
1137 self.redo_stack.push_back(entry);
1138 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1139 self.redo_stack.pop_front();
1140 }
1141 }
1142 }
1143}
1144
1145#[derive(Clone, Copy)]
1146pub struct RowHighlightOptions {
1147 pub autoscroll: bool,
1148 pub include_gutter: bool,
1149}
1150
1151impl Default for RowHighlightOptions {
1152 fn default() -> Self {
1153 Self {
1154 autoscroll: Default::default(),
1155 include_gutter: true,
1156 }
1157 }
1158}
1159
1160struct RowHighlight {
1161 index: usize,
1162 range: Range<Anchor>,
1163 color: Hsla,
1164 options: RowHighlightOptions,
1165 type_id: TypeId,
1166}
1167
1168#[derive(Clone, Debug)]
1169struct AddSelectionsState {
1170 above: bool,
1171 stack: Vec<usize>,
1172}
1173
1174#[derive(Clone)]
1175struct SelectNextState {
1176 query: AhoCorasick,
1177 wordwise: bool,
1178 done: bool,
1179}
1180
1181impl std::fmt::Debug for SelectNextState {
1182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1183 f.debug_struct(std::any::type_name::<Self>())
1184 .field("wordwise", &self.wordwise)
1185 .field("done", &self.done)
1186 .finish()
1187 }
1188}
1189
1190#[derive(Debug)]
1191struct AutocloseRegion {
1192 selection_id: usize,
1193 range: Range<Anchor>,
1194 pair: BracketPair,
1195}
1196
1197#[derive(Debug)]
1198struct SnippetState {
1199 ranges: Vec<Vec<Range<Anchor>>>,
1200 active_index: usize,
1201 choices: Vec<Option<Vec<String>>>,
1202}
1203
1204#[doc(hidden)]
1205pub struct RenameState {
1206 pub range: Range<Anchor>,
1207 pub old_name: Arc<str>,
1208 pub editor: Entity<Editor>,
1209 block_id: CustomBlockId,
1210}
1211
1212struct InvalidationStack<T>(Vec<T>);
1213
1214struct RegisteredInlineCompletionProvider {
1215 provider: Arc<dyn InlineCompletionProviderHandle>,
1216 _subscription: Subscription,
1217}
1218
1219#[derive(Debug, PartialEq, Eq)]
1220pub struct ActiveDiagnosticGroup {
1221 pub active_range: Range<Anchor>,
1222 pub active_message: String,
1223 pub group_id: usize,
1224 pub blocks: HashSet<CustomBlockId>,
1225}
1226
1227#[derive(Debug, PartialEq, Eq)]
1228#[allow(clippy::large_enum_variant)]
1229pub(crate) enum ActiveDiagnostic {
1230 None,
1231 All,
1232 Group(ActiveDiagnosticGroup),
1233}
1234
1235#[derive(Serialize, Deserialize, Clone, Debug)]
1236pub struct ClipboardSelection {
1237 /// The number of bytes in this selection.
1238 pub len: usize,
1239 /// Whether this was a full-line selection.
1240 pub is_entire_line: bool,
1241 /// The indentation of the first line when this content was originally copied.
1242 pub first_line_indent: u32,
1243}
1244
1245// selections, scroll behavior, was newest selection reversed
1246type SelectSyntaxNodeHistoryState = (
1247 Box<[Selection<usize>]>,
1248 SelectSyntaxNodeScrollBehavior,
1249 bool,
1250);
1251
1252#[derive(Default)]
1253struct SelectSyntaxNodeHistory {
1254 stack: Vec<SelectSyntaxNodeHistoryState>,
1255 // disable temporarily to allow changing selections without losing the stack
1256 pub disable_clearing: bool,
1257}
1258
1259impl SelectSyntaxNodeHistory {
1260 pub fn try_clear(&mut self) {
1261 if !self.disable_clearing {
1262 self.stack.clear();
1263 }
1264 }
1265
1266 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1267 self.stack.push(selection);
1268 }
1269
1270 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1271 self.stack.pop()
1272 }
1273}
1274
1275enum SelectSyntaxNodeScrollBehavior {
1276 CursorTop,
1277 FitSelection,
1278 CursorBottom,
1279}
1280
1281#[derive(Debug)]
1282pub(crate) struct NavigationData {
1283 cursor_anchor: Anchor,
1284 cursor_position: Point,
1285 scroll_anchor: ScrollAnchor,
1286 scroll_top_row: u32,
1287}
1288
1289#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1290pub enum GotoDefinitionKind {
1291 Symbol,
1292 Declaration,
1293 Type,
1294 Implementation,
1295}
1296
1297#[derive(Debug, Clone)]
1298enum InlayHintRefreshReason {
1299 ModifiersChanged(bool),
1300 Toggle(bool),
1301 SettingsChange(InlayHintSettings),
1302 NewLinesShown,
1303 BufferEdited(HashSet<Arc<Language>>),
1304 RefreshRequested,
1305 ExcerptsRemoved(Vec<ExcerptId>),
1306}
1307
1308impl InlayHintRefreshReason {
1309 fn description(&self) -> &'static str {
1310 match self {
1311 Self::ModifiersChanged(_) => "modifiers changed",
1312 Self::Toggle(_) => "toggle",
1313 Self::SettingsChange(_) => "settings change",
1314 Self::NewLinesShown => "new lines shown",
1315 Self::BufferEdited(_) => "buffer edited",
1316 Self::RefreshRequested => "refresh requested",
1317 Self::ExcerptsRemoved(_) => "excerpts removed",
1318 }
1319 }
1320}
1321
1322pub enum FormatTarget {
1323 Buffers,
1324 Ranges(Vec<Range<MultiBufferPoint>>),
1325}
1326
1327pub(crate) struct FocusedBlock {
1328 id: BlockId,
1329 focus_handle: WeakFocusHandle,
1330}
1331
1332#[derive(Clone)]
1333enum JumpData {
1334 MultiBufferRow {
1335 row: MultiBufferRow,
1336 line_offset_from_top: u32,
1337 },
1338 MultiBufferPoint {
1339 excerpt_id: ExcerptId,
1340 position: Point,
1341 anchor: text::Anchor,
1342 line_offset_from_top: u32,
1343 },
1344}
1345
1346pub enum MultibufferSelectionMode {
1347 First,
1348 All,
1349}
1350
1351#[derive(Clone, Copy, Debug, Default)]
1352pub struct RewrapOptions {
1353 pub override_language_settings: bool,
1354 pub preserve_existing_whitespace: bool,
1355}
1356
1357impl Editor {
1358 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1359 let buffer = cx.new(|cx| Buffer::local("", cx));
1360 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1361 Self::new(
1362 EditorMode::SingleLine { auto_width: false },
1363 buffer,
1364 None,
1365 window,
1366 cx,
1367 )
1368 }
1369
1370 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1371 let buffer = cx.new(|cx| Buffer::local("", cx));
1372 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1373 Self::new(EditorMode::full(), buffer, None, window, cx)
1374 }
1375
1376 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1377 let buffer = cx.new(|cx| Buffer::local("", cx));
1378 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1379 Self::new(
1380 EditorMode::SingleLine { auto_width: true },
1381 buffer,
1382 None,
1383 window,
1384 cx,
1385 )
1386 }
1387
1388 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1389 let buffer = cx.new(|cx| Buffer::local("", cx));
1390 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1391 Self::new(
1392 EditorMode::AutoHeight { max_lines },
1393 buffer,
1394 None,
1395 window,
1396 cx,
1397 )
1398 }
1399
1400 pub fn for_buffer(
1401 buffer: Entity<Buffer>,
1402 project: Option<Entity<Project>>,
1403 window: &mut Window,
1404 cx: &mut Context<Self>,
1405 ) -> Self {
1406 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1407 Self::new(EditorMode::full(), buffer, project, window, cx)
1408 }
1409
1410 pub fn for_multibuffer(
1411 buffer: Entity<MultiBuffer>,
1412 project: Option<Entity<Project>>,
1413 window: &mut Window,
1414 cx: &mut Context<Self>,
1415 ) -> Self {
1416 Self::new(EditorMode::full(), buffer, project, window, cx)
1417 }
1418
1419 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1420 let mut clone = Self::new(
1421 self.mode,
1422 self.buffer.clone(),
1423 self.project.clone(),
1424 window,
1425 cx,
1426 );
1427 self.display_map.update(cx, |display_map, cx| {
1428 let snapshot = display_map.snapshot(cx);
1429 clone.display_map.update(cx, |display_map, cx| {
1430 display_map.set_state(&snapshot, cx);
1431 });
1432 });
1433 clone.folds_did_change(cx);
1434 clone.selections.clone_state(&self.selections);
1435 clone.scroll_manager.clone_state(&self.scroll_manager);
1436 clone.searchable = self.searchable;
1437 clone.read_only = self.read_only;
1438 clone
1439 }
1440
1441 pub fn new(
1442 mode: EditorMode,
1443 buffer: Entity<MultiBuffer>,
1444 project: Option<Entity<Project>>,
1445 window: &mut Window,
1446 cx: &mut Context<Self>,
1447 ) -> Self {
1448 let style = window.text_style();
1449 let font_size = style.font_size.to_pixels(window.rem_size());
1450 let editor = cx.entity().downgrade();
1451 let fold_placeholder = FoldPlaceholder {
1452 constrain_width: true,
1453 render: Arc::new(move |fold_id, fold_range, cx| {
1454 let editor = editor.clone();
1455 div()
1456 .id(fold_id)
1457 .bg(cx.theme().colors().ghost_element_background)
1458 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1459 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1460 .rounded_xs()
1461 .size_full()
1462 .cursor_pointer()
1463 .child("⋯")
1464 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1465 .on_click(move |_, _window, cx| {
1466 editor
1467 .update(cx, |editor, cx| {
1468 editor.unfold_ranges(
1469 &[fold_range.start..fold_range.end],
1470 true,
1471 false,
1472 cx,
1473 );
1474 cx.stop_propagation();
1475 })
1476 .ok();
1477 })
1478 .into_any()
1479 }),
1480 merge_adjacent: true,
1481 ..Default::default()
1482 };
1483 let display_map = cx.new(|cx| {
1484 DisplayMap::new(
1485 buffer.clone(),
1486 style.font(),
1487 font_size,
1488 None,
1489 FILE_HEADER_HEIGHT,
1490 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1491 fold_placeholder,
1492 cx,
1493 )
1494 });
1495
1496 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1497
1498 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1499
1500 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1501 .then(|| language_settings::SoftWrap::None);
1502
1503 let mut project_subscriptions = Vec::new();
1504 if mode.is_full() {
1505 if let Some(project) = project.as_ref() {
1506 project_subscriptions.push(cx.subscribe_in(
1507 project,
1508 window,
1509 |editor, _, event, window, cx| match event {
1510 project::Event::RefreshCodeLens => {
1511 // we always query lens with actions, without storing them, always refreshing them
1512 }
1513 project::Event::RefreshInlayHints => {
1514 editor
1515 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1516 }
1517 project::Event::SnippetEdit(id, snippet_edits) => {
1518 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1519 let focus_handle = editor.focus_handle(cx);
1520 if focus_handle.is_focused(window) {
1521 let snapshot = buffer.read(cx).snapshot();
1522 for (range, snippet) in snippet_edits {
1523 let editor_range =
1524 language::range_from_lsp(*range).to_offset(&snapshot);
1525 editor
1526 .insert_snippet(
1527 &[editor_range],
1528 snippet.clone(),
1529 window,
1530 cx,
1531 )
1532 .ok();
1533 }
1534 }
1535 }
1536 }
1537 _ => {}
1538 },
1539 ));
1540 if let Some(task_inventory) = project
1541 .read(cx)
1542 .task_store()
1543 .read(cx)
1544 .task_inventory()
1545 .cloned()
1546 {
1547 project_subscriptions.push(cx.observe_in(
1548 &task_inventory,
1549 window,
1550 |editor, _, window, cx| {
1551 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1552 },
1553 ));
1554 };
1555
1556 project_subscriptions.push(cx.subscribe_in(
1557 &project.read(cx).breakpoint_store(),
1558 window,
1559 |editor, _, event, window, cx| match event {
1560 BreakpointStoreEvent::ActiveDebugLineChanged => {
1561 if editor.go_to_active_debug_line(window, cx) {
1562 cx.stop_propagation();
1563 }
1564
1565 editor.refresh_inline_values(cx);
1566 }
1567 _ => {}
1568 },
1569 ));
1570 }
1571 }
1572
1573 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1574
1575 let inlay_hint_settings =
1576 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1577 let focus_handle = cx.focus_handle();
1578 cx.on_focus(&focus_handle, window, Self::handle_focus)
1579 .detach();
1580 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1581 .detach();
1582 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1583 .detach();
1584 cx.on_blur(&focus_handle, window, Self::handle_blur)
1585 .detach();
1586
1587 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1588 Some(false)
1589 } else {
1590 None
1591 };
1592
1593 let breakpoint_store = match (mode, project.as_ref()) {
1594 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1595 _ => None,
1596 };
1597
1598 let mut code_action_providers = Vec::new();
1599 let mut load_uncommitted_diff = None;
1600 if let Some(project) = project.clone() {
1601 load_uncommitted_diff = Some(
1602 get_uncommitted_diff_for_buffer(
1603 &project,
1604 buffer.read(cx).all_buffers(),
1605 buffer.clone(),
1606 cx,
1607 )
1608 .shared(),
1609 );
1610 code_action_providers.push(Rc::new(project) as Rc<_>);
1611 }
1612
1613 let mut this = Self {
1614 focus_handle,
1615 show_cursor_when_unfocused: false,
1616 last_focused_descendant: None,
1617 buffer: buffer.clone(),
1618 display_map: display_map.clone(),
1619 selections,
1620 scroll_manager: ScrollManager::new(cx),
1621 columnar_selection_tail: None,
1622 add_selections_state: None,
1623 select_next_state: None,
1624 select_prev_state: None,
1625 selection_history: Default::default(),
1626 autoclose_regions: Default::default(),
1627 snippet_stack: Default::default(),
1628 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1629 ime_transaction: Default::default(),
1630 active_diagnostics: ActiveDiagnostic::None,
1631 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1632 inline_diagnostics_update: Task::ready(()),
1633 inline_diagnostics: Vec::new(),
1634 soft_wrap_mode_override,
1635 hard_wrap: None,
1636 completion_provider: project.clone().map(|project| Box::new(project) as _),
1637 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1638 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1639 project,
1640 blink_manager: blink_manager.clone(),
1641 show_local_selections: true,
1642 show_scrollbars: true,
1643 disable_scrolling: false,
1644 mode,
1645 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1646 show_gutter: mode.is_full(),
1647 show_line_numbers: None,
1648 use_relative_line_numbers: None,
1649 disable_expand_excerpt_buttons: false,
1650 show_git_diff_gutter: None,
1651 show_code_actions: None,
1652 show_runnables: None,
1653 show_breakpoints: None,
1654 show_wrap_guides: None,
1655 show_indent_guides,
1656 placeholder_text: None,
1657 highlight_order: 0,
1658 highlighted_rows: HashMap::default(),
1659 background_highlights: Default::default(),
1660 gutter_highlights: TreeMap::default(),
1661 scrollbar_marker_state: ScrollbarMarkerState::default(),
1662 active_indent_guides_state: ActiveIndentGuidesState::default(),
1663 nav_history: None,
1664 context_menu: RefCell::new(None),
1665 context_menu_options: None,
1666 mouse_context_menu: None,
1667 completion_tasks: Default::default(),
1668 signature_help_state: SignatureHelpState::default(),
1669 auto_signature_help: None,
1670 find_all_references_task_sources: Vec::new(),
1671 next_completion_id: 0,
1672 next_inlay_id: 0,
1673 code_action_providers,
1674 available_code_actions: Default::default(),
1675 code_actions_task: Default::default(),
1676 quick_selection_highlight_task: Default::default(),
1677 debounced_selection_highlight_task: Default::default(),
1678 document_highlights_task: Default::default(),
1679 linked_editing_range_task: Default::default(),
1680 pending_rename: Default::default(),
1681 searchable: true,
1682 cursor_shape: EditorSettings::get_global(cx)
1683 .cursor_shape
1684 .unwrap_or_default(),
1685 current_line_highlight: None,
1686 autoindent_mode: Some(AutoindentMode::EachLine),
1687 collapse_matches: false,
1688 workspace: None,
1689 input_enabled: true,
1690 use_modal_editing: mode.is_full(),
1691 read_only: false,
1692 use_autoclose: true,
1693 use_auto_surround: true,
1694 auto_replace_emoji_shortcode: false,
1695 jsx_tag_auto_close_enabled_in_any_buffer: false,
1696 leader_peer_id: None,
1697 remote_id: None,
1698 hover_state: Default::default(),
1699 pending_mouse_down: None,
1700 hovered_link_state: Default::default(),
1701 edit_prediction_provider: None,
1702 active_inline_completion: None,
1703 stale_inline_completion_in_menu: None,
1704 edit_prediction_preview: EditPredictionPreview::Inactive {
1705 released_too_fast: false,
1706 },
1707 inline_diagnostics_enabled: mode.is_full(),
1708 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1709 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1710
1711 gutter_hovered: false,
1712 pixel_position_of_newest_cursor: None,
1713 last_bounds: None,
1714 last_position_map: None,
1715 expect_bounds_change: None,
1716 gutter_dimensions: GutterDimensions::default(),
1717 style: None,
1718 show_cursor_names: false,
1719 hovered_cursors: Default::default(),
1720 next_editor_action_id: EditorActionId::default(),
1721 editor_actions: Rc::default(),
1722 inline_completions_hidden_for_vim_mode: false,
1723 show_inline_completions_override: None,
1724 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1725 edit_prediction_settings: EditPredictionSettings::Disabled,
1726 edit_prediction_indent_conflict: false,
1727 edit_prediction_requires_modifier_in_indent_conflict: true,
1728 custom_context_menu: None,
1729 show_git_blame_gutter: false,
1730 show_git_blame_inline: false,
1731 show_selection_menu: None,
1732 show_git_blame_inline_delay_task: None,
1733 git_blame_inline_tooltip: None,
1734 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1735 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1736 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1737 .session
1738 .restore_unsaved_buffers,
1739 blame: None,
1740 blame_subscription: None,
1741 tasks: Default::default(),
1742
1743 breakpoint_store,
1744 gutter_breakpoint_indicator: (None, None),
1745 _subscriptions: vec![
1746 cx.observe(&buffer, Self::on_buffer_changed),
1747 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1748 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1749 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1750 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1751 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1752 cx.observe_window_activation(window, |editor, window, cx| {
1753 let active = window.is_window_active();
1754 editor.blink_manager.update(cx, |blink_manager, cx| {
1755 if active {
1756 blink_manager.enable(cx);
1757 } else {
1758 blink_manager.disable(cx);
1759 }
1760 });
1761 }),
1762 ],
1763 tasks_update_task: None,
1764 linked_edit_ranges: Default::default(),
1765 in_project_search: false,
1766 previous_search_ranges: None,
1767 breadcrumb_header: None,
1768 focused_block: None,
1769 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1770 addons: HashMap::default(),
1771 registered_buffers: HashMap::default(),
1772 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1773 selection_mark_mode: false,
1774 toggle_fold_multiple_buffers: Task::ready(()),
1775 serialize_selections: Task::ready(()),
1776 serialize_folds: Task::ready(()),
1777 text_style_refinement: None,
1778 load_diff_task: load_uncommitted_diff,
1779 mouse_cursor_hidden: false,
1780 hide_mouse_mode: EditorSettings::get_global(cx)
1781 .hide_mouse
1782 .unwrap_or_default(),
1783 change_list: ChangeList::new(),
1784 };
1785 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1786 this._subscriptions
1787 .push(cx.observe(breakpoints, |_, _, cx| {
1788 cx.notify();
1789 }));
1790 }
1791 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1792 this._subscriptions.extend(project_subscriptions);
1793
1794 this._subscriptions.push(cx.subscribe_in(
1795 &cx.entity(),
1796 window,
1797 |editor, _, e: &EditorEvent, window, cx| match e {
1798 EditorEvent::ScrollPositionChanged { local, .. } => {
1799 if *local {
1800 let new_anchor = editor.scroll_manager.anchor();
1801 let snapshot = editor.snapshot(window, cx);
1802 editor.update_restoration_data(cx, move |data| {
1803 data.scroll_position = (
1804 new_anchor.top_row(&snapshot.buffer_snapshot),
1805 new_anchor.offset,
1806 );
1807 });
1808 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1809 }
1810 }
1811 EditorEvent::Edited { .. } => {
1812 if !vim_enabled(cx) {
1813 let (map, selections) = editor.selections.all_adjusted_display(cx);
1814 let pop_state = editor
1815 .change_list
1816 .last()
1817 .map(|previous| {
1818 previous.len() == selections.len()
1819 && previous.iter().enumerate().all(|(ix, p)| {
1820 p.to_display_point(&map).row()
1821 == selections[ix].head().row()
1822 })
1823 })
1824 .unwrap_or(false);
1825 let new_positions = selections
1826 .into_iter()
1827 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1828 .collect();
1829 editor
1830 .change_list
1831 .push_to_change_list(pop_state, new_positions);
1832 }
1833 }
1834 _ => (),
1835 },
1836 ));
1837
1838 if let Some(dap_store) = this
1839 .project
1840 .as_ref()
1841 .map(|project| project.read(cx).dap_store())
1842 {
1843 let weak_editor = cx.weak_entity();
1844
1845 this._subscriptions
1846 .push(
1847 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1848 let session_entity = cx.entity();
1849 weak_editor
1850 .update(cx, |editor, cx| {
1851 editor._subscriptions.push(
1852 cx.subscribe(&session_entity, Self::on_debug_session_event),
1853 );
1854 })
1855 .ok();
1856 }),
1857 );
1858
1859 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1860 this._subscriptions
1861 .push(cx.subscribe(&session, Self::on_debug_session_event));
1862 }
1863 }
1864
1865 this.end_selection(window, cx);
1866 this.scroll_manager.show_scrollbars(window, cx);
1867 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1868
1869 if mode.is_full() {
1870 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1871 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1872
1873 if this.git_blame_inline_enabled {
1874 this.git_blame_inline_enabled = true;
1875 this.start_git_blame_inline(false, window, cx);
1876 }
1877
1878 this.go_to_active_debug_line(window, cx);
1879
1880 if let Some(buffer) = buffer.read(cx).as_singleton() {
1881 if let Some(project) = this.project.as_ref() {
1882 let handle = project.update(cx, |project, cx| {
1883 project.register_buffer_with_language_servers(&buffer, cx)
1884 });
1885 this.registered_buffers
1886 .insert(buffer.read(cx).remote_id(), handle);
1887 }
1888 }
1889 }
1890
1891 this.report_editor_event("Editor Opened", None, cx);
1892 this
1893 }
1894
1895 pub fn deploy_mouse_context_menu(
1896 &mut self,
1897 position: gpui::Point<Pixels>,
1898 context_menu: Entity<ContextMenu>,
1899 window: &mut Window,
1900 cx: &mut Context<Self>,
1901 ) {
1902 self.mouse_context_menu = Some(MouseContextMenu::new(
1903 self,
1904 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1905 context_menu,
1906 window,
1907 cx,
1908 ));
1909 }
1910
1911 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1912 self.mouse_context_menu
1913 .as_ref()
1914 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1915 }
1916
1917 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1918 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1919 }
1920
1921 fn key_context_internal(
1922 &self,
1923 has_active_edit_prediction: bool,
1924 window: &Window,
1925 cx: &App,
1926 ) -> KeyContext {
1927 let mut key_context = KeyContext::new_with_defaults();
1928 key_context.add("Editor");
1929 let mode = match self.mode {
1930 EditorMode::SingleLine { .. } => "single_line",
1931 EditorMode::AutoHeight { .. } => "auto_height",
1932 EditorMode::Full { .. } => "full",
1933 };
1934
1935 if EditorSettings::jupyter_enabled(cx) {
1936 key_context.add("jupyter");
1937 }
1938
1939 key_context.set("mode", mode);
1940 if self.pending_rename.is_some() {
1941 key_context.add("renaming");
1942 }
1943
1944 match self.context_menu.borrow().as_ref() {
1945 Some(CodeContextMenu::Completions(_)) => {
1946 key_context.add("menu");
1947 key_context.add("showing_completions");
1948 }
1949 Some(CodeContextMenu::CodeActions(_)) => {
1950 key_context.add("menu");
1951 key_context.add("showing_code_actions")
1952 }
1953 None => {}
1954 }
1955
1956 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1957 if !self.focus_handle(cx).contains_focused(window, cx)
1958 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1959 {
1960 for addon in self.addons.values() {
1961 addon.extend_key_context(&mut key_context, cx)
1962 }
1963 }
1964
1965 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1966 if let Some(extension) = singleton_buffer
1967 .read(cx)
1968 .file()
1969 .and_then(|file| file.path().extension()?.to_str())
1970 {
1971 key_context.set("extension", extension.to_string());
1972 }
1973 } else {
1974 key_context.add("multibuffer");
1975 }
1976
1977 if has_active_edit_prediction {
1978 if self.edit_prediction_in_conflict() {
1979 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1980 } else {
1981 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1982 key_context.add("copilot_suggestion");
1983 }
1984 }
1985
1986 if self.selection_mark_mode {
1987 key_context.add("selection_mode");
1988 }
1989
1990 key_context
1991 }
1992
1993 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1994 self.mouse_cursor_hidden = match origin {
1995 HideMouseCursorOrigin::TypingAction => {
1996 matches!(
1997 self.hide_mouse_mode,
1998 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1999 )
2000 }
2001 HideMouseCursorOrigin::MovementAction => {
2002 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2003 }
2004 };
2005 }
2006
2007 pub fn edit_prediction_in_conflict(&self) -> bool {
2008 if !self.show_edit_predictions_in_menu() {
2009 return false;
2010 }
2011
2012 let showing_completions = self
2013 .context_menu
2014 .borrow()
2015 .as_ref()
2016 .map_or(false, |context| {
2017 matches!(context, CodeContextMenu::Completions(_))
2018 });
2019
2020 showing_completions
2021 || self.edit_prediction_requires_modifier()
2022 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2023 // bindings to insert tab characters.
2024 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2025 }
2026
2027 pub fn accept_edit_prediction_keybind(
2028 &self,
2029 window: &Window,
2030 cx: &App,
2031 ) -> AcceptEditPredictionBinding {
2032 let key_context = self.key_context_internal(true, window, cx);
2033 let in_conflict = self.edit_prediction_in_conflict();
2034
2035 AcceptEditPredictionBinding(
2036 window
2037 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2038 .into_iter()
2039 .filter(|binding| {
2040 !in_conflict
2041 || binding
2042 .keystrokes()
2043 .first()
2044 .map_or(false, |keystroke| keystroke.modifiers.modified())
2045 })
2046 .rev()
2047 .min_by_key(|binding| {
2048 binding
2049 .keystrokes()
2050 .first()
2051 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2052 }),
2053 )
2054 }
2055
2056 pub fn new_file(
2057 workspace: &mut Workspace,
2058 _: &workspace::NewFile,
2059 window: &mut Window,
2060 cx: &mut Context<Workspace>,
2061 ) {
2062 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2063 "Failed to create buffer",
2064 window,
2065 cx,
2066 |e, _, _| match e.error_code() {
2067 ErrorCode::RemoteUpgradeRequired => Some(format!(
2068 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2069 e.error_tag("required").unwrap_or("the latest version")
2070 )),
2071 _ => None,
2072 },
2073 );
2074 }
2075
2076 pub fn new_in_workspace(
2077 workspace: &mut Workspace,
2078 window: &mut Window,
2079 cx: &mut Context<Workspace>,
2080 ) -> Task<Result<Entity<Editor>>> {
2081 let project = workspace.project().clone();
2082 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2083
2084 cx.spawn_in(window, async move |workspace, cx| {
2085 let buffer = create.await?;
2086 workspace.update_in(cx, |workspace, window, cx| {
2087 let editor =
2088 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2089 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2090 editor
2091 })
2092 })
2093 }
2094
2095 fn new_file_vertical(
2096 workspace: &mut Workspace,
2097 _: &workspace::NewFileSplitVertical,
2098 window: &mut Window,
2099 cx: &mut Context<Workspace>,
2100 ) {
2101 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2102 }
2103
2104 fn new_file_horizontal(
2105 workspace: &mut Workspace,
2106 _: &workspace::NewFileSplitHorizontal,
2107 window: &mut Window,
2108 cx: &mut Context<Workspace>,
2109 ) {
2110 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2111 }
2112
2113 fn new_file_in_direction(
2114 workspace: &mut Workspace,
2115 direction: SplitDirection,
2116 window: &mut Window,
2117 cx: &mut Context<Workspace>,
2118 ) {
2119 let project = workspace.project().clone();
2120 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2121
2122 cx.spawn_in(window, async move |workspace, cx| {
2123 let buffer = create.await?;
2124 workspace.update_in(cx, move |workspace, window, cx| {
2125 workspace.split_item(
2126 direction,
2127 Box::new(
2128 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2129 ),
2130 window,
2131 cx,
2132 )
2133 })?;
2134 anyhow::Ok(())
2135 })
2136 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2137 match e.error_code() {
2138 ErrorCode::RemoteUpgradeRequired => Some(format!(
2139 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2140 e.error_tag("required").unwrap_or("the latest version")
2141 )),
2142 _ => None,
2143 }
2144 });
2145 }
2146
2147 pub fn leader_peer_id(&self) -> Option<PeerId> {
2148 self.leader_peer_id
2149 }
2150
2151 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2152 &self.buffer
2153 }
2154
2155 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2156 self.workspace.as_ref()?.0.upgrade()
2157 }
2158
2159 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2160 self.buffer().read(cx).title(cx)
2161 }
2162
2163 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2164 let git_blame_gutter_max_author_length = self
2165 .render_git_blame_gutter(cx)
2166 .then(|| {
2167 if let Some(blame) = self.blame.as_ref() {
2168 let max_author_length =
2169 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2170 Some(max_author_length)
2171 } else {
2172 None
2173 }
2174 })
2175 .flatten();
2176
2177 EditorSnapshot {
2178 mode: self.mode,
2179 show_gutter: self.show_gutter,
2180 show_line_numbers: self.show_line_numbers,
2181 show_git_diff_gutter: self.show_git_diff_gutter,
2182 show_code_actions: self.show_code_actions,
2183 show_runnables: self.show_runnables,
2184 show_breakpoints: self.show_breakpoints,
2185 git_blame_gutter_max_author_length,
2186 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2187 scroll_anchor: self.scroll_manager.anchor(),
2188 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2189 placeholder_text: self.placeholder_text.clone(),
2190 is_focused: self.focus_handle.is_focused(window),
2191 current_line_highlight: self
2192 .current_line_highlight
2193 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2194 gutter_hovered: self.gutter_hovered,
2195 }
2196 }
2197
2198 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2199 self.buffer.read(cx).language_at(point, cx)
2200 }
2201
2202 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2203 self.buffer.read(cx).read(cx).file_at(point).cloned()
2204 }
2205
2206 pub fn active_excerpt(
2207 &self,
2208 cx: &App,
2209 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2210 self.buffer
2211 .read(cx)
2212 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2213 }
2214
2215 pub fn mode(&self) -> EditorMode {
2216 self.mode
2217 }
2218
2219 pub fn set_mode(&mut self, mode: EditorMode) {
2220 self.mode = mode;
2221 }
2222
2223 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2224 self.collaboration_hub.as_deref()
2225 }
2226
2227 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2228 self.collaboration_hub = Some(hub);
2229 }
2230
2231 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2232 self.in_project_search = in_project_search;
2233 }
2234
2235 pub fn set_custom_context_menu(
2236 &mut self,
2237 f: impl 'static
2238 + Fn(
2239 &mut Self,
2240 DisplayPoint,
2241 &mut Window,
2242 &mut Context<Self>,
2243 ) -> Option<Entity<ui::ContextMenu>>,
2244 ) {
2245 self.custom_context_menu = Some(Box::new(f))
2246 }
2247
2248 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2249 self.completion_provider = provider;
2250 }
2251
2252 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2253 self.semantics_provider.clone()
2254 }
2255
2256 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2257 self.semantics_provider = provider;
2258 }
2259
2260 pub fn set_edit_prediction_provider<T>(
2261 &mut self,
2262 provider: Option<Entity<T>>,
2263 window: &mut Window,
2264 cx: &mut Context<Self>,
2265 ) where
2266 T: EditPredictionProvider,
2267 {
2268 self.edit_prediction_provider =
2269 provider.map(|provider| RegisteredInlineCompletionProvider {
2270 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2271 if this.focus_handle.is_focused(window) {
2272 this.update_visible_inline_completion(window, cx);
2273 }
2274 }),
2275 provider: Arc::new(provider),
2276 });
2277 self.update_edit_prediction_settings(cx);
2278 self.refresh_inline_completion(false, false, window, cx);
2279 }
2280
2281 pub fn placeholder_text(&self) -> Option<&str> {
2282 self.placeholder_text.as_deref()
2283 }
2284
2285 pub fn set_placeholder_text(
2286 &mut self,
2287 placeholder_text: impl Into<Arc<str>>,
2288 cx: &mut Context<Self>,
2289 ) {
2290 let placeholder_text = Some(placeholder_text.into());
2291 if self.placeholder_text != placeholder_text {
2292 self.placeholder_text = placeholder_text;
2293 cx.notify();
2294 }
2295 }
2296
2297 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2298 self.cursor_shape = cursor_shape;
2299
2300 // Disrupt blink for immediate user feedback that the cursor shape has changed
2301 self.blink_manager.update(cx, BlinkManager::show_cursor);
2302
2303 cx.notify();
2304 }
2305
2306 pub fn set_current_line_highlight(
2307 &mut self,
2308 current_line_highlight: Option<CurrentLineHighlight>,
2309 ) {
2310 self.current_line_highlight = current_line_highlight;
2311 }
2312
2313 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2314 self.collapse_matches = collapse_matches;
2315 }
2316
2317 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2318 let buffers = self.buffer.read(cx).all_buffers();
2319 let Some(project) = self.project.as_ref() else {
2320 return;
2321 };
2322 project.update(cx, |project, cx| {
2323 for buffer in buffers {
2324 self.registered_buffers
2325 .entry(buffer.read(cx).remote_id())
2326 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2327 }
2328 })
2329 }
2330
2331 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2332 if self.collapse_matches {
2333 return range.start..range.start;
2334 }
2335 range.clone()
2336 }
2337
2338 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2339 if self.display_map.read(cx).clip_at_line_ends != clip {
2340 self.display_map
2341 .update(cx, |map, _| map.clip_at_line_ends = clip);
2342 }
2343 }
2344
2345 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2346 self.input_enabled = input_enabled;
2347 }
2348
2349 pub fn set_inline_completions_hidden_for_vim_mode(
2350 &mut self,
2351 hidden: bool,
2352 window: &mut Window,
2353 cx: &mut Context<Self>,
2354 ) {
2355 if hidden != self.inline_completions_hidden_for_vim_mode {
2356 self.inline_completions_hidden_for_vim_mode = hidden;
2357 if hidden {
2358 self.update_visible_inline_completion(window, cx);
2359 } else {
2360 self.refresh_inline_completion(true, false, window, cx);
2361 }
2362 }
2363 }
2364
2365 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2366 self.menu_inline_completions_policy = value;
2367 }
2368
2369 pub fn set_autoindent(&mut self, autoindent: bool) {
2370 if autoindent {
2371 self.autoindent_mode = Some(AutoindentMode::EachLine);
2372 } else {
2373 self.autoindent_mode = None;
2374 }
2375 }
2376
2377 pub fn read_only(&self, cx: &App) -> bool {
2378 self.read_only || self.buffer.read(cx).read_only()
2379 }
2380
2381 pub fn set_read_only(&mut self, read_only: bool) {
2382 self.read_only = read_only;
2383 }
2384
2385 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2386 self.use_autoclose = autoclose;
2387 }
2388
2389 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2390 self.use_auto_surround = auto_surround;
2391 }
2392
2393 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2394 self.auto_replace_emoji_shortcode = auto_replace;
2395 }
2396
2397 pub fn toggle_edit_predictions(
2398 &mut self,
2399 _: &ToggleEditPrediction,
2400 window: &mut Window,
2401 cx: &mut Context<Self>,
2402 ) {
2403 if self.show_inline_completions_override.is_some() {
2404 self.set_show_edit_predictions(None, window, cx);
2405 } else {
2406 let show_edit_predictions = !self.edit_predictions_enabled();
2407 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2408 }
2409 }
2410
2411 pub fn set_show_edit_predictions(
2412 &mut self,
2413 show_edit_predictions: Option<bool>,
2414 window: &mut Window,
2415 cx: &mut Context<Self>,
2416 ) {
2417 self.show_inline_completions_override = show_edit_predictions;
2418 self.update_edit_prediction_settings(cx);
2419
2420 if let Some(false) = show_edit_predictions {
2421 self.discard_inline_completion(false, cx);
2422 } else {
2423 self.refresh_inline_completion(false, true, window, cx);
2424 }
2425 }
2426
2427 fn inline_completions_disabled_in_scope(
2428 &self,
2429 buffer: &Entity<Buffer>,
2430 buffer_position: language::Anchor,
2431 cx: &App,
2432 ) -> bool {
2433 let snapshot = buffer.read(cx).snapshot();
2434 let settings = snapshot.settings_at(buffer_position, cx);
2435
2436 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2437 return false;
2438 };
2439
2440 scope.override_name().map_or(false, |scope_name| {
2441 settings
2442 .edit_predictions_disabled_in
2443 .iter()
2444 .any(|s| s == scope_name)
2445 })
2446 }
2447
2448 pub fn set_use_modal_editing(&mut self, to: bool) {
2449 self.use_modal_editing = to;
2450 }
2451
2452 pub fn use_modal_editing(&self) -> bool {
2453 self.use_modal_editing
2454 }
2455
2456 fn selections_did_change(
2457 &mut self,
2458 local: bool,
2459 old_cursor_position: &Anchor,
2460 show_completions: bool,
2461 window: &mut Window,
2462 cx: &mut Context<Self>,
2463 ) {
2464 window.invalidate_character_coordinates();
2465
2466 // Copy selections to primary selection buffer
2467 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2468 if local {
2469 let selections = self.selections.all::<usize>(cx);
2470 let buffer_handle = self.buffer.read(cx).read(cx);
2471
2472 let mut text = String::new();
2473 for (index, selection) in selections.iter().enumerate() {
2474 let text_for_selection = buffer_handle
2475 .text_for_range(selection.start..selection.end)
2476 .collect::<String>();
2477
2478 text.push_str(&text_for_selection);
2479 if index != selections.len() - 1 {
2480 text.push('\n');
2481 }
2482 }
2483
2484 if !text.is_empty() {
2485 cx.write_to_primary(ClipboardItem::new_string(text));
2486 }
2487 }
2488
2489 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2490 self.buffer.update(cx, |buffer, cx| {
2491 buffer.set_active_selections(
2492 &self.selections.disjoint_anchors(),
2493 self.selections.line_mode,
2494 self.cursor_shape,
2495 cx,
2496 )
2497 });
2498 }
2499 let display_map = self
2500 .display_map
2501 .update(cx, |display_map, cx| display_map.snapshot(cx));
2502 let buffer = &display_map.buffer_snapshot;
2503 self.add_selections_state = None;
2504 self.select_next_state = None;
2505 self.select_prev_state = None;
2506 self.select_syntax_node_history.try_clear();
2507 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2508 self.snippet_stack
2509 .invalidate(&self.selections.disjoint_anchors(), buffer);
2510 self.take_rename(false, window, cx);
2511
2512 let new_cursor_position = self.selections.newest_anchor().head();
2513
2514 self.push_to_nav_history(
2515 *old_cursor_position,
2516 Some(new_cursor_position.to_point(buffer)),
2517 false,
2518 cx,
2519 );
2520
2521 if local {
2522 let new_cursor_position = self.selections.newest_anchor().head();
2523 let mut context_menu = self.context_menu.borrow_mut();
2524 let completion_menu = match context_menu.as_ref() {
2525 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2526 _ => {
2527 *context_menu = None;
2528 None
2529 }
2530 };
2531 if let Some(buffer_id) = new_cursor_position.buffer_id {
2532 if !self.registered_buffers.contains_key(&buffer_id) {
2533 if let Some(project) = self.project.as_ref() {
2534 project.update(cx, |project, cx| {
2535 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2536 return;
2537 };
2538 self.registered_buffers.insert(
2539 buffer_id,
2540 project.register_buffer_with_language_servers(&buffer, cx),
2541 );
2542 })
2543 }
2544 }
2545 }
2546
2547 if let Some(completion_menu) = completion_menu {
2548 let cursor_position = new_cursor_position.to_offset(buffer);
2549 let (word_range, kind) =
2550 buffer.surrounding_word(completion_menu.initial_position, true);
2551 if kind == Some(CharKind::Word)
2552 && word_range.to_inclusive().contains(&cursor_position)
2553 {
2554 let mut completion_menu = completion_menu.clone();
2555 drop(context_menu);
2556
2557 let query = Self::completion_query(buffer, cursor_position);
2558 cx.spawn(async move |this, cx| {
2559 completion_menu
2560 .filter(query.as_deref(), cx.background_executor().clone())
2561 .await;
2562
2563 this.update(cx, |this, cx| {
2564 let mut context_menu = this.context_menu.borrow_mut();
2565 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2566 else {
2567 return;
2568 };
2569
2570 if menu.id > completion_menu.id {
2571 return;
2572 }
2573
2574 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2575 drop(context_menu);
2576 cx.notify();
2577 })
2578 })
2579 .detach();
2580
2581 if show_completions {
2582 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2583 }
2584 } else {
2585 drop(context_menu);
2586 self.hide_context_menu(window, cx);
2587 }
2588 } else {
2589 drop(context_menu);
2590 }
2591
2592 hide_hover(self, cx);
2593
2594 if old_cursor_position.to_display_point(&display_map).row()
2595 != new_cursor_position.to_display_point(&display_map).row()
2596 {
2597 self.available_code_actions.take();
2598 }
2599 self.refresh_code_actions(window, cx);
2600 self.refresh_document_highlights(cx);
2601 self.refresh_selected_text_highlights(window, cx);
2602 refresh_matching_bracket_highlights(self, window, cx);
2603 self.update_visible_inline_completion(window, cx);
2604 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2605 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2606 if self.git_blame_inline_enabled {
2607 self.start_inline_blame_timer(window, cx);
2608 }
2609 }
2610
2611 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2612 cx.emit(EditorEvent::SelectionsChanged { local });
2613
2614 let selections = &self.selections.disjoint;
2615 if selections.len() == 1 {
2616 cx.emit(SearchEvent::ActiveMatchChanged)
2617 }
2618 if local {
2619 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2620 let inmemory_selections = selections
2621 .iter()
2622 .map(|s| {
2623 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2624 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2625 })
2626 .collect();
2627 self.update_restoration_data(cx, |data| {
2628 data.selections = inmemory_selections;
2629 });
2630
2631 if WorkspaceSettings::get(None, cx).restore_on_startup
2632 != RestoreOnStartupBehavior::None
2633 {
2634 if let Some(workspace_id) =
2635 self.workspace.as_ref().and_then(|workspace| workspace.1)
2636 {
2637 let snapshot = self.buffer().read(cx).snapshot(cx);
2638 let selections = selections.clone();
2639 let background_executor = cx.background_executor().clone();
2640 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2641 self.serialize_selections = cx.background_spawn(async move {
2642 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2643 let db_selections = selections
2644 .iter()
2645 .map(|selection| {
2646 (
2647 selection.start.to_offset(&snapshot),
2648 selection.end.to_offset(&snapshot),
2649 )
2650 })
2651 .collect();
2652
2653 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2654 .await
2655 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2656 .log_err();
2657 });
2658 }
2659 }
2660 }
2661 }
2662
2663 cx.notify();
2664 }
2665
2666 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2667 use text::ToOffset as _;
2668 use text::ToPoint as _;
2669
2670 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2671 return;
2672 }
2673
2674 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2675 return;
2676 };
2677
2678 let snapshot = singleton.read(cx).snapshot();
2679 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2680 let display_snapshot = display_map.snapshot(cx);
2681
2682 display_snapshot
2683 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2684 .map(|fold| {
2685 fold.range.start.text_anchor.to_point(&snapshot)
2686 ..fold.range.end.text_anchor.to_point(&snapshot)
2687 })
2688 .collect()
2689 });
2690 self.update_restoration_data(cx, |data| {
2691 data.folds = inmemory_folds;
2692 });
2693
2694 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2695 return;
2696 };
2697 let background_executor = cx.background_executor().clone();
2698 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2699 let db_folds = self.display_map.update(cx, |display_map, cx| {
2700 display_map
2701 .snapshot(cx)
2702 .folds_in_range(0..snapshot.len())
2703 .map(|fold| {
2704 (
2705 fold.range.start.text_anchor.to_offset(&snapshot),
2706 fold.range.end.text_anchor.to_offset(&snapshot),
2707 )
2708 })
2709 .collect()
2710 });
2711 self.serialize_folds = cx.background_spawn(async move {
2712 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2713 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2714 .await
2715 .with_context(|| {
2716 format!(
2717 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2718 )
2719 })
2720 .log_err();
2721 });
2722 }
2723
2724 pub fn sync_selections(
2725 &mut self,
2726 other: Entity<Editor>,
2727 cx: &mut Context<Self>,
2728 ) -> gpui::Subscription {
2729 let other_selections = other.read(cx).selections.disjoint.to_vec();
2730 self.selections.change_with(cx, |selections| {
2731 selections.select_anchors(other_selections);
2732 });
2733
2734 let other_subscription =
2735 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2736 EditorEvent::SelectionsChanged { local: true } => {
2737 let other_selections = other.read(cx).selections.disjoint.to_vec();
2738 if other_selections.is_empty() {
2739 return;
2740 }
2741 this.selections.change_with(cx, |selections| {
2742 selections.select_anchors(other_selections);
2743 });
2744 }
2745 _ => {}
2746 });
2747
2748 let this_subscription =
2749 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2750 EditorEvent::SelectionsChanged { local: true } => {
2751 let these_selections = this.selections.disjoint.to_vec();
2752 if these_selections.is_empty() {
2753 return;
2754 }
2755 other.update(cx, |other_editor, cx| {
2756 other_editor.selections.change_with(cx, |selections| {
2757 selections.select_anchors(these_selections);
2758 })
2759 });
2760 }
2761 _ => {}
2762 });
2763
2764 Subscription::join(other_subscription, this_subscription)
2765 }
2766
2767 pub fn change_selections<R>(
2768 &mut self,
2769 autoscroll: Option<Autoscroll>,
2770 window: &mut Window,
2771 cx: &mut Context<Self>,
2772 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2773 ) -> R {
2774 self.change_selections_inner(autoscroll, true, window, cx, change)
2775 }
2776
2777 fn change_selections_inner<R>(
2778 &mut self,
2779 autoscroll: Option<Autoscroll>,
2780 request_completions: bool,
2781 window: &mut Window,
2782 cx: &mut Context<Self>,
2783 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2784 ) -> R {
2785 let old_cursor_position = self.selections.newest_anchor().head();
2786 self.push_to_selection_history();
2787
2788 let (changed, result) = self.selections.change_with(cx, change);
2789
2790 if changed {
2791 if let Some(autoscroll) = autoscroll {
2792 self.request_autoscroll(autoscroll, cx);
2793 }
2794 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2795
2796 if self.should_open_signature_help_automatically(
2797 &old_cursor_position,
2798 self.signature_help_state.backspace_pressed(),
2799 cx,
2800 ) {
2801 self.show_signature_help(&ShowSignatureHelp, window, cx);
2802 }
2803 self.signature_help_state.set_backspace_pressed(false);
2804 }
2805
2806 result
2807 }
2808
2809 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2810 where
2811 I: IntoIterator<Item = (Range<S>, T)>,
2812 S: ToOffset,
2813 T: Into<Arc<str>>,
2814 {
2815 if self.read_only(cx) {
2816 return;
2817 }
2818
2819 self.buffer
2820 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2821 }
2822
2823 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2824 where
2825 I: IntoIterator<Item = (Range<S>, T)>,
2826 S: ToOffset,
2827 T: Into<Arc<str>>,
2828 {
2829 if self.read_only(cx) {
2830 return;
2831 }
2832
2833 self.buffer.update(cx, |buffer, cx| {
2834 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2835 });
2836 }
2837
2838 pub fn edit_with_block_indent<I, S, T>(
2839 &mut self,
2840 edits: I,
2841 original_indent_columns: Vec<Option<u32>>,
2842 cx: &mut Context<Self>,
2843 ) where
2844 I: IntoIterator<Item = (Range<S>, T)>,
2845 S: ToOffset,
2846 T: Into<Arc<str>>,
2847 {
2848 if self.read_only(cx) {
2849 return;
2850 }
2851
2852 self.buffer.update(cx, |buffer, cx| {
2853 buffer.edit(
2854 edits,
2855 Some(AutoindentMode::Block {
2856 original_indent_columns,
2857 }),
2858 cx,
2859 )
2860 });
2861 }
2862
2863 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2864 self.hide_context_menu(window, cx);
2865
2866 match phase {
2867 SelectPhase::Begin {
2868 position,
2869 add,
2870 click_count,
2871 } => self.begin_selection(position, add, click_count, window, cx),
2872 SelectPhase::BeginColumnar {
2873 position,
2874 goal_column,
2875 reset,
2876 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2877 SelectPhase::Extend {
2878 position,
2879 click_count,
2880 } => self.extend_selection(position, click_count, window, cx),
2881 SelectPhase::Update {
2882 position,
2883 goal_column,
2884 scroll_delta,
2885 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2886 SelectPhase::End => self.end_selection(window, cx),
2887 }
2888 }
2889
2890 fn extend_selection(
2891 &mut self,
2892 position: DisplayPoint,
2893 click_count: usize,
2894 window: &mut Window,
2895 cx: &mut Context<Self>,
2896 ) {
2897 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2898 let tail = self.selections.newest::<usize>(cx).tail();
2899 self.begin_selection(position, false, click_count, window, cx);
2900
2901 let position = position.to_offset(&display_map, Bias::Left);
2902 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2903
2904 let mut pending_selection = self
2905 .selections
2906 .pending_anchor()
2907 .expect("extend_selection not called with pending selection");
2908 if position >= tail {
2909 pending_selection.start = tail_anchor;
2910 } else {
2911 pending_selection.end = tail_anchor;
2912 pending_selection.reversed = true;
2913 }
2914
2915 let mut pending_mode = self.selections.pending_mode().unwrap();
2916 match &mut pending_mode {
2917 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2918 _ => {}
2919 }
2920
2921 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2922 s.set_pending(pending_selection, pending_mode)
2923 });
2924 }
2925
2926 fn begin_selection(
2927 &mut self,
2928 position: DisplayPoint,
2929 add: bool,
2930 click_count: usize,
2931 window: &mut Window,
2932 cx: &mut Context<Self>,
2933 ) {
2934 if !self.focus_handle.is_focused(window) {
2935 self.last_focused_descendant = None;
2936 window.focus(&self.focus_handle);
2937 }
2938
2939 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2940 let buffer = &display_map.buffer_snapshot;
2941 let newest_selection = self.selections.newest_anchor().clone();
2942 let position = display_map.clip_point(position, Bias::Left);
2943
2944 let start;
2945 let end;
2946 let mode;
2947 let mut auto_scroll;
2948 match click_count {
2949 1 => {
2950 start = buffer.anchor_before(position.to_point(&display_map));
2951 end = start;
2952 mode = SelectMode::Character;
2953 auto_scroll = true;
2954 }
2955 2 => {
2956 let range = movement::surrounding_word(&display_map, position);
2957 start = buffer.anchor_before(range.start.to_point(&display_map));
2958 end = buffer.anchor_before(range.end.to_point(&display_map));
2959 mode = SelectMode::Word(start..end);
2960 auto_scroll = true;
2961 }
2962 3 => {
2963 let position = display_map
2964 .clip_point(position, Bias::Left)
2965 .to_point(&display_map);
2966 let line_start = display_map.prev_line_boundary(position).0;
2967 let next_line_start = buffer.clip_point(
2968 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2969 Bias::Left,
2970 );
2971 start = buffer.anchor_before(line_start);
2972 end = buffer.anchor_before(next_line_start);
2973 mode = SelectMode::Line(start..end);
2974 auto_scroll = true;
2975 }
2976 _ => {
2977 start = buffer.anchor_before(0);
2978 end = buffer.anchor_before(buffer.len());
2979 mode = SelectMode::All;
2980 auto_scroll = false;
2981 }
2982 }
2983 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2984
2985 let point_to_delete: Option<usize> = {
2986 let selected_points: Vec<Selection<Point>> =
2987 self.selections.disjoint_in_range(start..end, cx);
2988
2989 if !add || click_count > 1 {
2990 None
2991 } else if !selected_points.is_empty() {
2992 Some(selected_points[0].id)
2993 } else {
2994 let clicked_point_already_selected =
2995 self.selections.disjoint.iter().find(|selection| {
2996 selection.start.to_point(buffer) == start.to_point(buffer)
2997 || selection.end.to_point(buffer) == end.to_point(buffer)
2998 });
2999
3000 clicked_point_already_selected.map(|selection| selection.id)
3001 }
3002 };
3003
3004 let selections_count = self.selections.count();
3005
3006 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3007 if let Some(point_to_delete) = point_to_delete {
3008 s.delete(point_to_delete);
3009
3010 if selections_count == 1 {
3011 s.set_pending_anchor_range(start..end, mode);
3012 }
3013 } else {
3014 if !add {
3015 s.clear_disjoint();
3016 } else if click_count > 1 {
3017 s.delete(newest_selection.id)
3018 }
3019
3020 s.set_pending_anchor_range(start..end, mode);
3021 }
3022 });
3023 }
3024
3025 fn begin_columnar_selection(
3026 &mut self,
3027 position: DisplayPoint,
3028 goal_column: u32,
3029 reset: bool,
3030 window: &mut Window,
3031 cx: &mut Context<Self>,
3032 ) {
3033 if !self.focus_handle.is_focused(window) {
3034 self.last_focused_descendant = None;
3035 window.focus(&self.focus_handle);
3036 }
3037
3038 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3039
3040 if reset {
3041 let pointer_position = display_map
3042 .buffer_snapshot
3043 .anchor_before(position.to_point(&display_map));
3044
3045 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3046 s.clear_disjoint();
3047 s.set_pending_anchor_range(
3048 pointer_position..pointer_position,
3049 SelectMode::Character,
3050 );
3051 });
3052 }
3053
3054 let tail = self.selections.newest::<Point>(cx).tail();
3055 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3056
3057 if !reset {
3058 self.select_columns(
3059 tail.to_display_point(&display_map),
3060 position,
3061 goal_column,
3062 &display_map,
3063 window,
3064 cx,
3065 );
3066 }
3067 }
3068
3069 fn update_selection(
3070 &mut self,
3071 position: DisplayPoint,
3072 goal_column: u32,
3073 scroll_delta: gpui::Point<f32>,
3074 window: &mut Window,
3075 cx: &mut Context<Self>,
3076 ) {
3077 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3078
3079 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3080 let tail = tail.to_display_point(&display_map);
3081 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3082 } else if let Some(mut pending) = self.selections.pending_anchor() {
3083 let buffer = self.buffer.read(cx).snapshot(cx);
3084 let head;
3085 let tail;
3086 let mode = self.selections.pending_mode().unwrap();
3087 match &mode {
3088 SelectMode::Character => {
3089 head = position.to_point(&display_map);
3090 tail = pending.tail().to_point(&buffer);
3091 }
3092 SelectMode::Word(original_range) => {
3093 let original_display_range = original_range.start.to_display_point(&display_map)
3094 ..original_range.end.to_display_point(&display_map);
3095 let original_buffer_range = original_display_range.start.to_point(&display_map)
3096 ..original_display_range.end.to_point(&display_map);
3097 if movement::is_inside_word(&display_map, position)
3098 || original_display_range.contains(&position)
3099 {
3100 let word_range = movement::surrounding_word(&display_map, position);
3101 if word_range.start < original_display_range.start {
3102 head = word_range.start.to_point(&display_map);
3103 } else {
3104 head = word_range.end.to_point(&display_map);
3105 }
3106 } else {
3107 head = position.to_point(&display_map);
3108 }
3109
3110 if head <= original_buffer_range.start {
3111 tail = original_buffer_range.end;
3112 } else {
3113 tail = original_buffer_range.start;
3114 }
3115 }
3116 SelectMode::Line(original_range) => {
3117 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3118
3119 let position = display_map
3120 .clip_point(position, Bias::Left)
3121 .to_point(&display_map);
3122 let line_start = display_map.prev_line_boundary(position).0;
3123 let next_line_start = buffer.clip_point(
3124 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3125 Bias::Left,
3126 );
3127
3128 if line_start < original_range.start {
3129 head = line_start
3130 } else {
3131 head = next_line_start
3132 }
3133
3134 if head <= original_range.start {
3135 tail = original_range.end;
3136 } else {
3137 tail = original_range.start;
3138 }
3139 }
3140 SelectMode::All => {
3141 return;
3142 }
3143 };
3144
3145 if head < tail {
3146 pending.start = buffer.anchor_before(head);
3147 pending.end = buffer.anchor_before(tail);
3148 pending.reversed = true;
3149 } else {
3150 pending.start = buffer.anchor_before(tail);
3151 pending.end = buffer.anchor_before(head);
3152 pending.reversed = false;
3153 }
3154
3155 self.change_selections(None, window, cx, |s| {
3156 s.set_pending(pending, mode);
3157 });
3158 } else {
3159 log::error!("update_selection dispatched with no pending selection");
3160 return;
3161 }
3162
3163 self.apply_scroll_delta(scroll_delta, window, cx);
3164 cx.notify();
3165 }
3166
3167 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3168 self.columnar_selection_tail.take();
3169 if self.selections.pending_anchor().is_some() {
3170 let selections = self.selections.all::<usize>(cx);
3171 self.change_selections(None, window, cx, |s| {
3172 s.select(selections);
3173 s.clear_pending();
3174 });
3175 }
3176 }
3177
3178 fn select_columns(
3179 &mut self,
3180 tail: DisplayPoint,
3181 head: DisplayPoint,
3182 goal_column: u32,
3183 display_map: &DisplaySnapshot,
3184 window: &mut Window,
3185 cx: &mut Context<Self>,
3186 ) {
3187 let start_row = cmp::min(tail.row(), head.row());
3188 let end_row = cmp::max(tail.row(), head.row());
3189 let start_column = cmp::min(tail.column(), goal_column);
3190 let end_column = cmp::max(tail.column(), goal_column);
3191 let reversed = start_column < tail.column();
3192
3193 let selection_ranges = (start_row.0..=end_row.0)
3194 .map(DisplayRow)
3195 .filter_map(|row| {
3196 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3197 let start = display_map
3198 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3199 .to_point(display_map);
3200 let end = display_map
3201 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3202 .to_point(display_map);
3203 if reversed {
3204 Some(end..start)
3205 } else {
3206 Some(start..end)
3207 }
3208 } else {
3209 None
3210 }
3211 })
3212 .collect::<Vec<_>>();
3213
3214 self.change_selections(None, window, cx, |s| {
3215 s.select_ranges(selection_ranges);
3216 });
3217 cx.notify();
3218 }
3219
3220 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3221 self.selections
3222 .all_adjusted(cx)
3223 .iter()
3224 .any(|selection| !selection.is_empty())
3225 }
3226
3227 pub fn has_pending_nonempty_selection(&self) -> bool {
3228 let pending_nonempty_selection = match self.selections.pending_anchor() {
3229 Some(Selection { start, end, .. }) => start != end,
3230 None => false,
3231 };
3232
3233 pending_nonempty_selection
3234 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3235 }
3236
3237 pub fn has_pending_selection(&self) -> bool {
3238 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3239 }
3240
3241 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3242 self.selection_mark_mode = false;
3243
3244 if self.clear_expanded_diff_hunks(cx) {
3245 cx.notify();
3246 return;
3247 }
3248 if self.dismiss_menus_and_popups(true, window, cx) {
3249 return;
3250 }
3251
3252 if self.mode.is_full()
3253 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3254 {
3255 return;
3256 }
3257
3258 cx.propagate();
3259 }
3260
3261 pub fn dismiss_menus_and_popups(
3262 &mut self,
3263 is_user_requested: bool,
3264 window: &mut Window,
3265 cx: &mut Context<Self>,
3266 ) -> bool {
3267 if self.take_rename(false, window, cx).is_some() {
3268 return true;
3269 }
3270
3271 if hide_hover(self, cx) {
3272 return true;
3273 }
3274
3275 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3276 return true;
3277 }
3278
3279 if self.hide_context_menu(window, cx).is_some() {
3280 return true;
3281 }
3282
3283 if self.mouse_context_menu.take().is_some() {
3284 return true;
3285 }
3286
3287 if is_user_requested && self.discard_inline_completion(true, cx) {
3288 return true;
3289 }
3290
3291 if self.snippet_stack.pop().is_some() {
3292 return true;
3293 }
3294
3295 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3296 self.dismiss_diagnostics(cx);
3297 return true;
3298 }
3299
3300 false
3301 }
3302
3303 fn linked_editing_ranges_for(
3304 &self,
3305 selection: Range<text::Anchor>,
3306 cx: &App,
3307 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3308 if self.linked_edit_ranges.is_empty() {
3309 return None;
3310 }
3311 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3312 selection.end.buffer_id.and_then(|end_buffer_id| {
3313 if selection.start.buffer_id != Some(end_buffer_id) {
3314 return None;
3315 }
3316 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3317 let snapshot = buffer.read(cx).snapshot();
3318 self.linked_edit_ranges
3319 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3320 .map(|ranges| (ranges, snapshot, buffer))
3321 })?;
3322 use text::ToOffset as TO;
3323 // find offset from the start of current range to current cursor position
3324 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3325
3326 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3327 let start_difference = start_offset - start_byte_offset;
3328 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3329 let end_difference = end_offset - start_byte_offset;
3330 // Current range has associated linked ranges.
3331 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3332 for range in linked_ranges.iter() {
3333 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3334 let end_offset = start_offset + end_difference;
3335 let start_offset = start_offset + start_difference;
3336 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3337 continue;
3338 }
3339 if self.selections.disjoint_anchor_ranges().any(|s| {
3340 if s.start.buffer_id != selection.start.buffer_id
3341 || s.end.buffer_id != selection.end.buffer_id
3342 {
3343 return false;
3344 }
3345 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3346 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3347 }) {
3348 continue;
3349 }
3350 let start = buffer_snapshot.anchor_after(start_offset);
3351 let end = buffer_snapshot.anchor_after(end_offset);
3352 linked_edits
3353 .entry(buffer.clone())
3354 .or_default()
3355 .push(start..end);
3356 }
3357 Some(linked_edits)
3358 }
3359
3360 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3361 let text: Arc<str> = text.into();
3362
3363 if self.read_only(cx) {
3364 return;
3365 }
3366
3367 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3368
3369 let selections = self.selections.all_adjusted(cx);
3370 let mut bracket_inserted = false;
3371 let mut edits = Vec::new();
3372 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3373 let mut new_selections = Vec::with_capacity(selections.len());
3374 let mut new_autoclose_regions = Vec::new();
3375 let snapshot = self.buffer.read(cx).read(cx);
3376 let mut clear_linked_edit_ranges = false;
3377
3378 for (selection, autoclose_region) in
3379 self.selections_with_autoclose_regions(selections, &snapshot)
3380 {
3381 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3382 // Determine if the inserted text matches the opening or closing
3383 // bracket of any of this language's bracket pairs.
3384 let mut bracket_pair = None;
3385 let mut is_bracket_pair_start = false;
3386 let mut is_bracket_pair_end = false;
3387 if !text.is_empty() {
3388 let mut bracket_pair_matching_end = None;
3389 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3390 // and they are removing the character that triggered IME popup.
3391 for (pair, enabled) in scope.brackets() {
3392 if !pair.close && !pair.surround {
3393 continue;
3394 }
3395
3396 if enabled && pair.start.ends_with(text.as_ref()) {
3397 let prefix_len = pair.start.len() - text.len();
3398 let preceding_text_matches_prefix = prefix_len == 0
3399 || (selection.start.column >= (prefix_len as u32)
3400 && snapshot.contains_str_at(
3401 Point::new(
3402 selection.start.row,
3403 selection.start.column - (prefix_len as u32),
3404 ),
3405 &pair.start[..prefix_len],
3406 ));
3407 if preceding_text_matches_prefix {
3408 bracket_pair = Some(pair.clone());
3409 is_bracket_pair_start = true;
3410 break;
3411 }
3412 }
3413 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3414 {
3415 // take first bracket pair matching end, but don't break in case a later bracket
3416 // pair matches start
3417 bracket_pair_matching_end = Some(pair.clone());
3418 }
3419 }
3420 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3421 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3422 is_bracket_pair_end = true;
3423 }
3424 }
3425
3426 if let Some(bracket_pair) = bracket_pair {
3427 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3428 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3429 let auto_surround =
3430 self.use_auto_surround && snapshot_settings.use_auto_surround;
3431 if selection.is_empty() {
3432 if is_bracket_pair_start {
3433 // If the inserted text is a suffix of an opening bracket and the
3434 // selection is preceded by the rest of the opening bracket, then
3435 // insert the closing bracket.
3436 let following_text_allows_autoclose = snapshot
3437 .chars_at(selection.start)
3438 .next()
3439 .map_or(true, |c| scope.should_autoclose_before(c));
3440
3441 let preceding_text_allows_autoclose = selection.start.column == 0
3442 || snapshot.reversed_chars_at(selection.start).next().map_or(
3443 true,
3444 |c| {
3445 bracket_pair.start != bracket_pair.end
3446 || !snapshot
3447 .char_classifier_at(selection.start)
3448 .is_word(c)
3449 },
3450 );
3451
3452 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3453 && bracket_pair.start.len() == 1
3454 {
3455 let target = bracket_pair.start.chars().next().unwrap();
3456 let current_line_count = snapshot
3457 .reversed_chars_at(selection.start)
3458 .take_while(|&c| c != '\n')
3459 .filter(|&c| c == target)
3460 .count();
3461 current_line_count % 2 == 1
3462 } else {
3463 false
3464 };
3465
3466 if autoclose
3467 && bracket_pair.close
3468 && following_text_allows_autoclose
3469 && preceding_text_allows_autoclose
3470 && !is_closing_quote
3471 {
3472 let anchor = snapshot.anchor_before(selection.end);
3473 new_selections.push((selection.map(|_| anchor), text.len()));
3474 new_autoclose_regions.push((
3475 anchor,
3476 text.len(),
3477 selection.id,
3478 bracket_pair.clone(),
3479 ));
3480 edits.push((
3481 selection.range(),
3482 format!("{}{}", text, bracket_pair.end).into(),
3483 ));
3484 bracket_inserted = true;
3485 continue;
3486 }
3487 }
3488
3489 if let Some(region) = autoclose_region {
3490 // If the selection is followed by an auto-inserted closing bracket,
3491 // then don't insert that closing bracket again; just move the selection
3492 // past the closing bracket.
3493 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3494 && text.as_ref() == region.pair.end.as_str();
3495 if should_skip {
3496 let anchor = snapshot.anchor_after(selection.end);
3497 new_selections
3498 .push((selection.map(|_| anchor), region.pair.end.len()));
3499 continue;
3500 }
3501 }
3502
3503 let always_treat_brackets_as_autoclosed = snapshot
3504 .language_settings_at(selection.start, cx)
3505 .always_treat_brackets_as_autoclosed;
3506 if always_treat_brackets_as_autoclosed
3507 && is_bracket_pair_end
3508 && snapshot.contains_str_at(selection.end, text.as_ref())
3509 {
3510 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3511 // and the inserted text is a closing bracket and the selection is followed
3512 // by the closing bracket then move the selection past the closing bracket.
3513 let anchor = snapshot.anchor_after(selection.end);
3514 new_selections.push((selection.map(|_| anchor), text.len()));
3515 continue;
3516 }
3517 }
3518 // If an opening bracket is 1 character long and is typed while
3519 // text is selected, then surround that text with the bracket pair.
3520 else if auto_surround
3521 && bracket_pair.surround
3522 && is_bracket_pair_start
3523 && bracket_pair.start.chars().count() == 1
3524 {
3525 edits.push((selection.start..selection.start, text.clone()));
3526 edits.push((
3527 selection.end..selection.end,
3528 bracket_pair.end.as_str().into(),
3529 ));
3530 bracket_inserted = true;
3531 new_selections.push((
3532 Selection {
3533 id: selection.id,
3534 start: snapshot.anchor_after(selection.start),
3535 end: snapshot.anchor_before(selection.end),
3536 reversed: selection.reversed,
3537 goal: selection.goal,
3538 },
3539 0,
3540 ));
3541 continue;
3542 }
3543 }
3544 }
3545
3546 if self.auto_replace_emoji_shortcode
3547 && selection.is_empty()
3548 && text.as_ref().ends_with(':')
3549 {
3550 if let Some(possible_emoji_short_code) =
3551 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3552 {
3553 if !possible_emoji_short_code.is_empty() {
3554 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3555 let emoji_shortcode_start = Point::new(
3556 selection.start.row,
3557 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3558 );
3559
3560 // Remove shortcode from buffer
3561 edits.push((
3562 emoji_shortcode_start..selection.start,
3563 "".to_string().into(),
3564 ));
3565 new_selections.push((
3566 Selection {
3567 id: selection.id,
3568 start: snapshot.anchor_after(emoji_shortcode_start),
3569 end: snapshot.anchor_before(selection.start),
3570 reversed: selection.reversed,
3571 goal: selection.goal,
3572 },
3573 0,
3574 ));
3575
3576 // Insert emoji
3577 let selection_start_anchor = snapshot.anchor_after(selection.start);
3578 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3579 edits.push((selection.start..selection.end, emoji.to_string().into()));
3580
3581 continue;
3582 }
3583 }
3584 }
3585 }
3586
3587 // If not handling any auto-close operation, then just replace the selected
3588 // text with the given input and move the selection to the end of the
3589 // newly inserted text.
3590 let anchor = snapshot.anchor_after(selection.end);
3591 if !self.linked_edit_ranges.is_empty() {
3592 let start_anchor = snapshot.anchor_before(selection.start);
3593
3594 let is_word_char = text.chars().next().map_or(true, |char| {
3595 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3596 classifier.is_word(char)
3597 });
3598
3599 if is_word_char {
3600 if let Some(ranges) = self
3601 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3602 {
3603 for (buffer, edits) in ranges {
3604 linked_edits
3605 .entry(buffer.clone())
3606 .or_default()
3607 .extend(edits.into_iter().map(|range| (range, text.clone())));
3608 }
3609 }
3610 } else {
3611 clear_linked_edit_ranges = true;
3612 }
3613 }
3614
3615 new_selections.push((selection.map(|_| anchor), 0));
3616 edits.push((selection.start..selection.end, text.clone()));
3617 }
3618
3619 drop(snapshot);
3620
3621 self.transact(window, cx, |this, window, cx| {
3622 if clear_linked_edit_ranges {
3623 this.linked_edit_ranges.clear();
3624 }
3625 let initial_buffer_versions =
3626 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3627
3628 this.buffer.update(cx, |buffer, cx| {
3629 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3630 });
3631 for (buffer, edits) in linked_edits {
3632 buffer.update(cx, |buffer, cx| {
3633 let snapshot = buffer.snapshot();
3634 let edits = edits
3635 .into_iter()
3636 .map(|(range, text)| {
3637 use text::ToPoint as TP;
3638 let end_point = TP::to_point(&range.end, &snapshot);
3639 let start_point = TP::to_point(&range.start, &snapshot);
3640 (start_point..end_point, text)
3641 })
3642 .sorted_by_key(|(range, _)| range.start);
3643 buffer.edit(edits, None, cx);
3644 })
3645 }
3646 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3647 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3648 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3649 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3650 .zip(new_selection_deltas)
3651 .map(|(selection, delta)| Selection {
3652 id: selection.id,
3653 start: selection.start + delta,
3654 end: selection.end + delta,
3655 reversed: selection.reversed,
3656 goal: SelectionGoal::None,
3657 })
3658 .collect::<Vec<_>>();
3659
3660 let mut i = 0;
3661 for (position, delta, selection_id, pair) in new_autoclose_regions {
3662 let position = position.to_offset(&map.buffer_snapshot) + delta;
3663 let start = map.buffer_snapshot.anchor_before(position);
3664 let end = map.buffer_snapshot.anchor_after(position);
3665 while let Some(existing_state) = this.autoclose_regions.get(i) {
3666 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3667 Ordering::Less => i += 1,
3668 Ordering::Greater => break,
3669 Ordering::Equal => {
3670 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3671 Ordering::Less => i += 1,
3672 Ordering::Equal => break,
3673 Ordering::Greater => break,
3674 }
3675 }
3676 }
3677 }
3678 this.autoclose_regions.insert(
3679 i,
3680 AutocloseRegion {
3681 selection_id,
3682 range: start..end,
3683 pair,
3684 },
3685 );
3686 }
3687
3688 let had_active_inline_completion = this.has_active_inline_completion();
3689 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3690 s.select(new_selections)
3691 });
3692
3693 if !bracket_inserted {
3694 if let Some(on_type_format_task) =
3695 this.trigger_on_type_formatting(text.to_string(), window, cx)
3696 {
3697 on_type_format_task.detach_and_log_err(cx);
3698 }
3699 }
3700
3701 let editor_settings = EditorSettings::get_global(cx);
3702 if bracket_inserted
3703 && (editor_settings.auto_signature_help
3704 || editor_settings.show_signature_help_after_edits)
3705 {
3706 this.show_signature_help(&ShowSignatureHelp, window, cx);
3707 }
3708
3709 let trigger_in_words =
3710 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3711 if this.hard_wrap.is_some() {
3712 let latest: Range<Point> = this.selections.newest(cx).range();
3713 if latest.is_empty()
3714 && this
3715 .buffer()
3716 .read(cx)
3717 .snapshot(cx)
3718 .line_len(MultiBufferRow(latest.start.row))
3719 == latest.start.column
3720 {
3721 this.rewrap_impl(
3722 RewrapOptions {
3723 override_language_settings: true,
3724 preserve_existing_whitespace: true,
3725 },
3726 cx,
3727 )
3728 }
3729 }
3730 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3731 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3732 this.refresh_inline_completion(true, false, window, cx);
3733 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3734 });
3735 }
3736
3737 fn find_possible_emoji_shortcode_at_position(
3738 snapshot: &MultiBufferSnapshot,
3739 position: Point,
3740 ) -> Option<String> {
3741 let mut chars = Vec::new();
3742 let mut found_colon = false;
3743 for char in snapshot.reversed_chars_at(position).take(100) {
3744 // Found a possible emoji shortcode in the middle of the buffer
3745 if found_colon {
3746 if char.is_whitespace() {
3747 chars.reverse();
3748 return Some(chars.iter().collect());
3749 }
3750 // If the previous character is not a whitespace, we are in the middle of a word
3751 // and we only want to complete the shortcode if the word is made up of other emojis
3752 let mut containing_word = String::new();
3753 for ch in snapshot
3754 .reversed_chars_at(position)
3755 .skip(chars.len() + 1)
3756 .take(100)
3757 {
3758 if ch.is_whitespace() {
3759 break;
3760 }
3761 containing_word.push(ch);
3762 }
3763 let containing_word = containing_word.chars().rev().collect::<String>();
3764 if util::word_consists_of_emojis(containing_word.as_str()) {
3765 chars.reverse();
3766 return Some(chars.iter().collect());
3767 }
3768 }
3769
3770 if char.is_whitespace() || !char.is_ascii() {
3771 return None;
3772 }
3773 if char == ':' {
3774 found_colon = true;
3775 } else {
3776 chars.push(char);
3777 }
3778 }
3779 // Found a possible emoji shortcode at the beginning of the buffer
3780 chars.reverse();
3781 Some(chars.iter().collect())
3782 }
3783
3784 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3785 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3786 self.transact(window, cx, |this, window, cx| {
3787 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3788 let selections = this.selections.all::<usize>(cx);
3789 let multi_buffer = this.buffer.read(cx);
3790 let buffer = multi_buffer.snapshot(cx);
3791 selections
3792 .iter()
3793 .map(|selection| {
3794 let start_point = selection.start.to_point(&buffer);
3795 let mut indent =
3796 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3797 indent.len = cmp::min(indent.len, start_point.column);
3798 let start = selection.start;
3799 let end = selection.end;
3800 let selection_is_empty = start == end;
3801 let language_scope = buffer.language_scope_at(start);
3802 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3803 &language_scope
3804 {
3805 let insert_extra_newline =
3806 insert_extra_newline_brackets(&buffer, start..end, language)
3807 || insert_extra_newline_tree_sitter(&buffer, start..end);
3808
3809 // Comment extension on newline is allowed only for cursor selections
3810 let comment_delimiter = maybe!({
3811 if !selection_is_empty {
3812 return None;
3813 }
3814
3815 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3816 return None;
3817 }
3818
3819 let delimiters = language.line_comment_prefixes();
3820 let max_len_of_delimiter =
3821 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3822 let (snapshot, range) =
3823 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3824
3825 let mut index_of_first_non_whitespace = 0;
3826 let comment_candidate = snapshot
3827 .chars_for_range(range)
3828 .skip_while(|c| {
3829 let should_skip = c.is_whitespace();
3830 if should_skip {
3831 index_of_first_non_whitespace += 1;
3832 }
3833 should_skip
3834 })
3835 .take(max_len_of_delimiter)
3836 .collect::<String>();
3837 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3838 comment_candidate.starts_with(comment_prefix.as_ref())
3839 })?;
3840 let cursor_is_placed_after_comment_marker =
3841 index_of_first_non_whitespace + comment_prefix.len()
3842 <= start_point.column as usize;
3843 if cursor_is_placed_after_comment_marker {
3844 Some(comment_prefix.clone())
3845 } else {
3846 None
3847 }
3848 });
3849 (comment_delimiter, insert_extra_newline)
3850 } else {
3851 (None, false)
3852 };
3853
3854 let capacity_for_delimiter = comment_delimiter
3855 .as_deref()
3856 .map(str::len)
3857 .unwrap_or_default();
3858 let mut new_text =
3859 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3860 new_text.push('\n');
3861 new_text.extend(indent.chars());
3862 if let Some(delimiter) = &comment_delimiter {
3863 new_text.push_str(delimiter);
3864 }
3865 if insert_extra_newline {
3866 new_text = new_text.repeat(2);
3867 }
3868
3869 let anchor = buffer.anchor_after(end);
3870 let new_selection = selection.map(|_| anchor);
3871 (
3872 (start..end, new_text),
3873 (insert_extra_newline, new_selection),
3874 )
3875 })
3876 .unzip()
3877 };
3878
3879 this.edit_with_autoindent(edits, cx);
3880 let buffer = this.buffer.read(cx).snapshot(cx);
3881 let new_selections = selection_fixup_info
3882 .into_iter()
3883 .map(|(extra_newline_inserted, new_selection)| {
3884 let mut cursor = new_selection.end.to_point(&buffer);
3885 if extra_newline_inserted {
3886 cursor.row -= 1;
3887 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3888 }
3889 new_selection.map(|_| cursor)
3890 })
3891 .collect();
3892
3893 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3894 s.select(new_selections)
3895 });
3896 this.refresh_inline_completion(true, false, window, cx);
3897 });
3898 }
3899
3900 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3901 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3902
3903 let buffer = self.buffer.read(cx);
3904 let snapshot = buffer.snapshot(cx);
3905
3906 let mut edits = Vec::new();
3907 let mut rows = Vec::new();
3908
3909 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3910 let cursor = selection.head();
3911 let row = cursor.row;
3912
3913 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3914
3915 let newline = "\n".to_string();
3916 edits.push((start_of_line..start_of_line, newline));
3917
3918 rows.push(row + rows_inserted as u32);
3919 }
3920
3921 self.transact(window, cx, |editor, window, cx| {
3922 editor.edit(edits, cx);
3923
3924 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3925 let mut index = 0;
3926 s.move_cursors_with(|map, _, _| {
3927 let row = rows[index];
3928 index += 1;
3929
3930 let point = Point::new(row, 0);
3931 let boundary = map.next_line_boundary(point).1;
3932 let clipped = map.clip_point(boundary, Bias::Left);
3933
3934 (clipped, SelectionGoal::None)
3935 });
3936 });
3937
3938 let mut indent_edits = Vec::new();
3939 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3940 for row in rows {
3941 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3942 for (row, indent) in indents {
3943 if indent.len == 0 {
3944 continue;
3945 }
3946
3947 let text = match indent.kind {
3948 IndentKind::Space => " ".repeat(indent.len as usize),
3949 IndentKind::Tab => "\t".repeat(indent.len as usize),
3950 };
3951 let point = Point::new(row.0, 0);
3952 indent_edits.push((point..point, text));
3953 }
3954 }
3955 editor.edit(indent_edits, cx);
3956 });
3957 }
3958
3959 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3960 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3961
3962 let buffer = self.buffer.read(cx);
3963 let snapshot = buffer.snapshot(cx);
3964
3965 let mut edits = Vec::new();
3966 let mut rows = Vec::new();
3967 let mut rows_inserted = 0;
3968
3969 for selection in self.selections.all_adjusted(cx) {
3970 let cursor = selection.head();
3971 let row = cursor.row;
3972
3973 let point = Point::new(row + 1, 0);
3974 let start_of_line = snapshot.clip_point(point, Bias::Left);
3975
3976 let newline = "\n".to_string();
3977 edits.push((start_of_line..start_of_line, newline));
3978
3979 rows_inserted += 1;
3980 rows.push(row + rows_inserted);
3981 }
3982
3983 self.transact(window, cx, |editor, window, cx| {
3984 editor.edit(edits, cx);
3985
3986 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3987 let mut index = 0;
3988 s.move_cursors_with(|map, _, _| {
3989 let row = rows[index];
3990 index += 1;
3991
3992 let point = Point::new(row, 0);
3993 let boundary = map.next_line_boundary(point).1;
3994 let clipped = map.clip_point(boundary, Bias::Left);
3995
3996 (clipped, SelectionGoal::None)
3997 });
3998 });
3999
4000 let mut indent_edits = Vec::new();
4001 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4002 for row in rows {
4003 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4004 for (row, indent) in indents {
4005 if indent.len == 0 {
4006 continue;
4007 }
4008
4009 let text = match indent.kind {
4010 IndentKind::Space => " ".repeat(indent.len as usize),
4011 IndentKind::Tab => "\t".repeat(indent.len as usize),
4012 };
4013 let point = Point::new(row.0, 0);
4014 indent_edits.push((point..point, text));
4015 }
4016 }
4017 editor.edit(indent_edits, cx);
4018 });
4019 }
4020
4021 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4022 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4023 original_indent_columns: Vec::new(),
4024 });
4025 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4026 }
4027
4028 fn insert_with_autoindent_mode(
4029 &mut self,
4030 text: &str,
4031 autoindent_mode: Option<AutoindentMode>,
4032 window: &mut Window,
4033 cx: &mut Context<Self>,
4034 ) {
4035 if self.read_only(cx) {
4036 return;
4037 }
4038
4039 let text: Arc<str> = text.into();
4040 self.transact(window, cx, |this, window, cx| {
4041 let old_selections = this.selections.all_adjusted(cx);
4042 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4043 let anchors = {
4044 let snapshot = buffer.read(cx);
4045 old_selections
4046 .iter()
4047 .map(|s| {
4048 let anchor = snapshot.anchor_after(s.head());
4049 s.map(|_| anchor)
4050 })
4051 .collect::<Vec<_>>()
4052 };
4053 buffer.edit(
4054 old_selections
4055 .iter()
4056 .map(|s| (s.start..s.end, text.clone())),
4057 autoindent_mode,
4058 cx,
4059 );
4060 anchors
4061 });
4062
4063 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4064 s.select_anchors(selection_anchors);
4065 });
4066
4067 cx.notify();
4068 });
4069 }
4070
4071 fn trigger_completion_on_input(
4072 &mut self,
4073 text: &str,
4074 trigger_in_words: bool,
4075 window: &mut Window,
4076 cx: &mut Context<Self>,
4077 ) {
4078 let ignore_completion_provider = self
4079 .context_menu
4080 .borrow()
4081 .as_ref()
4082 .map(|menu| match menu {
4083 CodeContextMenu::Completions(completions_menu) => {
4084 completions_menu.ignore_completion_provider
4085 }
4086 CodeContextMenu::CodeActions(_) => false,
4087 })
4088 .unwrap_or(false);
4089
4090 if ignore_completion_provider {
4091 self.show_word_completions(&ShowWordCompletions, window, cx);
4092 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4093 self.show_completions(
4094 &ShowCompletions {
4095 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4096 },
4097 window,
4098 cx,
4099 );
4100 } else {
4101 self.hide_context_menu(window, cx);
4102 }
4103 }
4104
4105 fn is_completion_trigger(
4106 &self,
4107 text: &str,
4108 trigger_in_words: bool,
4109 cx: &mut Context<Self>,
4110 ) -> bool {
4111 let position = self.selections.newest_anchor().head();
4112 let multibuffer = self.buffer.read(cx);
4113 let Some(buffer) = position
4114 .buffer_id
4115 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4116 else {
4117 return false;
4118 };
4119
4120 if let Some(completion_provider) = &self.completion_provider {
4121 completion_provider.is_completion_trigger(
4122 &buffer,
4123 position.text_anchor,
4124 text,
4125 trigger_in_words,
4126 cx,
4127 )
4128 } else {
4129 false
4130 }
4131 }
4132
4133 /// If any empty selections is touching the start of its innermost containing autoclose
4134 /// region, expand it to select the brackets.
4135 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4136 let selections = self.selections.all::<usize>(cx);
4137 let buffer = self.buffer.read(cx).read(cx);
4138 let new_selections = self
4139 .selections_with_autoclose_regions(selections, &buffer)
4140 .map(|(mut selection, region)| {
4141 if !selection.is_empty() {
4142 return selection;
4143 }
4144
4145 if let Some(region) = region {
4146 let mut range = region.range.to_offset(&buffer);
4147 if selection.start == range.start && range.start >= region.pair.start.len() {
4148 range.start -= region.pair.start.len();
4149 if buffer.contains_str_at(range.start, ®ion.pair.start)
4150 && buffer.contains_str_at(range.end, ®ion.pair.end)
4151 {
4152 range.end += region.pair.end.len();
4153 selection.start = range.start;
4154 selection.end = range.end;
4155
4156 return selection;
4157 }
4158 }
4159 }
4160
4161 let always_treat_brackets_as_autoclosed = buffer
4162 .language_settings_at(selection.start, cx)
4163 .always_treat_brackets_as_autoclosed;
4164
4165 if !always_treat_brackets_as_autoclosed {
4166 return selection;
4167 }
4168
4169 if let Some(scope) = buffer.language_scope_at(selection.start) {
4170 for (pair, enabled) in scope.brackets() {
4171 if !enabled || !pair.close {
4172 continue;
4173 }
4174
4175 if buffer.contains_str_at(selection.start, &pair.end) {
4176 let pair_start_len = pair.start.len();
4177 if buffer.contains_str_at(
4178 selection.start.saturating_sub(pair_start_len),
4179 &pair.start,
4180 ) {
4181 selection.start -= pair_start_len;
4182 selection.end += pair.end.len();
4183
4184 return selection;
4185 }
4186 }
4187 }
4188 }
4189
4190 selection
4191 })
4192 .collect();
4193
4194 drop(buffer);
4195 self.change_selections(None, window, cx, |selections| {
4196 selections.select(new_selections)
4197 });
4198 }
4199
4200 /// Iterate the given selections, and for each one, find the smallest surrounding
4201 /// autoclose region. This uses the ordering of the selections and the autoclose
4202 /// regions to avoid repeated comparisons.
4203 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4204 &'a self,
4205 selections: impl IntoIterator<Item = Selection<D>>,
4206 buffer: &'a MultiBufferSnapshot,
4207 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4208 let mut i = 0;
4209 let mut regions = self.autoclose_regions.as_slice();
4210 selections.into_iter().map(move |selection| {
4211 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4212
4213 let mut enclosing = None;
4214 while let Some(pair_state) = regions.get(i) {
4215 if pair_state.range.end.to_offset(buffer) < range.start {
4216 regions = ®ions[i + 1..];
4217 i = 0;
4218 } else if pair_state.range.start.to_offset(buffer) > range.end {
4219 break;
4220 } else {
4221 if pair_state.selection_id == selection.id {
4222 enclosing = Some(pair_state);
4223 }
4224 i += 1;
4225 }
4226 }
4227
4228 (selection, enclosing)
4229 })
4230 }
4231
4232 /// Remove any autoclose regions that no longer contain their selection.
4233 fn invalidate_autoclose_regions(
4234 &mut self,
4235 mut selections: &[Selection<Anchor>],
4236 buffer: &MultiBufferSnapshot,
4237 ) {
4238 self.autoclose_regions.retain(|state| {
4239 let mut i = 0;
4240 while let Some(selection) = selections.get(i) {
4241 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4242 selections = &selections[1..];
4243 continue;
4244 }
4245 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4246 break;
4247 }
4248 if selection.id == state.selection_id {
4249 return true;
4250 } else {
4251 i += 1;
4252 }
4253 }
4254 false
4255 });
4256 }
4257
4258 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4259 let offset = position.to_offset(buffer);
4260 let (word_range, kind) = buffer.surrounding_word(offset, true);
4261 if offset > word_range.start && kind == Some(CharKind::Word) {
4262 Some(
4263 buffer
4264 .text_for_range(word_range.start..offset)
4265 .collect::<String>(),
4266 )
4267 } else {
4268 None
4269 }
4270 }
4271
4272 pub fn toggle_inline_values(
4273 &mut self,
4274 _: &ToggleInlineValues,
4275 _: &mut Window,
4276 cx: &mut Context<Self>,
4277 ) {
4278 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4279
4280 self.refresh_inline_values(cx);
4281 }
4282
4283 pub fn toggle_inlay_hints(
4284 &mut self,
4285 _: &ToggleInlayHints,
4286 _: &mut Window,
4287 cx: &mut Context<Self>,
4288 ) {
4289 self.refresh_inlay_hints(
4290 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4291 cx,
4292 );
4293 }
4294
4295 pub fn inlay_hints_enabled(&self) -> bool {
4296 self.inlay_hint_cache.enabled
4297 }
4298
4299 pub fn inline_values_enabled(&self) -> bool {
4300 self.inline_value_cache.enabled
4301 }
4302
4303 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4304 if self.semantics_provider.is_none() || !self.mode.is_full() {
4305 return;
4306 }
4307
4308 let reason_description = reason.description();
4309 let ignore_debounce = matches!(
4310 reason,
4311 InlayHintRefreshReason::SettingsChange(_)
4312 | InlayHintRefreshReason::Toggle(_)
4313 | InlayHintRefreshReason::ExcerptsRemoved(_)
4314 | InlayHintRefreshReason::ModifiersChanged(_)
4315 );
4316 let (invalidate_cache, required_languages) = match reason {
4317 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4318 match self.inlay_hint_cache.modifiers_override(enabled) {
4319 Some(enabled) => {
4320 if enabled {
4321 (InvalidationStrategy::RefreshRequested, None)
4322 } else {
4323 self.splice_inlays(
4324 &self
4325 .visible_inlay_hints(cx)
4326 .iter()
4327 .map(|inlay| inlay.id)
4328 .collect::<Vec<InlayId>>(),
4329 Vec::new(),
4330 cx,
4331 );
4332 return;
4333 }
4334 }
4335 None => return,
4336 }
4337 }
4338 InlayHintRefreshReason::Toggle(enabled) => {
4339 if self.inlay_hint_cache.toggle(enabled) {
4340 if enabled {
4341 (InvalidationStrategy::RefreshRequested, None)
4342 } else {
4343 self.splice_inlays(
4344 &self
4345 .visible_inlay_hints(cx)
4346 .iter()
4347 .map(|inlay| inlay.id)
4348 .collect::<Vec<InlayId>>(),
4349 Vec::new(),
4350 cx,
4351 );
4352 return;
4353 }
4354 } else {
4355 return;
4356 }
4357 }
4358 InlayHintRefreshReason::SettingsChange(new_settings) => {
4359 match self.inlay_hint_cache.update_settings(
4360 &self.buffer,
4361 new_settings,
4362 self.visible_inlay_hints(cx),
4363 cx,
4364 ) {
4365 ControlFlow::Break(Some(InlaySplice {
4366 to_remove,
4367 to_insert,
4368 })) => {
4369 self.splice_inlays(&to_remove, to_insert, cx);
4370 return;
4371 }
4372 ControlFlow::Break(None) => return,
4373 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4374 }
4375 }
4376 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4377 if let Some(InlaySplice {
4378 to_remove,
4379 to_insert,
4380 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4381 {
4382 self.splice_inlays(&to_remove, to_insert, cx);
4383 }
4384 self.display_map.update(cx, |display_map, _| {
4385 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4386 });
4387 return;
4388 }
4389 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4390 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4391 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4392 }
4393 InlayHintRefreshReason::RefreshRequested => {
4394 (InvalidationStrategy::RefreshRequested, None)
4395 }
4396 };
4397
4398 if let Some(InlaySplice {
4399 to_remove,
4400 to_insert,
4401 }) = self.inlay_hint_cache.spawn_hint_refresh(
4402 reason_description,
4403 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4404 invalidate_cache,
4405 ignore_debounce,
4406 cx,
4407 ) {
4408 self.splice_inlays(&to_remove, to_insert, cx);
4409 }
4410 }
4411
4412 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4413 self.display_map
4414 .read(cx)
4415 .current_inlays()
4416 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4417 .cloned()
4418 .collect()
4419 }
4420
4421 pub fn excerpts_for_inlay_hints_query(
4422 &self,
4423 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4424 cx: &mut Context<Editor>,
4425 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4426 let Some(project) = self.project.as_ref() else {
4427 return HashMap::default();
4428 };
4429 let project = project.read(cx);
4430 let multi_buffer = self.buffer().read(cx);
4431 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4432 let multi_buffer_visible_start = self
4433 .scroll_manager
4434 .anchor()
4435 .anchor
4436 .to_point(&multi_buffer_snapshot);
4437 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4438 multi_buffer_visible_start
4439 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4440 Bias::Left,
4441 );
4442 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4443 multi_buffer_snapshot
4444 .range_to_buffer_ranges(multi_buffer_visible_range)
4445 .into_iter()
4446 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4447 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4448 let buffer_file = project::File::from_dyn(buffer.file())?;
4449 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4450 let worktree_entry = buffer_worktree
4451 .read(cx)
4452 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4453 if worktree_entry.is_ignored {
4454 return None;
4455 }
4456
4457 let language = buffer.language()?;
4458 if let Some(restrict_to_languages) = restrict_to_languages {
4459 if !restrict_to_languages.contains(language) {
4460 return None;
4461 }
4462 }
4463 Some((
4464 excerpt_id,
4465 (
4466 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4467 buffer.version().clone(),
4468 excerpt_visible_range,
4469 ),
4470 ))
4471 })
4472 .collect()
4473 }
4474
4475 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4476 TextLayoutDetails {
4477 text_system: window.text_system().clone(),
4478 editor_style: self.style.clone().unwrap(),
4479 rem_size: window.rem_size(),
4480 scroll_anchor: self.scroll_manager.anchor(),
4481 visible_rows: self.visible_line_count(),
4482 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4483 }
4484 }
4485
4486 pub fn splice_inlays(
4487 &self,
4488 to_remove: &[InlayId],
4489 to_insert: Vec<Inlay>,
4490 cx: &mut Context<Self>,
4491 ) {
4492 self.display_map.update(cx, |display_map, cx| {
4493 display_map.splice_inlays(to_remove, to_insert, cx)
4494 });
4495 cx.notify();
4496 }
4497
4498 fn trigger_on_type_formatting(
4499 &self,
4500 input: String,
4501 window: &mut Window,
4502 cx: &mut Context<Self>,
4503 ) -> Option<Task<Result<()>>> {
4504 if input.len() != 1 {
4505 return None;
4506 }
4507
4508 let project = self.project.as_ref()?;
4509 let position = self.selections.newest_anchor().head();
4510 let (buffer, buffer_position) = self
4511 .buffer
4512 .read(cx)
4513 .text_anchor_for_position(position, cx)?;
4514
4515 let settings = language_settings::language_settings(
4516 buffer
4517 .read(cx)
4518 .language_at(buffer_position)
4519 .map(|l| l.name()),
4520 buffer.read(cx).file(),
4521 cx,
4522 );
4523 if !settings.use_on_type_format {
4524 return None;
4525 }
4526
4527 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4528 // hence we do LSP request & edit on host side only — add formats to host's history.
4529 let push_to_lsp_host_history = true;
4530 // If this is not the host, append its history with new edits.
4531 let push_to_client_history = project.read(cx).is_via_collab();
4532
4533 let on_type_formatting = project.update(cx, |project, cx| {
4534 project.on_type_format(
4535 buffer.clone(),
4536 buffer_position,
4537 input,
4538 push_to_lsp_host_history,
4539 cx,
4540 )
4541 });
4542 Some(cx.spawn_in(window, async move |editor, cx| {
4543 if let Some(transaction) = on_type_formatting.await? {
4544 if push_to_client_history {
4545 buffer
4546 .update(cx, |buffer, _| {
4547 buffer.push_transaction(transaction, Instant::now());
4548 buffer.finalize_last_transaction();
4549 })
4550 .ok();
4551 }
4552 editor.update(cx, |editor, cx| {
4553 editor.refresh_document_highlights(cx);
4554 })?;
4555 }
4556 Ok(())
4557 }))
4558 }
4559
4560 pub fn show_word_completions(
4561 &mut self,
4562 _: &ShowWordCompletions,
4563 window: &mut Window,
4564 cx: &mut Context<Self>,
4565 ) {
4566 self.open_completions_menu(true, None, window, cx);
4567 }
4568
4569 pub fn show_completions(
4570 &mut self,
4571 options: &ShowCompletions,
4572 window: &mut Window,
4573 cx: &mut Context<Self>,
4574 ) {
4575 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4576 }
4577
4578 fn open_completions_menu(
4579 &mut self,
4580 ignore_completion_provider: bool,
4581 trigger: Option<&str>,
4582 window: &mut Window,
4583 cx: &mut Context<Self>,
4584 ) {
4585 if self.pending_rename.is_some() {
4586 return;
4587 }
4588 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4589 return;
4590 }
4591
4592 let position = self.selections.newest_anchor().head();
4593 if position.diff_base_anchor.is_some() {
4594 return;
4595 }
4596 let (buffer, buffer_position) =
4597 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4598 output
4599 } else {
4600 return;
4601 };
4602 let buffer_snapshot = buffer.read(cx).snapshot();
4603 let show_completion_documentation = buffer_snapshot
4604 .settings_at(buffer_position, cx)
4605 .show_completion_documentation;
4606
4607 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4608
4609 let trigger_kind = match trigger {
4610 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4611 CompletionTriggerKind::TRIGGER_CHARACTER
4612 }
4613 _ => CompletionTriggerKind::INVOKED,
4614 };
4615 let completion_context = CompletionContext {
4616 trigger_character: trigger.and_then(|trigger| {
4617 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4618 Some(String::from(trigger))
4619 } else {
4620 None
4621 }
4622 }),
4623 trigger_kind,
4624 };
4625
4626 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4627 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4628 let word_to_exclude = buffer_snapshot
4629 .text_for_range(old_range.clone())
4630 .collect::<String>();
4631 (
4632 buffer_snapshot.anchor_before(old_range.start)
4633 ..buffer_snapshot.anchor_after(old_range.end),
4634 Some(word_to_exclude),
4635 )
4636 } else {
4637 (buffer_position..buffer_position, None)
4638 };
4639
4640 let completion_settings = language_settings(
4641 buffer_snapshot
4642 .language_at(buffer_position)
4643 .map(|language| language.name()),
4644 buffer_snapshot.file(),
4645 cx,
4646 )
4647 .completions;
4648
4649 // The document can be large, so stay in reasonable bounds when searching for words,
4650 // otherwise completion pop-up might be slow to appear.
4651 const WORD_LOOKUP_ROWS: u32 = 5_000;
4652 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4653 let min_word_search = buffer_snapshot.clip_point(
4654 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4655 Bias::Left,
4656 );
4657 let max_word_search = buffer_snapshot.clip_point(
4658 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4659 Bias::Right,
4660 );
4661 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4662 ..buffer_snapshot.point_to_offset(max_word_search);
4663
4664 let provider = self
4665 .completion_provider
4666 .as_ref()
4667 .filter(|_| !ignore_completion_provider);
4668 let skip_digits = query
4669 .as_ref()
4670 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4671
4672 let (mut words, provided_completions) = match provider {
4673 Some(provider) => {
4674 let completions = provider.completions(
4675 position.excerpt_id,
4676 &buffer,
4677 buffer_position,
4678 completion_context,
4679 window,
4680 cx,
4681 );
4682
4683 let words = match completion_settings.words {
4684 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4685 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4686 .background_spawn(async move {
4687 buffer_snapshot.words_in_range(WordsQuery {
4688 fuzzy_contents: None,
4689 range: word_search_range,
4690 skip_digits,
4691 })
4692 }),
4693 };
4694
4695 (words, completions)
4696 }
4697 None => (
4698 cx.background_spawn(async move {
4699 buffer_snapshot.words_in_range(WordsQuery {
4700 fuzzy_contents: None,
4701 range: word_search_range,
4702 skip_digits,
4703 })
4704 }),
4705 Task::ready(Ok(None)),
4706 ),
4707 };
4708
4709 let sort_completions = provider
4710 .as_ref()
4711 .map_or(false, |provider| provider.sort_completions());
4712
4713 let filter_completions = provider
4714 .as_ref()
4715 .map_or(true, |provider| provider.filter_completions());
4716
4717 let id = post_inc(&mut self.next_completion_id);
4718 let task = cx.spawn_in(window, async move |editor, cx| {
4719 async move {
4720 editor.update(cx, |this, _| {
4721 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4722 })?;
4723
4724 let mut completions = Vec::new();
4725 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4726 completions.extend(provided_completions);
4727 if completion_settings.words == WordsCompletionMode::Fallback {
4728 words = Task::ready(BTreeMap::default());
4729 }
4730 }
4731
4732 let mut words = words.await;
4733 if let Some(word_to_exclude) = &word_to_exclude {
4734 words.remove(word_to_exclude);
4735 }
4736 for lsp_completion in &completions {
4737 words.remove(&lsp_completion.new_text);
4738 }
4739 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4740 replace_range: old_range.clone(),
4741 new_text: word.clone(),
4742 label: CodeLabel::plain(word, None),
4743 icon_path: None,
4744 documentation: None,
4745 source: CompletionSource::BufferWord {
4746 word_range,
4747 resolved: false,
4748 },
4749 insert_text_mode: Some(InsertTextMode::AS_IS),
4750 confirm: None,
4751 }));
4752
4753 let menu = if completions.is_empty() {
4754 None
4755 } else {
4756 let mut menu = CompletionsMenu::new(
4757 id,
4758 sort_completions,
4759 show_completion_documentation,
4760 ignore_completion_provider,
4761 position,
4762 buffer.clone(),
4763 completions.into(),
4764 );
4765
4766 menu.filter(
4767 if filter_completions {
4768 query.as_deref()
4769 } else {
4770 None
4771 },
4772 cx.background_executor().clone(),
4773 )
4774 .await;
4775
4776 menu.visible().then_some(menu)
4777 };
4778
4779 editor.update_in(cx, |editor, window, cx| {
4780 match editor.context_menu.borrow().as_ref() {
4781 None => {}
4782 Some(CodeContextMenu::Completions(prev_menu)) => {
4783 if prev_menu.id > id {
4784 return;
4785 }
4786 }
4787 _ => return,
4788 }
4789
4790 if editor.focus_handle.is_focused(window) && menu.is_some() {
4791 let mut menu = menu.unwrap();
4792 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4793
4794 *editor.context_menu.borrow_mut() =
4795 Some(CodeContextMenu::Completions(menu));
4796
4797 if editor.show_edit_predictions_in_menu() {
4798 editor.update_visible_inline_completion(window, cx);
4799 } else {
4800 editor.discard_inline_completion(false, cx);
4801 }
4802
4803 cx.notify();
4804 } else if editor.completion_tasks.len() <= 1 {
4805 // If there are no more completion tasks and the last menu was
4806 // empty, we should hide it.
4807 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4808 // If it was already hidden and we don't show inline
4809 // completions in the menu, we should also show the
4810 // inline-completion when available.
4811 if was_hidden && editor.show_edit_predictions_in_menu() {
4812 editor.update_visible_inline_completion(window, cx);
4813 }
4814 }
4815 })?;
4816
4817 anyhow::Ok(())
4818 }
4819 .log_err()
4820 .await
4821 });
4822
4823 self.completion_tasks.push((id, task));
4824 }
4825
4826 #[cfg(feature = "test-support")]
4827 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4828 let menu = self.context_menu.borrow();
4829 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4830 let completions = menu.completions.borrow();
4831 Some(completions.to_vec())
4832 } else {
4833 None
4834 }
4835 }
4836
4837 pub fn confirm_completion(
4838 &mut self,
4839 action: &ConfirmCompletion,
4840 window: &mut Window,
4841 cx: &mut Context<Self>,
4842 ) -> Option<Task<Result<()>>> {
4843 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4844 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4845 }
4846
4847 pub fn confirm_completion_insert(
4848 &mut self,
4849 _: &ConfirmCompletionInsert,
4850 window: &mut Window,
4851 cx: &mut Context<Self>,
4852 ) -> Option<Task<Result<()>>> {
4853 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4854 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4855 }
4856
4857 pub fn confirm_completion_replace(
4858 &mut self,
4859 _: &ConfirmCompletionReplace,
4860 window: &mut Window,
4861 cx: &mut Context<Self>,
4862 ) -> Option<Task<Result<()>>> {
4863 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4864 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4865 }
4866
4867 pub fn compose_completion(
4868 &mut self,
4869 action: &ComposeCompletion,
4870 window: &mut Window,
4871 cx: &mut Context<Self>,
4872 ) -> Option<Task<Result<()>>> {
4873 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4874 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4875 }
4876
4877 fn do_completion(
4878 &mut self,
4879 item_ix: Option<usize>,
4880 intent: CompletionIntent,
4881 window: &mut Window,
4882 cx: &mut Context<Editor>,
4883 ) -> Option<Task<Result<()>>> {
4884 use language::ToOffset as _;
4885
4886 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4887 else {
4888 return None;
4889 };
4890
4891 let candidate_id = {
4892 let entries = completions_menu.entries.borrow();
4893 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4894 if self.show_edit_predictions_in_menu() {
4895 self.discard_inline_completion(true, cx);
4896 }
4897 mat.candidate_id
4898 };
4899
4900 let buffer_handle = completions_menu.buffer;
4901 let completion = completions_menu
4902 .completions
4903 .borrow()
4904 .get(candidate_id)?
4905 .clone();
4906 cx.stop_propagation();
4907
4908 let snippet;
4909 let new_text;
4910 if completion.is_snippet() {
4911 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4912 new_text = snippet.as_ref().unwrap().text.clone();
4913 } else {
4914 snippet = None;
4915 new_text = completion.new_text.clone();
4916 };
4917
4918 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4919 let buffer = buffer_handle.read(cx);
4920 let snapshot = self.buffer.read(cx).snapshot(cx);
4921 let replace_range_multibuffer = {
4922 let excerpt = snapshot
4923 .excerpt_containing(self.selections.newest_anchor().range())
4924 .unwrap();
4925 let multibuffer_anchor = snapshot
4926 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
4927 .unwrap()
4928 ..snapshot
4929 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
4930 .unwrap();
4931 multibuffer_anchor.start.to_offset(&snapshot)
4932 ..multibuffer_anchor.end.to_offset(&snapshot)
4933 };
4934 let newest_anchor = self.selections.newest_anchor();
4935 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
4936 return None;
4937 }
4938
4939 let old_text = buffer
4940 .text_for_range(replace_range.clone())
4941 .collect::<String>();
4942 let lookbehind = newest_anchor
4943 .start
4944 .text_anchor
4945 .to_offset(buffer)
4946 .saturating_sub(replace_range.start);
4947 let lookahead = replace_range
4948 .end
4949 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
4950 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
4951 let suffix = &old_text[lookbehind.min(old_text.len())..];
4952
4953 let selections = self.selections.all::<usize>(cx);
4954 let mut ranges = Vec::new();
4955 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4956
4957 for selection in &selections {
4958 let range = if selection.id == newest_anchor.id {
4959 replace_range_multibuffer.clone()
4960 } else {
4961 let mut range = selection.range();
4962
4963 // if prefix is present, don't duplicate it
4964 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
4965 range.start = range.start.saturating_sub(lookbehind);
4966
4967 // if suffix is also present, mimic the newest cursor and replace it
4968 if selection.id != newest_anchor.id
4969 && snapshot.contains_str_at(range.end, suffix)
4970 {
4971 range.end += lookahead;
4972 }
4973 }
4974 range
4975 };
4976
4977 ranges.push(range);
4978
4979 if !self.linked_edit_ranges.is_empty() {
4980 let start_anchor = snapshot.anchor_before(selection.head());
4981 let end_anchor = snapshot.anchor_after(selection.tail());
4982 if let Some(ranges) = self
4983 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4984 {
4985 for (buffer, edits) in ranges {
4986 linked_edits
4987 .entry(buffer.clone())
4988 .or_default()
4989 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
4990 }
4991 }
4992 }
4993 }
4994
4995 cx.emit(EditorEvent::InputHandled {
4996 utf16_range_to_replace: None,
4997 text: new_text.clone().into(),
4998 });
4999
5000 self.transact(window, cx, |this, window, cx| {
5001 if let Some(mut snippet) = snippet {
5002 snippet.text = new_text.to_string();
5003 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5004 } else {
5005 this.buffer.update(cx, |buffer, cx| {
5006 let auto_indent = match completion.insert_text_mode {
5007 Some(InsertTextMode::AS_IS) => None,
5008 _ => this.autoindent_mode.clone(),
5009 };
5010 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5011 buffer.edit(edits, auto_indent, cx);
5012 });
5013 }
5014 for (buffer, edits) in linked_edits {
5015 buffer.update(cx, |buffer, cx| {
5016 let snapshot = buffer.snapshot();
5017 let edits = edits
5018 .into_iter()
5019 .map(|(range, text)| {
5020 use text::ToPoint as TP;
5021 let end_point = TP::to_point(&range.end, &snapshot);
5022 let start_point = TP::to_point(&range.start, &snapshot);
5023 (start_point..end_point, text)
5024 })
5025 .sorted_by_key(|(range, _)| range.start);
5026 buffer.edit(edits, None, cx);
5027 })
5028 }
5029
5030 this.refresh_inline_completion(true, false, window, cx);
5031 });
5032
5033 let show_new_completions_on_confirm = completion
5034 .confirm
5035 .as_ref()
5036 .map_or(false, |confirm| confirm(intent, window, cx));
5037 if show_new_completions_on_confirm {
5038 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5039 }
5040
5041 let provider = self.completion_provider.as_ref()?;
5042 drop(completion);
5043 let apply_edits = provider.apply_additional_edits_for_completion(
5044 buffer_handle,
5045 completions_menu.completions.clone(),
5046 candidate_id,
5047 true,
5048 cx,
5049 );
5050
5051 let editor_settings = EditorSettings::get_global(cx);
5052 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5053 // After the code completion is finished, users often want to know what signatures are needed.
5054 // so we should automatically call signature_help
5055 self.show_signature_help(&ShowSignatureHelp, window, cx);
5056 }
5057
5058 Some(cx.foreground_executor().spawn(async move {
5059 apply_edits.await?;
5060 Ok(())
5061 }))
5062 }
5063
5064 pub fn toggle_code_actions(
5065 &mut self,
5066 action: &ToggleCodeActions,
5067 window: &mut Window,
5068 cx: &mut Context<Self>,
5069 ) {
5070 let mut context_menu = self.context_menu.borrow_mut();
5071 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5072 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5073 // Toggle if we're selecting the same one
5074 *context_menu = None;
5075 cx.notify();
5076 return;
5077 } else {
5078 // Otherwise, clear it and start a new one
5079 *context_menu = None;
5080 cx.notify();
5081 }
5082 }
5083 drop(context_menu);
5084 let snapshot = self.snapshot(window, cx);
5085 let deployed_from_indicator = action.deployed_from_indicator;
5086 let mut task = self.code_actions_task.take();
5087 let action = action.clone();
5088 cx.spawn_in(window, async move |editor, cx| {
5089 while let Some(prev_task) = task {
5090 prev_task.await.log_err();
5091 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5092 }
5093
5094 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5095 if editor.focus_handle.is_focused(window) {
5096 let multibuffer_point = action
5097 .deployed_from_indicator
5098 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5099 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5100 let (buffer, buffer_row) = snapshot
5101 .buffer_snapshot
5102 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5103 .and_then(|(buffer_snapshot, range)| {
5104 editor
5105 .buffer
5106 .read(cx)
5107 .buffer(buffer_snapshot.remote_id())
5108 .map(|buffer| (buffer, range.start.row))
5109 })?;
5110 let (_, code_actions) = editor
5111 .available_code_actions
5112 .clone()
5113 .and_then(|(location, code_actions)| {
5114 let snapshot = location.buffer.read(cx).snapshot();
5115 let point_range = location.range.to_point(&snapshot);
5116 let point_range = point_range.start.row..=point_range.end.row;
5117 if point_range.contains(&buffer_row) {
5118 Some((location, code_actions))
5119 } else {
5120 None
5121 }
5122 })
5123 .unzip();
5124 let buffer_id = buffer.read(cx).remote_id();
5125 let tasks = editor
5126 .tasks
5127 .get(&(buffer_id, buffer_row))
5128 .map(|t| Arc::new(t.to_owned()));
5129 if tasks.is_none() && code_actions.is_none() {
5130 return None;
5131 }
5132
5133 editor.completion_tasks.clear();
5134 editor.discard_inline_completion(false, cx);
5135 let task_context =
5136 tasks
5137 .as_ref()
5138 .zip(editor.project.clone())
5139 .map(|(tasks, project)| {
5140 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5141 });
5142
5143 let debugger_flag = cx.has_flag::<Debugger>();
5144
5145 Some(cx.spawn_in(window, async move |editor, cx| {
5146 let task_context = match task_context {
5147 Some(task_context) => task_context.await,
5148 None => None,
5149 };
5150 let resolved_tasks =
5151 tasks
5152 .zip(task_context)
5153 .map(|(tasks, task_context)| ResolvedTasks {
5154 templates: tasks.resolve(&task_context).collect(),
5155 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5156 multibuffer_point.row,
5157 tasks.column,
5158 )),
5159 });
5160 let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
5161 tasks
5162 .templates
5163 .iter()
5164 .filter(|task| {
5165 if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
5166 debugger_flag
5167 } else {
5168 true
5169 }
5170 })
5171 .count()
5172 == 1
5173 }) && code_actions
5174 .as_ref()
5175 .map_or(true, |actions| actions.is_empty());
5176 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5177 *editor.context_menu.borrow_mut() =
5178 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5179 buffer,
5180 actions: CodeActionContents::new(
5181 resolved_tasks,
5182 code_actions,
5183 cx,
5184 ),
5185 selected_item: Default::default(),
5186 scroll_handle: UniformListScrollHandle::default(),
5187 deployed_from_indicator,
5188 }));
5189 if spawn_straight_away {
5190 if let Some(task) = editor.confirm_code_action(
5191 &ConfirmCodeAction { item_ix: Some(0) },
5192 window,
5193 cx,
5194 ) {
5195 cx.notify();
5196 return task;
5197 }
5198 }
5199 cx.notify();
5200 Task::ready(Ok(()))
5201 }) {
5202 task.await
5203 } else {
5204 Ok(())
5205 }
5206 }))
5207 } else {
5208 Some(Task::ready(Ok(())))
5209 }
5210 })?;
5211 if let Some(task) = spawned_test_task {
5212 task.await?;
5213 }
5214
5215 Ok::<_, anyhow::Error>(())
5216 })
5217 .detach_and_log_err(cx);
5218 }
5219
5220 pub fn confirm_code_action(
5221 &mut self,
5222 action: &ConfirmCodeAction,
5223 window: &mut Window,
5224 cx: &mut Context<Self>,
5225 ) -> Option<Task<Result<()>>> {
5226 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5227
5228 let actions_menu =
5229 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5230 menu
5231 } else {
5232 return None;
5233 };
5234
5235 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5236 let action = actions_menu.actions.get(action_ix)?;
5237 let title = action.label();
5238 let buffer = actions_menu.buffer;
5239 let workspace = self.workspace()?;
5240
5241 match action {
5242 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5243 match resolved_task.task_type() {
5244 task::TaskType::Script => workspace.update(cx, |workspace, cx| {
5245 workspace.schedule_resolved_task(
5246 task_source_kind,
5247 resolved_task,
5248 false,
5249 window,
5250 cx,
5251 );
5252
5253 Some(Task::ready(Ok(())))
5254 }),
5255 task::TaskType::Debug(_) => {
5256 workspace.update(cx, |workspace, cx| {
5257 workspace.schedule_debug_task(resolved_task, window, cx);
5258 });
5259 Some(Task::ready(Ok(())))
5260 }
5261 }
5262 }
5263 CodeActionsItem::CodeAction {
5264 excerpt_id,
5265 action,
5266 provider,
5267 } => {
5268 let apply_code_action =
5269 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5270 let workspace = workspace.downgrade();
5271 Some(cx.spawn_in(window, async move |editor, cx| {
5272 let project_transaction = apply_code_action.await?;
5273 Self::open_project_transaction(
5274 &editor,
5275 workspace,
5276 project_transaction,
5277 title,
5278 cx,
5279 )
5280 .await
5281 }))
5282 }
5283 }
5284 }
5285
5286 pub async fn open_project_transaction(
5287 this: &WeakEntity<Editor>,
5288 workspace: WeakEntity<Workspace>,
5289 transaction: ProjectTransaction,
5290 title: String,
5291 cx: &mut AsyncWindowContext,
5292 ) -> Result<()> {
5293 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5294 cx.update(|_, cx| {
5295 entries.sort_unstable_by_key(|(buffer, _)| {
5296 buffer.read(cx).file().map(|f| f.path().clone())
5297 });
5298 })?;
5299
5300 // If the project transaction's edits are all contained within this editor, then
5301 // avoid opening a new editor to display them.
5302
5303 if let Some((buffer, transaction)) = entries.first() {
5304 if entries.len() == 1 {
5305 let excerpt = this.update(cx, |editor, cx| {
5306 editor
5307 .buffer()
5308 .read(cx)
5309 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5310 })?;
5311 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5312 if excerpted_buffer == *buffer {
5313 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5314 let excerpt_range = excerpt_range.to_offset(buffer);
5315 buffer
5316 .edited_ranges_for_transaction::<usize>(transaction)
5317 .all(|range| {
5318 excerpt_range.start <= range.start
5319 && excerpt_range.end >= range.end
5320 })
5321 })?;
5322
5323 if all_edits_within_excerpt {
5324 return Ok(());
5325 }
5326 }
5327 }
5328 }
5329 } else {
5330 return Ok(());
5331 }
5332
5333 let mut ranges_to_highlight = Vec::new();
5334 let excerpt_buffer = cx.new(|cx| {
5335 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5336 for (buffer_handle, transaction) in &entries {
5337 let edited_ranges = buffer_handle
5338 .read(cx)
5339 .edited_ranges_for_transaction::<Point>(transaction)
5340 .collect::<Vec<_>>();
5341 let (ranges, _) = multibuffer.set_excerpts_for_path(
5342 PathKey::for_buffer(buffer_handle, cx),
5343 buffer_handle.clone(),
5344 edited_ranges,
5345 DEFAULT_MULTIBUFFER_CONTEXT,
5346 cx,
5347 );
5348
5349 ranges_to_highlight.extend(ranges);
5350 }
5351 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5352 multibuffer
5353 })?;
5354
5355 workspace.update_in(cx, |workspace, window, cx| {
5356 let project = workspace.project().clone();
5357 let editor =
5358 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5359 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5360 editor.update(cx, |editor, cx| {
5361 editor.highlight_background::<Self>(
5362 &ranges_to_highlight,
5363 |theme| theme.editor_highlighted_line_background,
5364 cx,
5365 );
5366 });
5367 })?;
5368
5369 Ok(())
5370 }
5371
5372 pub fn clear_code_action_providers(&mut self) {
5373 self.code_action_providers.clear();
5374 self.available_code_actions.take();
5375 }
5376
5377 pub fn add_code_action_provider(
5378 &mut self,
5379 provider: Rc<dyn CodeActionProvider>,
5380 window: &mut Window,
5381 cx: &mut Context<Self>,
5382 ) {
5383 if self
5384 .code_action_providers
5385 .iter()
5386 .any(|existing_provider| existing_provider.id() == provider.id())
5387 {
5388 return;
5389 }
5390
5391 self.code_action_providers.push(provider);
5392 self.refresh_code_actions(window, cx);
5393 }
5394
5395 pub fn remove_code_action_provider(
5396 &mut self,
5397 id: Arc<str>,
5398 window: &mut Window,
5399 cx: &mut Context<Self>,
5400 ) {
5401 self.code_action_providers
5402 .retain(|provider| provider.id() != id);
5403 self.refresh_code_actions(window, cx);
5404 }
5405
5406 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5407 let newest_selection = self.selections.newest_anchor().clone();
5408 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5409 let buffer = self.buffer.read(cx);
5410 if newest_selection.head().diff_base_anchor.is_some() {
5411 return None;
5412 }
5413 let (start_buffer, start) =
5414 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5415 let (end_buffer, end) =
5416 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5417 if start_buffer != end_buffer {
5418 return None;
5419 }
5420
5421 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5422 cx.background_executor()
5423 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5424 .await;
5425
5426 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5427 let providers = this.code_action_providers.clone();
5428 let tasks = this
5429 .code_action_providers
5430 .iter()
5431 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5432 .collect::<Vec<_>>();
5433 (providers, tasks)
5434 })?;
5435
5436 let mut actions = Vec::new();
5437 for (provider, provider_actions) in
5438 providers.into_iter().zip(future::join_all(tasks).await)
5439 {
5440 if let Some(provider_actions) = provider_actions.log_err() {
5441 actions.extend(provider_actions.into_iter().map(|action| {
5442 AvailableCodeAction {
5443 excerpt_id: newest_selection.start.excerpt_id,
5444 action,
5445 provider: provider.clone(),
5446 }
5447 }));
5448 }
5449 }
5450
5451 this.update(cx, |this, cx| {
5452 this.available_code_actions = if actions.is_empty() {
5453 None
5454 } else {
5455 Some((
5456 Location {
5457 buffer: start_buffer,
5458 range: start..end,
5459 },
5460 actions.into(),
5461 ))
5462 };
5463 cx.notify();
5464 })
5465 }));
5466 None
5467 }
5468
5469 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5470 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5471 self.show_git_blame_inline = false;
5472
5473 self.show_git_blame_inline_delay_task =
5474 Some(cx.spawn_in(window, async move |this, cx| {
5475 cx.background_executor().timer(delay).await;
5476
5477 this.update(cx, |this, cx| {
5478 this.show_git_blame_inline = true;
5479 cx.notify();
5480 })
5481 .log_err();
5482 }));
5483 }
5484 }
5485
5486 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5487 if self.pending_rename.is_some() {
5488 return None;
5489 }
5490
5491 let provider = self.semantics_provider.clone()?;
5492 let buffer = self.buffer.read(cx);
5493 let newest_selection = self.selections.newest_anchor().clone();
5494 let cursor_position = newest_selection.head();
5495 let (cursor_buffer, cursor_buffer_position) =
5496 buffer.text_anchor_for_position(cursor_position, cx)?;
5497 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5498 if cursor_buffer != tail_buffer {
5499 return None;
5500 }
5501 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5502 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5503 cx.background_executor()
5504 .timer(Duration::from_millis(debounce))
5505 .await;
5506
5507 let highlights = if let Some(highlights) = cx
5508 .update(|cx| {
5509 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5510 })
5511 .ok()
5512 .flatten()
5513 {
5514 highlights.await.log_err()
5515 } else {
5516 None
5517 };
5518
5519 if let Some(highlights) = highlights {
5520 this.update(cx, |this, cx| {
5521 if this.pending_rename.is_some() {
5522 return;
5523 }
5524
5525 let buffer_id = cursor_position.buffer_id;
5526 let buffer = this.buffer.read(cx);
5527 if !buffer
5528 .text_anchor_for_position(cursor_position, cx)
5529 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5530 {
5531 return;
5532 }
5533
5534 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5535 let mut write_ranges = Vec::new();
5536 let mut read_ranges = Vec::new();
5537 for highlight in highlights {
5538 for (excerpt_id, excerpt_range) in
5539 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5540 {
5541 let start = highlight
5542 .range
5543 .start
5544 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5545 let end = highlight
5546 .range
5547 .end
5548 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5549 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5550 continue;
5551 }
5552
5553 let range = Anchor {
5554 buffer_id,
5555 excerpt_id,
5556 text_anchor: start,
5557 diff_base_anchor: None,
5558 }..Anchor {
5559 buffer_id,
5560 excerpt_id,
5561 text_anchor: end,
5562 diff_base_anchor: None,
5563 };
5564 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5565 write_ranges.push(range);
5566 } else {
5567 read_ranges.push(range);
5568 }
5569 }
5570 }
5571
5572 this.highlight_background::<DocumentHighlightRead>(
5573 &read_ranges,
5574 |theme| theme.editor_document_highlight_read_background,
5575 cx,
5576 );
5577 this.highlight_background::<DocumentHighlightWrite>(
5578 &write_ranges,
5579 |theme| theme.editor_document_highlight_write_background,
5580 cx,
5581 );
5582 cx.notify();
5583 })
5584 .log_err();
5585 }
5586 }));
5587 None
5588 }
5589
5590 fn prepare_highlight_query_from_selection(
5591 &mut self,
5592 cx: &mut Context<Editor>,
5593 ) -> Option<(String, Range<Anchor>)> {
5594 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5595 return None;
5596 }
5597 if !EditorSettings::get_global(cx).selection_highlight {
5598 return None;
5599 }
5600 if self.selections.count() != 1 || self.selections.line_mode {
5601 return None;
5602 }
5603 let selection = self.selections.newest::<Point>(cx);
5604 if selection.is_empty() || selection.start.row != selection.end.row {
5605 return None;
5606 }
5607 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5608 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5609 let query = multi_buffer_snapshot
5610 .text_for_range(selection_anchor_range.clone())
5611 .collect::<String>();
5612 if query.trim().is_empty() {
5613 return None;
5614 }
5615 Some((query, selection_anchor_range))
5616 }
5617
5618 fn update_selection_occurrence_highlights(
5619 &mut self,
5620 query_text: String,
5621 query_range: Range<Anchor>,
5622 multi_buffer_range_to_query: Range<Point>,
5623 use_debounce: bool,
5624 window: &mut Window,
5625 cx: &mut Context<Editor>,
5626 ) -> Task<()> {
5627 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5628 cx.spawn_in(window, async move |editor, cx| {
5629 if use_debounce {
5630 cx.background_executor()
5631 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5632 .await;
5633 }
5634 let match_task = cx.background_spawn(async move {
5635 let buffer_ranges = multi_buffer_snapshot
5636 .range_to_buffer_ranges(multi_buffer_range_to_query)
5637 .into_iter()
5638 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5639 let mut match_ranges = Vec::new();
5640 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5641 match_ranges.extend(
5642 project::search::SearchQuery::text(
5643 query_text.clone(),
5644 false,
5645 false,
5646 false,
5647 Default::default(),
5648 Default::default(),
5649 false,
5650 None,
5651 )
5652 .unwrap()
5653 .search(&buffer_snapshot, Some(search_range.clone()))
5654 .await
5655 .into_iter()
5656 .filter_map(|match_range| {
5657 let match_start = buffer_snapshot
5658 .anchor_after(search_range.start + match_range.start);
5659 let match_end =
5660 buffer_snapshot.anchor_before(search_range.start + match_range.end);
5661 let match_anchor_range = Anchor::range_in_buffer(
5662 excerpt_id,
5663 buffer_snapshot.remote_id(),
5664 match_start..match_end,
5665 );
5666 (match_anchor_range != query_range).then_some(match_anchor_range)
5667 }),
5668 );
5669 }
5670 match_ranges
5671 });
5672 let match_ranges = match_task.await;
5673 editor
5674 .update_in(cx, |editor, _, cx| {
5675 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5676 if !match_ranges.is_empty() {
5677 editor.highlight_background::<SelectedTextHighlight>(
5678 &match_ranges,
5679 |theme| theme.editor_document_highlight_bracket_background,
5680 cx,
5681 )
5682 }
5683 })
5684 .log_err();
5685 })
5686 }
5687
5688 fn refresh_selected_text_highlights(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
5689 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5690 else {
5691 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5692 self.quick_selection_highlight_task.take();
5693 self.debounced_selection_highlight_task.take();
5694 return;
5695 };
5696 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5697 if self
5698 .quick_selection_highlight_task
5699 .as_ref()
5700 .map_or(true, |(prev_anchor_range, _)| {
5701 prev_anchor_range != &query_range
5702 })
5703 {
5704 let multi_buffer_visible_start = self
5705 .scroll_manager
5706 .anchor()
5707 .anchor
5708 .to_point(&multi_buffer_snapshot);
5709 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5710 multi_buffer_visible_start
5711 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5712 Bias::Left,
5713 );
5714 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5715 self.quick_selection_highlight_task = Some((
5716 query_range.clone(),
5717 self.update_selection_occurrence_highlights(
5718 query_text.clone(),
5719 query_range.clone(),
5720 multi_buffer_visible_range,
5721 false,
5722 window,
5723 cx,
5724 ),
5725 ));
5726 }
5727 if self
5728 .debounced_selection_highlight_task
5729 .as_ref()
5730 .map_or(true, |(prev_anchor_range, _)| {
5731 prev_anchor_range != &query_range
5732 })
5733 {
5734 let multi_buffer_start = multi_buffer_snapshot
5735 .anchor_before(0)
5736 .to_point(&multi_buffer_snapshot);
5737 let multi_buffer_end = multi_buffer_snapshot
5738 .anchor_after(multi_buffer_snapshot.len())
5739 .to_point(&multi_buffer_snapshot);
5740 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
5741 self.debounced_selection_highlight_task = Some((
5742 query_range.clone(),
5743 self.update_selection_occurrence_highlights(
5744 query_text,
5745 query_range,
5746 multi_buffer_full_range,
5747 true,
5748 window,
5749 cx,
5750 ),
5751 ));
5752 }
5753 }
5754
5755 pub fn refresh_inline_completion(
5756 &mut self,
5757 debounce: bool,
5758 user_requested: bool,
5759 window: &mut Window,
5760 cx: &mut Context<Self>,
5761 ) -> Option<()> {
5762 let provider = self.edit_prediction_provider()?;
5763 let cursor = self.selections.newest_anchor().head();
5764 let (buffer, cursor_buffer_position) =
5765 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5766
5767 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5768 self.discard_inline_completion(false, cx);
5769 return None;
5770 }
5771
5772 if !user_requested
5773 && (!self.should_show_edit_predictions()
5774 || !self.is_focused(window)
5775 || buffer.read(cx).is_empty())
5776 {
5777 self.discard_inline_completion(false, cx);
5778 return None;
5779 }
5780
5781 self.update_visible_inline_completion(window, cx);
5782 provider.refresh(
5783 self.project.clone(),
5784 buffer,
5785 cursor_buffer_position,
5786 debounce,
5787 cx,
5788 );
5789 Some(())
5790 }
5791
5792 fn show_edit_predictions_in_menu(&self) -> bool {
5793 match self.edit_prediction_settings {
5794 EditPredictionSettings::Disabled => false,
5795 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5796 }
5797 }
5798
5799 pub fn edit_predictions_enabled(&self) -> bool {
5800 match self.edit_prediction_settings {
5801 EditPredictionSettings::Disabled => false,
5802 EditPredictionSettings::Enabled { .. } => true,
5803 }
5804 }
5805
5806 fn edit_prediction_requires_modifier(&self) -> bool {
5807 match self.edit_prediction_settings {
5808 EditPredictionSettings::Disabled => false,
5809 EditPredictionSettings::Enabled {
5810 preview_requires_modifier,
5811 ..
5812 } => preview_requires_modifier,
5813 }
5814 }
5815
5816 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5817 if self.edit_prediction_provider.is_none() {
5818 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5819 } else {
5820 let selection = self.selections.newest_anchor();
5821 let cursor = selection.head();
5822
5823 if let Some((buffer, cursor_buffer_position)) =
5824 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5825 {
5826 self.edit_prediction_settings =
5827 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5828 }
5829 }
5830 }
5831
5832 fn edit_prediction_settings_at_position(
5833 &self,
5834 buffer: &Entity<Buffer>,
5835 buffer_position: language::Anchor,
5836 cx: &App,
5837 ) -> EditPredictionSettings {
5838 if !self.mode.is_full()
5839 || !self.show_inline_completions_override.unwrap_or(true)
5840 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5841 {
5842 return EditPredictionSettings::Disabled;
5843 }
5844
5845 let buffer = buffer.read(cx);
5846
5847 let file = buffer.file();
5848
5849 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5850 return EditPredictionSettings::Disabled;
5851 };
5852
5853 let by_provider = matches!(
5854 self.menu_inline_completions_policy,
5855 MenuInlineCompletionsPolicy::ByProvider
5856 );
5857
5858 let show_in_menu = by_provider
5859 && self
5860 .edit_prediction_provider
5861 .as_ref()
5862 .map_or(false, |provider| {
5863 provider.provider.show_completions_in_menu()
5864 });
5865
5866 let preview_requires_modifier =
5867 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5868
5869 EditPredictionSettings::Enabled {
5870 show_in_menu,
5871 preview_requires_modifier,
5872 }
5873 }
5874
5875 fn should_show_edit_predictions(&self) -> bool {
5876 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5877 }
5878
5879 pub fn edit_prediction_preview_is_active(&self) -> bool {
5880 matches!(
5881 self.edit_prediction_preview,
5882 EditPredictionPreview::Active { .. }
5883 )
5884 }
5885
5886 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5887 let cursor = self.selections.newest_anchor().head();
5888 if let Some((buffer, cursor_position)) =
5889 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5890 {
5891 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5892 } else {
5893 false
5894 }
5895 }
5896
5897 fn edit_predictions_enabled_in_buffer(
5898 &self,
5899 buffer: &Entity<Buffer>,
5900 buffer_position: language::Anchor,
5901 cx: &App,
5902 ) -> bool {
5903 maybe!({
5904 if self.read_only(cx) {
5905 return Some(false);
5906 }
5907 let provider = self.edit_prediction_provider()?;
5908 if !provider.is_enabled(&buffer, buffer_position, cx) {
5909 return Some(false);
5910 }
5911 let buffer = buffer.read(cx);
5912 let Some(file) = buffer.file() else {
5913 return Some(true);
5914 };
5915 let settings = all_language_settings(Some(file), cx);
5916 Some(settings.edit_predictions_enabled_for_file(file, cx))
5917 })
5918 .unwrap_or(false)
5919 }
5920
5921 fn cycle_inline_completion(
5922 &mut self,
5923 direction: Direction,
5924 window: &mut Window,
5925 cx: &mut Context<Self>,
5926 ) -> Option<()> {
5927 let provider = self.edit_prediction_provider()?;
5928 let cursor = self.selections.newest_anchor().head();
5929 let (buffer, cursor_buffer_position) =
5930 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5931 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5932 return None;
5933 }
5934
5935 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5936 self.update_visible_inline_completion(window, cx);
5937
5938 Some(())
5939 }
5940
5941 pub fn show_inline_completion(
5942 &mut self,
5943 _: &ShowEditPrediction,
5944 window: &mut Window,
5945 cx: &mut Context<Self>,
5946 ) {
5947 if !self.has_active_inline_completion() {
5948 self.refresh_inline_completion(false, true, window, cx);
5949 return;
5950 }
5951
5952 self.update_visible_inline_completion(window, cx);
5953 }
5954
5955 pub fn display_cursor_names(
5956 &mut self,
5957 _: &DisplayCursorNames,
5958 window: &mut Window,
5959 cx: &mut Context<Self>,
5960 ) {
5961 self.show_cursor_names(window, cx);
5962 }
5963
5964 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5965 self.show_cursor_names = true;
5966 cx.notify();
5967 cx.spawn_in(window, async move |this, cx| {
5968 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5969 this.update(cx, |this, cx| {
5970 this.show_cursor_names = false;
5971 cx.notify()
5972 })
5973 .ok()
5974 })
5975 .detach();
5976 }
5977
5978 pub fn next_edit_prediction(
5979 &mut self,
5980 _: &NextEditPrediction,
5981 window: &mut Window,
5982 cx: &mut Context<Self>,
5983 ) {
5984 if self.has_active_inline_completion() {
5985 self.cycle_inline_completion(Direction::Next, window, cx);
5986 } else {
5987 let is_copilot_disabled = self
5988 .refresh_inline_completion(false, true, window, cx)
5989 .is_none();
5990 if is_copilot_disabled {
5991 cx.propagate();
5992 }
5993 }
5994 }
5995
5996 pub fn previous_edit_prediction(
5997 &mut self,
5998 _: &PreviousEditPrediction,
5999 window: &mut Window,
6000 cx: &mut Context<Self>,
6001 ) {
6002 if self.has_active_inline_completion() {
6003 self.cycle_inline_completion(Direction::Prev, window, cx);
6004 } else {
6005 let is_copilot_disabled = self
6006 .refresh_inline_completion(false, true, window, cx)
6007 .is_none();
6008 if is_copilot_disabled {
6009 cx.propagate();
6010 }
6011 }
6012 }
6013
6014 pub fn accept_edit_prediction(
6015 &mut self,
6016 _: &AcceptEditPrediction,
6017 window: &mut Window,
6018 cx: &mut Context<Self>,
6019 ) {
6020 if self.show_edit_predictions_in_menu() {
6021 self.hide_context_menu(window, cx);
6022 }
6023
6024 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6025 return;
6026 };
6027
6028 self.report_inline_completion_event(
6029 active_inline_completion.completion_id.clone(),
6030 true,
6031 cx,
6032 );
6033
6034 match &active_inline_completion.completion {
6035 InlineCompletion::Move { target, .. } => {
6036 let target = *target;
6037
6038 if let Some(position_map) = &self.last_position_map {
6039 if position_map
6040 .visible_row_range
6041 .contains(&target.to_display_point(&position_map.snapshot).row())
6042 || !self.edit_prediction_requires_modifier()
6043 {
6044 self.unfold_ranges(&[target..target], true, false, cx);
6045 // Note that this is also done in vim's handler of the Tab action.
6046 self.change_selections(
6047 Some(Autoscroll::newest()),
6048 window,
6049 cx,
6050 |selections| {
6051 selections.select_anchor_ranges([target..target]);
6052 },
6053 );
6054 self.clear_row_highlights::<EditPredictionPreview>();
6055
6056 self.edit_prediction_preview
6057 .set_previous_scroll_position(None);
6058 } else {
6059 self.edit_prediction_preview
6060 .set_previous_scroll_position(Some(
6061 position_map.snapshot.scroll_anchor,
6062 ));
6063
6064 self.highlight_rows::<EditPredictionPreview>(
6065 target..target,
6066 cx.theme().colors().editor_highlighted_line_background,
6067 RowHighlightOptions {
6068 autoscroll: true,
6069 ..Default::default()
6070 },
6071 cx,
6072 );
6073 self.request_autoscroll(Autoscroll::fit(), cx);
6074 }
6075 }
6076 }
6077 InlineCompletion::Edit { edits, .. } => {
6078 if let Some(provider) = self.edit_prediction_provider() {
6079 provider.accept(cx);
6080 }
6081
6082 let snapshot = self.buffer.read(cx).snapshot(cx);
6083 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6084
6085 self.buffer.update(cx, |buffer, cx| {
6086 buffer.edit(edits.iter().cloned(), None, cx)
6087 });
6088
6089 self.change_selections(None, window, cx, |s| {
6090 s.select_anchor_ranges([last_edit_end..last_edit_end])
6091 });
6092
6093 self.update_visible_inline_completion(window, cx);
6094 if self.active_inline_completion.is_none() {
6095 self.refresh_inline_completion(true, true, window, cx);
6096 }
6097
6098 cx.notify();
6099 }
6100 }
6101
6102 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6103 }
6104
6105 pub fn accept_partial_inline_completion(
6106 &mut self,
6107 _: &AcceptPartialEditPrediction,
6108 window: &mut Window,
6109 cx: &mut Context<Self>,
6110 ) {
6111 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6112 return;
6113 };
6114 if self.selections.count() != 1 {
6115 return;
6116 }
6117
6118 self.report_inline_completion_event(
6119 active_inline_completion.completion_id.clone(),
6120 true,
6121 cx,
6122 );
6123
6124 match &active_inline_completion.completion {
6125 InlineCompletion::Move { target, .. } => {
6126 let target = *target;
6127 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6128 selections.select_anchor_ranges([target..target]);
6129 });
6130 }
6131 InlineCompletion::Edit { edits, .. } => {
6132 // Find an insertion that starts at the cursor position.
6133 let snapshot = self.buffer.read(cx).snapshot(cx);
6134 let cursor_offset = self.selections.newest::<usize>(cx).head();
6135 let insertion = edits.iter().find_map(|(range, text)| {
6136 let range = range.to_offset(&snapshot);
6137 if range.is_empty() && range.start == cursor_offset {
6138 Some(text)
6139 } else {
6140 None
6141 }
6142 });
6143
6144 if let Some(text) = insertion {
6145 let mut partial_completion = text
6146 .chars()
6147 .by_ref()
6148 .take_while(|c| c.is_alphabetic())
6149 .collect::<String>();
6150 if partial_completion.is_empty() {
6151 partial_completion = text
6152 .chars()
6153 .by_ref()
6154 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6155 .collect::<String>();
6156 }
6157
6158 cx.emit(EditorEvent::InputHandled {
6159 utf16_range_to_replace: None,
6160 text: partial_completion.clone().into(),
6161 });
6162
6163 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6164
6165 self.refresh_inline_completion(true, true, window, cx);
6166 cx.notify();
6167 } else {
6168 self.accept_edit_prediction(&Default::default(), window, cx);
6169 }
6170 }
6171 }
6172 }
6173
6174 fn discard_inline_completion(
6175 &mut self,
6176 should_report_inline_completion_event: bool,
6177 cx: &mut Context<Self>,
6178 ) -> bool {
6179 if should_report_inline_completion_event {
6180 let completion_id = self
6181 .active_inline_completion
6182 .as_ref()
6183 .and_then(|active_completion| active_completion.completion_id.clone());
6184
6185 self.report_inline_completion_event(completion_id, false, cx);
6186 }
6187
6188 if let Some(provider) = self.edit_prediction_provider() {
6189 provider.discard(cx);
6190 }
6191
6192 self.take_active_inline_completion(cx)
6193 }
6194
6195 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6196 let Some(provider) = self.edit_prediction_provider() else {
6197 return;
6198 };
6199
6200 let Some((_, buffer, _)) = self
6201 .buffer
6202 .read(cx)
6203 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6204 else {
6205 return;
6206 };
6207
6208 let extension = buffer
6209 .read(cx)
6210 .file()
6211 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6212
6213 let event_type = match accepted {
6214 true => "Edit Prediction Accepted",
6215 false => "Edit Prediction Discarded",
6216 };
6217 telemetry::event!(
6218 event_type,
6219 provider = provider.name(),
6220 prediction_id = id,
6221 suggestion_accepted = accepted,
6222 file_extension = extension,
6223 );
6224 }
6225
6226 pub fn has_active_inline_completion(&self) -> bool {
6227 self.active_inline_completion.is_some()
6228 }
6229
6230 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6231 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6232 return false;
6233 };
6234
6235 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6236 self.clear_highlights::<InlineCompletionHighlight>(cx);
6237 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6238 true
6239 }
6240
6241 /// Returns true when we're displaying the edit prediction popover below the cursor
6242 /// like we are not previewing and the LSP autocomplete menu is visible
6243 /// or we are in `when_holding_modifier` mode.
6244 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6245 if self.edit_prediction_preview_is_active()
6246 || !self.show_edit_predictions_in_menu()
6247 || !self.edit_predictions_enabled()
6248 {
6249 return false;
6250 }
6251
6252 if self.has_visible_completions_menu() {
6253 return true;
6254 }
6255
6256 has_completion && self.edit_prediction_requires_modifier()
6257 }
6258
6259 fn handle_modifiers_changed(
6260 &mut self,
6261 modifiers: Modifiers,
6262 position_map: &PositionMap,
6263 window: &mut Window,
6264 cx: &mut Context<Self>,
6265 ) {
6266 if self.show_edit_predictions_in_menu() {
6267 self.update_edit_prediction_preview(&modifiers, window, cx);
6268 }
6269
6270 self.update_selection_mode(&modifiers, position_map, window, cx);
6271
6272 let mouse_position = window.mouse_position();
6273 if !position_map.text_hitbox.is_hovered(window) {
6274 return;
6275 }
6276
6277 self.update_hovered_link(
6278 position_map.point_for_position(mouse_position),
6279 &position_map.snapshot,
6280 modifiers,
6281 window,
6282 cx,
6283 )
6284 }
6285
6286 fn update_selection_mode(
6287 &mut self,
6288 modifiers: &Modifiers,
6289 position_map: &PositionMap,
6290 window: &mut Window,
6291 cx: &mut Context<Self>,
6292 ) {
6293 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6294 return;
6295 }
6296
6297 let mouse_position = window.mouse_position();
6298 let point_for_position = position_map.point_for_position(mouse_position);
6299 let position = point_for_position.previous_valid;
6300
6301 self.select(
6302 SelectPhase::BeginColumnar {
6303 position,
6304 reset: false,
6305 goal_column: point_for_position.exact_unclipped.column(),
6306 },
6307 window,
6308 cx,
6309 );
6310 }
6311
6312 fn update_edit_prediction_preview(
6313 &mut self,
6314 modifiers: &Modifiers,
6315 window: &mut Window,
6316 cx: &mut Context<Self>,
6317 ) {
6318 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6319 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6320 return;
6321 };
6322
6323 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6324 if matches!(
6325 self.edit_prediction_preview,
6326 EditPredictionPreview::Inactive { .. }
6327 ) {
6328 self.edit_prediction_preview = EditPredictionPreview::Active {
6329 previous_scroll_position: None,
6330 since: Instant::now(),
6331 };
6332
6333 self.update_visible_inline_completion(window, cx);
6334 cx.notify();
6335 }
6336 } else if let EditPredictionPreview::Active {
6337 previous_scroll_position,
6338 since,
6339 } = self.edit_prediction_preview
6340 {
6341 if let (Some(previous_scroll_position), Some(position_map)) =
6342 (previous_scroll_position, self.last_position_map.as_ref())
6343 {
6344 self.set_scroll_position(
6345 previous_scroll_position
6346 .scroll_position(&position_map.snapshot.display_snapshot),
6347 window,
6348 cx,
6349 );
6350 }
6351
6352 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6353 released_too_fast: since.elapsed() < Duration::from_millis(200),
6354 };
6355 self.clear_row_highlights::<EditPredictionPreview>();
6356 self.update_visible_inline_completion(window, cx);
6357 cx.notify();
6358 }
6359 }
6360
6361 fn update_visible_inline_completion(
6362 &mut self,
6363 _window: &mut Window,
6364 cx: &mut Context<Self>,
6365 ) -> Option<()> {
6366 let selection = self.selections.newest_anchor();
6367 let cursor = selection.head();
6368 let multibuffer = self.buffer.read(cx).snapshot(cx);
6369 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6370 let excerpt_id = cursor.excerpt_id;
6371
6372 let show_in_menu = self.show_edit_predictions_in_menu();
6373 let completions_menu_has_precedence = !show_in_menu
6374 && (self.context_menu.borrow().is_some()
6375 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6376
6377 if completions_menu_has_precedence
6378 || !offset_selection.is_empty()
6379 || self
6380 .active_inline_completion
6381 .as_ref()
6382 .map_or(false, |completion| {
6383 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6384 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6385 !invalidation_range.contains(&offset_selection.head())
6386 })
6387 {
6388 self.discard_inline_completion(false, cx);
6389 return None;
6390 }
6391
6392 self.take_active_inline_completion(cx);
6393 let Some(provider) = self.edit_prediction_provider() else {
6394 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6395 return None;
6396 };
6397
6398 let (buffer, cursor_buffer_position) =
6399 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6400
6401 self.edit_prediction_settings =
6402 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6403
6404 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6405
6406 if self.edit_prediction_indent_conflict {
6407 let cursor_point = cursor.to_point(&multibuffer);
6408
6409 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6410
6411 if let Some((_, indent)) = indents.iter().next() {
6412 if indent.len == cursor_point.column {
6413 self.edit_prediction_indent_conflict = false;
6414 }
6415 }
6416 }
6417
6418 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6419 let edits = inline_completion
6420 .edits
6421 .into_iter()
6422 .flat_map(|(range, new_text)| {
6423 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6424 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6425 Some((start..end, new_text))
6426 })
6427 .collect::<Vec<_>>();
6428 if edits.is_empty() {
6429 return None;
6430 }
6431
6432 let first_edit_start = edits.first().unwrap().0.start;
6433 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6434 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6435
6436 let last_edit_end = edits.last().unwrap().0.end;
6437 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6438 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6439
6440 let cursor_row = cursor.to_point(&multibuffer).row;
6441
6442 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6443
6444 let mut inlay_ids = Vec::new();
6445 let invalidation_row_range;
6446 let move_invalidation_row_range = if cursor_row < edit_start_row {
6447 Some(cursor_row..edit_end_row)
6448 } else if cursor_row > edit_end_row {
6449 Some(edit_start_row..cursor_row)
6450 } else {
6451 None
6452 };
6453 let is_move =
6454 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6455 let completion = if is_move {
6456 invalidation_row_range =
6457 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6458 let target = first_edit_start;
6459 InlineCompletion::Move { target, snapshot }
6460 } else {
6461 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6462 && !self.inline_completions_hidden_for_vim_mode;
6463
6464 if show_completions_in_buffer {
6465 if edits
6466 .iter()
6467 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6468 {
6469 let mut inlays = Vec::new();
6470 for (range, new_text) in &edits {
6471 let inlay = Inlay::inline_completion(
6472 post_inc(&mut self.next_inlay_id),
6473 range.start,
6474 new_text.as_str(),
6475 );
6476 inlay_ids.push(inlay.id);
6477 inlays.push(inlay);
6478 }
6479
6480 self.splice_inlays(&[], inlays, cx);
6481 } else {
6482 let background_color = cx.theme().status().deleted_background;
6483 self.highlight_text::<InlineCompletionHighlight>(
6484 edits.iter().map(|(range, _)| range.clone()).collect(),
6485 HighlightStyle {
6486 background_color: Some(background_color),
6487 ..Default::default()
6488 },
6489 cx,
6490 );
6491 }
6492 }
6493
6494 invalidation_row_range = edit_start_row..edit_end_row;
6495
6496 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6497 if provider.show_tab_accept_marker() {
6498 EditDisplayMode::TabAccept
6499 } else {
6500 EditDisplayMode::Inline
6501 }
6502 } else {
6503 EditDisplayMode::DiffPopover
6504 };
6505
6506 InlineCompletion::Edit {
6507 edits,
6508 edit_preview: inline_completion.edit_preview,
6509 display_mode,
6510 snapshot,
6511 }
6512 };
6513
6514 let invalidation_range = multibuffer
6515 .anchor_before(Point::new(invalidation_row_range.start, 0))
6516 ..multibuffer.anchor_after(Point::new(
6517 invalidation_row_range.end,
6518 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6519 ));
6520
6521 self.stale_inline_completion_in_menu = None;
6522 self.active_inline_completion = Some(InlineCompletionState {
6523 inlay_ids,
6524 completion,
6525 completion_id: inline_completion.id,
6526 invalidation_range,
6527 });
6528
6529 cx.notify();
6530
6531 Some(())
6532 }
6533
6534 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6535 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6536 }
6537
6538 fn render_code_actions_indicator(
6539 &self,
6540 _style: &EditorStyle,
6541 row: DisplayRow,
6542 is_active: bool,
6543 breakpoint: Option<&(Anchor, Breakpoint)>,
6544 cx: &mut Context<Self>,
6545 ) -> Option<IconButton> {
6546 let color = Color::Muted;
6547 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6548 let show_tooltip = !self.context_menu_visible();
6549
6550 if self.available_code_actions.is_some() {
6551 Some(
6552 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6553 .shape(ui::IconButtonShape::Square)
6554 .icon_size(IconSize::XSmall)
6555 .icon_color(color)
6556 .toggle_state(is_active)
6557 .when(show_tooltip, |this| {
6558 this.tooltip({
6559 let focus_handle = self.focus_handle.clone();
6560 move |window, cx| {
6561 Tooltip::for_action_in(
6562 "Toggle Code Actions",
6563 &ToggleCodeActions {
6564 deployed_from_indicator: None,
6565 },
6566 &focus_handle,
6567 window,
6568 cx,
6569 )
6570 }
6571 })
6572 })
6573 .on_click(cx.listener(move |editor, _e, window, cx| {
6574 window.focus(&editor.focus_handle(cx));
6575 editor.toggle_code_actions(
6576 &ToggleCodeActions {
6577 deployed_from_indicator: Some(row),
6578 },
6579 window,
6580 cx,
6581 );
6582 }))
6583 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6584 editor.set_breakpoint_context_menu(
6585 row,
6586 position,
6587 event.down.position,
6588 window,
6589 cx,
6590 );
6591 })),
6592 )
6593 } else {
6594 None
6595 }
6596 }
6597
6598 fn clear_tasks(&mut self) {
6599 self.tasks.clear()
6600 }
6601
6602 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6603 if self.tasks.insert(key, value).is_some() {
6604 // This case should hopefully be rare, but just in case...
6605 log::error!(
6606 "multiple different run targets found on a single line, only the last target will be rendered"
6607 )
6608 }
6609 }
6610
6611 /// Get all display points of breakpoints that will be rendered within editor
6612 ///
6613 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6614 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6615 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6616 fn active_breakpoints(
6617 &self,
6618 range: Range<DisplayRow>,
6619 window: &mut Window,
6620 cx: &mut Context<Self>,
6621 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6622 let mut breakpoint_display_points = HashMap::default();
6623
6624 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6625 return breakpoint_display_points;
6626 };
6627
6628 let snapshot = self.snapshot(window, cx);
6629
6630 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6631 let Some(project) = self.project.as_ref() else {
6632 return breakpoint_display_points;
6633 };
6634
6635 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6636 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6637
6638 for (buffer_snapshot, range, excerpt_id) in
6639 multi_buffer_snapshot.range_to_buffer_ranges(range)
6640 {
6641 let Some(buffer) = project.read_with(cx, |this, cx| {
6642 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6643 }) else {
6644 continue;
6645 };
6646 let breakpoints = breakpoint_store.read(cx).breakpoints(
6647 &buffer,
6648 Some(
6649 buffer_snapshot.anchor_before(range.start)
6650 ..buffer_snapshot.anchor_after(range.end),
6651 ),
6652 buffer_snapshot,
6653 cx,
6654 );
6655 for (anchor, breakpoint) in breakpoints {
6656 let multi_buffer_anchor =
6657 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6658 let position = multi_buffer_anchor
6659 .to_point(&multi_buffer_snapshot)
6660 .to_display_point(&snapshot);
6661
6662 breakpoint_display_points
6663 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6664 }
6665 }
6666
6667 breakpoint_display_points
6668 }
6669
6670 fn breakpoint_context_menu(
6671 &self,
6672 anchor: Anchor,
6673 window: &mut Window,
6674 cx: &mut Context<Self>,
6675 ) -> Entity<ui::ContextMenu> {
6676 let weak_editor = cx.weak_entity();
6677 let focus_handle = self.focus_handle(cx);
6678
6679 let row = self
6680 .buffer
6681 .read(cx)
6682 .snapshot(cx)
6683 .summary_for_anchor::<Point>(&anchor)
6684 .row;
6685
6686 let breakpoint = self
6687 .breakpoint_at_row(row, window, cx)
6688 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6689
6690 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6691 "Edit Log Breakpoint"
6692 } else {
6693 "Set Log Breakpoint"
6694 };
6695
6696 let condition_breakpoint_msg = if breakpoint
6697 .as_ref()
6698 .is_some_and(|bp| bp.1.condition.is_some())
6699 {
6700 "Edit Condition Breakpoint"
6701 } else {
6702 "Set Condition Breakpoint"
6703 };
6704
6705 let hit_condition_breakpoint_msg = if breakpoint
6706 .as_ref()
6707 .is_some_and(|bp| bp.1.hit_condition.is_some())
6708 {
6709 "Edit Hit Condition Breakpoint"
6710 } else {
6711 "Set Hit Condition Breakpoint"
6712 };
6713
6714 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6715 "Unset Breakpoint"
6716 } else {
6717 "Set Breakpoint"
6718 };
6719
6720 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6721 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6722
6723 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6724 BreakpointState::Enabled => Some("Disable"),
6725 BreakpointState::Disabled => Some("Enable"),
6726 });
6727
6728 let (anchor, breakpoint) =
6729 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6730
6731 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6732 menu.on_blur_subscription(Subscription::new(|| {}))
6733 .context(focus_handle)
6734 .when(run_to_cursor, |this| {
6735 let weak_editor = weak_editor.clone();
6736 this.entry("Run to cursor", None, move |window, cx| {
6737 weak_editor
6738 .update(cx, |editor, cx| {
6739 editor.change_selections(None, window, cx, |s| {
6740 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6741 });
6742 })
6743 .ok();
6744
6745 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6746 })
6747 .separator()
6748 })
6749 .when_some(toggle_state_msg, |this, msg| {
6750 this.entry(msg, None, {
6751 let weak_editor = weak_editor.clone();
6752 let breakpoint = breakpoint.clone();
6753 move |_window, cx| {
6754 weak_editor
6755 .update(cx, |this, cx| {
6756 this.edit_breakpoint_at_anchor(
6757 anchor,
6758 breakpoint.as_ref().clone(),
6759 BreakpointEditAction::InvertState,
6760 cx,
6761 );
6762 })
6763 .log_err();
6764 }
6765 })
6766 })
6767 .entry(set_breakpoint_msg, None, {
6768 let weak_editor = weak_editor.clone();
6769 let breakpoint = breakpoint.clone();
6770 move |_window, cx| {
6771 weak_editor
6772 .update(cx, |this, cx| {
6773 this.edit_breakpoint_at_anchor(
6774 anchor,
6775 breakpoint.as_ref().clone(),
6776 BreakpointEditAction::Toggle,
6777 cx,
6778 );
6779 })
6780 .log_err();
6781 }
6782 })
6783 .entry(log_breakpoint_msg, None, {
6784 let breakpoint = breakpoint.clone();
6785 let weak_editor = weak_editor.clone();
6786 move |window, cx| {
6787 weak_editor
6788 .update(cx, |this, cx| {
6789 this.add_edit_breakpoint_block(
6790 anchor,
6791 breakpoint.as_ref(),
6792 BreakpointPromptEditAction::Log,
6793 window,
6794 cx,
6795 );
6796 })
6797 .log_err();
6798 }
6799 })
6800 .entry(condition_breakpoint_msg, None, {
6801 let breakpoint = breakpoint.clone();
6802 let weak_editor = weak_editor.clone();
6803 move |window, cx| {
6804 weak_editor
6805 .update(cx, |this, cx| {
6806 this.add_edit_breakpoint_block(
6807 anchor,
6808 breakpoint.as_ref(),
6809 BreakpointPromptEditAction::Condition,
6810 window,
6811 cx,
6812 );
6813 })
6814 .log_err();
6815 }
6816 })
6817 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6818 weak_editor
6819 .update(cx, |this, cx| {
6820 this.add_edit_breakpoint_block(
6821 anchor,
6822 breakpoint.as_ref(),
6823 BreakpointPromptEditAction::HitCondition,
6824 window,
6825 cx,
6826 );
6827 })
6828 .log_err();
6829 })
6830 })
6831 }
6832
6833 fn render_breakpoint(
6834 &self,
6835 position: Anchor,
6836 row: DisplayRow,
6837 breakpoint: &Breakpoint,
6838 cx: &mut Context<Self>,
6839 ) -> IconButton {
6840 let (color, icon) = {
6841 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6842 (false, false) => ui::IconName::DebugBreakpoint,
6843 (true, false) => ui::IconName::DebugLogBreakpoint,
6844 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6845 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6846 };
6847
6848 let color = if self
6849 .gutter_breakpoint_indicator
6850 .0
6851 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6852 {
6853 Color::Hint
6854 } else {
6855 Color::Debugger
6856 };
6857
6858 (color, icon)
6859 };
6860
6861 let breakpoint = Arc::from(breakpoint.clone());
6862
6863 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6864 .icon_size(IconSize::XSmall)
6865 .size(ui::ButtonSize::None)
6866 .icon_color(color)
6867 .style(ButtonStyle::Transparent)
6868 .on_click(cx.listener({
6869 let breakpoint = breakpoint.clone();
6870
6871 move |editor, event: &ClickEvent, window, cx| {
6872 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6873 BreakpointEditAction::InvertState
6874 } else {
6875 BreakpointEditAction::Toggle
6876 };
6877
6878 window.focus(&editor.focus_handle(cx));
6879 editor.edit_breakpoint_at_anchor(
6880 position,
6881 breakpoint.as_ref().clone(),
6882 edit_action,
6883 cx,
6884 );
6885 }
6886 }))
6887 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6888 editor.set_breakpoint_context_menu(
6889 row,
6890 Some(position),
6891 event.down.position,
6892 window,
6893 cx,
6894 );
6895 }))
6896 }
6897
6898 fn build_tasks_context(
6899 project: &Entity<Project>,
6900 buffer: &Entity<Buffer>,
6901 buffer_row: u32,
6902 tasks: &Arc<RunnableTasks>,
6903 cx: &mut Context<Self>,
6904 ) -> Task<Option<task::TaskContext>> {
6905 let position = Point::new(buffer_row, tasks.column);
6906 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6907 let location = Location {
6908 buffer: buffer.clone(),
6909 range: range_start..range_start,
6910 };
6911 // Fill in the environmental variables from the tree-sitter captures
6912 let mut captured_task_variables = TaskVariables::default();
6913 for (capture_name, value) in tasks.extra_variables.clone() {
6914 captured_task_variables.insert(
6915 task::VariableName::Custom(capture_name.into()),
6916 value.clone(),
6917 );
6918 }
6919 project.update(cx, |project, cx| {
6920 project.task_store().update(cx, |task_store, cx| {
6921 task_store.task_context_for_location(captured_task_variables, location, cx)
6922 })
6923 })
6924 }
6925
6926 pub fn spawn_nearest_task(
6927 &mut self,
6928 action: &SpawnNearestTask,
6929 window: &mut Window,
6930 cx: &mut Context<Self>,
6931 ) {
6932 let Some((workspace, _)) = self.workspace.clone() else {
6933 return;
6934 };
6935 let Some(project) = self.project.clone() else {
6936 return;
6937 };
6938
6939 // Try to find a closest, enclosing node using tree-sitter that has a
6940 // task
6941 let Some((buffer, buffer_row, tasks)) = self
6942 .find_enclosing_node_task(cx)
6943 // Or find the task that's closest in row-distance.
6944 .or_else(|| self.find_closest_task(cx))
6945 else {
6946 return;
6947 };
6948
6949 let reveal_strategy = action.reveal;
6950 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6951 cx.spawn_in(window, async move |_, cx| {
6952 let context = task_context.await?;
6953 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6954
6955 let resolved = resolved_task.resolved.as_mut()?;
6956 resolved.reveal = reveal_strategy;
6957
6958 workspace
6959 .update_in(cx, |workspace, window, cx| {
6960 workspace.schedule_resolved_task(
6961 task_source_kind,
6962 resolved_task,
6963 false,
6964 window,
6965 cx,
6966 );
6967 })
6968 .ok()
6969 })
6970 .detach();
6971 }
6972
6973 fn find_closest_task(
6974 &mut self,
6975 cx: &mut Context<Self>,
6976 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6977 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6978
6979 let ((buffer_id, row), tasks) = self
6980 .tasks
6981 .iter()
6982 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6983
6984 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6985 let tasks = Arc::new(tasks.to_owned());
6986 Some((buffer, *row, tasks))
6987 }
6988
6989 fn find_enclosing_node_task(
6990 &mut self,
6991 cx: &mut Context<Self>,
6992 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6993 let snapshot = self.buffer.read(cx).snapshot(cx);
6994 let offset = self.selections.newest::<usize>(cx).head();
6995 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6996 let buffer_id = excerpt.buffer().remote_id();
6997
6998 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6999 let mut cursor = layer.node().walk();
7000
7001 while cursor.goto_first_child_for_byte(offset).is_some() {
7002 if cursor.node().end_byte() == offset {
7003 cursor.goto_next_sibling();
7004 }
7005 }
7006
7007 // Ascend to the smallest ancestor that contains the range and has a task.
7008 loop {
7009 let node = cursor.node();
7010 let node_range = node.byte_range();
7011 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7012
7013 // Check if this node contains our offset
7014 if node_range.start <= offset && node_range.end >= offset {
7015 // If it contains offset, check for task
7016 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7017 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7018 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7019 }
7020 }
7021
7022 if !cursor.goto_parent() {
7023 break;
7024 }
7025 }
7026 None
7027 }
7028
7029 fn render_run_indicator(
7030 &self,
7031 _style: &EditorStyle,
7032 is_active: bool,
7033 row: DisplayRow,
7034 breakpoint: Option<(Anchor, Breakpoint)>,
7035 cx: &mut Context<Self>,
7036 ) -> IconButton {
7037 let color = Color::Muted;
7038 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
7039
7040 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7041 .shape(ui::IconButtonShape::Square)
7042 .icon_size(IconSize::XSmall)
7043 .icon_color(color)
7044 .toggle_state(is_active)
7045 .on_click(cx.listener(move |editor, _e, window, cx| {
7046 window.focus(&editor.focus_handle(cx));
7047 editor.toggle_code_actions(
7048 &ToggleCodeActions {
7049 deployed_from_indicator: Some(row),
7050 },
7051 window,
7052 cx,
7053 );
7054 }))
7055 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7056 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7057 }))
7058 }
7059
7060 pub fn context_menu_visible(&self) -> bool {
7061 !self.edit_prediction_preview_is_active()
7062 && self
7063 .context_menu
7064 .borrow()
7065 .as_ref()
7066 .map_or(false, |menu| menu.visible())
7067 }
7068
7069 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7070 self.context_menu
7071 .borrow()
7072 .as_ref()
7073 .map(|menu| menu.origin())
7074 }
7075
7076 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7077 self.context_menu_options = Some(options);
7078 }
7079
7080 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7081 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7082
7083 fn render_edit_prediction_popover(
7084 &mut self,
7085 text_bounds: &Bounds<Pixels>,
7086 content_origin: gpui::Point<Pixels>,
7087 editor_snapshot: &EditorSnapshot,
7088 visible_row_range: Range<DisplayRow>,
7089 scroll_top: f32,
7090 scroll_bottom: f32,
7091 line_layouts: &[LineWithInvisibles],
7092 line_height: Pixels,
7093 scroll_pixel_position: gpui::Point<Pixels>,
7094 newest_selection_head: Option<DisplayPoint>,
7095 editor_width: Pixels,
7096 style: &EditorStyle,
7097 window: &mut Window,
7098 cx: &mut App,
7099 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7100 let active_inline_completion = self.active_inline_completion.as_ref()?;
7101
7102 if self.edit_prediction_visible_in_cursor_popover(true) {
7103 return None;
7104 }
7105
7106 match &active_inline_completion.completion {
7107 InlineCompletion::Move { target, .. } => {
7108 let target_display_point = target.to_display_point(editor_snapshot);
7109
7110 if self.edit_prediction_requires_modifier() {
7111 if !self.edit_prediction_preview_is_active() {
7112 return None;
7113 }
7114
7115 self.render_edit_prediction_modifier_jump_popover(
7116 text_bounds,
7117 content_origin,
7118 visible_row_range,
7119 line_layouts,
7120 line_height,
7121 scroll_pixel_position,
7122 newest_selection_head,
7123 target_display_point,
7124 window,
7125 cx,
7126 )
7127 } else {
7128 self.render_edit_prediction_eager_jump_popover(
7129 text_bounds,
7130 content_origin,
7131 editor_snapshot,
7132 visible_row_range,
7133 scroll_top,
7134 scroll_bottom,
7135 line_height,
7136 scroll_pixel_position,
7137 target_display_point,
7138 editor_width,
7139 window,
7140 cx,
7141 )
7142 }
7143 }
7144 InlineCompletion::Edit {
7145 display_mode: EditDisplayMode::Inline,
7146 ..
7147 } => None,
7148 InlineCompletion::Edit {
7149 display_mode: EditDisplayMode::TabAccept,
7150 edits,
7151 ..
7152 } => {
7153 let range = &edits.first()?.0;
7154 let target_display_point = range.end.to_display_point(editor_snapshot);
7155
7156 self.render_edit_prediction_end_of_line_popover(
7157 "Accept",
7158 editor_snapshot,
7159 visible_row_range,
7160 target_display_point,
7161 line_height,
7162 scroll_pixel_position,
7163 content_origin,
7164 editor_width,
7165 window,
7166 cx,
7167 )
7168 }
7169 InlineCompletion::Edit {
7170 edits,
7171 edit_preview,
7172 display_mode: EditDisplayMode::DiffPopover,
7173 snapshot,
7174 } => self.render_edit_prediction_diff_popover(
7175 text_bounds,
7176 content_origin,
7177 editor_snapshot,
7178 visible_row_range,
7179 line_layouts,
7180 line_height,
7181 scroll_pixel_position,
7182 newest_selection_head,
7183 editor_width,
7184 style,
7185 edits,
7186 edit_preview,
7187 snapshot,
7188 window,
7189 cx,
7190 ),
7191 }
7192 }
7193
7194 fn render_edit_prediction_modifier_jump_popover(
7195 &mut self,
7196 text_bounds: &Bounds<Pixels>,
7197 content_origin: gpui::Point<Pixels>,
7198 visible_row_range: Range<DisplayRow>,
7199 line_layouts: &[LineWithInvisibles],
7200 line_height: Pixels,
7201 scroll_pixel_position: gpui::Point<Pixels>,
7202 newest_selection_head: Option<DisplayPoint>,
7203 target_display_point: DisplayPoint,
7204 window: &mut Window,
7205 cx: &mut App,
7206 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7207 let scrolled_content_origin =
7208 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7209
7210 const SCROLL_PADDING_Y: Pixels = px(12.);
7211
7212 if target_display_point.row() < visible_row_range.start {
7213 return self.render_edit_prediction_scroll_popover(
7214 |_| SCROLL_PADDING_Y,
7215 IconName::ArrowUp,
7216 visible_row_range,
7217 line_layouts,
7218 newest_selection_head,
7219 scrolled_content_origin,
7220 window,
7221 cx,
7222 );
7223 } else if target_display_point.row() >= visible_row_range.end {
7224 return self.render_edit_prediction_scroll_popover(
7225 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7226 IconName::ArrowDown,
7227 visible_row_range,
7228 line_layouts,
7229 newest_selection_head,
7230 scrolled_content_origin,
7231 window,
7232 cx,
7233 );
7234 }
7235
7236 const POLE_WIDTH: Pixels = px(2.);
7237
7238 let line_layout =
7239 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7240 let target_column = target_display_point.column() as usize;
7241
7242 let target_x = line_layout.x_for_index(target_column);
7243 let target_y =
7244 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7245
7246 let flag_on_right = target_x < text_bounds.size.width / 2.;
7247
7248 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7249 border_color.l += 0.001;
7250
7251 let mut element = v_flex()
7252 .items_end()
7253 .when(flag_on_right, |el| el.items_start())
7254 .child(if flag_on_right {
7255 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7256 .rounded_bl(px(0.))
7257 .rounded_tl(px(0.))
7258 .border_l_2()
7259 .border_color(border_color)
7260 } else {
7261 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7262 .rounded_br(px(0.))
7263 .rounded_tr(px(0.))
7264 .border_r_2()
7265 .border_color(border_color)
7266 })
7267 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7268 .into_any();
7269
7270 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7271
7272 let mut origin = scrolled_content_origin + point(target_x, target_y)
7273 - point(
7274 if flag_on_right {
7275 POLE_WIDTH
7276 } else {
7277 size.width - POLE_WIDTH
7278 },
7279 size.height - line_height,
7280 );
7281
7282 origin.x = origin.x.max(content_origin.x);
7283
7284 element.prepaint_at(origin, window, cx);
7285
7286 Some((element, origin))
7287 }
7288
7289 fn render_edit_prediction_scroll_popover(
7290 &mut self,
7291 to_y: impl Fn(Size<Pixels>) -> Pixels,
7292 scroll_icon: IconName,
7293 visible_row_range: Range<DisplayRow>,
7294 line_layouts: &[LineWithInvisibles],
7295 newest_selection_head: Option<DisplayPoint>,
7296 scrolled_content_origin: gpui::Point<Pixels>,
7297 window: &mut Window,
7298 cx: &mut App,
7299 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7300 let mut element = self
7301 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7302 .into_any();
7303
7304 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7305
7306 let cursor = newest_selection_head?;
7307 let cursor_row_layout =
7308 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7309 let cursor_column = cursor.column() as usize;
7310
7311 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7312
7313 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7314
7315 element.prepaint_at(origin, window, cx);
7316 Some((element, origin))
7317 }
7318
7319 fn render_edit_prediction_eager_jump_popover(
7320 &mut self,
7321 text_bounds: &Bounds<Pixels>,
7322 content_origin: gpui::Point<Pixels>,
7323 editor_snapshot: &EditorSnapshot,
7324 visible_row_range: Range<DisplayRow>,
7325 scroll_top: f32,
7326 scroll_bottom: f32,
7327 line_height: Pixels,
7328 scroll_pixel_position: gpui::Point<Pixels>,
7329 target_display_point: DisplayPoint,
7330 editor_width: Pixels,
7331 window: &mut Window,
7332 cx: &mut App,
7333 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7334 if target_display_point.row().as_f32() < scroll_top {
7335 let mut element = self
7336 .render_edit_prediction_line_popover(
7337 "Jump to Edit",
7338 Some(IconName::ArrowUp),
7339 window,
7340 cx,
7341 )?
7342 .into_any();
7343
7344 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7345 let offset = point(
7346 (text_bounds.size.width - size.width) / 2.,
7347 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7348 );
7349
7350 let origin = text_bounds.origin + offset;
7351 element.prepaint_at(origin, window, cx);
7352 Some((element, origin))
7353 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7354 let mut element = self
7355 .render_edit_prediction_line_popover(
7356 "Jump to Edit",
7357 Some(IconName::ArrowDown),
7358 window,
7359 cx,
7360 )?
7361 .into_any();
7362
7363 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7364 let offset = point(
7365 (text_bounds.size.width - size.width) / 2.,
7366 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7367 );
7368
7369 let origin = text_bounds.origin + offset;
7370 element.prepaint_at(origin, window, cx);
7371 Some((element, origin))
7372 } else {
7373 self.render_edit_prediction_end_of_line_popover(
7374 "Jump to Edit",
7375 editor_snapshot,
7376 visible_row_range,
7377 target_display_point,
7378 line_height,
7379 scroll_pixel_position,
7380 content_origin,
7381 editor_width,
7382 window,
7383 cx,
7384 )
7385 }
7386 }
7387
7388 fn render_edit_prediction_end_of_line_popover(
7389 self: &mut Editor,
7390 label: &'static str,
7391 editor_snapshot: &EditorSnapshot,
7392 visible_row_range: Range<DisplayRow>,
7393 target_display_point: DisplayPoint,
7394 line_height: Pixels,
7395 scroll_pixel_position: gpui::Point<Pixels>,
7396 content_origin: gpui::Point<Pixels>,
7397 editor_width: Pixels,
7398 window: &mut Window,
7399 cx: &mut App,
7400 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7401 let target_line_end = DisplayPoint::new(
7402 target_display_point.row(),
7403 editor_snapshot.line_len(target_display_point.row()),
7404 );
7405
7406 let mut element = self
7407 .render_edit_prediction_line_popover(label, None, window, cx)?
7408 .into_any();
7409
7410 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7411
7412 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7413
7414 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7415 let mut origin = start_point
7416 + line_origin
7417 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7418 origin.x = origin.x.max(content_origin.x);
7419
7420 let max_x = content_origin.x + editor_width - size.width;
7421
7422 if origin.x > max_x {
7423 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7424
7425 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7426 origin.y += offset;
7427 IconName::ArrowUp
7428 } else {
7429 origin.y -= offset;
7430 IconName::ArrowDown
7431 };
7432
7433 element = self
7434 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7435 .into_any();
7436
7437 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7438
7439 origin.x = content_origin.x + editor_width - size.width - px(2.);
7440 }
7441
7442 element.prepaint_at(origin, window, cx);
7443 Some((element, origin))
7444 }
7445
7446 fn render_edit_prediction_diff_popover(
7447 self: &Editor,
7448 text_bounds: &Bounds<Pixels>,
7449 content_origin: gpui::Point<Pixels>,
7450 editor_snapshot: &EditorSnapshot,
7451 visible_row_range: Range<DisplayRow>,
7452 line_layouts: &[LineWithInvisibles],
7453 line_height: Pixels,
7454 scroll_pixel_position: gpui::Point<Pixels>,
7455 newest_selection_head: Option<DisplayPoint>,
7456 editor_width: Pixels,
7457 style: &EditorStyle,
7458 edits: &Vec<(Range<Anchor>, String)>,
7459 edit_preview: &Option<language::EditPreview>,
7460 snapshot: &language::BufferSnapshot,
7461 window: &mut Window,
7462 cx: &mut App,
7463 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7464 let edit_start = edits
7465 .first()
7466 .unwrap()
7467 .0
7468 .start
7469 .to_display_point(editor_snapshot);
7470 let edit_end = edits
7471 .last()
7472 .unwrap()
7473 .0
7474 .end
7475 .to_display_point(editor_snapshot);
7476
7477 let is_visible = visible_row_range.contains(&edit_start.row())
7478 || visible_row_range.contains(&edit_end.row());
7479 if !is_visible {
7480 return None;
7481 }
7482
7483 let highlighted_edits =
7484 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7485
7486 let styled_text = highlighted_edits.to_styled_text(&style.text);
7487 let line_count = highlighted_edits.text.lines().count();
7488
7489 const BORDER_WIDTH: Pixels = px(1.);
7490
7491 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7492 let has_keybind = keybind.is_some();
7493
7494 let mut element = h_flex()
7495 .items_start()
7496 .child(
7497 h_flex()
7498 .bg(cx.theme().colors().editor_background)
7499 .border(BORDER_WIDTH)
7500 .shadow_sm()
7501 .border_color(cx.theme().colors().border)
7502 .rounded_l_lg()
7503 .when(line_count > 1, |el| el.rounded_br_lg())
7504 .pr_1()
7505 .child(styled_text),
7506 )
7507 .child(
7508 h_flex()
7509 .h(line_height + BORDER_WIDTH * 2.)
7510 .px_1p5()
7511 .gap_1()
7512 // Workaround: For some reason, there's a gap if we don't do this
7513 .ml(-BORDER_WIDTH)
7514 .shadow(smallvec![gpui::BoxShadow {
7515 color: gpui::black().opacity(0.05),
7516 offset: point(px(1.), px(1.)),
7517 blur_radius: px(2.),
7518 spread_radius: px(0.),
7519 }])
7520 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7521 .border(BORDER_WIDTH)
7522 .border_color(cx.theme().colors().border)
7523 .rounded_r_lg()
7524 .id("edit_prediction_diff_popover_keybind")
7525 .when(!has_keybind, |el| {
7526 let status_colors = cx.theme().status();
7527
7528 el.bg(status_colors.error_background)
7529 .border_color(status_colors.error.opacity(0.6))
7530 .child(Icon::new(IconName::Info).color(Color::Error))
7531 .cursor_default()
7532 .hoverable_tooltip(move |_window, cx| {
7533 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7534 })
7535 })
7536 .children(keybind),
7537 )
7538 .into_any();
7539
7540 let longest_row =
7541 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7542 let longest_line_width = if visible_row_range.contains(&longest_row) {
7543 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7544 } else {
7545 layout_line(
7546 longest_row,
7547 editor_snapshot,
7548 style,
7549 editor_width,
7550 |_| false,
7551 window,
7552 cx,
7553 )
7554 .width
7555 };
7556
7557 let viewport_bounds =
7558 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7559 right: -EditorElement::SCROLLBAR_WIDTH,
7560 ..Default::default()
7561 });
7562
7563 let x_after_longest =
7564 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7565 - scroll_pixel_position.x;
7566
7567 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7568
7569 // Fully visible if it can be displayed within the window (allow overlapping other
7570 // panes). However, this is only allowed if the popover starts within text_bounds.
7571 let can_position_to_the_right = x_after_longest < text_bounds.right()
7572 && x_after_longest + element_bounds.width < viewport_bounds.right();
7573
7574 let mut origin = if can_position_to_the_right {
7575 point(
7576 x_after_longest,
7577 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7578 - scroll_pixel_position.y,
7579 )
7580 } else {
7581 let cursor_row = newest_selection_head.map(|head| head.row());
7582 let above_edit = edit_start
7583 .row()
7584 .0
7585 .checked_sub(line_count as u32)
7586 .map(DisplayRow);
7587 let below_edit = Some(edit_end.row() + 1);
7588 let above_cursor =
7589 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7590 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7591
7592 // Place the edit popover adjacent to the edit if there is a location
7593 // available that is onscreen and does not obscure the cursor. Otherwise,
7594 // place it adjacent to the cursor.
7595 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7596 .into_iter()
7597 .flatten()
7598 .find(|&start_row| {
7599 let end_row = start_row + line_count as u32;
7600 visible_row_range.contains(&start_row)
7601 && visible_row_range.contains(&end_row)
7602 && cursor_row.map_or(true, |cursor_row| {
7603 !((start_row..end_row).contains(&cursor_row))
7604 })
7605 })?;
7606
7607 content_origin
7608 + point(
7609 -scroll_pixel_position.x,
7610 row_target.as_f32() * line_height - scroll_pixel_position.y,
7611 )
7612 };
7613
7614 origin.x -= BORDER_WIDTH;
7615
7616 window.defer_draw(element, origin, 1);
7617
7618 // Do not return an element, since it will already be drawn due to defer_draw.
7619 None
7620 }
7621
7622 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7623 px(30.)
7624 }
7625
7626 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7627 if self.read_only(cx) {
7628 cx.theme().players().read_only()
7629 } else {
7630 self.style.as_ref().unwrap().local_player
7631 }
7632 }
7633
7634 fn render_edit_prediction_accept_keybind(
7635 &self,
7636 window: &mut Window,
7637 cx: &App,
7638 ) -> Option<AnyElement> {
7639 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7640 let accept_keystroke = accept_binding.keystroke()?;
7641
7642 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7643
7644 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7645 Color::Accent
7646 } else {
7647 Color::Muted
7648 };
7649
7650 h_flex()
7651 .px_0p5()
7652 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7653 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7654 .text_size(TextSize::XSmall.rems(cx))
7655 .child(h_flex().children(ui::render_modifiers(
7656 &accept_keystroke.modifiers,
7657 PlatformStyle::platform(),
7658 Some(modifiers_color),
7659 Some(IconSize::XSmall.rems().into()),
7660 true,
7661 )))
7662 .when(is_platform_style_mac, |parent| {
7663 parent.child(accept_keystroke.key.clone())
7664 })
7665 .when(!is_platform_style_mac, |parent| {
7666 parent.child(
7667 Key::new(
7668 util::capitalize(&accept_keystroke.key),
7669 Some(Color::Default),
7670 )
7671 .size(Some(IconSize::XSmall.rems().into())),
7672 )
7673 })
7674 .into_any()
7675 .into()
7676 }
7677
7678 fn render_edit_prediction_line_popover(
7679 &self,
7680 label: impl Into<SharedString>,
7681 icon: Option<IconName>,
7682 window: &mut Window,
7683 cx: &App,
7684 ) -> Option<Stateful<Div>> {
7685 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7686
7687 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7688 let has_keybind = keybind.is_some();
7689
7690 let result = h_flex()
7691 .id("ep-line-popover")
7692 .py_0p5()
7693 .pl_1()
7694 .pr(padding_right)
7695 .gap_1()
7696 .rounded_md()
7697 .border_1()
7698 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7699 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7700 .shadow_sm()
7701 .when(!has_keybind, |el| {
7702 let status_colors = cx.theme().status();
7703
7704 el.bg(status_colors.error_background)
7705 .border_color(status_colors.error.opacity(0.6))
7706 .pl_2()
7707 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7708 .cursor_default()
7709 .hoverable_tooltip(move |_window, cx| {
7710 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7711 })
7712 })
7713 .children(keybind)
7714 .child(
7715 Label::new(label)
7716 .size(LabelSize::Small)
7717 .when(!has_keybind, |el| {
7718 el.color(cx.theme().status().error.into()).strikethrough()
7719 }),
7720 )
7721 .when(!has_keybind, |el| {
7722 el.child(
7723 h_flex().ml_1().child(
7724 Icon::new(IconName::Info)
7725 .size(IconSize::Small)
7726 .color(cx.theme().status().error.into()),
7727 ),
7728 )
7729 })
7730 .when_some(icon, |element, icon| {
7731 element.child(
7732 div()
7733 .mt(px(1.5))
7734 .child(Icon::new(icon).size(IconSize::Small)),
7735 )
7736 });
7737
7738 Some(result)
7739 }
7740
7741 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7742 let accent_color = cx.theme().colors().text_accent;
7743 let editor_bg_color = cx.theme().colors().editor_background;
7744 editor_bg_color.blend(accent_color.opacity(0.1))
7745 }
7746
7747 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7748 let accent_color = cx.theme().colors().text_accent;
7749 let editor_bg_color = cx.theme().colors().editor_background;
7750 editor_bg_color.blend(accent_color.opacity(0.6))
7751 }
7752
7753 fn render_edit_prediction_cursor_popover(
7754 &self,
7755 min_width: Pixels,
7756 max_width: Pixels,
7757 cursor_point: Point,
7758 style: &EditorStyle,
7759 accept_keystroke: Option<&gpui::Keystroke>,
7760 _window: &Window,
7761 cx: &mut Context<Editor>,
7762 ) -> Option<AnyElement> {
7763 let provider = self.edit_prediction_provider.as_ref()?;
7764
7765 if provider.provider.needs_terms_acceptance(cx) {
7766 return Some(
7767 h_flex()
7768 .min_w(min_width)
7769 .flex_1()
7770 .px_2()
7771 .py_1()
7772 .gap_3()
7773 .elevation_2(cx)
7774 .hover(|style| style.bg(cx.theme().colors().element_hover))
7775 .id("accept-terms")
7776 .cursor_pointer()
7777 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7778 .on_click(cx.listener(|this, _event, window, cx| {
7779 cx.stop_propagation();
7780 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7781 window.dispatch_action(
7782 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7783 cx,
7784 );
7785 }))
7786 .child(
7787 h_flex()
7788 .flex_1()
7789 .gap_2()
7790 .child(Icon::new(IconName::ZedPredict))
7791 .child(Label::new("Accept Terms of Service"))
7792 .child(div().w_full())
7793 .child(
7794 Icon::new(IconName::ArrowUpRight)
7795 .color(Color::Muted)
7796 .size(IconSize::Small),
7797 )
7798 .into_any_element(),
7799 )
7800 .into_any(),
7801 );
7802 }
7803
7804 let is_refreshing = provider.provider.is_refreshing(cx);
7805
7806 fn pending_completion_container() -> Div {
7807 h_flex()
7808 .h_full()
7809 .flex_1()
7810 .gap_2()
7811 .child(Icon::new(IconName::ZedPredict))
7812 }
7813
7814 let completion = match &self.active_inline_completion {
7815 Some(prediction) => {
7816 if !self.has_visible_completions_menu() {
7817 const RADIUS: Pixels = px(6.);
7818 const BORDER_WIDTH: Pixels = px(1.);
7819
7820 return Some(
7821 h_flex()
7822 .elevation_2(cx)
7823 .border(BORDER_WIDTH)
7824 .border_color(cx.theme().colors().border)
7825 .when(accept_keystroke.is_none(), |el| {
7826 el.border_color(cx.theme().status().error)
7827 })
7828 .rounded(RADIUS)
7829 .rounded_tl(px(0.))
7830 .overflow_hidden()
7831 .child(div().px_1p5().child(match &prediction.completion {
7832 InlineCompletion::Move { target, snapshot } => {
7833 use text::ToPoint as _;
7834 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7835 {
7836 Icon::new(IconName::ZedPredictDown)
7837 } else {
7838 Icon::new(IconName::ZedPredictUp)
7839 }
7840 }
7841 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7842 }))
7843 .child(
7844 h_flex()
7845 .gap_1()
7846 .py_1()
7847 .px_2()
7848 .rounded_r(RADIUS - BORDER_WIDTH)
7849 .border_l_1()
7850 .border_color(cx.theme().colors().border)
7851 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7852 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7853 el.child(
7854 Label::new("Hold")
7855 .size(LabelSize::Small)
7856 .when(accept_keystroke.is_none(), |el| {
7857 el.strikethrough()
7858 })
7859 .line_height_style(LineHeightStyle::UiLabel),
7860 )
7861 })
7862 .id("edit_prediction_cursor_popover_keybind")
7863 .when(accept_keystroke.is_none(), |el| {
7864 let status_colors = cx.theme().status();
7865
7866 el.bg(status_colors.error_background)
7867 .border_color(status_colors.error.opacity(0.6))
7868 .child(Icon::new(IconName::Info).color(Color::Error))
7869 .cursor_default()
7870 .hoverable_tooltip(move |_window, cx| {
7871 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7872 .into()
7873 })
7874 })
7875 .when_some(
7876 accept_keystroke.as_ref(),
7877 |el, accept_keystroke| {
7878 el.child(h_flex().children(ui::render_modifiers(
7879 &accept_keystroke.modifiers,
7880 PlatformStyle::platform(),
7881 Some(Color::Default),
7882 Some(IconSize::XSmall.rems().into()),
7883 false,
7884 )))
7885 },
7886 ),
7887 )
7888 .into_any(),
7889 );
7890 }
7891
7892 self.render_edit_prediction_cursor_popover_preview(
7893 prediction,
7894 cursor_point,
7895 style,
7896 cx,
7897 )?
7898 }
7899
7900 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7901 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7902 stale_completion,
7903 cursor_point,
7904 style,
7905 cx,
7906 )?,
7907
7908 None => {
7909 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7910 }
7911 },
7912
7913 None => pending_completion_container().child(Label::new("No Prediction")),
7914 };
7915
7916 let completion = if is_refreshing {
7917 completion
7918 .with_animation(
7919 "loading-completion",
7920 Animation::new(Duration::from_secs(2))
7921 .repeat()
7922 .with_easing(pulsating_between(0.4, 0.8)),
7923 |label, delta| label.opacity(delta),
7924 )
7925 .into_any_element()
7926 } else {
7927 completion.into_any_element()
7928 };
7929
7930 let has_completion = self.active_inline_completion.is_some();
7931
7932 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7933 Some(
7934 h_flex()
7935 .min_w(min_width)
7936 .max_w(max_width)
7937 .flex_1()
7938 .elevation_2(cx)
7939 .border_color(cx.theme().colors().border)
7940 .child(
7941 div()
7942 .flex_1()
7943 .py_1()
7944 .px_2()
7945 .overflow_hidden()
7946 .child(completion),
7947 )
7948 .when_some(accept_keystroke, |el, accept_keystroke| {
7949 if !accept_keystroke.modifiers.modified() {
7950 return el;
7951 }
7952
7953 el.child(
7954 h_flex()
7955 .h_full()
7956 .border_l_1()
7957 .rounded_r_lg()
7958 .border_color(cx.theme().colors().border)
7959 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7960 .gap_1()
7961 .py_1()
7962 .px_2()
7963 .child(
7964 h_flex()
7965 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7966 .when(is_platform_style_mac, |parent| parent.gap_1())
7967 .child(h_flex().children(ui::render_modifiers(
7968 &accept_keystroke.modifiers,
7969 PlatformStyle::platform(),
7970 Some(if !has_completion {
7971 Color::Muted
7972 } else {
7973 Color::Default
7974 }),
7975 None,
7976 false,
7977 ))),
7978 )
7979 .child(Label::new("Preview").into_any_element())
7980 .opacity(if has_completion { 1.0 } else { 0.4 }),
7981 )
7982 })
7983 .into_any(),
7984 )
7985 }
7986
7987 fn render_edit_prediction_cursor_popover_preview(
7988 &self,
7989 completion: &InlineCompletionState,
7990 cursor_point: Point,
7991 style: &EditorStyle,
7992 cx: &mut Context<Editor>,
7993 ) -> Option<Div> {
7994 use text::ToPoint as _;
7995
7996 fn render_relative_row_jump(
7997 prefix: impl Into<String>,
7998 current_row: u32,
7999 target_row: u32,
8000 ) -> Div {
8001 let (row_diff, arrow) = if target_row < current_row {
8002 (current_row - target_row, IconName::ArrowUp)
8003 } else {
8004 (target_row - current_row, IconName::ArrowDown)
8005 };
8006
8007 h_flex()
8008 .child(
8009 Label::new(format!("{}{}", prefix.into(), row_diff))
8010 .color(Color::Muted)
8011 .size(LabelSize::Small),
8012 )
8013 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8014 }
8015
8016 match &completion.completion {
8017 InlineCompletion::Move {
8018 target, snapshot, ..
8019 } => Some(
8020 h_flex()
8021 .px_2()
8022 .gap_2()
8023 .flex_1()
8024 .child(
8025 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8026 Icon::new(IconName::ZedPredictDown)
8027 } else {
8028 Icon::new(IconName::ZedPredictUp)
8029 },
8030 )
8031 .child(Label::new("Jump to Edit")),
8032 ),
8033
8034 InlineCompletion::Edit {
8035 edits,
8036 edit_preview,
8037 snapshot,
8038 display_mode: _,
8039 } => {
8040 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8041
8042 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8043 &snapshot,
8044 &edits,
8045 edit_preview.as_ref()?,
8046 true,
8047 cx,
8048 )
8049 .first_line_preview();
8050
8051 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8052 .with_default_highlights(&style.text, highlighted_edits.highlights);
8053
8054 let preview = h_flex()
8055 .gap_1()
8056 .min_w_16()
8057 .child(styled_text)
8058 .when(has_more_lines, |parent| parent.child("…"));
8059
8060 let left = if first_edit_row != cursor_point.row {
8061 render_relative_row_jump("", cursor_point.row, first_edit_row)
8062 .into_any_element()
8063 } else {
8064 Icon::new(IconName::ZedPredict).into_any_element()
8065 };
8066
8067 Some(
8068 h_flex()
8069 .h_full()
8070 .flex_1()
8071 .gap_2()
8072 .pr_1()
8073 .overflow_x_hidden()
8074 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8075 .child(left)
8076 .child(preview),
8077 )
8078 }
8079 }
8080 }
8081
8082 fn render_context_menu(
8083 &self,
8084 style: &EditorStyle,
8085 max_height_in_lines: u32,
8086 window: &mut Window,
8087 cx: &mut Context<Editor>,
8088 ) -> Option<AnyElement> {
8089 let menu = self.context_menu.borrow();
8090 let menu = menu.as_ref()?;
8091 if !menu.visible() {
8092 return None;
8093 };
8094 Some(menu.render(style, max_height_in_lines, window, cx))
8095 }
8096
8097 fn render_context_menu_aside(
8098 &mut self,
8099 max_size: Size<Pixels>,
8100 window: &mut Window,
8101 cx: &mut Context<Editor>,
8102 ) -> Option<AnyElement> {
8103 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8104 if menu.visible() {
8105 menu.render_aside(self, max_size, window, cx)
8106 } else {
8107 None
8108 }
8109 })
8110 }
8111
8112 fn hide_context_menu(
8113 &mut self,
8114 window: &mut Window,
8115 cx: &mut Context<Self>,
8116 ) -> Option<CodeContextMenu> {
8117 cx.notify();
8118 self.completion_tasks.clear();
8119 let context_menu = self.context_menu.borrow_mut().take();
8120 self.stale_inline_completion_in_menu.take();
8121 self.update_visible_inline_completion(window, cx);
8122 context_menu
8123 }
8124
8125 fn show_snippet_choices(
8126 &mut self,
8127 choices: &Vec<String>,
8128 selection: Range<Anchor>,
8129 cx: &mut Context<Self>,
8130 ) {
8131 if selection.start.buffer_id.is_none() {
8132 return;
8133 }
8134 let buffer_id = selection.start.buffer_id.unwrap();
8135 let buffer = self.buffer().read(cx).buffer(buffer_id);
8136 let id = post_inc(&mut self.next_completion_id);
8137
8138 if let Some(buffer) = buffer {
8139 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8140 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
8141 ));
8142 }
8143 }
8144
8145 pub fn insert_snippet(
8146 &mut self,
8147 insertion_ranges: &[Range<usize>],
8148 snippet: Snippet,
8149 window: &mut Window,
8150 cx: &mut Context<Self>,
8151 ) -> Result<()> {
8152 struct Tabstop<T> {
8153 is_end_tabstop: bool,
8154 ranges: Vec<Range<T>>,
8155 choices: Option<Vec<String>>,
8156 }
8157
8158 let tabstops = self.buffer.update(cx, |buffer, cx| {
8159 let snippet_text: Arc<str> = snippet.text.clone().into();
8160 let edits = insertion_ranges
8161 .iter()
8162 .cloned()
8163 .map(|range| (range, snippet_text.clone()));
8164 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8165
8166 let snapshot = &*buffer.read(cx);
8167 let snippet = &snippet;
8168 snippet
8169 .tabstops
8170 .iter()
8171 .map(|tabstop| {
8172 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8173 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8174 });
8175 let mut tabstop_ranges = tabstop
8176 .ranges
8177 .iter()
8178 .flat_map(|tabstop_range| {
8179 let mut delta = 0_isize;
8180 insertion_ranges.iter().map(move |insertion_range| {
8181 let insertion_start = insertion_range.start as isize + delta;
8182 delta +=
8183 snippet.text.len() as isize - insertion_range.len() as isize;
8184
8185 let start = ((insertion_start + tabstop_range.start) as usize)
8186 .min(snapshot.len());
8187 let end = ((insertion_start + tabstop_range.end) as usize)
8188 .min(snapshot.len());
8189 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8190 })
8191 })
8192 .collect::<Vec<_>>();
8193 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8194
8195 Tabstop {
8196 is_end_tabstop,
8197 ranges: tabstop_ranges,
8198 choices: tabstop.choices.clone(),
8199 }
8200 })
8201 .collect::<Vec<_>>()
8202 });
8203 if let Some(tabstop) = tabstops.first() {
8204 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8205 s.select_ranges(tabstop.ranges.iter().cloned());
8206 });
8207
8208 if let Some(choices) = &tabstop.choices {
8209 if let Some(selection) = tabstop.ranges.first() {
8210 self.show_snippet_choices(choices, selection.clone(), cx)
8211 }
8212 }
8213
8214 // If we're already at the last tabstop and it's at the end of the snippet,
8215 // we're done, we don't need to keep the state around.
8216 if !tabstop.is_end_tabstop {
8217 let choices = tabstops
8218 .iter()
8219 .map(|tabstop| tabstop.choices.clone())
8220 .collect();
8221
8222 let ranges = tabstops
8223 .into_iter()
8224 .map(|tabstop| tabstop.ranges)
8225 .collect::<Vec<_>>();
8226
8227 self.snippet_stack.push(SnippetState {
8228 active_index: 0,
8229 ranges,
8230 choices,
8231 });
8232 }
8233
8234 // Check whether the just-entered snippet ends with an auto-closable bracket.
8235 if self.autoclose_regions.is_empty() {
8236 let snapshot = self.buffer.read(cx).snapshot(cx);
8237 for selection in &mut self.selections.all::<Point>(cx) {
8238 let selection_head = selection.head();
8239 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8240 continue;
8241 };
8242
8243 let mut bracket_pair = None;
8244 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8245 let prev_chars = snapshot
8246 .reversed_chars_at(selection_head)
8247 .collect::<String>();
8248 for (pair, enabled) in scope.brackets() {
8249 if enabled
8250 && pair.close
8251 && prev_chars.starts_with(pair.start.as_str())
8252 && next_chars.starts_with(pair.end.as_str())
8253 {
8254 bracket_pair = Some(pair.clone());
8255 break;
8256 }
8257 }
8258 if let Some(pair) = bracket_pair {
8259 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8260 let autoclose_enabled =
8261 self.use_autoclose && snapshot_settings.use_autoclose;
8262 if autoclose_enabled {
8263 let start = snapshot.anchor_after(selection_head);
8264 let end = snapshot.anchor_after(selection_head);
8265 self.autoclose_regions.push(AutocloseRegion {
8266 selection_id: selection.id,
8267 range: start..end,
8268 pair,
8269 });
8270 }
8271 }
8272 }
8273 }
8274 }
8275 Ok(())
8276 }
8277
8278 pub fn move_to_next_snippet_tabstop(
8279 &mut self,
8280 window: &mut Window,
8281 cx: &mut Context<Self>,
8282 ) -> bool {
8283 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8284 }
8285
8286 pub fn move_to_prev_snippet_tabstop(
8287 &mut self,
8288 window: &mut Window,
8289 cx: &mut Context<Self>,
8290 ) -> bool {
8291 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8292 }
8293
8294 pub fn move_to_snippet_tabstop(
8295 &mut self,
8296 bias: Bias,
8297 window: &mut Window,
8298 cx: &mut Context<Self>,
8299 ) -> bool {
8300 if let Some(mut snippet) = self.snippet_stack.pop() {
8301 match bias {
8302 Bias::Left => {
8303 if snippet.active_index > 0 {
8304 snippet.active_index -= 1;
8305 } else {
8306 self.snippet_stack.push(snippet);
8307 return false;
8308 }
8309 }
8310 Bias::Right => {
8311 if snippet.active_index + 1 < snippet.ranges.len() {
8312 snippet.active_index += 1;
8313 } else {
8314 self.snippet_stack.push(snippet);
8315 return false;
8316 }
8317 }
8318 }
8319 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8320 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8321 s.select_anchor_ranges(current_ranges.iter().cloned())
8322 });
8323
8324 if let Some(choices) = &snippet.choices[snippet.active_index] {
8325 if let Some(selection) = current_ranges.first() {
8326 self.show_snippet_choices(&choices, selection.clone(), cx);
8327 }
8328 }
8329
8330 // If snippet state is not at the last tabstop, push it back on the stack
8331 if snippet.active_index + 1 < snippet.ranges.len() {
8332 self.snippet_stack.push(snippet);
8333 }
8334 return true;
8335 }
8336 }
8337
8338 false
8339 }
8340
8341 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8342 self.transact(window, cx, |this, window, cx| {
8343 this.select_all(&SelectAll, window, cx);
8344 this.insert("", window, cx);
8345 });
8346 }
8347
8348 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8349 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8350 self.transact(window, cx, |this, window, cx| {
8351 this.select_autoclose_pair(window, cx);
8352 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8353 if !this.linked_edit_ranges.is_empty() {
8354 let selections = this.selections.all::<MultiBufferPoint>(cx);
8355 let snapshot = this.buffer.read(cx).snapshot(cx);
8356
8357 for selection in selections.iter() {
8358 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8359 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8360 if selection_start.buffer_id != selection_end.buffer_id {
8361 continue;
8362 }
8363 if let Some(ranges) =
8364 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8365 {
8366 for (buffer, entries) in ranges {
8367 linked_ranges.entry(buffer).or_default().extend(entries);
8368 }
8369 }
8370 }
8371 }
8372
8373 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8374 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8375 for selection in &mut selections {
8376 if selection.is_empty() {
8377 let old_head = selection.head();
8378 let mut new_head =
8379 movement::left(&display_map, old_head.to_display_point(&display_map))
8380 .to_point(&display_map);
8381 if let Some((buffer, line_buffer_range)) = display_map
8382 .buffer_snapshot
8383 .buffer_line_for_row(MultiBufferRow(old_head.row))
8384 {
8385 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8386 let indent_len = match indent_size.kind {
8387 IndentKind::Space => {
8388 buffer.settings_at(line_buffer_range.start, cx).tab_size
8389 }
8390 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8391 };
8392 if old_head.column <= indent_size.len && old_head.column > 0 {
8393 let indent_len = indent_len.get();
8394 new_head = cmp::min(
8395 new_head,
8396 MultiBufferPoint::new(
8397 old_head.row,
8398 ((old_head.column - 1) / indent_len) * indent_len,
8399 ),
8400 );
8401 }
8402 }
8403
8404 selection.set_head(new_head, SelectionGoal::None);
8405 }
8406 }
8407
8408 this.signature_help_state.set_backspace_pressed(true);
8409 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8410 s.select(selections)
8411 });
8412 this.insert("", window, cx);
8413 let empty_str: Arc<str> = Arc::from("");
8414 for (buffer, edits) in linked_ranges {
8415 let snapshot = buffer.read(cx).snapshot();
8416 use text::ToPoint as TP;
8417
8418 let edits = edits
8419 .into_iter()
8420 .map(|range| {
8421 let end_point = TP::to_point(&range.end, &snapshot);
8422 let mut start_point = TP::to_point(&range.start, &snapshot);
8423
8424 if end_point == start_point {
8425 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8426 .saturating_sub(1);
8427 start_point =
8428 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8429 };
8430
8431 (start_point..end_point, empty_str.clone())
8432 })
8433 .sorted_by_key(|(range, _)| range.start)
8434 .collect::<Vec<_>>();
8435 buffer.update(cx, |this, cx| {
8436 this.edit(edits, None, cx);
8437 })
8438 }
8439 this.refresh_inline_completion(true, false, window, cx);
8440 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8441 });
8442 }
8443
8444 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8445 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8446 self.transact(window, cx, |this, window, cx| {
8447 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8448 s.move_with(|map, selection| {
8449 if selection.is_empty() {
8450 let cursor = movement::right(map, selection.head());
8451 selection.end = cursor;
8452 selection.reversed = true;
8453 selection.goal = SelectionGoal::None;
8454 }
8455 })
8456 });
8457 this.insert("", window, cx);
8458 this.refresh_inline_completion(true, false, window, cx);
8459 });
8460 }
8461
8462 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8463 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8464 if self.move_to_prev_snippet_tabstop(window, cx) {
8465 return;
8466 }
8467 self.outdent(&Outdent, window, cx);
8468 }
8469
8470 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8471 if self.move_to_next_snippet_tabstop(window, cx) {
8472 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8473 return;
8474 }
8475 if self.read_only(cx) {
8476 return;
8477 }
8478 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8479 let mut selections = self.selections.all_adjusted(cx);
8480 let buffer = self.buffer.read(cx);
8481 let snapshot = buffer.snapshot(cx);
8482 let rows_iter = selections.iter().map(|s| s.head().row);
8483 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8484
8485 let mut edits = Vec::new();
8486 let mut prev_edited_row = 0;
8487 let mut row_delta = 0;
8488 for selection in &mut selections {
8489 if selection.start.row != prev_edited_row {
8490 row_delta = 0;
8491 }
8492 prev_edited_row = selection.end.row;
8493
8494 // If the selection is non-empty, then increase the indentation of the selected lines.
8495 if !selection.is_empty() {
8496 row_delta =
8497 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8498 continue;
8499 }
8500
8501 // If the selection is empty and the cursor is in the leading whitespace before the
8502 // suggested indentation, then auto-indent the line.
8503 let cursor = selection.head();
8504 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8505 if let Some(suggested_indent) =
8506 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8507 {
8508 if cursor.column < suggested_indent.len
8509 && cursor.column <= current_indent.len
8510 && current_indent.len <= suggested_indent.len
8511 {
8512 selection.start = Point::new(cursor.row, suggested_indent.len);
8513 selection.end = selection.start;
8514 if row_delta == 0 {
8515 edits.extend(Buffer::edit_for_indent_size_adjustment(
8516 cursor.row,
8517 current_indent,
8518 suggested_indent,
8519 ));
8520 row_delta = suggested_indent.len - current_indent.len;
8521 }
8522 continue;
8523 }
8524 }
8525
8526 // Otherwise, insert a hard or soft tab.
8527 let settings = buffer.language_settings_at(cursor, cx);
8528 let tab_size = if settings.hard_tabs {
8529 IndentSize::tab()
8530 } else {
8531 let tab_size = settings.tab_size.get();
8532 let indent_remainder = snapshot
8533 .text_for_range(Point::new(cursor.row, 0)..cursor)
8534 .flat_map(str::chars)
8535 .fold(row_delta % tab_size, |counter: u32, c| {
8536 if c == '\t' {
8537 0
8538 } else {
8539 (counter + 1) % tab_size
8540 }
8541 });
8542
8543 let chars_to_next_tab_stop = tab_size - indent_remainder;
8544 IndentSize::spaces(chars_to_next_tab_stop)
8545 };
8546 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8547 selection.end = selection.start;
8548 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8549 row_delta += tab_size.len;
8550 }
8551
8552 self.transact(window, cx, |this, window, cx| {
8553 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8554 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8555 s.select(selections)
8556 });
8557 this.refresh_inline_completion(true, false, window, cx);
8558 });
8559 }
8560
8561 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8562 if self.read_only(cx) {
8563 return;
8564 }
8565 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8566 let mut selections = self.selections.all::<Point>(cx);
8567 let mut prev_edited_row = 0;
8568 let mut row_delta = 0;
8569 let mut edits = Vec::new();
8570 let buffer = self.buffer.read(cx);
8571 let snapshot = buffer.snapshot(cx);
8572 for selection in &mut selections {
8573 if selection.start.row != prev_edited_row {
8574 row_delta = 0;
8575 }
8576 prev_edited_row = selection.end.row;
8577
8578 row_delta =
8579 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8580 }
8581
8582 self.transact(window, cx, |this, window, cx| {
8583 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8584 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8585 s.select(selections)
8586 });
8587 });
8588 }
8589
8590 fn indent_selection(
8591 buffer: &MultiBuffer,
8592 snapshot: &MultiBufferSnapshot,
8593 selection: &mut Selection<Point>,
8594 edits: &mut Vec<(Range<Point>, String)>,
8595 delta_for_start_row: u32,
8596 cx: &App,
8597 ) -> u32 {
8598 let settings = buffer.language_settings_at(selection.start, cx);
8599 let tab_size = settings.tab_size.get();
8600 let indent_kind = if settings.hard_tabs {
8601 IndentKind::Tab
8602 } else {
8603 IndentKind::Space
8604 };
8605 let mut start_row = selection.start.row;
8606 let mut end_row = selection.end.row + 1;
8607
8608 // If a selection ends at the beginning of a line, don't indent
8609 // that last line.
8610 if selection.end.column == 0 && selection.end.row > selection.start.row {
8611 end_row -= 1;
8612 }
8613
8614 // Avoid re-indenting a row that has already been indented by a
8615 // previous selection, but still update this selection's column
8616 // to reflect that indentation.
8617 if delta_for_start_row > 0 {
8618 start_row += 1;
8619 selection.start.column += delta_for_start_row;
8620 if selection.end.row == selection.start.row {
8621 selection.end.column += delta_for_start_row;
8622 }
8623 }
8624
8625 let mut delta_for_end_row = 0;
8626 let has_multiple_rows = start_row + 1 != end_row;
8627 for row in start_row..end_row {
8628 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8629 let indent_delta = match (current_indent.kind, indent_kind) {
8630 (IndentKind::Space, IndentKind::Space) => {
8631 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8632 IndentSize::spaces(columns_to_next_tab_stop)
8633 }
8634 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8635 (_, IndentKind::Tab) => IndentSize::tab(),
8636 };
8637
8638 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8639 0
8640 } else {
8641 selection.start.column
8642 };
8643 let row_start = Point::new(row, start);
8644 edits.push((
8645 row_start..row_start,
8646 indent_delta.chars().collect::<String>(),
8647 ));
8648
8649 // Update this selection's endpoints to reflect the indentation.
8650 if row == selection.start.row {
8651 selection.start.column += indent_delta.len;
8652 }
8653 if row == selection.end.row {
8654 selection.end.column += indent_delta.len;
8655 delta_for_end_row = indent_delta.len;
8656 }
8657 }
8658
8659 if selection.start.row == selection.end.row {
8660 delta_for_start_row + delta_for_end_row
8661 } else {
8662 delta_for_end_row
8663 }
8664 }
8665
8666 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8667 if self.read_only(cx) {
8668 return;
8669 }
8670 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8671 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8672 let selections = self.selections.all::<Point>(cx);
8673 let mut deletion_ranges = Vec::new();
8674 let mut last_outdent = None;
8675 {
8676 let buffer = self.buffer.read(cx);
8677 let snapshot = buffer.snapshot(cx);
8678 for selection in &selections {
8679 let settings = buffer.language_settings_at(selection.start, cx);
8680 let tab_size = settings.tab_size.get();
8681 let mut rows = selection.spanned_rows(false, &display_map);
8682
8683 // Avoid re-outdenting a row that has already been outdented by a
8684 // previous selection.
8685 if let Some(last_row) = last_outdent {
8686 if last_row == rows.start {
8687 rows.start = rows.start.next_row();
8688 }
8689 }
8690 let has_multiple_rows = rows.len() > 1;
8691 for row in rows.iter_rows() {
8692 let indent_size = snapshot.indent_size_for_line(row);
8693 if indent_size.len > 0 {
8694 let deletion_len = match indent_size.kind {
8695 IndentKind::Space => {
8696 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8697 if columns_to_prev_tab_stop == 0 {
8698 tab_size
8699 } else {
8700 columns_to_prev_tab_stop
8701 }
8702 }
8703 IndentKind::Tab => 1,
8704 };
8705 let start = if has_multiple_rows
8706 || deletion_len > selection.start.column
8707 || indent_size.len < selection.start.column
8708 {
8709 0
8710 } else {
8711 selection.start.column - deletion_len
8712 };
8713 deletion_ranges.push(
8714 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8715 );
8716 last_outdent = Some(row);
8717 }
8718 }
8719 }
8720 }
8721
8722 self.transact(window, cx, |this, window, cx| {
8723 this.buffer.update(cx, |buffer, cx| {
8724 let empty_str: Arc<str> = Arc::default();
8725 buffer.edit(
8726 deletion_ranges
8727 .into_iter()
8728 .map(|range| (range, empty_str.clone())),
8729 None,
8730 cx,
8731 );
8732 });
8733 let selections = this.selections.all::<usize>(cx);
8734 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8735 s.select(selections)
8736 });
8737 });
8738 }
8739
8740 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8741 if self.read_only(cx) {
8742 return;
8743 }
8744 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8745 let selections = self
8746 .selections
8747 .all::<usize>(cx)
8748 .into_iter()
8749 .map(|s| s.range());
8750
8751 self.transact(window, cx, |this, window, cx| {
8752 this.buffer.update(cx, |buffer, cx| {
8753 buffer.autoindent_ranges(selections, cx);
8754 });
8755 let selections = this.selections.all::<usize>(cx);
8756 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8757 s.select(selections)
8758 });
8759 });
8760 }
8761
8762 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8763 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8764 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8765 let selections = self.selections.all::<Point>(cx);
8766
8767 let mut new_cursors = Vec::new();
8768 let mut edit_ranges = Vec::new();
8769 let mut selections = selections.iter().peekable();
8770 while let Some(selection) = selections.next() {
8771 let mut rows = selection.spanned_rows(false, &display_map);
8772 let goal_display_column = selection.head().to_display_point(&display_map).column();
8773
8774 // Accumulate contiguous regions of rows that we want to delete.
8775 while let Some(next_selection) = selections.peek() {
8776 let next_rows = next_selection.spanned_rows(false, &display_map);
8777 if next_rows.start <= rows.end {
8778 rows.end = next_rows.end;
8779 selections.next().unwrap();
8780 } else {
8781 break;
8782 }
8783 }
8784
8785 let buffer = &display_map.buffer_snapshot;
8786 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8787 let edit_end;
8788 let cursor_buffer_row;
8789 if buffer.max_point().row >= rows.end.0 {
8790 // If there's a line after the range, delete the \n from the end of the row range
8791 // and position the cursor on the next line.
8792 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8793 cursor_buffer_row = rows.end;
8794 } else {
8795 // If there isn't a line after the range, delete the \n from the line before the
8796 // start of the row range and position the cursor there.
8797 edit_start = edit_start.saturating_sub(1);
8798 edit_end = buffer.len();
8799 cursor_buffer_row = rows.start.previous_row();
8800 }
8801
8802 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8803 *cursor.column_mut() =
8804 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8805
8806 new_cursors.push((
8807 selection.id,
8808 buffer.anchor_after(cursor.to_point(&display_map)),
8809 ));
8810 edit_ranges.push(edit_start..edit_end);
8811 }
8812
8813 self.transact(window, cx, |this, window, cx| {
8814 let buffer = this.buffer.update(cx, |buffer, cx| {
8815 let empty_str: Arc<str> = Arc::default();
8816 buffer.edit(
8817 edit_ranges
8818 .into_iter()
8819 .map(|range| (range, empty_str.clone())),
8820 None,
8821 cx,
8822 );
8823 buffer.snapshot(cx)
8824 });
8825 let new_selections = new_cursors
8826 .into_iter()
8827 .map(|(id, cursor)| {
8828 let cursor = cursor.to_point(&buffer);
8829 Selection {
8830 id,
8831 start: cursor,
8832 end: cursor,
8833 reversed: false,
8834 goal: SelectionGoal::None,
8835 }
8836 })
8837 .collect();
8838
8839 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8840 s.select(new_selections);
8841 });
8842 });
8843 }
8844
8845 pub fn join_lines_impl(
8846 &mut self,
8847 insert_whitespace: bool,
8848 window: &mut Window,
8849 cx: &mut Context<Self>,
8850 ) {
8851 if self.read_only(cx) {
8852 return;
8853 }
8854 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8855 for selection in self.selections.all::<Point>(cx) {
8856 let start = MultiBufferRow(selection.start.row);
8857 // Treat single line selections as if they include the next line. Otherwise this action
8858 // would do nothing for single line selections individual cursors.
8859 let end = if selection.start.row == selection.end.row {
8860 MultiBufferRow(selection.start.row + 1)
8861 } else {
8862 MultiBufferRow(selection.end.row)
8863 };
8864
8865 if let Some(last_row_range) = row_ranges.last_mut() {
8866 if start <= last_row_range.end {
8867 last_row_range.end = end;
8868 continue;
8869 }
8870 }
8871 row_ranges.push(start..end);
8872 }
8873
8874 let snapshot = self.buffer.read(cx).snapshot(cx);
8875 let mut cursor_positions = Vec::new();
8876 for row_range in &row_ranges {
8877 let anchor = snapshot.anchor_before(Point::new(
8878 row_range.end.previous_row().0,
8879 snapshot.line_len(row_range.end.previous_row()),
8880 ));
8881 cursor_positions.push(anchor..anchor);
8882 }
8883
8884 self.transact(window, cx, |this, window, cx| {
8885 for row_range in row_ranges.into_iter().rev() {
8886 for row in row_range.iter_rows().rev() {
8887 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8888 let next_line_row = row.next_row();
8889 let indent = snapshot.indent_size_for_line(next_line_row);
8890 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8891
8892 let replace =
8893 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8894 " "
8895 } else {
8896 ""
8897 };
8898
8899 this.buffer.update(cx, |buffer, cx| {
8900 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8901 });
8902 }
8903 }
8904
8905 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8906 s.select_anchor_ranges(cursor_positions)
8907 });
8908 });
8909 }
8910
8911 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8912 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8913 self.join_lines_impl(true, window, cx);
8914 }
8915
8916 pub fn sort_lines_case_sensitive(
8917 &mut self,
8918 _: &SortLinesCaseSensitive,
8919 window: &mut Window,
8920 cx: &mut Context<Self>,
8921 ) {
8922 self.manipulate_lines(window, cx, |lines| lines.sort())
8923 }
8924
8925 pub fn sort_lines_case_insensitive(
8926 &mut self,
8927 _: &SortLinesCaseInsensitive,
8928 window: &mut Window,
8929 cx: &mut Context<Self>,
8930 ) {
8931 self.manipulate_lines(window, cx, |lines| {
8932 lines.sort_by_key(|line| line.to_lowercase())
8933 })
8934 }
8935
8936 pub fn unique_lines_case_insensitive(
8937 &mut self,
8938 _: &UniqueLinesCaseInsensitive,
8939 window: &mut Window,
8940 cx: &mut Context<Self>,
8941 ) {
8942 self.manipulate_lines(window, cx, |lines| {
8943 let mut seen = HashSet::default();
8944 lines.retain(|line| seen.insert(line.to_lowercase()));
8945 })
8946 }
8947
8948 pub fn unique_lines_case_sensitive(
8949 &mut self,
8950 _: &UniqueLinesCaseSensitive,
8951 window: &mut Window,
8952 cx: &mut Context<Self>,
8953 ) {
8954 self.manipulate_lines(window, cx, |lines| {
8955 let mut seen = HashSet::default();
8956 lines.retain(|line| seen.insert(*line));
8957 })
8958 }
8959
8960 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8961 let Some(project) = self.project.clone() else {
8962 return;
8963 };
8964 self.reload(project, window, cx)
8965 .detach_and_notify_err(window, cx);
8966 }
8967
8968 pub fn restore_file(
8969 &mut self,
8970 _: &::git::RestoreFile,
8971 window: &mut Window,
8972 cx: &mut Context<Self>,
8973 ) {
8974 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8975 let mut buffer_ids = HashSet::default();
8976 let snapshot = self.buffer().read(cx).snapshot(cx);
8977 for selection in self.selections.all::<usize>(cx) {
8978 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8979 }
8980
8981 let buffer = self.buffer().read(cx);
8982 let ranges = buffer_ids
8983 .into_iter()
8984 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8985 .collect::<Vec<_>>();
8986
8987 self.restore_hunks_in_ranges(ranges, window, cx);
8988 }
8989
8990 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8991 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8992 let selections = self
8993 .selections
8994 .all(cx)
8995 .into_iter()
8996 .map(|s| s.range())
8997 .collect();
8998 self.restore_hunks_in_ranges(selections, window, cx);
8999 }
9000
9001 pub fn restore_hunks_in_ranges(
9002 &mut self,
9003 ranges: Vec<Range<Point>>,
9004 window: &mut Window,
9005 cx: &mut Context<Editor>,
9006 ) {
9007 let mut revert_changes = HashMap::default();
9008 let chunk_by = self
9009 .snapshot(window, cx)
9010 .hunks_for_ranges(ranges)
9011 .into_iter()
9012 .chunk_by(|hunk| hunk.buffer_id);
9013 for (buffer_id, hunks) in &chunk_by {
9014 let hunks = hunks.collect::<Vec<_>>();
9015 for hunk in &hunks {
9016 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9017 }
9018 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9019 }
9020 drop(chunk_by);
9021 if !revert_changes.is_empty() {
9022 self.transact(window, cx, |editor, window, cx| {
9023 editor.restore(revert_changes, window, cx);
9024 });
9025 }
9026 }
9027
9028 pub fn open_active_item_in_terminal(
9029 &mut self,
9030 _: &OpenInTerminal,
9031 window: &mut Window,
9032 cx: &mut Context<Self>,
9033 ) {
9034 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9035 let project_path = buffer.read(cx).project_path(cx)?;
9036 let project = self.project.as_ref()?.read(cx);
9037 let entry = project.entry_for_path(&project_path, cx)?;
9038 let parent = match &entry.canonical_path {
9039 Some(canonical_path) => canonical_path.to_path_buf(),
9040 None => project.absolute_path(&project_path, cx)?,
9041 }
9042 .parent()?
9043 .to_path_buf();
9044 Some(parent)
9045 }) {
9046 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9047 }
9048 }
9049
9050 fn set_breakpoint_context_menu(
9051 &mut self,
9052 display_row: DisplayRow,
9053 position: Option<Anchor>,
9054 clicked_point: gpui::Point<Pixels>,
9055 window: &mut Window,
9056 cx: &mut Context<Self>,
9057 ) {
9058 if !cx.has_flag::<Debugger>() {
9059 return;
9060 }
9061 let source = self
9062 .buffer
9063 .read(cx)
9064 .snapshot(cx)
9065 .anchor_before(Point::new(display_row.0, 0u32));
9066
9067 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9068
9069 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9070 self,
9071 source,
9072 clicked_point,
9073 context_menu,
9074 window,
9075 cx,
9076 );
9077 }
9078
9079 fn add_edit_breakpoint_block(
9080 &mut self,
9081 anchor: Anchor,
9082 breakpoint: &Breakpoint,
9083 edit_action: BreakpointPromptEditAction,
9084 window: &mut Window,
9085 cx: &mut Context<Self>,
9086 ) {
9087 let weak_editor = cx.weak_entity();
9088 let bp_prompt = cx.new(|cx| {
9089 BreakpointPromptEditor::new(
9090 weak_editor,
9091 anchor,
9092 breakpoint.clone(),
9093 edit_action,
9094 window,
9095 cx,
9096 )
9097 });
9098
9099 let height = bp_prompt.update(cx, |this, cx| {
9100 this.prompt
9101 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9102 });
9103 let cloned_prompt = bp_prompt.clone();
9104 let blocks = vec![BlockProperties {
9105 style: BlockStyle::Sticky,
9106 placement: BlockPlacement::Above(anchor),
9107 height: Some(height),
9108 render: Arc::new(move |cx| {
9109 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
9110 cloned_prompt.clone().into_any_element()
9111 }),
9112 priority: 0,
9113 }];
9114
9115 let focus_handle = bp_prompt.focus_handle(cx);
9116 window.focus(&focus_handle);
9117
9118 let block_ids = self.insert_blocks(blocks, None, cx);
9119 bp_prompt.update(cx, |prompt, _| {
9120 prompt.add_block_ids(block_ids);
9121 });
9122 }
9123
9124 pub(crate) fn breakpoint_at_row(
9125 &self,
9126 row: u32,
9127 window: &mut Window,
9128 cx: &mut Context<Self>,
9129 ) -> Option<(Anchor, Breakpoint)> {
9130 let snapshot = self.snapshot(window, cx);
9131 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9132
9133 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9134 }
9135
9136 pub(crate) fn breakpoint_at_anchor(
9137 &self,
9138 breakpoint_position: Anchor,
9139 snapshot: &EditorSnapshot,
9140 cx: &mut Context<Self>,
9141 ) -> Option<(Anchor, Breakpoint)> {
9142 let project = self.project.clone()?;
9143
9144 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9145 snapshot
9146 .buffer_snapshot
9147 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9148 })?;
9149
9150 let enclosing_excerpt = breakpoint_position.excerpt_id;
9151 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9152 let buffer_snapshot = buffer.read(cx).snapshot();
9153
9154 let row = buffer_snapshot
9155 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9156 .row;
9157
9158 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9159 let anchor_end = snapshot
9160 .buffer_snapshot
9161 .anchor_after(Point::new(row, line_len));
9162
9163 let bp = self
9164 .breakpoint_store
9165 .as_ref()?
9166 .read_with(cx, |breakpoint_store, cx| {
9167 breakpoint_store
9168 .breakpoints(
9169 &buffer,
9170 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9171 &buffer_snapshot,
9172 cx,
9173 )
9174 .next()
9175 .and_then(|(anchor, bp)| {
9176 let breakpoint_row = buffer_snapshot
9177 .summary_for_anchor::<text::PointUtf16>(anchor)
9178 .row;
9179
9180 if breakpoint_row == row {
9181 snapshot
9182 .buffer_snapshot
9183 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9184 .map(|anchor| (anchor, bp.clone()))
9185 } else {
9186 None
9187 }
9188 })
9189 });
9190 bp
9191 }
9192
9193 pub fn edit_log_breakpoint(
9194 &mut self,
9195 _: &EditLogBreakpoint,
9196 window: &mut Window,
9197 cx: &mut Context<Self>,
9198 ) {
9199 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9200 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9201 message: None,
9202 state: BreakpointState::Enabled,
9203 condition: None,
9204 hit_condition: None,
9205 });
9206
9207 self.add_edit_breakpoint_block(
9208 anchor,
9209 &breakpoint,
9210 BreakpointPromptEditAction::Log,
9211 window,
9212 cx,
9213 );
9214 }
9215 }
9216
9217 fn breakpoints_at_cursors(
9218 &self,
9219 window: &mut Window,
9220 cx: &mut Context<Self>,
9221 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9222 let snapshot = self.snapshot(window, cx);
9223 let cursors = self
9224 .selections
9225 .disjoint_anchors()
9226 .into_iter()
9227 .map(|selection| {
9228 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9229
9230 let breakpoint_position = self
9231 .breakpoint_at_row(cursor_position.row, window, cx)
9232 .map(|bp| bp.0)
9233 .unwrap_or_else(|| {
9234 snapshot
9235 .display_snapshot
9236 .buffer_snapshot
9237 .anchor_after(Point::new(cursor_position.row, 0))
9238 });
9239
9240 let breakpoint = self
9241 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9242 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9243
9244 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9245 })
9246 // 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.
9247 .collect::<HashMap<Anchor, _>>();
9248
9249 cursors.into_iter().collect()
9250 }
9251
9252 pub fn enable_breakpoint(
9253 &mut self,
9254 _: &crate::actions::EnableBreakpoint,
9255 window: &mut Window,
9256 cx: &mut Context<Self>,
9257 ) {
9258 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9259 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9260 continue;
9261 };
9262 self.edit_breakpoint_at_anchor(
9263 anchor,
9264 breakpoint,
9265 BreakpointEditAction::InvertState,
9266 cx,
9267 );
9268 }
9269 }
9270
9271 pub fn disable_breakpoint(
9272 &mut self,
9273 _: &crate::actions::DisableBreakpoint,
9274 window: &mut Window,
9275 cx: &mut Context<Self>,
9276 ) {
9277 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9278 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9279 continue;
9280 };
9281 self.edit_breakpoint_at_anchor(
9282 anchor,
9283 breakpoint,
9284 BreakpointEditAction::InvertState,
9285 cx,
9286 );
9287 }
9288 }
9289
9290 pub fn toggle_breakpoint(
9291 &mut self,
9292 _: &crate::actions::ToggleBreakpoint,
9293 window: &mut Window,
9294 cx: &mut Context<Self>,
9295 ) {
9296 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9297 if let Some(breakpoint) = breakpoint {
9298 self.edit_breakpoint_at_anchor(
9299 anchor,
9300 breakpoint,
9301 BreakpointEditAction::Toggle,
9302 cx,
9303 );
9304 } else {
9305 self.edit_breakpoint_at_anchor(
9306 anchor,
9307 Breakpoint::new_standard(),
9308 BreakpointEditAction::Toggle,
9309 cx,
9310 );
9311 }
9312 }
9313 }
9314
9315 pub fn edit_breakpoint_at_anchor(
9316 &mut self,
9317 breakpoint_position: Anchor,
9318 breakpoint: Breakpoint,
9319 edit_action: BreakpointEditAction,
9320 cx: &mut Context<Self>,
9321 ) {
9322 let Some(breakpoint_store) = &self.breakpoint_store else {
9323 return;
9324 };
9325
9326 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9327 if breakpoint_position == Anchor::min() {
9328 self.buffer()
9329 .read(cx)
9330 .excerpt_buffer_ids()
9331 .into_iter()
9332 .next()
9333 } else {
9334 None
9335 }
9336 }) else {
9337 return;
9338 };
9339
9340 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9341 return;
9342 };
9343
9344 breakpoint_store.update(cx, |breakpoint_store, cx| {
9345 breakpoint_store.toggle_breakpoint(
9346 buffer,
9347 (breakpoint_position.text_anchor, breakpoint),
9348 edit_action,
9349 cx,
9350 );
9351 });
9352
9353 cx.notify();
9354 }
9355
9356 #[cfg(any(test, feature = "test-support"))]
9357 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9358 self.breakpoint_store.clone()
9359 }
9360
9361 pub fn prepare_restore_change(
9362 &self,
9363 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9364 hunk: &MultiBufferDiffHunk,
9365 cx: &mut App,
9366 ) -> Option<()> {
9367 if hunk.is_created_file() {
9368 return None;
9369 }
9370 let buffer = self.buffer.read(cx);
9371 let diff = buffer.diff_for(hunk.buffer_id)?;
9372 let buffer = buffer.buffer(hunk.buffer_id)?;
9373 let buffer = buffer.read(cx);
9374 let original_text = diff
9375 .read(cx)
9376 .base_text()
9377 .as_rope()
9378 .slice(hunk.diff_base_byte_range.clone());
9379 let buffer_snapshot = buffer.snapshot();
9380 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9381 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9382 probe
9383 .0
9384 .start
9385 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9386 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9387 }) {
9388 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9389 Some(())
9390 } else {
9391 None
9392 }
9393 }
9394
9395 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9396 self.manipulate_lines(window, cx, |lines| lines.reverse())
9397 }
9398
9399 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9400 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9401 }
9402
9403 fn manipulate_lines<Fn>(
9404 &mut self,
9405 window: &mut Window,
9406 cx: &mut Context<Self>,
9407 mut callback: Fn,
9408 ) where
9409 Fn: FnMut(&mut Vec<&str>),
9410 {
9411 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9412
9413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9414 let buffer = self.buffer.read(cx).snapshot(cx);
9415
9416 let mut edits = Vec::new();
9417
9418 let selections = self.selections.all::<Point>(cx);
9419 let mut selections = selections.iter().peekable();
9420 let mut contiguous_row_selections = Vec::new();
9421 let mut new_selections = Vec::new();
9422 let mut added_lines = 0;
9423 let mut removed_lines = 0;
9424
9425 while let Some(selection) = selections.next() {
9426 let (start_row, end_row) = consume_contiguous_rows(
9427 &mut contiguous_row_selections,
9428 selection,
9429 &display_map,
9430 &mut selections,
9431 );
9432
9433 let start_point = Point::new(start_row.0, 0);
9434 let end_point = Point::new(
9435 end_row.previous_row().0,
9436 buffer.line_len(end_row.previous_row()),
9437 );
9438 let text = buffer
9439 .text_for_range(start_point..end_point)
9440 .collect::<String>();
9441
9442 let mut lines = text.split('\n').collect_vec();
9443
9444 let lines_before = lines.len();
9445 callback(&mut lines);
9446 let lines_after = lines.len();
9447
9448 edits.push((start_point..end_point, lines.join("\n")));
9449
9450 // Selections must change based on added and removed line count
9451 let start_row =
9452 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9453 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9454 new_selections.push(Selection {
9455 id: selection.id,
9456 start: start_row,
9457 end: end_row,
9458 goal: SelectionGoal::None,
9459 reversed: selection.reversed,
9460 });
9461
9462 if lines_after > lines_before {
9463 added_lines += lines_after - lines_before;
9464 } else if lines_before > lines_after {
9465 removed_lines += lines_before - lines_after;
9466 }
9467 }
9468
9469 self.transact(window, cx, |this, window, cx| {
9470 let buffer = this.buffer.update(cx, |buffer, cx| {
9471 buffer.edit(edits, None, cx);
9472 buffer.snapshot(cx)
9473 });
9474
9475 // Recalculate offsets on newly edited buffer
9476 let new_selections = new_selections
9477 .iter()
9478 .map(|s| {
9479 let start_point = Point::new(s.start.0, 0);
9480 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9481 Selection {
9482 id: s.id,
9483 start: buffer.point_to_offset(start_point),
9484 end: buffer.point_to_offset(end_point),
9485 goal: s.goal,
9486 reversed: s.reversed,
9487 }
9488 })
9489 .collect();
9490
9491 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9492 s.select(new_selections);
9493 });
9494
9495 this.request_autoscroll(Autoscroll::fit(), cx);
9496 });
9497 }
9498
9499 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9500 self.manipulate_text(window, cx, |text| {
9501 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9502 if has_upper_case_characters {
9503 text.to_lowercase()
9504 } else {
9505 text.to_uppercase()
9506 }
9507 })
9508 }
9509
9510 pub fn convert_to_upper_case(
9511 &mut self,
9512 _: &ConvertToUpperCase,
9513 window: &mut Window,
9514 cx: &mut Context<Self>,
9515 ) {
9516 self.manipulate_text(window, cx, |text| text.to_uppercase())
9517 }
9518
9519 pub fn convert_to_lower_case(
9520 &mut self,
9521 _: &ConvertToLowerCase,
9522 window: &mut Window,
9523 cx: &mut Context<Self>,
9524 ) {
9525 self.manipulate_text(window, cx, |text| text.to_lowercase())
9526 }
9527
9528 pub fn convert_to_title_case(
9529 &mut self,
9530 _: &ConvertToTitleCase,
9531 window: &mut Window,
9532 cx: &mut Context<Self>,
9533 ) {
9534 self.manipulate_text(window, cx, |text| {
9535 text.split('\n')
9536 .map(|line| line.to_case(Case::Title))
9537 .join("\n")
9538 })
9539 }
9540
9541 pub fn convert_to_snake_case(
9542 &mut self,
9543 _: &ConvertToSnakeCase,
9544 window: &mut Window,
9545 cx: &mut Context<Self>,
9546 ) {
9547 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9548 }
9549
9550 pub fn convert_to_kebab_case(
9551 &mut self,
9552 _: &ConvertToKebabCase,
9553 window: &mut Window,
9554 cx: &mut Context<Self>,
9555 ) {
9556 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9557 }
9558
9559 pub fn convert_to_upper_camel_case(
9560 &mut self,
9561 _: &ConvertToUpperCamelCase,
9562 window: &mut Window,
9563 cx: &mut Context<Self>,
9564 ) {
9565 self.manipulate_text(window, cx, |text| {
9566 text.split('\n')
9567 .map(|line| line.to_case(Case::UpperCamel))
9568 .join("\n")
9569 })
9570 }
9571
9572 pub fn convert_to_lower_camel_case(
9573 &mut self,
9574 _: &ConvertToLowerCamelCase,
9575 window: &mut Window,
9576 cx: &mut Context<Self>,
9577 ) {
9578 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9579 }
9580
9581 pub fn convert_to_opposite_case(
9582 &mut self,
9583 _: &ConvertToOppositeCase,
9584 window: &mut Window,
9585 cx: &mut Context<Self>,
9586 ) {
9587 self.manipulate_text(window, cx, |text| {
9588 text.chars()
9589 .fold(String::with_capacity(text.len()), |mut t, c| {
9590 if c.is_uppercase() {
9591 t.extend(c.to_lowercase());
9592 } else {
9593 t.extend(c.to_uppercase());
9594 }
9595 t
9596 })
9597 })
9598 }
9599
9600 pub fn convert_to_rot13(
9601 &mut self,
9602 _: &ConvertToRot13,
9603 window: &mut Window,
9604 cx: &mut Context<Self>,
9605 ) {
9606 self.manipulate_text(window, cx, |text| {
9607 text.chars()
9608 .map(|c| match c {
9609 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9610 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9611 _ => c,
9612 })
9613 .collect()
9614 })
9615 }
9616
9617 pub fn convert_to_rot47(
9618 &mut self,
9619 _: &ConvertToRot47,
9620 window: &mut Window,
9621 cx: &mut Context<Self>,
9622 ) {
9623 self.manipulate_text(window, cx, |text| {
9624 text.chars()
9625 .map(|c| {
9626 let code_point = c as u32;
9627 if code_point >= 33 && code_point <= 126 {
9628 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9629 }
9630 c
9631 })
9632 .collect()
9633 })
9634 }
9635
9636 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9637 where
9638 Fn: FnMut(&str) -> String,
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 new_selections = Vec::new();
9644 let mut edits = Vec::new();
9645 let mut selection_adjustment = 0i32;
9646
9647 for selection in self.selections.all::<usize>(cx) {
9648 let selection_is_empty = selection.is_empty();
9649
9650 let (start, end) = if selection_is_empty {
9651 let word_range = movement::surrounding_word(
9652 &display_map,
9653 selection.start.to_display_point(&display_map),
9654 );
9655 let start = word_range.start.to_offset(&display_map, Bias::Left);
9656 let end = word_range.end.to_offset(&display_map, Bias::Left);
9657 (start, end)
9658 } else {
9659 (selection.start, selection.end)
9660 };
9661
9662 let text = buffer.text_for_range(start..end).collect::<String>();
9663 let old_length = text.len() as i32;
9664 let text = callback(&text);
9665
9666 new_selections.push(Selection {
9667 start: (start as i32 - selection_adjustment) as usize,
9668 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9669 goal: SelectionGoal::None,
9670 ..selection
9671 });
9672
9673 selection_adjustment += old_length - text.len() as i32;
9674
9675 edits.push((start..end, text));
9676 }
9677
9678 self.transact(window, cx, |this, window, cx| {
9679 this.buffer.update(cx, |buffer, cx| {
9680 buffer.edit(edits, None, cx);
9681 });
9682
9683 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9684 s.select(new_selections);
9685 });
9686
9687 this.request_autoscroll(Autoscroll::fit(), cx);
9688 });
9689 }
9690
9691 pub fn duplicate(
9692 &mut self,
9693 upwards: bool,
9694 whole_lines: bool,
9695 window: &mut Window,
9696 cx: &mut Context<Self>,
9697 ) {
9698 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9699
9700 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9701 let buffer = &display_map.buffer_snapshot;
9702 let selections = self.selections.all::<Point>(cx);
9703
9704 let mut edits = Vec::new();
9705 let mut selections_iter = selections.iter().peekable();
9706 while let Some(selection) = selections_iter.next() {
9707 let mut rows = selection.spanned_rows(false, &display_map);
9708 // duplicate line-wise
9709 if whole_lines || selection.start == selection.end {
9710 // Avoid duplicating the same lines twice.
9711 while let Some(next_selection) = selections_iter.peek() {
9712 let next_rows = next_selection.spanned_rows(false, &display_map);
9713 if next_rows.start < rows.end {
9714 rows.end = next_rows.end;
9715 selections_iter.next().unwrap();
9716 } else {
9717 break;
9718 }
9719 }
9720
9721 // Copy the text from the selected row region and splice it either at the start
9722 // or end of the region.
9723 let start = Point::new(rows.start.0, 0);
9724 let end = Point::new(
9725 rows.end.previous_row().0,
9726 buffer.line_len(rows.end.previous_row()),
9727 );
9728 let text = buffer
9729 .text_for_range(start..end)
9730 .chain(Some("\n"))
9731 .collect::<String>();
9732 let insert_location = if upwards {
9733 Point::new(rows.end.0, 0)
9734 } else {
9735 start
9736 };
9737 edits.push((insert_location..insert_location, text));
9738 } else {
9739 // duplicate character-wise
9740 let start = selection.start;
9741 let end = selection.end;
9742 let text = buffer.text_for_range(start..end).collect::<String>();
9743 edits.push((selection.end..selection.end, text));
9744 }
9745 }
9746
9747 self.transact(window, cx, |this, _, cx| {
9748 this.buffer.update(cx, |buffer, cx| {
9749 buffer.edit(edits, None, cx);
9750 });
9751
9752 this.request_autoscroll(Autoscroll::fit(), cx);
9753 });
9754 }
9755
9756 pub fn duplicate_line_up(
9757 &mut self,
9758 _: &DuplicateLineUp,
9759 window: &mut Window,
9760 cx: &mut Context<Self>,
9761 ) {
9762 self.duplicate(true, true, window, cx);
9763 }
9764
9765 pub fn duplicate_line_down(
9766 &mut self,
9767 _: &DuplicateLineDown,
9768 window: &mut Window,
9769 cx: &mut Context<Self>,
9770 ) {
9771 self.duplicate(false, true, window, cx);
9772 }
9773
9774 pub fn duplicate_selection(
9775 &mut self,
9776 _: &DuplicateSelection,
9777 window: &mut Window,
9778 cx: &mut Context<Self>,
9779 ) {
9780 self.duplicate(false, false, window, cx);
9781 }
9782
9783 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9784 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9785
9786 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9787 let buffer = self.buffer.read(cx).snapshot(cx);
9788
9789 let mut edits = Vec::new();
9790 let mut unfold_ranges = Vec::new();
9791 let mut refold_creases = Vec::new();
9792
9793 let selections = self.selections.all::<Point>(cx);
9794 let mut selections = selections.iter().peekable();
9795 let mut contiguous_row_selections = Vec::new();
9796 let mut new_selections = Vec::new();
9797
9798 while let Some(selection) = selections.next() {
9799 // Find all the selections that span a contiguous row range
9800 let (start_row, end_row) = consume_contiguous_rows(
9801 &mut contiguous_row_selections,
9802 selection,
9803 &display_map,
9804 &mut selections,
9805 );
9806
9807 // Move the text spanned by the row range to be before the line preceding the row range
9808 if start_row.0 > 0 {
9809 let range_to_move = Point::new(
9810 start_row.previous_row().0,
9811 buffer.line_len(start_row.previous_row()),
9812 )
9813 ..Point::new(
9814 end_row.previous_row().0,
9815 buffer.line_len(end_row.previous_row()),
9816 );
9817 let insertion_point = display_map
9818 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9819 .0;
9820
9821 // Don't move lines across excerpts
9822 if buffer
9823 .excerpt_containing(insertion_point..range_to_move.end)
9824 .is_some()
9825 {
9826 let text = buffer
9827 .text_for_range(range_to_move.clone())
9828 .flat_map(|s| s.chars())
9829 .skip(1)
9830 .chain(['\n'])
9831 .collect::<String>();
9832
9833 edits.push((
9834 buffer.anchor_after(range_to_move.start)
9835 ..buffer.anchor_before(range_to_move.end),
9836 String::new(),
9837 ));
9838 let insertion_anchor = buffer.anchor_after(insertion_point);
9839 edits.push((insertion_anchor..insertion_anchor, text));
9840
9841 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9842
9843 // Move selections up
9844 new_selections.extend(contiguous_row_selections.drain(..).map(
9845 |mut selection| {
9846 selection.start.row -= row_delta;
9847 selection.end.row -= row_delta;
9848 selection
9849 },
9850 ));
9851
9852 // Move folds up
9853 unfold_ranges.push(range_to_move.clone());
9854 for fold in display_map.folds_in_range(
9855 buffer.anchor_before(range_to_move.start)
9856 ..buffer.anchor_after(range_to_move.end),
9857 ) {
9858 let mut start = fold.range.start.to_point(&buffer);
9859 let mut end = fold.range.end.to_point(&buffer);
9860 start.row -= row_delta;
9861 end.row -= row_delta;
9862 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9863 }
9864 }
9865 }
9866
9867 // If we didn't move line(s), preserve the existing selections
9868 new_selections.append(&mut contiguous_row_selections);
9869 }
9870
9871 self.transact(window, cx, |this, window, cx| {
9872 this.unfold_ranges(&unfold_ranges, true, true, cx);
9873 this.buffer.update(cx, |buffer, cx| {
9874 for (range, text) in edits {
9875 buffer.edit([(range, text)], None, cx);
9876 }
9877 });
9878 this.fold_creases(refold_creases, true, window, cx);
9879 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9880 s.select(new_selections);
9881 })
9882 });
9883 }
9884
9885 pub fn move_line_down(
9886 &mut self,
9887 _: &MoveLineDown,
9888 window: &mut Window,
9889 cx: &mut Context<Self>,
9890 ) {
9891 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9892
9893 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9894 let buffer = self.buffer.read(cx).snapshot(cx);
9895
9896 let mut edits = Vec::new();
9897 let mut unfold_ranges = Vec::new();
9898 let mut refold_creases = Vec::new();
9899
9900 let selections = self.selections.all::<Point>(cx);
9901 let mut selections = selections.iter().peekable();
9902 let mut contiguous_row_selections = Vec::new();
9903 let mut new_selections = Vec::new();
9904
9905 while let Some(selection) = selections.next() {
9906 // Find all the selections that span a contiguous row range
9907 let (start_row, end_row) = consume_contiguous_rows(
9908 &mut contiguous_row_selections,
9909 selection,
9910 &display_map,
9911 &mut selections,
9912 );
9913
9914 // Move the text spanned by the row range to be after the last line of the row range
9915 if end_row.0 <= buffer.max_point().row {
9916 let range_to_move =
9917 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9918 let insertion_point = display_map
9919 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9920 .0;
9921
9922 // Don't move lines across excerpt boundaries
9923 if buffer
9924 .excerpt_containing(range_to_move.start..insertion_point)
9925 .is_some()
9926 {
9927 let mut text = String::from("\n");
9928 text.extend(buffer.text_for_range(range_to_move.clone()));
9929 text.pop(); // Drop trailing newline
9930 edits.push((
9931 buffer.anchor_after(range_to_move.start)
9932 ..buffer.anchor_before(range_to_move.end),
9933 String::new(),
9934 ));
9935 let insertion_anchor = buffer.anchor_after(insertion_point);
9936 edits.push((insertion_anchor..insertion_anchor, text));
9937
9938 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9939
9940 // Move selections down
9941 new_selections.extend(contiguous_row_selections.drain(..).map(
9942 |mut selection| {
9943 selection.start.row += row_delta;
9944 selection.end.row += row_delta;
9945 selection
9946 },
9947 ));
9948
9949 // Move folds down
9950 unfold_ranges.push(range_to_move.clone());
9951 for fold in display_map.folds_in_range(
9952 buffer.anchor_before(range_to_move.start)
9953 ..buffer.anchor_after(range_to_move.end),
9954 ) {
9955 let mut start = fold.range.start.to_point(&buffer);
9956 let mut end = fold.range.end.to_point(&buffer);
9957 start.row += row_delta;
9958 end.row += row_delta;
9959 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9960 }
9961 }
9962 }
9963
9964 // If we didn't move line(s), preserve the existing selections
9965 new_selections.append(&mut contiguous_row_selections);
9966 }
9967
9968 self.transact(window, cx, |this, window, cx| {
9969 this.unfold_ranges(&unfold_ranges, true, true, cx);
9970 this.buffer.update(cx, |buffer, cx| {
9971 for (range, text) in edits {
9972 buffer.edit([(range, text)], None, cx);
9973 }
9974 });
9975 this.fold_creases(refold_creases, true, window, cx);
9976 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9977 s.select(new_selections)
9978 });
9979 });
9980 }
9981
9982 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9983 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9984 let text_layout_details = &self.text_layout_details(window);
9985 self.transact(window, cx, |this, window, cx| {
9986 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9987 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9988 s.move_with(|display_map, selection| {
9989 if !selection.is_empty() {
9990 return;
9991 }
9992
9993 let mut head = selection.head();
9994 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9995 if head.column() == display_map.line_len(head.row()) {
9996 transpose_offset = display_map
9997 .buffer_snapshot
9998 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9999 }
10000
10001 if transpose_offset == 0 {
10002 return;
10003 }
10004
10005 *head.column_mut() += 1;
10006 head = display_map.clip_point(head, Bias::Right);
10007 let goal = SelectionGoal::HorizontalPosition(
10008 display_map
10009 .x_for_display_point(head, text_layout_details)
10010 .into(),
10011 );
10012 selection.collapse_to(head, goal);
10013
10014 let transpose_start = display_map
10015 .buffer_snapshot
10016 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10017 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10018 let transpose_end = display_map
10019 .buffer_snapshot
10020 .clip_offset(transpose_offset + 1, Bias::Right);
10021 if let Some(ch) =
10022 display_map.buffer_snapshot.chars_at(transpose_start).next()
10023 {
10024 edits.push((transpose_start..transpose_offset, String::new()));
10025 edits.push((transpose_end..transpose_end, ch.to_string()));
10026 }
10027 }
10028 });
10029 edits
10030 });
10031 this.buffer
10032 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10033 let selections = this.selections.all::<usize>(cx);
10034 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10035 s.select(selections);
10036 });
10037 });
10038 }
10039
10040 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10041 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10042 self.rewrap_impl(RewrapOptions::default(), cx)
10043 }
10044
10045 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10046 let buffer = self.buffer.read(cx).snapshot(cx);
10047 let selections = self.selections.all::<Point>(cx);
10048 let mut selections = selections.iter().peekable();
10049
10050 let mut edits = Vec::new();
10051 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10052
10053 while let Some(selection) = selections.next() {
10054 let mut start_row = selection.start.row;
10055 let mut end_row = selection.end.row;
10056
10057 // Skip selections that overlap with a range that has already been rewrapped.
10058 let selection_range = start_row..end_row;
10059 if rewrapped_row_ranges
10060 .iter()
10061 .any(|range| range.overlaps(&selection_range))
10062 {
10063 continue;
10064 }
10065
10066 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10067
10068 // Since not all lines in the selection may be at the same indent
10069 // level, choose the indent size that is the most common between all
10070 // of the lines.
10071 //
10072 // If there is a tie, we use the deepest indent.
10073 let (indent_size, indent_end) = {
10074 let mut indent_size_occurrences = HashMap::default();
10075 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10076
10077 for row in start_row..=end_row {
10078 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10079 rows_by_indent_size.entry(indent).or_default().push(row);
10080 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10081 }
10082
10083 let indent_size = indent_size_occurrences
10084 .into_iter()
10085 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10086 .map(|(indent, _)| indent)
10087 .unwrap_or_default();
10088 let row = rows_by_indent_size[&indent_size][0];
10089 let indent_end = Point::new(row, indent_size.len);
10090
10091 (indent_size, indent_end)
10092 };
10093
10094 let mut line_prefix = indent_size.chars().collect::<String>();
10095
10096 let mut inside_comment = false;
10097 if let Some(comment_prefix) =
10098 buffer
10099 .language_scope_at(selection.head())
10100 .and_then(|language| {
10101 language
10102 .line_comment_prefixes()
10103 .iter()
10104 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10105 .cloned()
10106 })
10107 {
10108 line_prefix.push_str(&comment_prefix);
10109 inside_comment = true;
10110 }
10111
10112 let language_settings = buffer.language_settings_at(selection.head(), cx);
10113 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10114 RewrapBehavior::InComments => inside_comment,
10115 RewrapBehavior::InSelections => !selection.is_empty(),
10116 RewrapBehavior::Anywhere => true,
10117 };
10118
10119 let should_rewrap = options.override_language_settings
10120 || allow_rewrap_based_on_language
10121 || self.hard_wrap.is_some();
10122 if !should_rewrap {
10123 continue;
10124 }
10125
10126 if selection.is_empty() {
10127 'expand_upwards: while start_row > 0 {
10128 let prev_row = start_row - 1;
10129 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10130 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10131 {
10132 start_row = prev_row;
10133 } else {
10134 break 'expand_upwards;
10135 }
10136 }
10137
10138 'expand_downwards: while end_row < buffer.max_point().row {
10139 let next_row = end_row + 1;
10140 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10141 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10142 {
10143 end_row = next_row;
10144 } else {
10145 break 'expand_downwards;
10146 }
10147 }
10148 }
10149
10150 let start = Point::new(start_row, 0);
10151 let start_offset = start.to_offset(&buffer);
10152 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10153 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10154 let Some(lines_without_prefixes) = selection_text
10155 .lines()
10156 .map(|line| {
10157 line.strip_prefix(&line_prefix)
10158 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10159 .ok_or_else(|| {
10160 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10161 })
10162 })
10163 .collect::<Result<Vec<_>, _>>()
10164 .log_err()
10165 else {
10166 continue;
10167 };
10168
10169 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10170 buffer
10171 .language_settings_at(Point::new(start_row, 0), cx)
10172 .preferred_line_length as usize
10173 });
10174 let wrapped_text = wrap_with_prefix(
10175 line_prefix,
10176 lines_without_prefixes.join("\n"),
10177 wrap_column,
10178 tab_size,
10179 options.preserve_existing_whitespace,
10180 );
10181
10182 // TODO: should always use char-based diff while still supporting cursor behavior that
10183 // matches vim.
10184 let mut diff_options = DiffOptions::default();
10185 if options.override_language_settings {
10186 diff_options.max_word_diff_len = 0;
10187 diff_options.max_word_diff_line_count = 0;
10188 } else {
10189 diff_options.max_word_diff_len = usize::MAX;
10190 diff_options.max_word_diff_line_count = usize::MAX;
10191 }
10192
10193 for (old_range, new_text) in
10194 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10195 {
10196 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10197 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10198 edits.push((edit_start..edit_end, new_text));
10199 }
10200
10201 rewrapped_row_ranges.push(start_row..=end_row);
10202 }
10203
10204 self.buffer
10205 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10206 }
10207
10208 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10209 let mut text = String::new();
10210 let buffer = self.buffer.read(cx).snapshot(cx);
10211 let mut selections = self.selections.all::<Point>(cx);
10212 let mut clipboard_selections = Vec::with_capacity(selections.len());
10213 {
10214 let max_point = buffer.max_point();
10215 let mut is_first = true;
10216 for selection in &mut selections {
10217 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10218 if is_entire_line {
10219 selection.start = Point::new(selection.start.row, 0);
10220 if !selection.is_empty() && selection.end.column == 0 {
10221 selection.end = cmp::min(max_point, selection.end);
10222 } else {
10223 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10224 }
10225 selection.goal = SelectionGoal::None;
10226 }
10227 if is_first {
10228 is_first = false;
10229 } else {
10230 text += "\n";
10231 }
10232 let mut len = 0;
10233 for chunk in buffer.text_for_range(selection.start..selection.end) {
10234 text.push_str(chunk);
10235 len += chunk.len();
10236 }
10237 clipboard_selections.push(ClipboardSelection {
10238 len,
10239 is_entire_line,
10240 first_line_indent: buffer
10241 .indent_size_for_line(MultiBufferRow(selection.start.row))
10242 .len,
10243 });
10244 }
10245 }
10246
10247 self.transact(window, cx, |this, window, cx| {
10248 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10249 s.select(selections);
10250 });
10251 this.insert("", window, cx);
10252 });
10253 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10254 }
10255
10256 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10257 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10258 let item = self.cut_common(window, cx);
10259 cx.write_to_clipboard(item);
10260 }
10261
10262 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10263 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10264 self.change_selections(None, window, cx, |s| {
10265 s.move_with(|snapshot, sel| {
10266 if sel.is_empty() {
10267 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10268 }
10269 });
10270 });
10271 let item = self.cut_common(window, cx);
10272 cx.set_global(KillRing(item))
10273 }
10274
10275 pub fn kill_ring_yank(
10276 &mut self,
10277 _: &KillRingYank,
10278 window: &mut Window,
10279 cx: &mut Context<Self>,
10280 ) {
10281 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10282 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10283 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10284 (kill_ring.text().to_string(), kill_ring.metadata_json())
10285 } else {
10286 return;
10287 }
10288 } else {
10289 return;
10290 };
10291 self.do_paste(&text, metadata, false, window, cx);
10292 }
10293
10294 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10295 self.do_copy(true, cx);
10296 }
10297
10298 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10299 self.do_copy(false, cx);
10300 }
10301
10302 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10303 let selections = self.selections.all::<Point>(cx);
10304 let buffer = self.buffer.read(cx).read(cx);
10305 let mut text = String::new();
10306
10307 let mut clipboard_selections = Vec::with_capacity(selections.len());
10308 {
10309 let max_point = buffer.max_point();
10310 let mut is_first = true;
10311 for selection in &selections {
10312 let mut start = selection.start;
10313 let mut end = selection.end;
10314 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10315 if is_entire_line {
10316 start = Point::new(start.row, 0);
10317 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10318 }
10319
10320 let mut trimmed_selections = Vec::new();
10321 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10322 let row = MultiBufferRow(start.row);
10323 let first_indent = buffer.indent_size_for_line(row);
10324 if first_indent.len == 0 || start.column > first_indent.len {
10325 trimmed_selections.push(start..end);
10326 } else {
10327 trimmed_selections.push(
10328 Point::new(row.0, first_indent.len)
10329 ..Point::new(row.0, buffer.line_len(row)),
10330 );
10331 for row in start.row + 1..=end.row {
10332 let mut line_len = buffer.line_len(MultiBufferRow(row));
10333 if row == end.row {
10334 line_len = end.column;
10335 }
10336 if line_len == 0 {
10337 trimmed_selections
10338 .push(Point::new(row, 0)..Point::new(row, line_len));
10339 continue;
10340 }
10341 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10342 if row_indent_size.len >= first_indent.len {
10343 trimmed_selections.push(
10344 Point::new(row, first_indent.len)..Point::new(row, line_len),
10345 );
10346 } else {
10347 trimmed_selections.clear();
10348 trimmed_selections.push(start..end);
10349 break;
10350 }
10351 }
10352 }
10353 } else {
10354 trimmed_selections.push(start..end);
10355 }
10356
10357 for trimmed_range in trimmed_selections {
10358 if is_first {
10359 is_first = false;
10360 } else {
10361 text += "\n";
10362 }
10363 let mut len = 0;
10364 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10365 text.push_str(chunk);
10366 len += chunk.len();
10367 }
10368 clipboard_selections.push(ClipboardSelection {
10369 len,
10370 is_entire_line,
10371 first_line_indent: buffer
10372 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10373 .len,
10374 });
10375 }
10376 }
10377 }
10378
10379 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10380 text,
10381 clipboard_selections,
10382 ));
10383 }
10384
10385 pub fn do_paste(
10386 &mut self,
10387 text: &String,
10388 clipboard_selections: Option<Vec<ClipboardSelection>>,
10389 handle_entire_lines: bool,
10390 window: &mut Window,
10391 cx: &mut Context<Self>,
10392 ) {
10393 if self.read_only(cx) {
10394 return;
10395 }
10396
10397 let clipboard_text = Cow::Borrowed(text);
10398
10399 self.transact(window, cx, |this, window, cx| {
10400 if let Some(mut clipboard_selections) = clipboard_selections {
10401 let old_selections = this.selections.all::<usize>(cx);
10402 let all_selections_were_entire_line =
10403 clipboard_selections.iter().all(|s| s.is_entire_line);
10404 let first_selection_indent_column =
10405 clipboard_selections.first().map(|s| s.first_line_indent);
10406 if clipboard_selections.len() != old_selections.len() {
10407 clipboard_selections.drain(..);
10408 }
10409 let cursor_offset = this.selections.last::<usize>(cx).head();
10410 let mut auto_indent_on_paste = true;
10411
10412 this.buffer.update(cx, |buffer, cx| {
10413 let snapshot = buffer.read(cx);
10414 auto_indent_on_paste = snapshot
10415 .language_settings_at(cursor_offset, cx)
10416 .auto_indent_on_paste;
10417
10418 let mut start_offset = 0;
10419 let mut edits = Vec::new();
10420 let mut original_indent_columns = Vec::new();
10421 for (ix, selection) in old_selections.iter().enumerate() {
10422 let to_insert;
10423 let entire_line;
10424 let original_indent_column;
10425 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10426 let end_offset = start_offset + clipboard_selection.len;
10427 to_insert = &clipboard_text[start_offset..end_offset];
10428 entire_line = clipboard_selection.is_entire_line;
10429 start_offset = end_offset + 1;
10430 original_indent_column = Some(clipboard_selection.first_line_indent);
10431 } else {
10432 to_insert = clipboard_text.as_str();
10433 entire_line = all_selections_were_entire_line;
10434 original_indent_column = first_selection_indent_column
10435 }
10436
10437 // If the corresponding selection was empty when this slice of the
10438 // clipboard text was written, then the entire line containing the
10439 // selection was copied. If this selection is also currently empty,
10440 // then paste the line before the current line of the buffer.
10441 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10442 let column = selection.start.to_point(&snapshot).column as usize;
10443 let line_start = selection.start - column;
10444 line_start..line_start
10445 } else {
10446 selection.range()
10447 };
10448
10449 edits.push((range, to_insert));
10450 original_indent_columns.push(original_indent_column);
10451 }
10452 drop(snapshot);
10453
10454 buffer.edit(
10455 edits,
10456 if auto_indent_on_paste {
10457 Some(AutoindentMode::Block {
10458 original_indent_columns,
10459 })
10460 } else {
10461 None
10462 },
10463 cx,
10464 );
10465 });
10466
10467 let selections = this.selections.all::<usize>(cx);
10468 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10469 s.select(selections)
10470 });
10471 } else {
10472 this.insert(&clipboard_text, window, cx);
10473 }
10474 });
10475 }
10476
10477 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10478 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10479 if let Some(item) = cx.read_from_clipboard() {
10480 let entries = item.entries();
10481
10482 match entries.first() {
10483 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10484 // of all the pasted entries.
10485 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10486 .do_paste(
10487 clipboard_string.text(),
10488 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10489 true,
10490 window,
10491 cx,
10492 ),
10493 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10494 }
10495 }
10496 }
10497
10498 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10499 if self.read_only(cx) {
10500 return;
10501 }
10502
10503 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10504
10505 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10506 if let Some((selections, _)) =
10507 self.selection_history.transaction(transaction_id).cloned()
10508 {
10509 self.change_selections(None, window, cx, |s| {
10510 s.select_anchors(selections.to_vec());
10511 });
10512 } else {
10513 log::error!(
10514 "No entry in selection_history found for undo. \
10515 This may correspond to a bug where undo does not update the selection. \
10516 If this is occurring, please add details to \
10517 https://github.com/zed-industries/zed/issues/22692"
10518 );
10519 }
10520 self.request_autoscroll(Autoscroll::fit(), cx);
10521 self.unmark_text(window, cx);
10522 self.refresh_inline_completion(true, false, window, cx);
10523 cx.emit(EditorEvent::Edited { transaction_id });
10524 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10525 }
10526 }
10527
10528 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10529 if self.read_only(cx) {
10530 return;
10531 }
10532
10533 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10534
10535 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10536 if let Some((_, Some(selections))) =
10537 self.selection_history.transaction(transaction_id).cloned()
10538 {
10539 self.change_selections(None, window, cx, |s| {
10540 s.select_anchors(selections.to_vec());
10541 });
10542 } else {
10543 log::error!(
10544 "No entry in selection_history found for redo. \
10545 This may correspond to a bug where undo does not update the selection. \
10546 If this is occurring, please add details to \
10547 https://github.com/zed-industries/zed/issues/22692"
10548 );
10549 }
10550 self.request_autoscroll(Autoscroll::fit(), cx);
10551 self.unmark_text(window, cx);
10552 self.refresh_inline_completion(true, false, window, cx);
10553 cx.emit(EditorEvent::Edited { transaction_id });
10554 }
10555 }
10556
10557 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10558 self.buffer
10559 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10560 }
10561
10562 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10563 self.buffer
10564 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10565 }
10566
10567 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10568 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10569 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10570 s.move_with(|map, selection| {
10571 let cursor = if selection.is_empty() {
10572 movement::left(map, selection.start)
10573 } else {
10574 selection.start
10575 };
10576 selection.collapse_to(cursor, SelectionGoal::None);
10577 });
10578 })
10579 }
10580
10581 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10582 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10583 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10584 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10585 })
10586 }
10587
10588 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10589 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10590 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10591 s.move_with(|map, selection| {
10592 let cursor = if selection.is_empty() {
10593 movement::right(map, selection.end)
10594 } else {
10595 selection.end
10596 };
10597 selection.collapse_to(cursor, SelectionGoal::None)
10598 });
10599 })
10600 }
10601
10602 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10603 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10604 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10605 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10606 })
10607 }
10608
10609 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10610 if self.take_rename(true, window, cx).is_some() {
10611 return;
10612 }
10613
10614 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10615 cx.propagate();
10616 return;
10617 }
10618
10619 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10620
10621 let text_layout_details = &self.text_layout_details(window);
10622 let selection_count = self.selections.count();
10623 let first_selection = self.selections.first_anchor();
10624
10625 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10626 s.move_with(|map, selection| {
10627 if !selection.is_empty() {
10628 selection.goal = SelectionGoal::None;
10629 }
10630 let (cursor, goal) = movement::up(
10631 map,
10632 selection.start,
10633 selection.goal,
10634 false,
10635 text_layout_details,
10636 );
10637 selection.collapse_to(cursor, goal);
10638 });
10639 });
10640
10641 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10642 {
10643 cx.propagate();
10644 }
10645 }
10646
10647 pub fn move_up_by_lines(
10648 &mut self,
10649 action: &MoveUpByLines,
10650 window: &mut Window,
10651 cx: &mut Context<Self>,
10652 ) {
10653 if self.take_rename(true, window, cx).is_some() {
10654 return;
10655 }
10656
10657 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10658 cx.propagate();
10659 return;
10660 }
10661
10662 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10663
10664 let text_layout_details = &self.text_layout_details(window);
10665
10666 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10667 s.move_with(|map, selection| {
10668 if !selection.is_empty() {
10669 selection.goal = SelectionGoal::None;
10670 }
10671 let (cursor, goal) = movement::up_by_rows(
10672 map,
10673 selection.start,
10674 action.lines,
10675 selection.goal,
10676 false,
10677 text_layout_details,
10678 );
10679 selection.collapse_to(cursor, goal);
10680 });
10681 })
10682 }
10683
10684 pub fn move_down_by_lines(
10685 &mut self,
10686 action: &MoveDownByLines,
10687 window: &mut Window,
10688 cx: &mut Context<Self>,
10689 ) {
10690 if self.take_rename(true, window, cx).is_some() {
10691 return;
10692 }
10693
10694 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10695 cx.propagate();
10696 return;
10697 }
10698
10699 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10700
10701 let text_layout_details = &self.text_layout_details(window);
10702
10703 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10704 s.move_with(|map, selection| {
10705 if !selection.is_empty() {
10706 selection.goal = SelectionGoal::None;
10707 }
10708 let (cursor, goal) = movement::down_by_rows(
10709 map,
10710 selection.start,
10711 action.lines,
10712 selection.goal,
10713 false,
10714 text_layout_details,
10715 );
10716 selection.collapse_to(cursor, goal);
10717 });
10718 })
10719 }
10720
10721 pub fn select_down_by_lines(
10722 &mut self,
10723 action: &SelectDownByLines,
10724 window: &mut Window,
10725 cx: &mut Context<Self>,
10726 ) {
10727 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10728 let text_layout_details = &self.text_layout_details(window);
10729 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10730 s.move_heads_with(|map, head, goal| {
10731 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10732 })
10733 })
10734 }
10735
10736 pub fn select_up_by_lines(
10737 &mut self,
10738 action: &SelectUpByLines,
10739 window: &mut Window,
10740 cx: &mut Context<Self>,
10741 ) {
10742 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10743 let text_layout_details = &self.text_layout_details(window);
10744 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10745 s.move_heads_with(|map, head, goal| {
10746 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10747 })
10748 })
10749 }
10750
10751 pub fn select_page_up(
10752 &mut self,
10753 _: &SelectPageUp,
10754 window: &mut Window,
10755 cx: &mut Context<Self>,
10756 ) {
10757 let Some(row_count) = self.visible_row_count() else {
10758 return;
10759 };
10760
10761 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10762
10763 let text_layout_details = &self.text_layout_details(window);
10764
10765 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10766 s.move_heads_with(|map, head, goal| {
10767 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10768 })
10769 })
10770 }
10771
10772 pub fn move_page_up(
10773 &mut self,
10774 action: &MovePageUp,
10775 window: &mut Window,
10776 cx: &mut Context<Self>,
10777 ) {
10778 if self.take_rename(true, window, cx).is_some() {
10779 return;
10780 }
10781
10782 if self
10783 .context_menu
10784 .borrow_mut()
10785 .as_mut()
10786 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10787 .unwrap_or(false)
10788 {
10789 return;
10790 }
10791
10792 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10793 cx.propagate();
10794 return;
10795 }
10796
10797 let Some(row_count) = self.visible_row_count() else {
10798 return;
10799 };
10800
10801 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10802
10803 let autoscroll = if action.center_cursor {
10804 Autoscroll::center()
10805 } else {
10806 Autoscroll::fit()
10807 };
10808
10809 let text_layout_details = &self.text_layout_details(window);
10810
10811 self.change_selections(Some(autoscroll), window, cx, |s| {
10812 s.move_with(|map, selection| {
10813 if !selection.is_empty() {
10814 selection.goal = SelectionGoal::None;
10815 }
10816 let (cursor, goal) = movement::up_by_rows(
10817 map,
10818 selection.end,
10819 row_count,
10820 selection.goal,
10821 false,
10822 text_layout_details,
10823 );
10824 selection.collapse_to(cursor, goal);
10825 });
10826 });
10827 }
10828
10829 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10830 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10831 let text_layout_details = &self.text_layout_details(window);
10832 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10833 s.move_heads_with(|map, head, goal| {
10834 movement::up(map, head, goal, false, text_layout_details)
10835 })
10836 })
10837 }
10838
10839 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10840 self.take_rename(true, window, cx);
10841
10842 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10843 cx.propagate();
10844 return;
10845 }
10846
10847 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10848
10849 let text_layout_details = &self.text_layout_details(window);
10850 let selection_count = self.selections.count();
10851 let first_selection = self.selections.first_anchor();
10852
10853 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10854 s.move_with(|map, selection| {
10855 if !selection.is_empty() {
10856 selection.goal = SelectionGoal::None;
10857 }
10858 let (cursor, goal) = movement::down(
10859 map,
10860 selection.end,
10861 selection.goal,
10862 false,
10863 text_layout_details,
10864 );
10865 selection.collapse_to(cursor, goal);
10866 });
10867 });
10868
10869 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10870 {
10871 cx.propagate();
10872 }
10873 }
10874
10875 pub fn select_page_down(
10876 &mut self,
10877 _: &SelectPageDown,
10878 window: &mut Window,
10879 cx: &mut Context<Self>,
10880 ) {
10881 let Some(row_count) = self.visible_row_count() else {
10882 return;
10883 };
10884
10885 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10886
10887 let text_layout_details = &self.text_layout_details(window);
10888
10889 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10890 s.move_heads_with(|map, head, goal| {
10891 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10892 })
10893 })
10894 }
10895
10896 pub fn move_page_down(
10897 &mut self,
10898 action: &MovePageDown,
10899 window: &mut Window,
10900 cx: &mut Context<Self>,
10901 ) {
10902 if self.take_rename(true, window, cx).is_some() {
10903 return;
10904 }
10905
10906 if self
10907 .context_menu
10908 .borrow_mut()
10909 .as_mut()
10910 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10911 .unwrap_or(false)
10912 {
10913 return;
10914 }
10915
10916 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10917 cx.propagate();
10918 return;
10919 }
10920
10921 let Some(row_count) = self.visible_row_count() else {
10922 return;
10923 };
10924
10925 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10926
10927 let autoscroll = if action.center_cursor {
10928 Autoscroll::center()
10929 } else {
10930 Autoscroll::fit()
10931 };
10932
10933 let text_layout_details = &self.text_layout_details(window);
10934 self.change_selections(Some(autoscroll), window, cx, |s| {
10935 s.move_with(|map, selection| {
10936 if !selection.is_empty() {
10937 selection.goal = SelectionGoal::None;
10938 }
10939 let (cursor, goal) = movement::down_by_rows(
10940 map,
10941 selection.end,
10942 row_count,
10943 selection.goal,
10944 false,
10945 text_layout_details,
10946 );
10947 selection.collapse_to(cursor, goal);
10948 });
10949 });
10950 }
10951
10952 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10953 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10954 let text_layout_details = &self.text_layout_details(window);
10955 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10956 s.move_heads_with(|map, head, goal| {
10957 movement::down(map, head, goal, false, text_layout_details)
10958 })
10959 });
10960 }
10961
10962 pub fn context_menu_first(
10963 &mut self,
10964 _: &ContextMenuFirst,
10965 _window: &mut Window,
10966 cx: &mut Context<Self>,
10967 ) {
10968 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10969 context_menu.select_first(self.completion_provider.as_deref(), cx);
10970 }
10971 }
10972
10973 pub fn context_menu_prev(
10974 &mut self,
10975 _: &ContextMenuPrevious,
10976 _window: &mut Window,
10977 cx: &mut Context<Self>,
10978 ) {
10979 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10980 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10981 }
10982 }
10983
10984 pub fn context_menu_next(
10985 &mut self,
10986 _: &ContextMenuNext,
10987 _window: &mut Window,
10988 cx: &mut Context<Self>,
10989 ) {
10990 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10991 context_menu.select_next(self.completion_provider.as_deref(), cx);
10992 }
10993 }
10994
10995 pub fn context_menu_last(
10996 &mut self,
10997 _: &ContextMenuLast,
10998 _window: &mut Window,
10999 cx: &mut Context<Self>,
11000 ) {
11001 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11002 context_menu.select_last(self.completion_provider.as_deref(), cx);
11003 }
11004 }
11005
11006 pub fn move_to_previous_word_start(
11007 &mut self,
11008 _: &MoveToPreviousWordStart,
11009 window: &mut Window,
11010 cx: &mut Context<Self>,
11011 ) {
11012 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11013 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11014 s.move_cursors_with(|map, head, _| {
11015 (
11016 movement::previous_word_start(map, head),
11017 SelectionGoal::None,
11018 )
11019 });
11020 })
11021 }
11022
11023 pub fn move_to_previous_subword_start(
11024 &mut self,
11025 _: &MoveToPreviousSubwordStart,
11026 window: &mut Window,
11027 cx: &mut Context<Self>,
11028 ) {
11029 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11030 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11031 s.move_cursors_with(|map, head, _| {
11032 (
11033 movement::previous_subword_start(map, head),
11034 SelectionGoal::None,
11035 )
11036 });
11037 })
11038 }
11039
11040 pub fn select_to_previous_word_start(
11041 &mut self,
11042 _: &SelectToPreviousWordStart,
11043 window: &mut Window,
11044 cx: &mut Context<Self>,
11045 ) {
11046 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11047 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11048 s.move_heads_with(|map, head, _| {
11049 (
11050 movement::previous_word_start(map, head),
11051 SelectionGoal::None,
11052 )
11053 });
11054 })
11055 }
11056
11057 pub fn select_to_previous_subword_start(
11058 &mut self,
11059 _: &SelectToPreviousSubwordStart,
11060 window: &mut Window,
11061 cx: &mut Context<Self>,
11062 ) {
11063 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11064 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11065 s.move_heads_with(|map, head, _| {
11066 (
11067 movement::previous_subword_start(map, head),
11068 SelectionGoal::None,
11069 )
11070 });
11071 })
11072 }
11073
11074 pub fn delete_to_previous_word_start(
11075 &mut self,
11076 action: &DeleteToPreviousWordStart,
11077 window: &mut Window,
11078 cx: &mut Context<Self>,
11079 ) {
11080 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11081 self.transact(window, cx, |this, window, cx| {
11082 this.select_autoclose_pair(window, cx);
11083 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11084 s.move_with(|map, selection| {
11085 if selection.is_empty() {
11086 let cursor = if action.ignore_newlines {
11087 movement::previous_word_start(map, selection.head())
11088 } else {
11089 movement::previous_word_start_or_newline(map, selection.head())
11090 };
11091 selection.set_head(cursor, SelectionGoal::None);
11092 }
11093 });
11094 });
11095 this.insert("", window, cx);
11096 });
11097 }
11098
11099 pub fn delete_to_previous_subword_start(
11100 &mut self,
11101 _: &DeleteToPreviousSubwordStart,
11102 window: &mut Window,
11103 cx: &mut Context<Self>,
11104 ) {
11105 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11106 self.transact(window, cx, |this, window, cx| {
11107 this.select_autoclose_pair(window, cx);
11108 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11109 s.move_with(|map, selection| {
11110 if selection.is_empty() {
11111 let cursor = movement::previous_subword_start(map, selection.head());
11112 selection.set_head(cursor, SelectionGoal::None);
11113 }
11114 });
11115 });
11116 this.insert("", window, cx);
11117 });
11118 }
11119
11120 pub fn move_to_next_word_end(
11121 &mut self,
11122 _: &MoveToNextWordEnd,
11123 window: &mut Window,
11124 cx: &mut Context<Self>,
11125 ) {
11126 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11127 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11128 s.move_cursors_with(|map, head, _| {
11129 (movement::next_word_end(map, head), SelectionGoal::None)
11130 });
11131 })
11132 }
11133
11134 pub fn move_to_next_subword_end(
11135 &mut self,
11136 _: &MoveToNextSubwordEnd,
11137 window: &mut Window,
11138 cx: &mut Context<Self>,
11139 ) {
11140 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11141 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11142 s.move_cursors_with(|map, head, _| {
11143 (movement::next_subword_end(map, head), SelectionGoal::None)
11144 });
11145 })
11146 }
11147
11148 pub fn select_to_next_word_end(
11149 &mut self,
11150 _: &SelectToNextWordEnd,
11151 window: &mut Window,
11152 cx: &mut Context<Self>,
11153 ) {
11154 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11155 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11156 s.move_heads_with(|map, head, _| {
11157 (movement::next_word_end(map, head), SelectionGoal::None)
11158 });
11159 })
11160 }
11161
11162 pub fn select_to_next_subword_end(
11163 &mut self,
11164 _: &SelectToNextSubwordEnd,
11165 window: &mut Window,
11166 cx: &mut Context<Self>,
11167 ) {
11168 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11169 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11170 s.move_heads_with(|map, head, _| {
11171 (movement::next_subword_end(map, head), SelectionGoal::None)
11172 });
11173 })
11174 }
11175
11176 pub fn delete_to_next_word_end(
11177 &mut self,
11178 action: &DeleteToNextWordEnd,
11179 window: &mut Window,
11180 cx: &mut Context<Self>,
11181 ) {
11182 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11183 self.transact(window, cx, |this, window, cx| {
11184 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11185 s.move_with(|map, selection| {
11186 if selection.is_empty() {
11187 let cursor = if action.ignore_newlines {
11188 movement::next_word_end(map, selection.head())
11189 } else {
11190 movement::next_word_end_or_newline(map, selection.head())
11191 };
11192 selection.set_head(cursor, SelectionGoal::None);
11193 }
11194 });
11195 });
11196 this.insert("", window, cx);
11197 });
11198 }
11199
11200 pub fn delete_to_next_subword_end(
11201 &mut self,
11202 _: &DeleteToNextSubwordEnd,
11203 window: &mut Window,
11204 cx: &mut Context<Self>,
11205 ) {
11206 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11207 self.transact(window, cx, |this, window, cx| {
11208 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11209 s.move_with(|map, selection| {
11210 if selection.is_empty() {
11211 let cursor = movement::next_subword_end(map, selection.head());
11212 selection.set_head(cursor, SelectionGoal::None);
11213 }
11214 });
11215 });
11216 this.insert("", window, cx);
11217 });
11218 }
11219
11220 pub fn move_to_beginning_of_line(
11221 &mut self,
11222 action: &MoveToBeginningOfLine,
11223 window: &mut Window,
11224 cx: &mut Context<Self>,
11225 ) {
11226 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11227 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11228 s.move_cursors_with(|map, head, _| {
11229 (
11230 movement::indented_line_beginning(
11231 map,
11232 head,
11233 action.stop_at_soft_wraps,
11234 action.stop_at_indent,
11235 ),
11236 SelectionGoal::None,
11237 )
11238 });
11239 })
11240 }
11241
11242 pub fn select_to_beginning_of_line(
11243 &mut self,
11244 action: &SelectToBeginningOfLine,
11245 window: &mut Window,
11246 cx: &mut Context<Self>,
11247 ) {
11248 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11249 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11250 s.move_heads_with(|map, head, _| {
11251 (
11252 movement::indented_line_beginning(
11253 map,
11254 head,
11255 action.stop_at_soft_wraps,
11256 action.stop_at_indent,
11257 ),
11258 SelectionGoal::None,
11259 )
11260 });
11261 });
11262 }
11263
11264 pub fn delete_to_beginning_of_line(
11265 &mut self,
11266 action: &DeleteToBeginningOfLine,
11267 window: &mut Window,
11268 cx: &mut Context<Self>,
11269 ) {
11270 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11271 self.transact(window, cx, |this, window, cx| {
11272 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11273 s.move_with(|_, selection| {
11274 selection.reversed = true;
11275 });
11276 });
11277
11278 this.select_to_beginning_of_line(
11279 &SelectToBeginningOfLine {
11280 stop_at_soft_wraps: false,
11281 stop_at_indent: action.stop_at_indent,
11282 },
11283 window,
11284 cx,
11285 );
11286 this.backspace(&Backspace, window, cx);
11287 });
11288 }
11289
11290 pub fn move_to_end_of_line(
11291 &mut self,
11292 action: &MoveToEndOfLine,
11293 window: &mut Window,
11294 cx: &mut Context<Self>,
11295 ) {
11296 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11297 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11298 s.move_cursors_with(|map, head, _| {
11299 (
11300 movement::line_end(map, head, action.stop_at_soft_wraps),
11301 SelectionGoal::None,
11302 )
11303 });
11304 })
11305 }
11306
11307 pub fn select_to_end_of_line(
11308 &mut self,
11309 action: &SelectToEndOfLine,
11310 window: &mut Window,
11311 cx: &mut Context<Self>,
11312 ) {
11313 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11314 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11315 s.move_heads_with(|map, head, _| {
11316 (
11317 movement::line_end(map, head, action.stop_at_soft_wraps),
11318 SelectionGoal::None,
11319 )
11320 });
11321 })
11322 }
11323
11324 pub fn delete_to_end_of_line(
11325 &mut self,
11326 _: &DeleteToEndOfLine,
11327 window: &mut Window,
11328 cx: &mut Context<Self>,
11329 ) {
11330 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11331 self.transact(window, cx, |this, window, cx| {
11332 this.select_to_end_of_line(
11333 &SelectToEndOfLine {
11334 stop_at_soft_wraps: false,
11335 },
11336 window,
11337 cx,
11338 );
11339 this.delete(&Delete, window, cx);
11340 });
11341 }
11342
11343 pub fn cut_to_end_of_line(
11344 &mut self,
11345 _: &CutToEndOfLine,
11346 window: &mut Window,
11347 cx: &mut Context<Self>,
11348 ) {
11349 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11350 self.transact(window, cx, |this, window, cx| {
11351 this.select_to_end_of_line(
11352 &SelectToEndOfLine {
11353 stop_at_soft_wraps: false,
11354 },
11355 window,
11356 cx,
11357 );
11358 this.cut(&Cut, window, cx);
11359 });
11360 }
11361
11362 pub fn move_to_start_of_paragraph(
11363 &mut self,
11364 _: &MoveToStartOfParagraph,
11365 window: &mut Window,
11366 cx: &mut Context<Self>,
11367 ) {
11368 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11369 cx.propagate();
11370 return;
11371 }
11372 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11373 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11374 s.move_with(|map, selection| {
11375 selection.collapse_to(
11376 movement::start_of_paragraph(map, selection.head(), 1),
11377 SelectionGoal::None,
11378 )
11379 });
11380 })
11381 }
11382
11383 pub fn move_to_end_of_paragraph(
11384 &mut self,
11385 _: &MoveToEndOfParagraph,
11386 window: &mut Window,
11387 cx: &mut Context<Self>,
11388 ) {
11389 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11390 cx.propagate();
11391 return;
11392 }
11393 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11394 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11395 s.move_with(|map, selection| {
11396 selection.collapse_to(
11397 movement::end_of_paragraph(map, selection.head(), 1),
11398 SelectionGoal::None,
11399 )
11400 });
11401 })
11402 }
11403
11404 pub fn select_to_start_of_paragraph(
11405 &mut self,
11406 _: &SelectToStartOfParagraph,
11407 window: &mut Window,
11408 cx: &mut Context<Self>,
11409 ) {
11410 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11411 cx.propagate();
11412 return;
11413 }
11414 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11415 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11416 s.move_heads_with(|map, head, _| {
11417 (
11418 movement::start_of_paragraph(map, head, 1),
11419 SelectionGoal::None,
11420 )
11421 });
11422 })
11423 }
11424
11425 pub fn select_to_end_of_paragraph(
11426 &mut self,
11427 _: &SelectToEndOfParagraph,
11428 window: &mut Window,
11429 cx: &mut Context<Self>,
11430 ) {
11431 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11432 cx.propagate();
11433 return;
11434 }
11435 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11436 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11437 s.move_heads_with(|map, head, _| {
11438 (
11439 movement::end_of_paragraph(map, head, 1),
11440 SelectionGoal::None,
11441 )
11442 });
11443 })
11444 }
11445
11446 pub fn move_to_start_of_excerpt(
11447 &mut self,
11448 _: &MoveToStartOfExcerpt,
11449 window: &mut Window,
11450 cx: &mut Context<Self>,
11451 ) {
11452 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11453 cx.propagate();
11454 return;
11455 }
11456 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11457 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11458 s.move_with(|map, selection| {
11459 selection.collapse_to(
11460 movement::start_of_excerpt(
11461 map,
11462 selection.head(),
11463 workspace::searchable::Direction::Prev,
11464 ),
11465 SelectionGoal::None,
11466 )
11467 });
11468 })
11469 }
11470
11471 pub fn move_to_start_of_next_excerpt(
11472 &mut self,
11473 _: &MoveToStartOfNextExcerpt,
11474 window: &mut Window,
11475 cx: &mut Context<Self>,
11476 ) {
11477 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11478 cx.propagate();
11479 return;
11480 }
11481
11482 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11483 s.move_with(|map, selection| {
11484 selection.collapse_to(
11485 movement::start_of_excerpt(
11486 map,
11487 selection.head(),
11488 workspace::searchable::Direction::Next,
11489 ),
11490 SelectionGoal::None,
11491 )
11492 });
11493 })
11494 }
11495
11496 pub fn move_to_end_of_excerpt(
11497 &mut self,
11498 _: &MoveToEndOfExcerpt,
11499 window: &mut Window,
11500 cx: &mut Context<Self>,
11501 ) {
11502 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11503 cx.propagate();
11504 return;
11505 }
11506 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11507 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11508 s.move_with(|map, selection| {
11509 selection.collapse_to(
11510 movement::end_of_excerpt(
11511 map,
11512 selection.head(),
11513 workspace::searchable::Direction::Next,
11514 ),
11515 SelectionGoal::None,
11516 )
11517 });
11518 })
11519 }
11520
11521 pub fn move_to_end_of_previous_excerpt(
11522 &mut self,
11523 _: &MoveToEndOfPreviousExcerpt,
11524 window: &mut Window,
11525 cx: &mut Context<Self>,
11526 ) {
11527 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11528 cx.propagate();
11529 return;
11530 }
11531 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11532 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11533 s.move_with(|map, selection| {
11534 selection.collapse_to(
11535 movement::end_of_excerpt(
11536 map,
11537 selection.head(),
11538 workspace::searchable::Direction::Prev,
11539 ),
11540 SelectionGoal::None,
11541 )
11542 });
11543 })
11544 }
11545
11546 pub fn select_to_start_of_excerpt(
11547 &mut self,
11548 _: &SelectToStartOfExcerpt,
11549 window: &mut Window,
11550 cx: &mut Context<Self>,
11551 ) {
11552 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11553 cx.propagate();
11554 return;
11555 }
11556 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11557 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11558 s.move_heads_with(|map, head, _| {
11559 (
11560 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11561 SelectionGoal::None,
11562 )
11563 });
11564 })
11565 }
11566
11567 pub fn select_to_start_of_next_excerpt(
11568 &mut self,
11569 _: &SelectToStartOfNextExcerpt,
11570 window: &mut Window,
11571 cx: &mut Context<Self>,
11572 ) {
11573 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11574 cx.propagate();
11575 return;
11576 }
11577 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11578 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11579 s.move_heads_with(|map, head, _| {
11580 (
11581 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11582 SelectionGoal::None,
11583 )
11584 });
11585 })
11586 }
11587
11588 pub fn select_to_end_of_excerpt(
11589 &mut self,
11590 _: &SelectToEndOfExcerpt,
11591 window: &mut Window,
11592 cx: &mut Context<Self>,
11593 ) {
11594 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11595 cx.propagate();
11596 return;
11597 }
11598 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11599 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11600 s.move_heads_with(|map, head, _| {
11601 (
11602 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11603 SelectionGoal::None,
11604 )
11605 });
11606 })
11607 }
11608
11609 pub fn select_to_end_of_previous_excerpt(
11610 &mut self,
11611 _: &SelectToEndOfPreviousExcerpt,
11612 window: &mut Window,
11613 cx: &mut Context<Self>,
11614 ) {
11615 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11616 cx.propagate();
11617 return;
11618 }
11619 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11620 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11621 s.move_heads_with(|map, head, _| {
11622 (
11623 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11624 SelectionGoal::None,
11625 )
11626 });
11627 })
11628 }
11629
11630 pub fn move_to_beginning(
11631 &mut self,
11632 _: &MoveToBeginning,
11633 window: &mut Window,
11634 cx: &mut Context<Self>,
11635 ) {
11636 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11637 cx.propagate();
11638 return;
11639 }
11640 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11641 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11642 s.select_ranges(vec![0..0]);
11643 });
11644 }
11645
11646 pub fn select_to_beginning(
11647 &mut self,
11648 _: &SelectToBeginning,
11649 window: &mut Window,
11650 cx: &mut Context<Self>,
11651 ) {
11652 let mut selection = self.selections.last::<Point>(cx);
11653 selection.set_head(Point::zero(), SelectionGoal::None);
11654 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11655 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11656 s.select(vec![selection]);
11657 });
11658 }
11659
11660 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11661 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11662 cx.propagate();
11663 return;
11664 }
11665 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11666 let cursor = self.buffer.read(cx).read(cx).len();
11667 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11668 s.select_ranges(vec![cursor..cursor])
11669 });
11670 }
11671
11672 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11673 self.nav_history = nav_history;
11674 }
11675
11676 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11677 self.nav_history.as_ref()
11678 }
11679
11680 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11681 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11682 }
11683
11684 fn push_to_nav_history(
11685 &mut self,
11686 cursor_anchor: Anchor,
11687 new_position: Option<Point>,
11688 is_deactivate: bool,
11689 cx: &mut Context<Self>,
11690 ) {
11691 if let Some(nav_history) = self.nav_history.as_mut() {
11692 let buffer = self.buffer.read(cx).read(cx);
11693 let cursor_position = cursor_anchor.to_point(&buffer);
11694 let scroll_state = self.scroll_manager.anchor();
11695 let scroll_top_row = scroll_state.top_row(&buffer);
11696 drop(buffer);
11697
11698 if let Some(new_position) = new_position {
11699 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11700 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11701 return;
11702 }
11703 }
11704
11705 nav_history.push(
11706 Some(NavigationData {
11707 cursor_anchor,
11708 cursor_position,
11709 scroll_anchor: scroll_state,
11710 scroll_top_row,
11711 }),
11712 cx,
11713 );
11714 cx.emit(EditorEvent::PushedToNavHistory {
11715 anchor: cursor_anchor,
11716 is_deactivate,
11717 })
11718 }
11719 }
11720
11721 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11722 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11723 let buffer = self.buffer.read(cx).snapshot(cx);
11724 let mut selection = self.selections.first::<usize>(cx);
11725 selection.set_head(buffer.len(), SelectionGoal::None);
11726 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11727 s.select(vec![selection]);
11728 });
11729 }
11730
11731 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11732 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11733 let end = self.buffer.read(cx).read(cx).len();
11734 self.change_selections(None, window, cx, |s| {
11735 s.select_ranges(vec![0..end]);
11736 });
11737 }
11738
11739 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11740 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11741 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11742 let mut selections = self.selections.all::<Point>(cx);
11743 let max_point = display_map.buffer_snapshot.max_point();
11744 for selection in &mut selections {
11745 let rows = selection.spanned_rows(true, &display_map);
11746 selection.start = Point::new(rows.start.0, 0);
11747 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11748 selection.reversed = false;
11749 }
11750 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11751 s.select(selections);
11752 });
11753 }
11754
11755 pub fn split_selection_into_lines(
11756 &mut self,
11757 _: &SplitSelectionIntoLines,
11758 window: &mut Window,
11759 cx: &mut Context<Self>,
11760 ) {
11761 let selections = self
11762 .selections
11763 .all::<Point>(cx)
11764 .into_iter()
11765 .map(|selection| selection.start..selection.end)
11766 .collect::<Vec<_>>();
11767 self.unfold_ranges(&selections, true, true, cx);
11768
11769 let mut new_selection_ranges = Vec::new();
11770 {
11771 let buffer = self.buffer.read(cx).read(cx);
11772 for selection in selections {
11773 for row in selection.start.row..selection.end.row {
11774 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11775 new_selection_ranges.push(cursor..cursor);
11776 }
11777
11778 let is_multiline_selection = selection.start.row != selection.end.row;
11779 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11780 // so this action feels more ergonomic when paired with other selection operations
11781 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11782 if !should_skip_last {
11783 new_selection_ranges.push(selection.end..selection.end);
11784 }
11785 }
11786 }
11787 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11788 s.select_ranges(new_selection_ranges);
11789 });
11790 }
11791
11792 pub fn add_selection_above(
11793 &mut self,
11794 _: &AddSelectionAbove,
11795 window: &mut Window,
11796 cx: &mut Context<Self>,
11797 ) {
11798 self.add_selection(true, window, cx);
11799 }
11800
11801 pub fn add_selection_below(
11802 &mut self,
11803 _: &AddSelectionBelow,
11804 window: &mut Window,
11805 cx: &mut Context<Self>,
11806 ) {
11807 self.add_selection(false, window, cx);
11808 }
11809
11810 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11811 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11812
11813 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11814 let mut selections = self.selections.all::<Point>(cx);
11815 let text_layout_details = self.text_layout_details(window);
11816 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11817 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11818 let range = oldest_selection.display_range(&display_map).sorted();
11819
11820 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11821 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11822 let positions = start_x.min(end_x)..start_x.max(end_x);
11823
11824 selections.clear();
11825 let mut stack = Vec::new();
11826 for row in range.start.row().0..=range.end.row().0 {
11827 if let Some(selection) = self.selections.build_columnar_selection(
11828 &display_map,
11829 DisplayRow(row),
11830 &positions,
11831 oldest_selection.reversed,
11832 &text_layout_details,
11833 ) {
11834 stack.push(selection.id);
11835 selections.push(selection);
11836 }
11837 }
11838
11839 if above {
11840 stack.reverse();
11841 }
11842
11843 AddSelectionsState { above, stack }
11844 });
11845
11846 let last_added_selection = *state.stack.last().unwrap();
11847 let mut new_selections = Vec::new();
11848 if above == state.above {
11849 let end_row = if above {
11850 DisplayRow(0)
11851 } else {
11852 display_map.max_point().row()
11853 };
11854
11855 'outer: for selection in selections {
11856 if selection.id == last_added_selection {
11857 let range = selection.display_range(&display_map).sorted();
11858 debug_assert_eq!(range.start.row(), range.end.row());
11859 let mut row = range.start.row();
11860 let positions =
11861 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11862 px(start)..px(end)
11863 } else {
11864 let start_x =
11865 display_map.x_for_display_point(range.start, &text_layout_details);
11866 let end_x =
11867 display_map.x_for_display_point(range.end, &text_layout_details);
11868 start_x.min(end_x)..start_x.max(end_x)
11869 };
11870
11871 while row != end_row {
11872 if above {
11873 row.0 -= 1;
11874 } else {
11875 row.0 += 1;
11876 }
11877
11878 if let Some(new_selection) = self.selections.build_columnar_selection(
11879 &display_map,
11880 row,
11881 &positions,
11882 selection.reversed,
11883 &text_layout_details,
11884 ) {
11885 state.stack.push(new_selection.id);
11886 if above {
11887 new_selections.push(new_selection);
11888 new_selections.push(selection);
11889 } else {
11890 new_selections.push(selection);
11891 new_selections.push(new_selection);
11892 }
11893
11894 continue 'outer;
11895 }
11896 }
11897 }
11898
11899 new_selections.push(selection);
11900 }
11901 } else {
11902 new_selections = selections;
11903 new_selections.retain(|s| s.id != last_added_selection);
11904 state.stack.pop();
11905 }
11906
11907 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11908 s.select(new_selections);
11909 });
11910 if state.stack.len() > 1 {
11911 self.add_selections_state = Some(state);
11912 }
11913 }
11914
11915 pub fn select_next_match_internal(
11916 &mut self,
11917 display_map: &DisplaySnapshot,
11918 replace_newest: bool,
11919 autoscroll: Option<Autoscroll>,
11920 window: &mut Window,
11921 cx: &mut Context<Self>,
11922 ) -> Result<()> {
11923 fn select_next_match_ranges(
11924 this: &mut Editor,
11925 range: Range<usize>,
11926 reversed: bool,
11927 replace_newest: bool,
11928 auto_scroll: Option<Autoscroll>,
11929 window: &mut Window,
11930 cx: &mut Context<Editor>,
11931 ) {
11932 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
11933 this.change_selections(auto_scroll, window, cx, |s| {
11934 if replace_newest {
11935 s.delete(s.newest_anchor().id);
11936 }
11937 if reversed {
11938 s.insert_range(range.end..range.start);
11939 } else {
11940 s.insert_range(range);
11941 }
11942 });
11943 }
11944
11945 let buffer = &display_map.buffer_snapshot;
11946 let mut selections = self.selections.all::<usize>(cx);
11947 if let Some(mut select_next_state) = self.select_next_state.take() {
11948 let query = &select_next_state.query;
11949 if !select_next_state.done {
11950 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11951 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11952 let mut next_selected_range = None;
11953
11954 let bytes_after_last_selection =
11955 buffer.bytes_in_range(last_selection.end..buffer.len());
11956 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11957 let query_matches = query
11958 .stream_find_iter(bytes_after_last_selection)
11959 .map(|result| (last_selection.end, result))
11960 .chain(
11961 query
11962 .stream_find_iter(bytes_before_first_selection)
11963 .map(|result| (0, result)),
11964 );
11965
11966 for (start_offset, query_match) in query_matches {
11967 let query_match = query_match.unwrap(); // can only fail due to I/O
11968 let offset_range =
11969 start_offset + query_match.start()..start_offset + query_match.end();
11970 let display_range = offset_range.start.to_display_point(display_map)
11971 ..offset_range.end.to_display_point(display_map);
11972
11973 if !select_next_state.wordwise
11974 || (!movement::is_inside_word(display_map, display_range.start)
11975 && !movement::is_inside_word(display_map, display_range.end))
11976 {
11977 // TODO: This is n^2, because we might check all the selections
11978 if !selections
11979 .iter()
11980 .any(|selection| selection.range().overlaps(&offset_range))
11981 {
11982 next_selected_range = Some(offset_range);
11983 break;
11984 }
11985 }
11986 }
11987
11988 if let Some(next_selected_range) = next_selected_range {
11989 select_next_match_ranges(
11990 self,
11991 next_selected_range,
11992 last_selection.reversed,
11993 replace_newest,
11994 autoscroll,
11995 window,
11996 cx,
11997 );
11998 } else {
11999 select_next_state.done = true;
12000 }
12001 }
12002
12003 self.select_next_state = Some(select_next_state);
12004 } else {
12005 let mut only_carets = true;
12006 let mut same_text_selected = true;
12007 let mut selected_text = None;
12008
12009 let mut selections_iter = selections.iter().peekable();
12010 while let Some(selection) = selections_iter.next() {
12011 if selection.start != selection.end {
12012 only_carets = false;
12013 }
12014
12015 if same_text_selected {
12016 if selected_text.is_none() {
12017 selected_text =
12018 Some(buffer.text_for_range(selection.range()).collect::<String>());
12019 }
12020
12021 if let Some(next_selection) = selections_iter.peek() {
12022 if next_selection.range().len() == selection.range().len() {
12023 let next_selected_text = buffer
12024 .text_for_range(next_selection.range())
12025 .collect::<String>();
12026 if Some(next_selected_text) != selected_text {
12027 same_text_selected = false;
12028 selected_text = None;
12029 }
12030 } else {
12031 same_text_selected = false;
12032 selected_text = None;
12033 }
12034 }
12035 }
12036 }
12037
12038 if only_carets {
12039 for selection in &mut selections {
12040 let word_range = movement::surrounding_word(
12041 display_map,
12042 selection.start.to_display_point(display_map),
12043 );
12044 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12045 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12046 selection.goal = SelectionGoal::None;
12047 selection.reversed = false;
12048 select_next_match_ranges(
12049 self,
12050 selection.start..selection.end,
12051 selection.reversed,
12052 replace_newest,
12053 autoscroll,
12054 window,
12055 cx,
12056 );
12057 }
12058
12059 if selections.len() == 1 {
12060 let selection = selections
12061 .last()
12062 .expect("ensured that there's only one selection");
12063 let query = buffer
12064 .text_for_range(selection.start..selection.end)
12065 .collect::<String>();
12066 let is_empty = query.is_empty();
12067 let select_state = SelectNextState {
12068 query: AhoCorasick::new(&[query])?,
12069 wordwise: true,
12070 done: is_empty,
12071 };
12072 self.select_next_state = Some(select_state);
12073 } else {
12074 self.select_next_state = None;
12075 }
12076 } else if let Some(selected_text) = selected_text {
12077 self.select_next_state = Some(SelectNextState {
12078 query: AhoCorasick::new(&[selected_text])?,
12079 wordwise: false,
12080 done: false,
12081 });
12082 self.select_next_match_internal(
12083 display_map,
12084 replace_newest,
12085 autoscroll,
12086 window,
12087 cx,
12088 )?;
12089 }
12090 }
12091 Ok(())
12092 }
12093
12094 pub fn select_all_matches(
12095 &mut self,
12096 _action: &SelectAllMatches,
12097 window: &mut Window,
12098 cx: &mut Context<Self>,
12099 ) -> Result<()> {
12100 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12101
12102 self.push_to_selection_history();
12103 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12104
12105 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12106 let Some(select_next_state) = self.select_next_state.as_mut() else {
12107 return Ok(());
12108 };
12109 if select_next_state.done {
12110 return Ok(());
12111 }
12112
12113 let mut new_selections = Vec::new();
12114
12115 let reversed = self.selections.oldest::<usize>(cx).reversed;
12116 let buffer = &display_map.buffer_snapshot;
12117 let query_matches = select_next_state
12118 .query
12119 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12120
12121 for query_match in query_matches.into_iter() {
12122 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12123 let offset_range = if reversed {
12124 query_match.end()..query_match.start()
12125 } else {
12126 query_match.start()..query_match.end()
12127 };
12128 let display_range = offset_range.start.to_display_point(&display_map)
12129 ..offset_range.end.to_display_point(&display_map);
12130
12131 if !select_next_state.wordwise
12132 || (!movement::is_inside_word(&display_map, display_range.start)
12133 && !movement::is_inside_word(&display_map, display_range.end))
12134 {
12135 new_selections.push(offset_range.start..offset_range.end);
12136 }
12137 }
12138
12139 select_next_state.done = true;
12140 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12141 self.change_selections(None, window, cx, |selections| {
12142 selections.select_ranges(new_selections)
12143 });
12144
12145 Ok(())
12146 }
12147
12148 pub fn select_next(
12149 &mut self,
12150 action: &SelectNext,
12151 window: &mut Window,
12152 cx: &mut Context<Self>,
12153 ) -> Result<()> {
12154 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12155 self.push_to_selection_history();
12156 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12157 self.select_next_match_internal(
12158 &display_map,
12159 action.replace_newest,
12160 Some(Autoscroll::newest()),
12161 window,
12162 cx,
12163 )?;
12164 Ok(())
12165 }
12166
12167 pub fn select_previous(
12168 &mut self,
12169 action: &SelectPrevious,
12170 window: &mut Window,
12171 cx: &mut Context<Self>,
12172 ) -> Result<()> {
12173 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12174 self.push_to_selection_history();
12175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12176 let buffer = &display_map.buffer_snapshot;
12177 let mut selections = self.selections.all::<usize>(cx);
12178 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12179 let query = &select_prev_state.query;
12180 if !select_prev_state.done {
12181 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12182 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12183 let mut next_selected_range = None;
12184 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12185 let bytes_before_last_selection =
12186 buffer.reversed_bytes_in_range(0..last_selection.start);
12187 let bytes_after_first_selection =
12188 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12189 let query_matches = query
12190 .stream_find_iter(bytes_before_last_selection)
12191 .map(|result| (last_selection.start, result))
12192 .chain(
12193 query
12194 .stream_find_iter(bytes_after_first_selection)
12195 .map(|result| (buffer.len(), result)),
12196 );
12197 for (end_offset, query_match) in query_matches {
12198 let query_match = query_match.unwrap(); // can only fail due to I/O
12199 let offset_range =
12200 end_offset - query_match.end()..end_offset - query_match.start();
12201 let display_range = offset_range.start.to_display_point(&display_map)
12202 ..offset_range.end.to_display_point(&display_map);
12203
12204 if !select_prev_state.wordwise
12205 || (!movement::is_inside_word(&display_map, display_range.start)
12206 && !movement::is_inside_word(&display_map, display_range.end))
12207 {
12208 next_selected_range = Some(offset_range);
12209 break;
12210 }
12211 }
12212
12213 if let Some(next_selected_range) = next_selected_range {
12214 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
12215 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12216 if action.replace_newest {
12217 s.delete(s.newest_anchor().id);
12218 }
12219 if last_selection.reversed {
12220 s.insert_range(next_selected_range.end..next_selected_range.start);
12221 } else {
12222 s.insert_range(next_selected_range);
12223 }
12224 });
12225 } else {
12226 select_prev_state.done = true;
12227 }
12228 }
12229
12230 self.select_prev_state = Some(select_prev_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 }
12276 if selections.len() == 1 {
12277 let selection = selections
12278 .last()
12279 .expect("ensured that there's only one selection");
12280 let query = buffer
12281 .text_for_range(selection.start..selection.end)
12282 .collect::<String>();
12283 let is_empty = query.is_empty();
12284 let select_state = SelectNextState {
12285 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12286 wordwise: true,
12287 done: is_empty,
12288 };
12289 self.select_prev_state = Some(select_state);
12290 } else {
12291 self.select_prev_state = None;
12292 }
12293
12294 self.unfold_ranges(
12295 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
12296 false,
12297 true,
12298 cx,
12299 );
12300 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12301 s.select(selections);
12302 });
12303 } else if let Some(selected_text) = selected_text {
12304 self.select_prev_state = Some(SelectNextState {
12305 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12306 wordwise: false,
12307 done: false,
12308 });
12309 self.select_previous(action, window, cx)?;
12310 }
12311 }
12312 Ok(())
12313 }
12314
12315 pub fn find_next_match(
12316 &mut self,
12317 _: &FindNextMatch,
12318 window: &mut Window,
12319 cx: &mut Context<Self>,
12320 ) -> Result<()> {
12321 let selections = self.selections.disjoint_anchors();
12322 match selections.first() {
12323 Some(first) if selections.len() >= 2 => {
12324 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12325 s.select_ranges([first.range()]);
12326 });
12327 }
12328 _ => self.select_next(
12329 &SelectNext {
12330 replace_newest: true,
12331 },
12332 window,
12333 cx,
12334 )?,
12335 }
12336 Ok(())
12337 }
12338
12339 pub fn find_previous_match(
12340 &mut self,
12341 _: &FindPreviousMatch,
12342 window: &mut Window,
12343 cx: &mut Context<Self>,
12344 ) -> Result<()> {
12345 let selections = self.selections.disjoint_anchors();
12346 match selections.last() {
12347 Some(last) if selections.len() >= 2 => {
12348 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12349 s.select_ranges([last.range()]);
12350 });
12351 }
12352 _ => self.select_previous(
12353 &SelectPrevious {
12354 replace_newest: true,
12355 },
12356 window,
12357 cx,
12358 )?,
12359 }
12360 Ok(())
12361 }
12362
12363 pub fn toggle_comments(
12364 &mut self,
12365 action: &ToggleComments,
12366 window: &mut Window,
12367 cx: &mut Context<Self>,
12368 ) {
12369 if self.read_only(cx) {
12370 return;
12371 }
12372 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12373 let text_layout_details = &self.text_layout_details(window);
12374 self.transact(window, cx, |this, window, cx| {
12375 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12376 let mut edits = Vec::new();
12377 let mut selection_edit_ranges = Vec::new();
12378 let mut last_toggled_row = None;
12379 let snapshot = this.buffer.read(cx).read(cx);
12380 let empty_str: Arc<str> = Arc::default();
12381 let mut suffixes_inserted = Vec::new();
12382 let ignore_indent = action.ignore_indent;
12383
12384 fn comment_prefix_range(
12385 snapshot: &MultiBufferSnapshot,
12386 row: MultiBufferRow,
12387 comment_prefix: &str,
12388 comment_prefix_whitespace: &str,
12389 ignore_indent: bool,
12390 ) -> Range<Point> {
12391 let indent_size = if ignore_indent {
12392 0
12393 } else {
12394 snapshot.indent_size_for_line(row).len
12395 };
12396
12397 let start = Point::new(row.0, indent_size);
12398
12399 let mut line_bytes = snapshot
12400 .bytes_in_range(start..snapshot.max_point())
12401 .flatten()
12402 .copied();
12403
12404 // If this line currently begins with the line comment prefix, then record
12405 // the range containing the prefix.
12406 if line_bytes
12407 .by_ref()
12408 .take(comment_prefix.len())
12409 .eq(comment_prefix.bytes())
12410 {
12411 // Include any whitespace that matches the comment prefix.
12412 let matching_whitespace_len = line_bytes
12413 .zip(comment_prefix_whitespace.bytes())
12414 .take_while(|(a, b)| a == b)
12415 .count() as u32;
12416 let end = Point::new(
12417 start.row,
12418 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12419 );
12420 start..end
12421 } else {
12422 start..start
12423 }
12424 }
12425
12426 fn comment_suffix_range(
12427 snapshot: &MultiBufferSnapshot,
12428 row: MultiBufferRow,
12429 comment_suffix: &str,
12430 comment_suffix_has_leading_space: bool,
12431 ) -> Range<Point> {
12432 let end = Point::new(row.0, snapshot.line_len(row));
12433 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12434
12435 let mut line_end_bytes = snapshot
12436 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12437 .flatten()
12438 .copied();
12439
12440 let leading_space_len = if suffix_start_column > 0
12441 && line_end_bytes.next() == Some(b' ')
12442 && comment_suffix_has_leading_space
12443 {
12444 1
12445 } else {
12446 0
12447 };
12448
12449 // If this line currently begins with the line comment prefix, then record
12450 // the range containing the prefix.
12451 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12452 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12453 start..end
12454 } else {
12455 end..end
12456 }
12457 }
12458
12459 // TODO: Handle selections that cross excerpts
12460 for selection in &mut selections {
12461 let start_column = snapshot
12462 .indent_size_for_line(MultiBufferRow(selection.start.row))
12463 .len;
12464 let language = if let Some(language) =
12465 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12466 {
12467 language
12468 } else {
12469 continue;
12470 };
12471
12472 selection_edit_ranges.clear();
12473
12474 // If multiple selections contain a given row, avoid processing that
12475 // row more than once.
12476 let mut start_row = MultiBufferRow(selection.start.row);
12477 if last_toggled_row == Some(start_row) {
12478 start_row = start_row.next_row();
12479 }
12480 let end_row =
12481 if selection.end.row > selection.start.row && selection.end.column == 0 {
12482 MultiBufferRow(selection.end.row - 1)
12483 } else {
12484 MultiBufferRow(selection.end.row)
12485 };
12486 last_toggled_row = Some(end_row);
12487
12488 if start_row > end_row {
12489 continue;
12490 }
12491
12492 // If the language has line comments, toggle those.
12493 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12494
12495 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12496 if ignore_indent {
12497 full_comment_prefixes = full_comment_prefixes
12498 .into_iter()
12499 .map(|s| Arc::from(s.trim_end()))
12500 .collect();
12501 }
12502
12503 if !full_comment_prefixes.is_empty() {
12504 let first_prefix = full_comment_prefixes
12505 .first()
12506 .expect("prefixes is non-empty");
12507 let prefix_trimmed_lengths = full_comment_prefixes
12508 .iter()
12509 .map(|p| p.trim_end_matches(' ').len())
12510 .collect::<SmallVec<[usize; 4]>>();
12511
12512 let mut all_selection_lines_are_comments = true;
12513
12514 for row in start_row.0..=end_row.0 {
12515 let row = MultiBufferRow(row);
12516 if start_row < end_row && snapshot.is_line_blank(row) {
12517 continue;
12518 }
12519
12520 let prefix_range = full_comment_prefixes
12521 .iter()
12522 .zip(prefix_trimmed_lengths.iter().copied())
12523 .map(|(prefix, trimmed_prefix_len)| {
12524 comment_prefix_range(
12525 snapshot.deref(),
12526 row,
12527 &prefix[..trimmed_prefix_len],
12528 &prefix[trimmed_prefix_len..],
12529 ignore_indent,
12530 )
12531 })
12532 .max_by_key(|range| range.end.column - range.start.column)
12533 .expect("prefixes is non-empty");
12534
12535 if prefix_range.is_empty() {
12536 all_selection_lines_are_comments = false;
12537 }
12538
12539 selection_edit_ranges.push(prefix_range);
12540 }
12541
12542 if all_selection_lines_are_comments {
12543 edits.extend(
12544 selection_edit_ranges
12545 .iter()
12546 .cloned()
12547 .map(|range| (range, empty_str.clone())),
12548 );
12549 } else {
12550 let min_column = selection_edit_ranges
12551 .iter()
12552 .map(|range| range.start.column)
12553 .min()
12554 .unwrap_or(0);
12555 edits.extend(selection_edit_ranges.iter().map(|range| {
12556 let position = Point::new(range.start.row, min_column);
12557 (position..position, first_prefix.clone())
12558 }));
12559 }
12560 } else if let Some((full_comment_prefix, comment_suffix)) =
12561 language.block_comment_delimiters()
12562 {
12563 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12564 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12565 let prefix_range = comment_prefix_range(
12566 snapshot.deref(),
12567 start_row,
12568 comment_prefix,
12569 comment_prefix_whitespace,
12570 ignore_indent,
12571 );
12572 let suffix_range = comment_suffix_range(
12573 snapshot.deref(),
12574 end_row,
12575 comment_suffix.trim_start_matches(' '),
12576 comment_suffix.starts_with(' '),
12577 );
12578
12579 if prefix_range.is_empty() || suffix_range.is_empty() {
12580 edits.push((
12581 prefix_range.start..prefix_range.start,
12582 full_comment_prefix.clone(),
12583 ));
12584 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12585 suffixes_inserted.push((end_row, comment_suffix.len()));
12586 } else {
12587 edits.push((prefix_range, empty_str.clone()));
12588 edits.push((suffix_range, empty_str.clone()));
12589 }
12590 } else {
12591 continue;
12592 }
12593 }
12594
12595 drop(snapshot);
12596 this.buffer.update(cx, |buffer, cx| {
12597 buffer.edit(edits, None, cx);
12598 });
12599
12600 // Adjust selections so that they end before any comment suffixes that
12601 // were inserted.
12602 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12603 let mut selections = this.selections.all::<Point>(cx);
12604 let snapshot = this.buffer.read(cx).read(cx);
12605 for selection in &mut selections {
12606 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12607 match row.cmp(&MultiBufferRow(selection.end.row)) {
12608 Ordering::Less => {
12609 suffixes_inserted.next();
12610 continue;
12611 }
12612 Ordering::Greater => break,
12613 Ordering::Equal => {
12614 if selection.end.column == snapshot.line_len(row) {
12615 if selection.is_empty() {
12616 selection.start.column -= suffix_len as u32;
12617 }
12618 selection.end.column -= suffix_len as u32;
12619 }
12620 break;
12621 }
12622 }
12623 }
12624 }
12625
12626 drop(snapshot);
12627 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12628 s.select(selections)
12629 });
12630
12631 let selections = this.selections.all::<Point>(cx);
12632 let selections_on_single_row = selections.windows(2).all(|selections| {
12633 selections[0].start.row == selections[1].start.row
12634 && selections[0].end.row == selections[1].end.row
12635 && selections[0].start.row == selections[0].end.row
12636 });
12637 let selections_selecting = selections
12638 .iter()
12639 .any(|selection| selection.start != selection.end);
12640 let advance_downwards = action.advance_downwards
12641 && selections_on_single_row
12642 && !selections_selecting
12643 && !matches!(this.mode, EditorMode::SingleLine { .. });
12644
12645 if advance_downwards {
12646 let snapshot = this.buffer.read(cx).snapshot(cx);
12647
12648 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12649 s.move_cursors_with(|display_snapshot, display_point, _| {
12650 let mut point = display_point.to_point(display_snapshot);
12651 point.row += 1;
12652 point = snapshot.clip_point(point, Bias::Left);
12653 let display_point = point.to_display_point(display_snapshot);
12654 let goal = SelectionGoal::HorizontalPosition(
12655 display_snapshot
12656 .x_for_display_point(display_point, text_layout_details)
12657 .into(),
12658 );
12659 (display_point, goal)
12660 })
12661 });
12662 }
12663 });
12664 }
12665
12666 pub fn select_enclosing_symbol(
12667 &mut self,
12668 _: &SelectEnclosingSymbol,
12669 window: &mut Window,
12670 cx: &mut Context<Self>,
12671 ) {
12672 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12673
12674 let buffer = self.buffer.read(cx).snapshot(cx);
12675 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12676
12677 fn update_selection(
12678 selection: &Selection<usize>,
12679 buffer_snap: &MultiBufferSnapshot,
12680 ) -> Option<Selection<usize>> {
12681 let cursor = selection.head();
12682 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12683 for symbol in symbols.iter().rev() {
12684 let start = symbol.range.start.to_offset(buffer_snap);
12685 let end = symbol.range.end.to_offset(buffer_snap);
12686 let new_range = start..end;
12687 if start < selection.start || end > selection.end {
12688 return Some(Selection {
12689 id: selection.id,
12690 start: new_range.start,
12691 end: new_range.end,
12692 goal: SelectionGoal::None,
12693 reversed: selection.reversed,
12694 });
12695 }
12696 }
12697 None
12698 }
12699
12700 let mut selected_larger_symbol = false;
12701 let new_selections = old_selections
12702 .iter()
12703 .map(|selection| match update_selection(selection, &buffer) {
12704 Some(new_selection) => {
12705 if new_selection.range() != selection.range() {
12706 selected_larger_symbol = true;
12707 }
12708 new_selection
12709 }
12710 None => selection.clone(),
12711 })
12712 .collect::<Vec<_>>();
12713
12714 if selected_larger_symbol {
12715 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12716 s.select(new_selections);
12717 });
12718 }
12719 }
12720
12721 pub fn select_larger_syntax_node(
12722 &mut self,
12723 _: &SelectLargerSyntaxNode,
12724 window: &mut Window,
12725 cx: &mut Context<Self>,
12726 ) {
12727 let Some(visible_row_count) = self.visible_row_count() else {
12728 return;
12729 };
12730 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12731 if old_selections.is_empty() {
12732 return;
12733 }
12734
12735 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12736
12737 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12738 let buffer = self.buffer.read(cx).snapshot(cx);
12739
12740 let mut selected_larger_node = false;
12741 let mut new_selections = old_selections
12742 .iter()
12743 .map(|selection| {
12744 let old_range = selection.start..selection.end;
12745
12746 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
12747 // manually select word at selection
12748 if ["string_content", "inline"].contains(&node.kind()) {
12749 let word_range = {
12750 let display_point = buffer
12751 .offset_to_point(old_range.start)
12752 .to_display_point(&display_map);
12753 let Range { start, end } =
12754 movement::surrounding_word(&display_map, display_point);
12755 start.to_point(&display_map).to_offset(&buffer)
12756 ..end.to_point(&display_map).to_offset(&buffer)
12757 };
12758 // ignore if word is already selected
12759 if !word_range.is_empty() && old_range != word_range {
12760 let last_word_range = {
12761 let display_point = buffer
12762 .offset_to_point(old_range.end)
12763 .to_display_point(&display_map);
12764 let Range { start, end } =
12765 movement::surrounding_word(&display_map, display_point);
12766 start.to_point(&display_map).to_offset(&buffer)
12767 ..end.to_point(&display_map).to_offset(&buffer)
12768 };
12769 // only select word if start and end point belongs to same word
12770 if word_range == last_word_range {
12771 selected_larger_node = true;
12772 return Selection {
12773 id: selection.id,
12774 start: word_range.start,
12775 end: word_range.end,
12776 goal: SelectionGoal::None,
12777 reversed: selection.reversed,
12778 };
12779 }
12780 }
12781 }
12782 }
12783
12784 let mut new_range = old_range.clone();
12785 let mut new_node = None;
12786 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12787 {
12788 new_node = Some(node);
12789 new_range = match containing_range {
12790 MultiOrSingleBufferOffsetRange::Single(_) => break,
12791 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12792 };
12793 if !display_map.intersects_fold(new_range.start)
12794 && !display_map.intersects_fold(new_range.end)
12795 {
12796 break;
12797 }
12798 }
12799
12800 if let Some(node) = new_node {
12801 // Log the ancestor, to support using this action as a way to explore TreeSitter
12802 // nodes. Parent and grandparent are also logged because this operation will not
12803 // visit nodes that have the same range as their parent.
12804 log::info!("Node: {node:?}");
12805 let parent = node.parent();
12806 log::info!("Parent: {parent:?}");
12807 let grandparent = parent.and_then(|x| x.parent());
12808 log::info!("Grandparent: {grandparent:?}");
12809 }
12810
12811 selected_larger_node |= new_range != old_range;
12812 Selection {
12813 id: selection.id,
12814 start: new_range.start,
12815 end: new_range.end,
12816 goal: SelectionGoal::None,
12817 reversed: selection.reversed,
12818 }
12819 })
12820 .collect::<Vec<_>>();
12821
12822 if !selected_larger_node {
12823 return; // don't put this call in the history
12824 }
12825
12826 // scroll based on transformation done to the last selection created by the user
12827 let (last_old, last_new) = old_selections
12828 .last()
12829 .zip(new_selections.last().cloned())
12830 .expect("old_selections isn't empty");
12831
12832 // revert selection
12833 let is_selection_reversed = {
12834 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12835 new_selections.last_mut().expect("checked above").reversed =
12836 should_newest_selection_be_reversed;
12837 should_newest_selection_be_reversed
12838 };
12839
12840 if selected_larger_node {
12841 self.select_syntax_node_history.disable_clearing = true;
12842 self.change_selections(None, window, cx, |s| {
12843 s.select(new_selections.clone());
12844 });
12845 self.select_syntax_node_history.disable_clearing = false;
12846 }
12847
12848 let start_row = last_new.start.to_display_point(&display_map).row().0;
12849 let end_row = last_new.end.to_display_point(&display_map).row().0;
12850 let selection_height = end_row - start_row + 1;
12851 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12852
12853 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
12854 let scroll_behavior = if fits_on_the_screen {
12855 self.request_autoscroll(Autoscroll::fit(), cx);
12856 SelectSyntaxNodeScrollBehavior::FitSelection
12857 } else if is_selection_reversed {
12858 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12859 SelectSyntaxNodeScrollBehavior::CursorTop
12860 } else {
12861 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12862 SelectSyntaxNodeScrollBehavior::CursorBottom
12863 };
12864
12865 self.select_syntax_node_history.push((
12866 old_selections,
12867 scroll_behavior,
12868 is_selection_reversed,
12869 ));
12870 }
12871
12872 pub fn select_smaller_syntax_node(
12873 &mut self,
12874 _: &SelectSmallerSyntaxNode,
12875 window: &mut Window,
12876 cx: &mut Context<Self>,
12877 ) {
12878 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12879
12880 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12881 self.select_syntax_node_history.pop()
12882 {
12883 if let Some(selection) = selections.last_mut() {
12884 selection.reversed = is_selection_reversed;
12885 }
12886
12887 self.select_syntax_node_history.disable_clearing = true;
12888 self.change_selections(None, window, cx, |s| {
12889 s.select(selections.to_vec());
12890 });
12891 self.select_syntax_node_history.disable_clearing = false;
12892
12893 match scroll_behavior {
12894 SelectSyntaxNodeScrollBehavior::CursorTop => {
12895 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12896 }
12897 SelectSyntaxNodeScrollBehavior::FitSelection => {
12898 self.request_autoscroll(Autoscroll::fit(), cx);
12899 }
12900 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12901 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12902 }
12903 }
12904 }
12905 }
12906
12907 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12908 if !EditorSettings::get_global(cx).gutter.runnables {
12909 self.clear_tasks();
12910 return Task::ready(());
12911 }
12912 let project = self.project.as_ref().map(Entity::downgrade);
12913 let task_sources = self.lsp_task_sources(cx);
12914 cx.spawn_in(window, async move |editor, cx| {
12915 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12916 let Some(project) = project.and_then(|p| p.upgrade()) else {
12917 return;
12918 };
12919 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
12920 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12921 }) else {
12922 return;
12923 };
12924
12925 let hide_runnables = project
12926 .update(cx, |project, cx| {
12927 // Do not display any test indicators in non-dev server remote projects.
12928 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12929 })
12930 .unwrap_or(true);
12931 if hide_runnables {
12932 return;
12933 }
12934 let new_rows =
12935 cx.background_spawn({
12936 let snapshot = display_snapshot.clone();
12937 async move {
12938 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12939 }
12940 })
12941 .await;
12942 let Ok(lsp_tasks) =
12943 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
12944 else {
12945 return;
12946 };
12947 let lsp_tasks = lsp_tasks.await;
12948
12949 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
12950 lsp_tasks
12951 .into_iter()
12952 .flat_map(|(kind, tasks)| {
12953 tasks.into_iter().filter_map(move |(location, task)| {
12954 Some((kind.clone(), location?, task))
12955 })
12956 })
12957 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
12958 let buffer = location.target.buffer;
12959 let buffer_snapshot = buffer.read(cx).snapshot();
12960 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
12961 |(excerpt_id, snapshot, _)| {
12962 if snapshot.remote_id() == buffer_snapshot.remote_id() {
12963 display_snapshot
12964 .buffer_snapshot
12965 .anchor_in_excerpt(excerpt_id, location.target.range.start)
12966 } else {
12967 None
12968 }
12969 },
12970 );
12971 if let Some(offset) = offset {
12972 let task_buffer_range =
12973 location.target.range.to_point(&buffer_snapshot);
12974 let context_buffer_range =
12975 task_buffer_range.to_offset(&buffer_snapshot);
12976 let context_range = BufferOffset(context_buffer_range.start)
12977 ..BufferOffset(context_buffer_range.end);
12978
12979 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
12980 .or_insert_with(|| RunnableTasks {
12981 templates: Vec::new(),
12982 offset,
12983 column: task_buffer_range.start.column,
12984 extra_variables: HashMap::default(),
12985 context_range,
12986 })
12987 .templates
12988 .push((kind, task.original_task().clone()));
12989 }
12990
12991 acc
12992 })
12993 }) else {
12994 return;
12995 };
12996
12997 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12998 editor
12999 .update(cx, |editor, _| {
13000 editor.clear_tasks();
13001 for (key, mut value) in rows {
13002 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13003 value.templates.extend(lsp_tasks.templates);
13004 }
13005
13006 editor.insert_tasks(key, value);
13007 }
13008 for (key, value) in lsp_tasks_by_rows {
13009 editor.insert_tasks(key, value);
13010 }
13011 })
13012 .ok();
13013 })
13014 }
13015 fn fetch_runnable_ranges(
13016 snapshot: &DisplaySnapshot,
13017 range: Range<Anchor>,
13018 ) -> Vec<language::RunnableRange> {
13019 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13020 }
13021
13022 fn runnable_rows(
13023 project: Entity<Project>,
13024 snapshot: DisplaySnapshot,
13025 runnable_ranges: Vec<RunnableRange>,
13026 mut cx: AsyncWindowContext,
13027 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13028 runnable_ranges
13029 .into_iter()
13030 .filter_map(|mut runnable| {
13031 let tasks = cx
13032 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13033 .ok()?;
13034 if tasks.is_empty() {
13035 return None;
13036 }
13037
13038 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13039
13040 let row = snapshot
13041 .buffer_snapshot
13042 .buffer_line_for_row(MultiBufferRow(point.row))?
13043 .1
13044 .start
13045 .row;
13046
13047 let context_range =
13048 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13049 Some((
13050 (runnable.buffer_id, row),
13051 RunnableTasks {
13052 templates: tasks,
13053 offset: snapshot
13054 .buffer_snapshot
13055 .anchor_before(runnable.run_range.start),
13056 context_range,
13057 column: point.column,
13058 extra_variables: runnable.extra_captures,
13059 },
13060 ))
13061 })
13062 .collect()
13063 }
13064
13065 fn templates_with_tags(
13066 project: &Entity<Project>,
13067 runnable: &mut Runnable,
13068 cx: &mut App,
13069 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13070 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13071 let (worktree_id, file) = project
13072 .buffer_for_id(runnable.buffer, cx)
13073 .and_then(|buffer| buffer.read(cx).file())
13074 .map(|file| (file.worktree_id(cx), file.clone()))
13075 .unzip();
13076
13077 (
13078 project.task_store().read(cx).task_inventory().cloned(),
13079 worktree_id,
13080 file,
13081 )
13082 });
13083
13084 let mut templates_with_tags = mem::take(&mut runnable.tags)
13085 .into_iter()
13086 .flat_map(|RunnableTag(tag)| {
13087 inventory
13088 .as_ref()
13089 .into_iter()
13090 .flat_map(|inventory| {
13091 inventory.read(cx).list_tasks(
13092 file.clone(),
13093 Some(runnable.language.clone()),
13094 worktree_id,
13095 cx,
13096 )
13097 })
13098 .filter(move |(_, template)| {
13099 template.tags.iter().any(|source_tag| source_tag == &tag)
13100 })
13101 })
13102 .sorted_by_key(|(kind, _)| kind.to_owned())
13103 .collect::<Vec<_>>();
13104 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13105 // Strongest source wins; if we have worktree tag binding, prefer that to
13106 // global and language bindings;
13107 // if we have a global binding, prefer that to language binding.
13108 let first_mismatch = templates_with_tags
13109 .iter()
13110 .position(|(tag_source, _)| tag_source != leading_tag_source);
13111 if let Some(index) = first_mismatch {
13112 templates_with_tags.truncate(index);
13113 }
13114 }
13115
13116 templates_with_tags
13117 }
13118
13119 pub fn move_to_enclosing_bracket(
13120 &mut self,
13121 _: &MoveToEnclosingBracket,
13122 window: &mut Window,
13123 cx: &mut Context<Self>,
13124 ) {
13125 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13126 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13127 s.move_offsets_with(|snapshot, selection| {
13128 let Some(enclosing_bracket_ranges) =
13129 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13130 else {
13131 return;
13132 };
13133
13134 let mut best_length = usize::MAX;
13135 let mut best_inside = false;
13136 let mut best_in_bracket_range = false;
13137 let mut best_destination = None;
13138 for (open, close) in enclosing_bracket_ranges {
13139 let close = close.to_inclusive();
13140 let length = close.end() - open.start;
13141 let inside = selection.start >= open.end && selection.end <= *close.start();
13142 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13143 || close.contains(&selection.head());
13144
13145 // If best is next to a bracket and current isn't, skip
13146 if !in_bracket_range && best_in_bracket_range {
13147 continue;
13148 }
13149
13150 // Prefer smaller lengths unless best is inside and current isn't
13151 if length > best_length && (best_inside || !inside) {
13152 continue;
13153 }
13154
13155 best_length = length;
13156 best_inside = inside;
13157 best_in_bracket_range = in_bracket_range;
13158 best_destination = Some(
13159 if close.contains(&selection.start) && close.contains(&selection.end) {
13160 if inside { open.end } else { open.start }
13161 } else if inside {
13162 *close.start()
13163 } else {
13164 *close.end()
13165 },
13166 );
13167 }
13168
13169 if let Some(destination) = best_destination {
13170 selection.collapse_to(destination, SelectionGoal::None);
13171 }
13172 })
13173 });
13174 }
13175
13176 pub fn undo_selection(
13177 &mut self,
13178 _: &UndoSelection,
13179 window: &mut Window,
13180 cx: &mut Context<Self>,
13181 ) {
13182 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13183 self.end_selection(window, cx);
13184 self.selection_history.mode = SelectionHistoryMode::Undoing;
13185 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13186 self.change_selections(None, window, cx, |s| {
13187 s.select_anchors(entry.selections.to_vec())
13188 });
13189 self.select_next_state = entry.select_next_state;
13190 self.select_prev_state = entry.select_prev_state;
13191 self.add_selections_state = entry.add_selections_state;
13192 self.request_autoscroll(Autoscroll::newest(), cx);
13193 }
13194 self.selection_history.mode = SelectionHistoryMode::Normal;
13195 }
13196
13197 pub fn redo_selection(
13198 &mut self,
13199 _: &RedoSelection,
13200 window: &mut Window,
13201 cx: &mut Context<Self>,
13202 ) {
13203 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13204 self.end_selection(window, cx);
13205 self.selection_history.mode = SelectionHistoryMode::Redoing;
13206 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13207 self.change_selections(None, window, cx, |s| {
13208 s.select_anchors(entry.selections.to_vec())
13209 });
13210 self.select_next_state = entry.select_next_state;
13211 self.select_prev_state = entry.select_prev_state;
13212 self.add_selections_state = entry.add_selections_state;
13213 self.request_autoscroll(Autoscroll::newest(), cx);
13214 }
13215 self.selection_history.mode = SelectionHistoryMode::Normal;
13216 }
13217
13218 pub fn expand_excerpts(
13219 &mut self,
13220 action: &ExpandExcerpts,
13221 _: &mut Window,
13222 cx: &mut Context<Self>,
13223 ) {
13224 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13225 }
13226
13227 pub fn expand_excerpts_down(
13228 &mut self,
13229 action: &ExpandExcerptsDown,
13230 _: &mut Window,
13231 cx: &mut Context<Self>,
13232 ) {
13233 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13234 }
13235
13236 pub fn expand_excerpts_up(
13237 &mut self,
13238 action: &ExpandExcerptsUp,
13239 _: &mut Window,
13240 cx: &mut Context<Self>,
13241 ) {
13242 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13243 }
13244
13245 pub fn expand_excerpts_for_direction(
13246 &mut self,
13247 lines: u32,
13248 direction: ExpandExcerptDirection,
13249
13250 cx: &mut Context<Self>,
13251 ) {
13252 let selections = self.selections.disjoint_anchors();
13253
13254 let lines = if lines == 0 {
13255 EditorSettings::get_global(cx).expand_excerpt_lines
13256 } else {
13257 lines
13258 };
13259
13260 self.buffer.update(cx, |buffer, cx| {
13261 let snapshot = buffer.snapshot(cx);
13262 let mut excerpt_ids = selections
13263 .iter()
13264 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13265 .collect::<Vec<_>>();
13266 excerpt_ids.sort();
13267 excerpt_ids.dedup();
13268 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13269 })
13270 }
13271
13272 pub fn expand_excerpt(
13273 &mut self,
13274 excerpt: ExcerptId,
13275 direction: ExpandExcerptDirection,
13276 window: &mut Window,
13277 cx: &mut Context<Self>,
13278 ) {
13279 let current_scroll_position = self.scroll_position(cx);
13280 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13281 let mut should_scroll_up = false;
13282
13283 if direction == ExpandExcerptDirection::Down {
13284 let multi_buffer = self.buffer.read(cx);
13285 let snapshot = multi_buffer.snapshot(cx);
13286 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13287 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13288 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13289 let buffer_snapshot = buffer.read(cx).snapshot();
13290 let excerpt_end_row =
13291 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13292 let last_row = buffer_snapshot.max_point().row;
13293 let lines_below = last_row.saturating_sub(excerpt_end_row);
13294 should_scroll_up = lines_below >= lines_to_expand;
13295 }
13296 }
13297 }
13298 }
13299
13300 self.buffer.update(cx, |buffer, cx| {
13301 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13302 });
13303
13304 if should_scroll_up {
13305 let new_scroll_position =
13306 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13307 self.set_scroll_position(new_scroll_position, window, cx);
13308 }
13309 }
13310
13311 pub fn go_to_singleton_buffer_point(
13312 &mut self,
13313 point: Point,
13314 window: &mut Window,
13315 cx: &mut Context<Self>,
13316 ) {
13317 self.go_to_singleton_buffer_range(point..point, window, cx);
13318 }
13319
13320 pub fn go_to_singleton_buffer_range(
13321 &mut self,
13322 range: Range<Point>,
13323 window: &mut Window,
13324 cx: &mut Context<Self>,
13325 ) {
13326 let multibuffer = self.buffer().read(cx);
13327 let Some(buffer) = multibuffer.as_singleton() else {
13328 return;
13329 };
13330 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13331 return;
13332 };
13333 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13334 return;
13335 };
13336 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13337 s.select_anchor_ranges([start..end])
13338 });
13339 }
13340
13341 pub fn go_to_diagnostic(
13342 &mut self,
13343 _: &GoToDiagnostic,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) {
13347 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13348 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13349 }
13350
13351 pub fn go_to_prev_diagnostic(
13352 &mut self,
13353 _: &GoToPreviousDiagnostic,
13354 window: &mut Window,
13355 cx: &mut Context<Self>,
13356 ) {
13357 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13358 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13359 }
13360
13361 pub fn go_to_diagnostic_impl(
13362 &mut self,
13363 direction: Direction,
13364 window: &mut Window,
13365 cx: &mut Context<Self>,
13366 ) {
13367 let buffer = self.buffer.read(cx).snapshot(cx);
13368 let selection = self.selections.newest::<usize>(cx);
13369
13370 let mut active_group_id = None;
13371 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13372 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13373 active_group_id = Some(active_group.group_id);
13374 }
13375 }
13376
13377 fn filtered(
13378 snapshot: EditorSnapshot,
13379 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13380 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13381 diagnostics
13382 .filter(|entry| entry.range.start != entry.range.end)
13383 .filter(|entry| !entry.diagnostic.is_unnecessary)
13384 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13385 }
13386
13387 let snapshot = self.snapshot(window, cx);
13388 let before = filtered(
13389 snapshot.clone(),
13390 buffer
13391 .diagnostics_in_range(0..selection.start)
13392 .filter(|entry| entry.range.start <= selection.start),
13393 );
13394 let after = filtered(
13395 snapshot,
13396 buffer
13397 .diagnostics_in_range(selection.start..buffer.len())
13398 .filter(|entry| entry.range.start >= selection.start),
13399 );
13400
13401 let mut found: Option<DiagnosticEntry<usize>> = None;
13402 if direction == Direction::Prev {
13403 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13404 {
13405 for diagnostic in prev_diagnostics.into_iter().rev() {
13406 if diagnostic.range.start != selection.start
13407 || active_group_id
13408 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13409 {
13410 found = Some(diagnostic);
13411 break 'outer;
13412 }
13413 }
13414 }
13415 } else {
13416 for diagnostic in after.chain(before) {
13417 if diagnostic.range.start != selection.start
13418 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13419 {
13420 found = Some(diagnostic);
13421 break;
13422 }
13423 }
13424 }
13425 let Some(next_diagnostic) = found else {
13426 return;
13427 };
13428
13429 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13430 return;
13431 };
13432 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13433 s.select_ranges(vec![
13434 next_diagnostic.range.start..next_diagnostic.range.start,
13435 ])
13436 });
13437 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13438 self.refresh_inline_completion(false, true, window, cx);
13439 }
13440
13441 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13442 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13443 let snapshot = self.snapshot(window, cx);
13444 let selection = self.selections.newest::<Point>(cx);
13445 self.go_to_hunk_before_or_after_position(
13446 &snapshot,
13447 selection.head(),
13448 Direction::Next,
13449 window,
13450 cx,
13451 );
13452 }
13453
13454 pub fn go_to_hunk_before_or_after_position(
13455 &mut self,
13456 snapshot: &EditorSnapshot,
13457 position: Point,
13458 direction: Direction,
13459 window: &mut Window,
13460 cx: &mut Context<Editor>,
13461 ) {
13462 let row = if direction == Direction::Next {
13463 self.hunk_after_position(snapshot, position)
13464 .map(|hunk| hunk.row_range.start)
13465 } else {
13466 self.hunk_before_position(snapshot, position)
13467 };
13468
13469 if let Some(row) = row {
13470 let destination = Point::new(row.0, 0);
13471 let autoscroll = Autoscroll::center();
13472
13473 self.unfold_ranges(&[destination..destination], false, false, cx);
13474 self.change_selections(Some(autoscroll), window, cx, |s| {
13475 s.select_ranges([destination..destination]);
13476 });
13477 }
13478 }
13479
13480 fn hunk_after_position(
13481 &mut self,
13482 snapshot: &EditorSnapshot,
13483 position: Point,
13484 ) -> Option<MultiBufferDiffHunk> {
13485 snapshot
13486 .buffer_snapshot
13487 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13488 .find(|hunk| hunk.row_range.start.0 > position.row)
13489 .or_else(|| {
13490 snapshot
13491 .buffer_snapshot
13492 .diff_hunks_in_range(Point::zero()..position)
13493 .find(|hunk| hunk.row_range.end.0 < position.row)
13494 })
13495 }
13496
13497 fn go_to_prev_hunk(
13498 &mut self,
13499 _: &GoToPreviousHunk,
13500 window: &mut Window,
13501 cx: &mut Context<Self>,
13502 ) {
13503 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13504 let snapshot = self.snapshot(window, cx);
13505 let selection = self.selections.newest::<Point>(cx);
13506 self.go_to_hunk_before_or_after_position(
13507 &snapshot,
13508 selection.head(),
13509 Direction::Prev,
13510 window,
13511 cx,
13512 );
13513 }
13514
13515 fn hunk_before_position(
13516 &mut self,
13517 snapshot: &EditorSnapshot,
13518 position: Point,
13519 ) -> Option<MultiBufferRow> {
13520 snapshot
13521 .buffer_snapshot
13522 .diff_hunk_before(position)
13523 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13524 }
13525
13526 fn go_to_next_change(
13527 &mut self,
13528 _: &GoToNextChange,
13529 window: &mut Window,
13530 cx: &mut Context<Self>,
13531 ) {
13532 if let Some(selections) = self
13533 .change_list
13534 .next_change(1, Direction::Next)
13535 .map(|s| s.to_vec())
13536 {
13537 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13538 let map = s.display_map();
13539 s.select_display_ranges(selections.iter().map(|a| {
13540 let point = a.to_display_point(&map);
13541 point..point
13542 }))
13543 })
13544 }
13545 }
13546
13547 fn go_to_previous_change(
13548 &mut self,
13549 _: &GoToPreviousChange,
13550 window: &mut Window,
13551 cx: &mut Context<Self>,
13552 ) {
13553 if let Some(selections) = self
13554 .change_list
13555 .next_change(1, Direction::Prev)
13556 .map(|s| s.to_vec())
13557 {
13558 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13559 let map = s.display_map();
13560 s.select_display_ranges(selections.iter().map(|a| {
13561 let point = a.to_display_point(&map);
13562 point..point
13563 }))
13564 })
13565 }
13566 }
13567
13568 fn go_to_line<T: 'static>(
13569 &mut self,
13570 position: Anchor,
13571 highlight_color: Option<Hsla>,
13572 window: &mut Window,
13573 cx: &mut Context<Self>,
13574 ) {
13575 let snapshot = self.snapshot(window, cx).display_snapshot;
13576 let position = position.to_point(&snapshot.buffer_snapshot);
13577 let start = snapshot
13578 .buffer_snapshot
13579 .clip_point(Point::new(position.row, 0), Bias::Left);
13580 let end = start + Point::new(1, 0);
13581 let start = snapshot.buffer_snapshot.anchor_before(start);
13582 let end = snapshot.buffer_snapshot.anchor_before(end);
13583
13584 self.highlight_rows::<T>(
13585 start..end,
13586 highlight_color
13587 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13588 Default::default(),
13589 cx,
13590 );
13591 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13592 }
13593
13594 pub fn go_to_definition(
13595 &mut self,
13596 _: &GoToDefinition,
13597 window: &mut Window,
13598 cx: &mut Context<Self>,
13599 ) -> Task<Result<Navigated>> {
13600 let definition =
13601 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13602 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13603 cx.spawn_in(window, async move |editor, cx| {
13604 if definition.await? == Navigated::Yes {
13605 return Ok(Navigated::Yes);
13606 }
13607 match fallback_strategy {
13608 GoToDefinitionFallback::None => Ok(Navigated::No),
13609 GoToDefinitionFallback::FindAllReferences => {
13610 match editor.update_in(cx, |editor, window, cx| {
13611 editor.find_all_references(&FindAllReferences, window, cx)
13612 })? {
13613 Some(references) => references.await,
13614 None => Ok(Navigated::No),
13615 }
13616 }
13617 }
13618 })
13619 }
13620
13621 pub fn go_to_declaration(
13622 &mut self,
13623 _: &GoToDeclaration,
13624 window: &mut Window,
13625 cx: &mut Context<Self>,
13626 ) -> Task<Result<Navigated>> {
13627 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13628 }
13629
13630 pub fn go_to_declaration_split(
13631 &mut self,
13632 _: &GoToDeclaration,
13633 window: &mut Window,
13634 cx: &mut Context<Self>,
13635 ) -> Task<Result<Navigated>> {
13636 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13637 }
13638
13639 pub fn go_to_implementation(
13640 &mut self,
13641 _: &GoToImplementation,
13642 window: &mut Window,
13643 cx: &mut Context<Self>,
13644 ) -> Task<Result<Navigated>> {
13645 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13646 }
13647
13648 pub fn go_to_implementation_split(
13649 &mut self,
13650 _: &GoToImplementationSplit,
13651 window: &mut Window,
13652 cx: &mut Context<Self>,
13653 ) -> Task<Result<Navigated>> {
13654 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13655 }
13656
13657 pub fn go_to_type_definition(
13658 &mut self,
13659 _: &GoToTypeDefinition,
13660 window: &mut Window,
13661 cx: &mut Context<Self>,
13662 ) -> Task<Result<Navigated>> {
13663 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13664 }
13665
13666 pub fn go_to_definition_split(
13667 &mut self,
13668 _: &GoToDefinitionSplit,
13669 window: &mut Window,
13670 cx: &mut Context<Self>,
13671 ) -> Task<Result<Navigated>> {
13672 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13673 }
13674
13675 pub fn go_to_type_definition_split(
13676 &mut self,
13677 _: &GoToTypeDefinitionSplit,
13678 window: &mut Window,
13679 cx: &mut Context<Self>,
13680 ) -> Task<Result<Navigated>> {
13681 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13682 }
13683
13684 fn go_to_definition_of_kind(
13685 &mut self,
13686 kind: GotoDefinitionKind,
13687 split: bool,
13688 window: &mut Window,
13689 cx: &mut Context<Self>,
13690 ) -> Task<Result<Navigated>> {
13691 let Some(provider) = self.semantics_provider.clone() else {
13692 return Task::ready(Ok(Navigated::No));
13693 };
13694 let head = self.selections.newest::<usize>(cx).head();
13695 let buffer = self.buffer.read(cx);
13696 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13697 text_anchor
13698 } else {
13699 return Task::ready(Ok(Navigated::No));
13700 };
13701
13702 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13703 return Task::ready(Ok(Navigated::No));
13704 };
13705
13706 cx.spawn_in(window, async move |editor, cx| {
13707 let definitions = definitions.await?;
13708 let navigated = editor
13709 .update_in(cx, |editor, window, cx| {
13710 editor.navigate_to_hover_links(
13711 Some(kind),
13712 definitions
13713 .into_iter()
13714 .filter(|location| {
13715 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13716 })
13717 .map(HoverLink::Text)
13718 .collect::<Vec<_>>(),
13719 split,
13720 window,
13721 cx,
13722 )
13723 })?
13724 .await?;
13725 anyhow::Ok(navigated)
13726 })
13727 }
13728
13729 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13730 let selection = self.selections.newest_anchor();
13731 let head = selection.head();
13732 let tail = selection.tail();
13733
13734 let Some((buffer, start_position)) =
13735 self.buffer.read(cx).text_anchor_for_position(head, cx)
13736 else {
13737 return;
13738 };
13739
13740 let end_position = if head != tail {
13741 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13742 return;
13743 };
13744 Some(pos)
13745 } else {
13746 None
13747 };
13748
13749 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13750 let url = if let Some(end_pos) = end_position {
13751 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13752 } else {
13753 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13754 };
13755
13756 if let Some(url) = url {
13757 editor.update(cx, |_, cx| {
13758 cx.open_url(&url);
13759 })
13760 } else {
13761 Ok(())
13762 }
13763 });
13764
13765 url_finder.detach();
13766 }
13767
13768 pub fn open_selected_filename(
13769 &mut self,
13770 _: &OpenSelectedFilename,
13771 window: &mut Window,
13772 cx: &mut Context<Self>,
13773 ) {
13774 let Some(workspace) = self.workspace() else {
13775 return;
13776 };
13777
13778 let position = self.selections.newest_anchor().head();
13779
13780 let Some((buffer, buffer_position)) =
13781 self.buffer.read(cx).text_anchor_for_position(position, cx)
13782 else {
13783 return;
13784 };
13785
13786 let project = self.project.clone();
13787
13788 cx.spawn_in(window, async move |_, cx| {
13789 let result = find_file(&buffer, project, buffer_position, cx).await;
13790
13791 if let Some((_, path)) = result {
13792 workspace
13793 .update_in(cx, |workspace, window, cx| {
13794 workspace.open_resolved_path(path, window, cx)
13795 })?
13796 .await?;
13797 }
13798 anyhow::Ok(())
13799 })
13800 .detach();
13801 }
13802
13803 pub(crate) fn navigate_to_hover_links(
13804 &mut self,
13805 kind: Option<GotoDefinitionKind>,
13806 mut definitions: Vec<HoverLink>,
13807 split: bool,
13808 window: &mut Window,
13809 cx: &mut Context<Editor>,
13810 ) -> Task<Result<Navigated>> {
13811 // If there is one definition, just open it directly
13812 if definitions.len() == 1 {
13813 let definition = definitions.pop().unwrap();
13814
13815 enum TargetTaskResult {
13816 Location(Option<Location>),
13817 AlreadyNavigated,
13818 }
13819
13820 let target_task = match definition {
13821 HoverLink::Text(link) => {
13822 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13823 }
13824 HoverLink::InlayHint(lsp_location, server_id) => {
13825 let computation =
13826 self.compute_target_location(lsp_location, server_id, window, cx);
13827 cx.background_spawn(async move {
13828 let location = computation.await?;
13829 Ok(TargetTaskResult::Location(location))
13830 })
13831 }
13832 HoverLink::Url(url) => {
13833 cx.open_url(&url);
13834 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13835 }
13836 HoverLink::File(path) => {
13837 if let Some(workspace) = self.workspace() {
13838 cx.spawn_in(window, async move |_, cx| {
13839 workspace
13840 .update_in(cx, |workspace, window, cx| {
13841 workspace.open_resolved_path(path, window, cx)
13842 })?
13843 .await
13844 .map(|_| TargetTaskResult::AlreadyNavigated)
13845 })
13846 } else {
13847 Task::ready(Ok(TargetTaskResult::Location(None)))
13848 }
13849 }
13850 };
13851 cx.spawn_in(window, async move |editor, cx| {
13852 let target = match target_task.await.context("target resolution task")? {
13853 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13854 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13855 TargetTaskResult::Location(Some(target)) => target,
13856 };
13857
13858 editor.update_in(cx, |editor, window, cx| {
13859 let Some(workspace) = editor.workspace() else {
13860 return Navigated::No;
13861 };
13862 let pane = workspace.read(cx).active_pane().clone();
13863
13864 let range = target.range.to_point(target.buffer.read(cx));
13865 let range = editor.range_for_match(&range);
13866 let range = collapse_multiline_range(range);
13867
13868 if !split
13869 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13870 {
13871 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13872 } else {
13873 window.defer(cx, move |window, cx| {
13874 let target_editor: Entity<Self> =
13875 workspace.update(cx, |workspace, cx| {
13876 let pane = if split {
13877 workspace.adjacent_pane(window, cx)
13878 } else {
13879 workspace.active_pane().clone()
13880 };
13881
13882 workspace.open_project_item(
13883 pane,
13884 target.buffer.clone(),
13885 true,
13886 true,
13887 window,
13888 cx,
13889 )
13890 });
13891 target_editor.update(cx, |target_editor, cx| {
13892 // When selecting a definition in a different buffer, disable the nav history
13893 // to avoid creating a history entry at the previous cursor location.
13894 pane.update(cx, |pane, _| pane.disable_history());
13895 target_editor.go_to_singleton_buffer_range(range, window, cx);
13896 pane.update(cx, |pane, _| pane.enable_history());
13897 });
13898 });
13899 }
13900 Navigated::Yes
13901 })
13902 })
13903 } else if !definitions.is_empty() {
13904 cx.spawn_in(window, async move |editor, cx| {
13905 let (title, location_tasks, workspace) = editor
13906 .update_in(cx, |editor, window, cx| {
13907 let tab_kind = match kind {
13908 Some(GotoDefinitionKind::Implementation) => "Implementations",
13909 _ => "Definitions",
13910 };
13911 let title = definitions
13912 .iter()
13913 .find_map(|definition| match definition {
13914 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13915 let buffer = origin.buffer.read(cx);
13916 format!(
13917 "{} for {}",
13918 tab_kind,
13919 buffer
13920 .text_for_range(origin.range.clone())
13921 .collect::<String>()
13922 )
13923 }),
13924 HoverLink::InlayHint(_, _) => None,
13925 HoverLink::Url(_) => None,
13926 HoverLink::File(_) => None,
13927 })
13928 .unwrap_or(tab_kind.to_string());
13929 let location_tasks = definitions
13930 .into_iter()
13931 .map(|definition| match definition {
13932 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13933 HoverLink::InlayHint(lsp_location, server_id) => editor
13934 .compute_target_location(lsp_location, server_id, window, cx),
13935 HoverLink::Url(_) => Task::ready(Ok(None)),
13936 HoverLink::File(_) => Task::ready(Ok(None)),
13937 })
13938 .collect::<Vec<_>>();
13939 (title, location_tasks, editor.workspace().clone())
13940 })
13941 .context("location tasks preparation")?;
13942
13943 let locations = future::join_all(location_tasks)
13944 .await
13945 .into_iter()
13946 .filter_map(|location| location.transpose())
13947 .collect::<Result<_>>()
13948 .context("location tasks")?;
13949
13950 let Some(workspace) = workspace else {
13951 return Ok(Navigated::No);
13952 };
13953 let opened = workspace
13954 .update_in(cx, |workspace, window, cx| {
13955 Self::open_locations_in_multibuffer(
13956 workspace,
13957 locations,
13958 title,
13959 split,
13960 MultibufferSelectionMode::First,
13961 window,
13962 cx,
13963 )
13964 })
13965 .ok();
13966
13967 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13968 })
13969 } else {
13970 Task::ready(Ok(Navigated::No))
13971 }
13972 }
13973
13974 fn compute_target_location(
13975 &self,
13976 lsp_location: lsp::Location,
13977 server_id: LanguageServerId,
13978 window: &mut Window,
13979 cx: &mut Context<Self>,
13980 ) -> Task<anyhow::Result<Option<Location>>> {
13981 let Some(project) = self.project.clone() else {
13982 return Task::ready(Ok(None));
13983 };
13984
13985 cx.spawn_in(window, async move |editor, cx| {
13986 let location_task = editor.update(cx, |_, cx| {
13987 project.update(cx, |project, cx| {
13988 let language_server_name = project
13989 .language_server_statuses(cx)
13990 .find(|(id, _)| server_id == *id)
13991 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13992 language_server_name.map(|language_server_name| {
13993 project.open_local_buffer_via_lsp(
13994 lsp_location.uri.clone(),
13995 server_id,
13996 language_server_name,
13997 cx,
13998 )
13999 })
14000 })
14001 })?;
14002 let location = match location_task {
14003 Some(task) => Some({
14004 let target_buffer_handle = task.await.context("open local buffer")?;
14005 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14006 let target_start = target_buffer
14007 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14008 let target_end = target_buffer
14009 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14010 target_buffer.anchor_after(target_start)
14011 ..target_buffer.anchor_before(target_end)
14012 })?;
14013 Location {
14014 buffer: target_buffer_handle,
14015 range,
14016 }
14017 }),
14018 None => None,
14019 };
14020 Ok(location)
14021 })
14022 }
14023
14024 pub fn find_all_references(
14025 &mut self,
14026 _: &FindAllReferences,
14027 window: &mut Window,
14028 cx: &mut Context<Self>,
14029 ) -> Option<Task<Result<Navigated>>> {
14030 let selection = self.selections.newest::<usize>(cx);
14031 let multi_buffer = self.buffer.read(cx);
14032 let head = selection.head();
14033
14034 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14035 let head_anchor = multi_buffer_snapshot.anchor_at(
14036 head,
14037 if head < selection.tail() {
14038 Bias::Right
14039 } else {
14040 Bias::Left
14041 },
14042 );
14043
14044 match self
14045 .find_all_references_task_sources
14046 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14047 {
14048 Ok(_) => {
14049 log::info!(
14050 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14051 );
14052 return None;
14053 }
14054 Err(i) => {
14055 self.find_all_references_task_sources.insert(i, head_anchor);
14056 }
14057 }
14058
14059 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14060 let workspace = self.workspace()?;
14061 let project = workspace.read(cx).project().clone();
14062 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14063 Some(cx.spawn_in(window, async move |editor, cx| {
14064 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14065 if let Ok(i) = editor
14066 .find_all_references_task_sources
14067 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14068 {
14069 editor.find_all_references_task_sources.remove(i);
14070 }
14071 });
14072
14073 let locations = references.await?;
14074 if locations.is_empty() {
14075 return anyhow::Ok(Navigated::No);
14076 }
14077
14078 workspace.update_in(cx, |workspace, window, cx| {
14079 let title = locations
14080 .first()
14081 .as_ref()
14082 .map(|location| {
14083 let buffer = location.buffer.read(cx);
14084 format!(
14085 "References to `{}`",
14086 buffer
14087 .text_for_range(location.range.clone())
14088 .collect::<String>()
14089 )
14090 })
14091 .unwrap();
14092 Self::open_locations_in_multibuffer(
14093 workspace,
14094 locations,
14095 title,
14096 false,
14097 MultibufferSelectionMode::First,
14098 window,
14099 cx,
14100 );
14101 Navigated::Yes
14102 })
14103 }))
14104 }
14105
14106 /// Opens a multibuffer with the given project locations in it
14107 pub fn open_locations_in_multibuffer(
14108 workspace: &mut Workspace,
14109 mut locations: Vec<Location>,
14110 title: String,
14111 split: bool,
14112 multibuffer_selection_mode: MultibufferSelectionMode,
14113 window: &mut Window,
14114 cx: &mut Context<Workspace>,
14115 ) {
14116 // If there are multiple definitions, open them in a multibuffer
14117 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14118 let mut locations = locations.into_iter().peekable();
14119 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14120 let capability = workspace.project().read(cx).capability();
14121
14122 let excerpt_buffer = cx.new(|cx| {
14123 let mut multibuffer = MultiBuffer::new(capability);
14124 while let Some(location) = locations.next() {
14125 let buffer = location.buffer.read(cx);
14126 let mut ranges_for_buffer = Vec::new();
14127 let range = location.range.to_point(buffer);
14128 ranges_for_buffer.push(range.clone());
14129
14130 while let Some(next_location) = locations.peek() {
14131 if next_location.buffer == location.buffer {
14132 ranges_for_buffer.push(next_location.range.to_point(buffer));
14133 locations.next();
14134 } else {
14135 break;
14136 }
14137 }
14138
14139 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14140 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14141 PathKey::for_buffer(&location.buffer, cx),
14142 location.buffer.clone(),
14143 ranges_for_buffer,
14144 DEFAULT_MULTIBUFFER_CONTEXT,
14145 cx,
14146 );
14147 ranges.extend(new_ranges)
14148 }
14149
14150 multibuffer.with_title(title)
14151 });
14152
14153 let editor = cx.new(|cx| {
14154 Editor::for_multibuffer(
14155 excerpt_buffer,
14156 Some(workspace.project().clone()),
14157 window,
14158 cx,
14159 )
14160 });
14161 editor.update(cx, |editor, cx| {
14162 match multibuffer_selection_mode {
14163 MultibufferSelectionMode::First => {
14164 if let Some(first_range) = ranges.first() {
14165 editor.change_selections(None, window, cx, |selections| {
14166 selections.clear_disjoint();
14167 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14168 });
14169 }
14170 editor.highlight_background::<Self>(
14171 &ranges,
14172 |theme| theme.editor_highlighted_line_background,
14173 cx,
14174 );
14175 }
14176 MultibufferSelectionMode::All => {
14177 editor.change_selections(None, window, cx, |selections| {
14178 selections.clear_disjoint();
14179 selections.select_anchor_ranges(ranges);
14180 });
14181 }
14182 }
14183 editor.register_buffers_with_language_servers(cx);
14184 });
14185
14186 let item = Box::new(editor);
14187 let item_id = item.item_id();
14188
14189 if split {
14190 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14191 } else {
14192 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14193 let (preview_item_id, preview_item_idx) =
14194 workspace.active_pane().update(cx, |pane, _| {
14195 (pane.preview_item_id(), pane.preview_item_idx())
14196 });
14197
14198 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14199
14200 if let Some(preview_item_id) = preview_item_id {
14201 workspace.active_pane().update(cx, |pane, cx| {
14202 pane.remove_item(preview_item_id, false, false, window, cx);
14203 });
14204 }
14205 } else {
14206 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14207 }
14208 }
14209 workspace.active_pane().update(cx, |pane, cx| {
14210 pane.set_preview_item_id(Some(item_id), cx);
14211 });
14212 }
14213
14214 pub fn rename(
14215 &mut self,
14216 _: &Rename,
14217 window: &mut Window,
14218 cx: &mut Context<Self>,
14219 ) -> Option<Task<Result<()>>> {
14220 use language::ToOffset as _;
14221
14222 let provider = self.semantics_provider.clone()?;
14223 let selection = self.selections.newest_anchor().clone();
14224 let (cursor_buffer, cursor_buffer_position) = self
14225 .buffer
14226 .read(cx)
14227 .text_anchor_for_position(selection.head(), cx)?;
14228 let (tail_buffer, cursor_buffer_position_end) = self
14229 .buffer
14230 .read(cx)
14231 .text_anchor_for_position(selection.tail(), cx)?;
14232 if tail_buffer != cursor_buffer {
14233 return None;
14234 }
14235
14236 let snapshot = cursor_buffer.read(cx).snapshot();
14237 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14238 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14239 let prepare_rename = provider
14240 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14241 .unwrap_or_else(|| Task::ready(Ok(None)));
14242 drop(snapshot);
14243
14244 Some(cx.spawn_in(window, async move |this, cx| {
14245 let rename_range = if let Some(range) = prepare_rename.await? {
14246 Some(range)
14247 } else {
14248 this.update(cx, |this, cx| {
14249 let buffer = this.buffer.read(cx).snapshot(cx);
14250 let mut buffer_highlights = this
14251 .document_highlights_for_position(selection.head(), &buffer)
14252 .filter(|highlight| {
14253 highlight.start.excerpt_id == selection.head().excerpt_id
14254 && highlight.end.excerpt_id == selection.head().excerpt_id
14255 });
14256 buffer_highlights
14257 .next()
14258 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14259 })?
14260 };
14261 if let Some(rename_range) = rename_range {
14262 this.update_in(cx, |this, window, cx| {
14263 let snapshot = cursor_buffer.read(cx).snapshot();
14264 let rename_buffer_range = rename_range.to_offset(&snapshot);
14265 let cursor_offset_in_rename_range =
14266 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14267 let cursor_offset_in_rename_range_end =
14268 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14269
14270 this.take_rename(false, window, cx);
14271 let buffer = this.buffer.read(cx).read(cx);
14272 let cursor_offset = selection.head().to_offset(&buffer);
14273 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14274 let rename_end = rename_start + rename_buffer_range.len();
14275 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14276 let mut old_highlight_id = None;
14277 let old_name: Arc<str> = buffer
14278 .chunks(rename_start..rename_end, true)
14279 .map(|chunk| {
14280 if old_highlight_id.is_none() {
14281 old_highlight_id = chunk.syntax_highlight_id;
14282 }
14283 chunk.text
14284 })
14285 .collect::<String>()
14286 .into();
14287
14288 drop(buffer);
14289
14290 // Position the selection in the rename editor so that it matches the current selection.
14291 this.show_local_selections = false;
14292 let rename_editor = cx.new(|cx| {
14293 let mut editor = Editor::single_line(window, cx);
14294 editor.buffer.update(cx, |buffer, cx| {
14295 buffer.edit([(0..0, old_name.clone())], None, cx)
14296 });
14297 let rename_selection_range = match cursor_offset_in_rename_range
14298 .cmp(&cursor_offset_in_rename_range_end)
14299 {
14300 Ordering::Equal => {
14301 editor.select_all(&SelectAll, window, cx);
14302 return editor;
14303 }
14304 Ordering::Less => {
14305 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14306 }
14307 Ordering::Greater => {
14308 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14309 }
14310 };
14311 if rename_selection_range.end > old_name.len() {
14312 editor.select_all(&SelectAll, window, cx);
14313 } else {
14314 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14315 s.select_ranges([rename_selection_range]);
14316 });
14317 }
14318 editor
14319 });
14320 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14321 if e == &EditorEvent::Focused {
14322 cx.emit(EditorEvent::FocusedIn)
14323 }
14324 })
14325 .detach();
14326
14327 let write_highlights =
14328 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14329 let read_highlights =
14330 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14331 let ranges = write_highlights
14332 .iter()
14333 .flat_map(|(_, ranges)| ranges.iter())
14334 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14335 .cloned()
14336 .collect();
14337
14338 this.highlight_text::<Rename>(
14339 ranges,
14340 HighlightStyle {
14341 fade_out: Some(0.6),
14342 ..Default::default()
14343 },
14344 cx,
14345 );
14346 let rename_focus_handle = rename_editor.focus_handle(cx);
14347 window.focus(&rename_focus_handle);
14348 let block_id = this.insert_blocks(
14349 [BlockProperties {
14350 style: BlockStyle::Flex,
14351 placement: BlockPlacement::Below(range.start),
14352 height: Some(1),
14353 render: Arc::new({
14354 let rename_editor = rename_editor.clone();
14355 move |cx: &mut BlockContext| {
14356 let mut text_style = cx.editor_style.text.clone();
14357 if let Some(highlight_style) = old_highlight_id
14358 .and_then(|h| h.style(&cx.editor_style.syntax))
14359 {
14360 text_style = text_style.highlight(highlight_style);
14361 }
14362 div()
14363 .block_mouse_down()
14364 .pl(cx.anchor_x)
14365 .child(EditorElement::new(
14366 &rename_editor,
14367 EditorStyle {
14368 background: cx.theme().system().transparent,
14369 local_player: cx.editor_style.local_player,
14370 text: text_style,
14371 scrollbar_width: cx.editor_style.scrollbar_width,
14372 syntax: cx.editor_style.syntax.clone(),
14373 status: cx.editor_style.status.clone(),
14374 inlay_hints_style: HighlightStyle {
14375 font_weight: Some(FontWeight::BOLD),
14376 ..make_inlay_hints_style(cx.app)
14377 },
14378 inline_completion_styles: make_suggestion_styles(
14379 cx.app,
14380 ),
14381 ..EditorStyle::default()
14382 },
14383 ))
14384 .into_any_element()
14385 }
14386 }),
14387 priority: 0,
14388 }],
14389 Some(Autoscroll::fit()),
14390 cx,
14391 )[0];
14392 this.pending_rename = Some(RenameState {
14393 range,
14394 old_name,
14395 editor: rename_editor,
14396 block_id,
14397 });
14398 })?;
14399 }
14400
14401 Ok(())
14402 }))
14403 }
14404
14405 pub fn confirm_rename(
14406 &mut self,
14407 _: &ConfirmRename,
14408 window: &mut Window,
14409 cx: &mut Context<Self>,
14410 ) -> Option<Task<Result<()>>> {
14411 let rename = self.take_rename(false, window, cx)?;
14412 let workspace = self.workspace()?.downgrade();
14413 let (buffer, start) = self
14414 .buffer
14415 .read(cx)
14416 .text_anchor_for_position(rename.range.start, cx)?;
14417 let (end_buffer, _) = self
14418 .buffer
14419 .read(cx)
14420 .text_anchor_for_position(rename.range.end, cx)?;
14421 if buffer != end_buffer {
14422 return None;
14423 }
14424
14425 let old_name = rename.old_name;
14426 let new_name = rename.editor.read(cx).text(cx);
14427
14428 let rename = self.semantics_provider.as_ref()?.perform_rename(
14429 &buffer,
14430 start,
14431 new_name.clone(),
14432 cx,
14433 )?;
14434
14435 Some(cx.spawn_in(window, async move |editor, cx| {
14436 let project_transaction = rename.await?;
14437 Self::open_project_transaction(
14438 &editor,
14439 workspace,
14440 project_transaction,
14441 format!("Rename: {} → {}", old_name, new_name),
14442 cx,
14443 )
14444 .await?;
14445
14446 editor.update(cx, |editor, cx| {
14447 editor.refresh_document_highlights(cx);
14448 })?;
14449 Ok(())
14450 }))
14451 }
14452
14453 fn take_rename(
14454 &mut self,
14455 moving_cursor: bool,
14456 window: &mut Window,
14457 cx: &mut Context<Self>,
14458 ) -> Option<RenameState> {
14459 let rename = self.pending_rename.take()?;
14460 if rename.editor.focus_handle(cx).is_focused(window) {
14461 window.focus(&self.focus_handle);
14462 }
14463
14464 self.remove_blocks(
14465 [rename.block_id].into_iter().collect(),
14466 Some(Autoscroll::fit()),
14467 cx,
14468 );
14469 self.clear_highlights::<Rename>(cx);
14470 self.show_local_selections = true;
14471
14472 if moving_cursor {
14473 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14474 editor.selections.newest::<usize>(cx).head()
14475 });
14476
14477 // Update the selection to match the position of the selection inside
14478 // the rename editor.
14479 let snapshot = self.buffer.read(cx).read(cx);
14480 let rename_range = rename.range.to_offset(&snapshot);
14481 let cursor_in_editor = snapshot
14482 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14483 .min(rename_range.end);
14484 drop(snapshot);
14485
14486 self.change_selections(None, window, cx, |s| {
14487 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14488 });
14489 } else {
14490 self.refresh_document_highlights(cx);
14491 }
14492
14493 Some(rename)
14494 }
14495
14496 pub fn pending_rename(&self) -> Option<&RenameState> {
14497 self.pending_rename.as_ref()
14498 }
14499
14500 fn format(
14501 &mut self,
14502 _: &Format,
14503 window: &mut Window,
14504 cx: &mut Context<Self>,
14505 ) -> Option<Task<Result<()>>> {
14506 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14507
14508 let project = match &self.project {
14509 Some(project) => project.clone(),
14510 None => return None,
14511 };
14512
14513 Some(self.perform_format(
14514 project,
14515 FormatTrigger::Manual,
14516 FormatTarget::Buffers,
14517 window,
14518 cx,
14519 ))
14520 }
14521
14522 fn format_selections(
14523 &mut self,
14524 _: &FormatSelections,
14525 window: &mut Window,
14526 cx: &mut Context<Self>,
14527 ) -> Option<Task<Result<()>>> {
14528 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14529
14530 let project = match &self.project {
14531 Some(project) => project.clone(),
14532 None => return None,
14533 };
14534
14535 let ranges = self
14536 .selections
14537 .all_adjusted(cx)
14538 .into_iter()
14539 .map(|selection| selection.range())
14540 .collect_vec();
14541
14542 Some(self.perform_format(
14543 project,
14544 FormatTrigger::Manual,
14545 FormatTarget::Ranges(ranges),
14546 window,
14547 cx,
14548 ))
14549 }
14550
14551 fn perform_format(
14552 &mut self,
14553 project: Entity<Project>,
14554 trigger: FormatTrigger,
14555 target: FormatTarget,
14556 window: &mut Window,
14557 cx: &mut Context<Self>,
14558 ) -> Task<Result<()>> {
14559 let buffer = self.buffer.clone();
14560 let (buffers, target) = match target {
14561 FormatTarget::Buffers => {
14562 let mut buffers = buffer.read(cx).all_buffers();
14563 if trigger == FormatTrigger::Save {
14564 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14565 }
14566 (buffers, LspFormatTarget::Buffers)
14567 }
14568 FormatTarget::Ranges(selection_ranges) => {
14569 let multi_buffer = buffer.read(cx);
14570 let snapshot = multi_buffer.read(cx);
14571 let mut buffers = HashSet::default();
14572 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14573 BTreeMap::new();
14574 for selection_range in selection_ranges {
14575 for (buffer, buffer_range, _) in
14576 snapshot.range_to_buffer_ranges(selection_range)
14577 {
14578 let buffer_id = buffer.remote_id();
14579 let start = buffer.anchor_before(buffer_range.start);
14580 let end = buffer.anchor_after(buffer_range.end);
14581 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14582 buffer_id_to_ranges
14583 .entry(buffer_id)
14584 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14585 .or_insert_with(|| vec![start..end]);
14586 }
14587 }
14588 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14589 }
14590 };
14591
14592 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14593 let selections_prev = transaction_id_prev
14594 .and_then(|transaction_id_prev| {
14595 // default to selections as they were after the last edit, if we have them,
14596 // instead of how they are now.
14597 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14598 // will take you back to where you made the last edit, instead of staying where you scrolled
14599 self.selection_history
14600 .transaction(transaction_id_prev)
14601 .map(|t| t.0.clone())
14602 })
14603 .unwrap_or_else(|| {
14604 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14605 self.selections.disjoint_anchors()
14606 });
14607
14608 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14609 let format = project.update(cx, |project, cx| {
14610 project.format(buffers, target, true, trigger, cx)
14611 });
14612
14613 cx.spawn_in(window, async move |editor, cx| {
14614 let transaction = futures::select_biased! {
14615 transaction = format.log_err().fuse() => transaction,
14616 () = timeout => {
14617 log::warn!("timed out waiting for formatting");
14618 None
14619 }
14620 };
14621
14622 buffer
14623 .update(cx, |buffer, cx| {
14624 if let Some(transaction) = transaction {
14625 if !buffer.is_singleton() {
14626 buffer.push_transaction(&transaction.0, cx);
14627 }
14628 }
14629 cx.notify();
14630 })
14631 .ok();
14632
14633 if let Some(transaction_id_now) =
14634 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14635 {
14636 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14637 if has_new_transaction {
14638 _ = editor.update(cx, |editor, _| {
14639 editor
14640 .selection_history
14641 .insert_transaction(transaction_id_now, selections_prev);
14642 });
14643 }
14644 }
14645
14646 Ok(())
14647 })
14648 }
14649
14650 fn organize_imports(
14651 &mut self,
14652 _: &OrganizeImports,
14653 window: &mut Window,
14654 cx: &mut Context<Self>,
14655 ) -> Option<Task<Result<()>>> {
14656 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14657 let project = match &self.project {
14658 Some(project) => project.clone(),
14659 None => return None,
14660 };
14661 Some(self.perform_code_action_kind(
14662 project,
14663 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14664 window,
14665 cx,
14666 ))
14667 }
14668
14669 fn perform_code_action_kind(
14670 &mut self,
14671 project: Entity<Project>,
14672 kind: CodeActionKind,
14673 window: &mut Window,
14674 cx: &mut Context<Self>,
14675 ) -> Task<Result<()>> {
14676 let buffer = self.buffer.clone();
14677 let buffers = buffer.read(cx).all_buffers();
14678 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14679 let apply_action = project.update(cx, |project, cx| {
14680 project.apply_code_action_kind(buffers, kind, true, cx)
14681 });
14682 cx.spawn_in(window, async move |_, cx| {
14683 let transaction = futures::select_biased! {
14684 () = timeout => {
14685 log::warn!("timed out waiting for executing code action");
14686 None
14687 }
14688 transaction = apply_action.log_err().fuse() => transaction,
14689 };
14690 buffer
14691 .update(cx, |buffer, cx| {
14692 // check if we need this
14693 if let Some(transaction) = transaction {
14694 if !buffer.is_singleton() {
14695 buffer.push_transaction(&transaction.0, cx);
14696 }
14697 }
14698 cx.notify();
14699 })
14700 .ok();
14701 Ok(())
14702 })
14703 }
14704
14705 fn restart_language_server(
14706 &mut self,
14707 _: &RestartLanguageServer,
14708 _: &mut Window,
14709 cx: &mut Context<Self>,
14710 ) {
14711 if let Some(project) = self.project.clone() {
14712 self.buffer.update(cx, |multi_buffer, cx| {
14713 project.update(cx, |project, cx| {
14714 project.restart_language_servers_for_buffers(
14715 multi_buffer.all_buffers().into_iter().collect(),
14716 cx,
14717 );
14718 });
14719 })
14720 }
14721 }
14722
14723 fn stop_language_server(
14724 &mut self,
14725 _: &StopLanguageServer,
14726 _: &mut Window,
14727 cx: &mut Context<Self>,
14728 ) {
14729 if let Some(project) = self.project.clone() {
14730 self.buffer.update(cx, |multi_buffer, cx| {
14731 project.update(cx, |project, cx| {
14732 project.stop_language_servers_for_buffers(
14733 multi_buffer.all_buffers().into_iter().collect(),
14734 cx,
14735 );
14736 cx.emit(project::Event::RefreshInlayHints);
14737 });
14738 });
14739 }
14740 }
14741
14742 fn cancel_language_server_work(
14743 workspace: &mut Workspace,
14744 _: &actions::CancelLanguageServerWork,
14745 _: &mut Window,
14746 cx: &mut Context<Workspace>,
14747 ) {
14748 let project = workspace.project();
14749 let buffers = workspace
14750 .active_item(cx)
14751 .and_then(|item| item.act_as::<Editor>(cx))
14752 .map_or(HashSet::default(), |editor| {
14753 editor.read(cx).buffer.read(cx).all_buffers()
14754 });
14755 project.update(cx, |project, cx| {
14756 project.cancel_language_server_work_for_buffers(buffers, cx);
14757 });
14758 }
14759
14760 fn show_character_palette(
14761 &mut self,
14762 _: &ShowCharacterPalette,
14763 window: &mut Window,
14764 _: &mut Context<Self>,
14765 ) {
14766 window.show_character_palette();
14767 }
14768
14769 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14770 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14771 let buffer = self.buffer.read(cx).snapshot(cx);
14772 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
14773 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
14774 let is_valid = buffer
14775 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14776 .any(|entry| {
14777 entry.diagnostic.is_primary
14778 && !entry.range.is_empty()
14779 && entry.range.start == primary_range_start
14780 && entry.diagnostic.message == active_diagnostics.active_message
14781 });
14782
14783 if !is_valid {
14784 self.dismiss_diagnostics(cx);
14785 }
14786 }
14787 }
14788
14789 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
14790 match &self.active_diagnostics {
14791 ActiveDiagnostic::Group(group) => Some(group),
14792 _ => None,
14793 }
14794 }
14795
14796 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
14797 self.dismiss_diagnostics(cx);
14798 self.active_diagnostics = ActiveDiagnostic::All;
14799 }
14800
14801 fn activate_diagnostics(
14802 &mut self,
14803 buffer_id: BufferId,
14804 diagnostic: DiagnosticEntry<usize>,
14805 window: &mut Window,
14806 cx: &mut Context<Self>,
14807 ) {
14808 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14809 return;
14810 }
14811 self.dismiss_diagnostics(cx);
14812 let snapshot = self.snapshot(window, cx);
14813 let buffer = self.buffer.read(cx).snapshot(cx);
14814 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
14815 return;
14816 };
14817
14818 let diagnostic_group = buffer
14819 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
14820 .collect::<Vec<_>>();
14821
14822 let blocks =
14823 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
14824
14825 let blocks = self.display_map.update(cx, |display_map, cx| {
14826 display_map.insert_blocks(blocks, cx).into_iter().collect()
14827 });
14828 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
14829 active_range: buffer.anchor_before(diagnostic.range.start)
14830 ..buffer.anchor_after(diagnostic.range.end),
14831 active_message: diagnostic.diagnostic.message.clone(),
14832 group_id: diagnostic.diagnostic.group_id,
14833 blocks,
14834 });
14835 cx.notify();
14836 }
14837
14838 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14839 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14840 return;
14841 };
14842
14843 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
14844 if let ActiveDiagnostic::Group(group) = prev {
14845 self.display_map.update(cx, |display_map, cx| {
14846 display_map.remove_blocks(group.blocks, cx);
14847 });
14848 cx.notify();
14849 }
14850 }
14851
14852 /// Disable inline diagnostics rendering for this editor.
14853 pub fn disable_inline_diagnostics(&mut self) {
14854 self.inline_diagnostics_enabled = false;
14855 self.inline_diagnostics_update = Task::ready(());
14856 self.inline_diagnostics.clear();
14857 }
14858
14859 pub fn inline_diagnostics_enabled(&self) -> bool {
14860 self.inline_diagnostics_enabled
14861 }
14862
14863 pub fn show_inline_diagnostics(&self) -> bool {
14864 self.show_inline_diagnostics
14865 }
14866
14867 pub fn toggle_inline_diagnostics(
14868 &mut self,
14869 _: &ToggleInlineDiagnostics,
14870 window: &mut Window,
14871 cx: &mut Context<Editor>,
14872 ) {
14873 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14874 self.refresh_inline_diagnostics(false, window, cx);
14875 }
14876
14877 fn refresh_inline_diagnostics(
14878 &mut self,
14879 debounce: bool,
14880 window: &mut Window,
14881 cx: &mut Context<Self>,
14882 ) {
14883 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14884 self.inline_diagnostics_update = Task::ready(());
14885 self.inline_diagnostics.clear();
14886 return;
14887 }
14888
14889 let debounce_ms = ProjectSettings::get_global(cx)
14890 .diagnostics
14891 .inline
14892 .update_debounce_ms;
14893 let debounce = if debounce && debounce_ms > 0 {
14894 Some(Duration::from_millis(debounce_ms))
14895 } else {
14896 None
14897 };
14898 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14899 let editor = editor.upgrade().unwrap();
14900
14901 if let Some(debounce) = debounce {
14902 cx.background_executor().timer(debounce).await;
14903 }
14904 let Some(snapshot) = editor
14905 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14906 .ok()
14907 else {
14908 return;
14909 };
14910
14911 let new_inline_diagnostics = cx
14912 .background_spawn(async move {
14913 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14914 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14915 let message = diagnostic_entry
14916 .diagnostic
14917 .message
14918 .split_once('\n')
14919 .map(|(line, _)| line)
14920 .map(SharedString::new)
14921 .unwrap_or_else(|| {
14922 SharedString::from(diagnostic_entry.diagnostic.message)
14923 });
14924 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14925 let (Ok(i) | Err(i)) = inline_diagnostics
14926 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14927 inline_diagnostics.insert(
14928 i,
14929 (
14930 start_anchor,
14931 InlineDiagnostic {
14932 message,
14933 group_id: diagnostic_entry.diagnostic.group_id,
14934 start: diagnostic_entry.range.start.to_point(&snapshot),
14935 is_primary: diagnostic_entry.diagnostic.is_primary,
14936 severity: diagnostic_entry.diagnostic.severity,
14937 },
14938 ),
14939 );
14940 }
14941 inline_diagnostics
14942 })
14943 .await;
14944
14945 editor
14946 .update(cx, |editor, cx| {
14947 editor.inline_diagnostics = new_inline_diagnostics;
14948 cx.notify();
14949 })
14950 .ok();
14951 });
14952 }
14953
14954 pub fn set_selections_from_remote(
14955 &mut self,
14956 selections: Vec<Selection<Anchor>>,
14957 pending_selection: Option<Selection<Anchor>>,
14958 window: &mut Window,
14959 cx: &mut Context<Self>,
14960 ) {
14961 let old_cursor_position = self.selections.newest_anchor().head();
14962 self.selections.change_with(cx, |s| {
14963 s.select_anchors(selections);
14964 if let Some(pending_selection) = pending_selection {
14965 s.set_pending(pending_selection, SelectMode::Character);
14966 } else {
14967 s.clear_pending();
14968 }
14969 });
14970 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14971 }
14972
14973 fn push_to_selection_history(&mut self) {
14974 self.selection_history.push(SelectionHistoryEntry {
14975 selections: self.selections.disjoint_anchors(),
14976 select_next_state: self.select_next_state.clone(),
14977 select_prev_state: self.select_prev_state.clone(),
14978 add_selections_state: self.add_selections_state.clone(),
14979 });
14980 }
14981
14982 pub fn transact(
14983 &mut self,
14984 window: &mut Window,
14985 cx: &mut Context<Self>,
14986 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14987 ) -> Option<TransactionId> {
14988 self.start_transaction_at(Instant::now(), window, cx);
14989 update(self, window, cx);
14990 self.end_transaction_at(Instant::now(), cx)
14991 }
14992
14993 pub fn start_transaction_at(
14994 &mut self,
14995 now: Instant,
14996 window: &mut Window,
14997 cx: &mut Context<Self>,
14998 ) {
14999 self.end_selection(window, cx);
15000 if let Some(tx_id) = self
15001 .buffer
15002 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15003 {
15004 self.selection_history
15005 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15006 cx.emit(EditorEvent::TransactionBegun {
15007 transaction_id: tx_id,
15008 })
15009 }
15010 }
15011
15012 pub fn end_transaction_at(
15013 &mut self,
15014 now: Instant,
15015 cx: &mut Context<Self>,
15016 ) -> Option<TransactionId> {
15017 if let Some(transaction_id) = self
15018 .buffer
15019 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15020 {
15021 if let Some((_, end_selections)) =
15022 self.selection_history.transaction_mut(transaction_id)
15023 {
15024 *end_selections = Some(self.selections.disjoint_anchors());
15025 } else {
15026 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15027 }
15028
15029 cx.emit(EditorEvent::Edited { transaction_id });
15030 Some(transaction_id)
15031 } else {
15032 None
15033 }
15034 }
15035
15036 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15037 if self.selection_mark_mode {
15038 self.change_selections(None, window, cx, |s| {
15039 s.move_with(|_, sel| {
15040 sel.collapse_to(sel.head(), SelectionGoal::None);
15041 });
15042 })
15043 }
15044 self.selection_mark_mode = true;
15045 cx.notify();
15046 }
15047
15048 pub fn swap_selection_ends(
15049 &mut self,
15050 _: &actions::SwapSelectionEnds,
15051 window: &mut Window,
15052 cx: &mut Context<Self>,
15053 ) {
15054 self.change_selections(None, window, cx, |s| {
15055 s.move_with(|_, sel| {
15056 if sel.start != sel.end {
15057 sel.reversed = !sel.reversed
15058 }
15059 });
15060 });
15061 self.request_autoscroll(Autoscroll::newest(), cx);
15062 cx.notify();
15063 }
15064
15065 pub fn toggle_fold(
15066 &mut self,
15067 _: &actions::ToggleFold,
15068 window: &mut Window,
15069 cx: &mut Context<Self>,
15070 ) {
15071 if self.is_singleton(cx) {
15072 let selection = self.selections.newest::<Point>(cx);
15073
15074 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15075 let range = if selection.is_empty() {
15076 let point = selection.head().to_display_point(&display_map);
15077 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15078 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15079 .to_point(&display_map);
15080 start..end
15081 } else {
15082 selection.range()
15083 };
15084 if display_map.folds_in_range(range).next().is_some() {
15085 self.unfold_lines(&Default::default(), window, cx)
15086 } else {
15087 self.fold(&Default::default(), window, cx)
15088 }
15089 } else {
15090 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15091 let buffer_ids: HashSet<_> = self
15092 .selections
15093 .disjoint_anchor_ranges()
15094 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15095 .collect();
15096
15097 let should_unfold = buffer_ids
15098 .iter()
15099 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15100
15101 for buffer_id in buffer_ids {
15102 if should_unfold {
15103 self.unfold_buffer(buffer_id, cx);
15104 } else {
15105 self.fold_buffer(buffer_id, cx);
15106 }
15107 }
15108 }
15109 }
15110
15111 pub fn toggle_fold_recursive(
15112 &mut self,
15113 _: &actions::ToggleFoldRecursive,
15114 window: &mut Window,
15115 cx: &mut Context<Self>,
15116 ) {
15117 let selection = self.selections.newest::<Point>(cx);
15118
15119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15120 let range = if selection.is_empty() {
15121 let point = selection.head().to_display_point(&display_map);
15122 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15123 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15124 .to_point(&display_map);
15125 start..end
15126 } else {
15127 selection.range()
15128 };
15129 if display_map.folds_in_range(range).next().is_some() {
15130 self.unfold_recursive(&Default::default(), window, cx)
15131 } else {
15132 self.fold_recursive(&Default::default(), window, cx)
15133 }
15134 }
15135
15136 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15137 if self.is_singleton(cx) {
15138 let mut to_fold = Vec::new();
15139 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15140 let selections = self.selections.all_adjusted(cx);
15141
15142 for selection in selections {
15143 let range = selection.range().sorted();
15144 let buffer_start_row = range.start.row;
15145
15146 if range.start.row != range.end.row {
15147 let mut found = false;
15148 let mut row = range.start.row;
15149 while row <= range.end.row {
15150 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15151 {
15152 found = true;
15153 row = crease.range().end.row + 1;
15154 to_fold.push(crease);
15155 } else {
15156 row += 1
15157 }
15158 }
15159 if found {
15160 continue;
15161 }
15162 }
15163
15164 for row in (0..=range.start.row).rev() {
15165 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15166 if crease.range().end.row >= buffer_start_row {
15167 to_fold.push(crease);
15168 if row <= range.start.row {
15169 break;
15170 }
15171 }
15172 }
15173 }
15174 }
15175
15176 self.fold_creases(to_fold, true, window, cx);
15177 } else {
15178 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15179 let buffer_ids = self
15180 .selections
15181 .disjoint_anchor_ranges()
15182 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15183 .collect::<HashSet<_>>();
15184 for buffer_id in buffer_ids {
15185 self.fold_buffer(buffer_id, cx);
15186 }
15187 }
15188 }
15189
15190 fn fold_at_level(
15191 &mut self,
15192 fold_at: &FoldAtLevel,
15193 window: &mut Window,
15194 cx: &mut Context<Self>,
15195 ) {
15196 if !self.buffer.read(cx).is_singleton() {
15197 return;
15198 }
15199
15200 let fold_at_level = fold_at.0;
15201 let snapshot = self.buffer.read(cx).snapshot(cx);
15202 let mut to_fold = Vec::new();
15203 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15204
15205 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15206 while start_row < end_row {
15207 match self
15208 .snapshot(window, cx)
15209 .crease_for_buffer_row(MultiBufferRow(start_row))
15210 {
15211 Some(crease) => {
15212 let nested_start_row = crease.range().start.row + 1;
15213 let nested_end_row = crease.range().end.row;
15214
15215 if current_level < fold_at_level {
15216 stack.push((nested_start_row, nested_end_row, current_level + 1));
15217 } else if current_level == fold_at_level {
15218 to_fold.push(crease);
15219 }
15220
15221 start_row = nested_end_row + 1;
15222 }
15223 None => start_row += 1,
15224 }
15225 }
15226 }
15227
15228 self.fold_creases(to_fold, true, window, cx);
15229 }
15230
15231 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15232 if self.buffer.read(cx).is_singleton() {
15233 let mut fold_ranges = Vec::new();
15234 let snapshot = self.buffer.read(cx).snapshot(cx);
15235
15236 for row in 0..snapshot.max_row().0 {
15237 if let Some(foldable_range) = self
15238 .snapshot(window, cx)
15239 .crease_for_buffer_row(MultiBufferRow(row))
15240 {
15241 fold_ranges.push(foldable_range);
15242 }
15243 }
15244
15245 self.fold_creases(fold_ranges, true, window, cx);
15246 } else {
15247 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15248 editor
15249 .update_in(cx, |editor, _, cx| {
15250 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15251 editor.fold_buffer(buffer_id, cx);
15252 }
15253 })
15254 .ok();
15255 });
15256 }
15257 }
15258
15259 pub fn fold_function_bodies(
15260 &mut self,
15261 _: &actions::FoldFunctionBodies,
15262 window: &mut Window,
15263 cx: &mut Context<Self>,
15264 ) {
15265 let snapshot = self.buffer.read(cx).snapshot(cx);
15266
15267 let ranges = snapshot
15268 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15269 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15270 .collect::<Vec<_>>();
15271
15272 let creases = ranges
15273 .into_iter()
15274 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15275 .collect();
15276
15277 self.fold_creases(creases, true, window, cx);
15278 }
15279
15280 pub fn fold_recursive(
15281 &mut self,
15282 _: &actions::FoldRecursive,
15283 window: &mut Window,
15284 cx: &mut Context<Self>,
15285 ) {
15286 let mut to_fold = Vec::new();
15287 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15288 let selections = self.selections.all_adjusted(cx);
15289
15290 for selection in selections {
15291 let range = selection.range().sorted();
15292 let buffer_start_row = range.start.row;
15293
15294 if range.start.row != range.end.row {
15295 let mut found = false;
15296 for row in range.start.row..=range.end.row {
15297 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15298 found = true;
15299 to_fold.push(crease);
15300 }
15301 }
15302 if found {
15303 continue;
15304 }
15305 }
15306
15307 for row in (0..=range.start.row).rev() {
15308 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15309 if crease.range().end.row >= buffer_start_row {
15310 to_fold.push(crease);
15311 } else {
15312 break;
15313 }
15314 }
15315 }
15316 }
15317
15318 self.fold_creases(to_fold, true, window, cx);
15319 }
15320
15321 pub fn fold_at(
15322 &mut self,
15323 buffer_row: MultiBufferRow,
15324 window: &mut Window,
15325 cx: &mut Context<Self>,
15326 ) {
15327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15328
15329 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15330 let autoscroll = self
15331 .selections
15332 .all::<Point>(cx)
15333 .iter()
15334 .any(|selection| crease.range().overlaps(&selection.range()));
15335
15336 self.fold_creases(vec![crease], autoscroll, window, cx);
15337 }
15338 }
15339
15340 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15341 if self.is_singleton(cx) {
15342 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15343 let buffer = &display_map.buffer_snapshot;
15344 let selections = self.selections.all::<Point>(cx);
15345 let ranges = selections
15346 .iter()
15347 .map(|s| {
15348 let range = s.display_range(&display_map).sorted();
15349 let mut start = range.start.to_point(&display_map);
15350 let mut end = range.end.to_point(&display_map);
15351 start.column = 0;
15352 end.column = buffer.line_len(MultiBufferRow(end.row));
15353 start..end
15354 })
15355 .collect::<Vec<_>>();
15356
15357 self.unfold_ranges(&ranges, true, true, cx);
15358 } else {
15359 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15360 let buffer_ids = self
15361 .selections
15362 .disjoint_anchor_ranges()
15363 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15364 .collect::<HashSet<_>>();
15365 for buffer_id in buffer_ids {
15366 self.unfold_buffer(buffer_id, cx);
15367 }
15368 }
15369 }
15370
15371 pub fn unfold_recursive(
15372 &mut self,
15373 _: &UnfoldRecursive,
15374 _window: &mut Window,
15375 cx: &mut Context<Self>,
15376 ) {
15377 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15378 let selections = self.selections.all::<Point>(cx);
15379 let ranges = selections
15380 .iter()
15381 .map(|s| {
15382 let mut range = s.display_range(&display_map).sorted();
15383 *range.start.column_mut() = 0;
15384 *range.end.column_mut() = display_map.line_len(range.end.row());
15385 let start = range.start.to_point(&display_map);
15386 let end = range.end.to_point(&display_map);
15387 start..end
15388 })
15389 .collect::<Vec<_>>();
15390
15391 self.unfold_ranges(&ranges, true, true, cx);
15392 }
15393
15394 pub fn unfold_at(
15395 &mut self,
15396 buffer_row: MultiBufferRow,
15397 _window: &mut Window,
15398 cx: &mut Context<Self>,
15399 ) {
15400 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15401
15402 let intersection_range = Point::new(buffer_row.0, 0)
15403 ..Point::new(
15404 buffer_row.0,
15405 display_map.buffer_snapshot.line_len(buffer_row),
15406 );
15407
15408 let autoscroll = self
15409 .selections
15410 .all::<Point>(cx)
15411 .iter()
15412 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15413
15414 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15415 }
15416
15417 pub fn unfold_all(
15418 &mut self,
15419 _: &actions::UnfoldAll,
15420 _window: &mut Window,
15421 cx: &mut Context<Self>,
15422 ) {
15423 if self.buffer.read(cx).is_singleton() {
15424 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15425 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15426 } else {
15427 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15428 editor
15429 .update(cx, |editor, cx| {
15430 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15431 editor.unfold_buffer(buffer_id, cx);
15432 }
15433 })
15434 .ok();
15435 });
15436 }
15437 }
15438
15439 pub fn fold_selected_ranges(
15440 &mut self,
15441 _: &FoldSelectedRanges,
15442 window: &mut Window,
15443 cx: &mut Context<Self>,
15444 ) {
15445 let selections = self.selections.all_adjusted(cx);
15446 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15447 let ranges = selections
15448 .into_iter()
15449 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15450 .collect::<Vec<_>>();
15451 self.fold_creases(ranges, true, window, cx);
15452 }
15453
15454 pub fn fold_ranges<T: ToOffset + Clone>(
15455 &mut self,
15456 ranges: Vec<Range<T>>,
15457 auto_scroll: bool,
15458 window: &mut Window,
15459 cx: &mut Context<Self>,
15460 ) {
15461 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15462 let ranges = ranges
15463 .into_iter()
15464 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15465 .collect::<Vec<_>>();
15466 self.fold_creases(ranges, auto_scroll, window, cx);
15467 }
15468
15469 pub fn fold_creases<T: ToOffset + Clone>(
15470 &mut self,
15471 creases: Vec<Crease<T>>,
15472 auto_scroll: bool,
15473 _window: &mut Window,
15474 cx: &mut Context<Self>,
15475 ) {
15476 if creases.is_empty() {
15477 return;
15478 }
15479
15480 let mut buffers_affected = HashSet::default();
15481 let multi_buffer = self.buffer().read(cx);
15482 for crease in &creases {
15483 if let Some((_, buffer, _)) =
15484 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15485 {
15486 buffers_affected.insert(buffer.read(cx).remote_id());
15487 };
15488 }
15489
15490 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15491
15492 if auto_scroll {
15493 self.request_autoscroll(Autoscroll::fit(), cx);
15494 }
15495
15496 cx.notify();
15497
15498 self.scrollbar_marker_state.dirty = true;
15499 self.folds_did_change(cx);
15500 }
15501
15502 /// Removes any folds whose ranges intersect any of the given ranges.
15503 pub fn unfold_ranges<T: ToOffset + Clone>(
15504 &mut self,
15505 ranges: &[Range<T>],
15506 inclusive: bool,
15507 auto_scroll: bool,
15508 cx: &mut Context<Self>,
15509 ) {
15510 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15511 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15512 });
15513 self.folds_did_change(cx);
15514 }
15515
15516 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15517 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15518 return;
15519 }
15520 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15521 self.display_map.update(cx, |display_map, cx| {
15522 display_map.fold_buffers([buffer_id], cx)
15523 });
15524 cx.emit(EditorEvent::BufferFoldToggled {
15525 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15526 folded: true,
15527 });
15528 cx.notify();
15529 }
15530
15531 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15532 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15533 return;
15534 }
15535 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15536 self.display_map.update(cx, |display_map, cx| {
15537 display_map.unfold_buffers([buffer_id], cx);
15538 });
15539 cx.emit(EditorEvent::BufferFoldToggled {
15540 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15541 folded: false,
15542 });
15543 cx.notify();
15544 }
15545
15546 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15547 self.display_map.read(cx).is_buffer_folded(buffer)
15548 }
15549
15550 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15551 self.display_map.read(cx).folded_buffers()
15552 }
15553
15554 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15555 self.display_map.update(cx, |display_map, cx| {
15556 display_map.disable_header_for_buffer(buffer_id, cx);
15557 });
15558 cx.notify();
15559 }
15560
15561 /// Removes any folds with the given ranges.
15562 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15563 &mut self,
15564 ranges: &[Range<T>],
15565 type_id: TypeId,
15566 auto_scroll: bool,
15567 cx: &mut Context<Self>,
15568 ) {
15569 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15570 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15571 });
15572 self.folds_did_change(cx);
15573 }
15574
15575 fn remove_folds_with<T: ToOffset + Clone>(
15576 &mut self,
15577 ranges: &[Range<T>],
15578 auto_scroll: bool,
15579 cx: &mut Context<Self>,
15580 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15581 ) {
15582 if ranges.is_empty() {
15583 return;
15584 }
15585
15586 let mut buffers_affected = HashSet::default();
15587 let multi_buffer = self.buffer().read(cx);
15588 for range in ranges {
15589 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15590 buffers_affected.insert(buffer.read(cx).remote_id());
15591 };
15592 }
15593
15594 self.display_map.update(cx, update);
15595
15596 if auto_scroll {
15597 self.request_autoscroll(Autoscroll::fit(), cx);
15598 }
15599
15600 cx.notify();
15601 self.scrollbar_marker_state.dirty = true;
15602 self.active_indent_guides_state.dirty = true;
15603 }
15604
15605 pub fn update_fold_widths(
15606 &mut self,
15607 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15608 cx: &mut Context<Self>,
15609 ) -> bool {
15610 self.display_map
15611 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15612 }
15613
15614 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15615 self.display_map.read(cx).fold_placeholder.clone()
15616 }
15617
15618 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15619 self.buffer.update(cx, |buffer, cx| {
15620 buffer.set_all_diff_hunks_expanded(cx);
15621 });
15622 }
15623
15624 pub fn expand_all_diff_hunks(
15625 &mut self,
15626 _: &ExpandAllDiffHunks,
15627 _window: &mut Window,
15628 cx: &mut Context<Self>,
15629 ) {
15630 self.buffer.update(cx, |buffer, cx| {
15631 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15632 });
15633 }
15634
15635 pub fn toggle_selected_diff_hunks(
15636 &mut self,
15637 _: &ToggleSelectedDiffHunks,
15638 _window: &mut Window,
15639 cx: &mut Context<Self>,
15640 ) {
15641 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15642 self.toggle_diff_hunks_in_ranges(ranges, cx);
15643 }
15644
15645 pub fn diff_hunks_in_ranges<'a>(
15646 &'a self,
15647 ranges: &'a [Range<Anchor>],
15648 buffer: &'a MultiBufferSnapshot,
15649 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15650 ranges.iter().flat_map(move |range| {
15651 let end_excerpt_id = range.end.excerpt_id;
15652 let range = range.to_point(buffer);
15653 let mut peek_end = range.end;
15654 if range.end.row < buffer.max_row().0 {
15655 peek_end = Point::new(range.end.row + 1, 0);
15656 }
15657 buffer
15658 .diff_hunks_in_range(range.start..peek_end)
15659 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15660 })
15661 }
15662
15663 pub fn has_stageable_diff_hunks_in_ranges(
15664 &self,
15665 ranges: &[Range<Anchor>],
15666 snapshot: &MultiBufferSnapshot,
15667 ) -> bool {
15668 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15669 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15670 }
15671
15672 pub fn toggle_staged_selected_diff_hunks(
15673 &mut self,
15674 _: &::git::ToggleStaged,
15675 _: &mut Window,
15676 cx: &mut Context<Self>,
15677 ) {
15678 let snapshot = self.buffer.read(cx).snapshot(cx);
15679 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15680 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15681 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15682 }
15683
15684 pub fn set_render_diff_hunk_controls(
15685 &mut self,
15686 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15687 cx: &mut Context<Self>,
15688 ) {
15689 self.render_diff_hunk_controls = render_diff_hunk_controls;
15690 cx.notify();
15691 }
15692
15693 pub fn stage_and_next(
15694 &mut self,
15695 _: &::git::StageAndNext,
15696 window: &mut Window,
15697 cx: &mut Context<Self>,
15698 ) {
15699 self.do_stage_or_unstage_and_next(true, window, cx);
15700 }
15701
15702 pub fn unstage_and_next(
15703 &mut self,
15704 _: &::git::UnstageAndNext,
15705 window: &mut Window,
15706 cx: &mut Context<Self>,
15707 ) {
15708 self.do_stage_or_unstage_and_next(false, window, cx);
15709 }
15710
15711 pub fn stage_or_unstage_diff_hunks(
15712 &mut self,
15713 stage: bool,
15714 ranges: Vec<Range<Anchor>>,
15715 cx: &mut Context<Self>,
15716 ) {
15717 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15718 cx.spawn(async move |this, cx| {
15719 task.await?;
15720 this.update(cx, |this, cx| {
15721 let snapshot = this.buffer.read(cx).snapshot(cx);
15722 let chunk_by = this
15723 .diff_hunks_in_ranges(&ranges, &snapshot)
15724 .chunk_by(|hunk| hunk.buffer_id);
15725 for (buffer_id, hunks) in &chunk_by {
15726 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15727 }
15728 })
15729 })
15730 .detach_and_log_err(cx);
15731 }
15732
15733 fn save_buffers_for_ranges_if_needed(
15734 &mut self,
15735 ranges: &[Range<Anchor>],
15736 cx: &mut Context<Editor>,
15737 ) -> Task<Result<()>> {
15738 let multibuffer = self.buffer.read(cx);
15739 let snapshot = multibuffer.read(cx);
15740 let buffer_ids: HashSet<_> = ranges
15741 .iter()
15742 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15743 .collect();
15744 drop(snapshot);
15745
15746 let mut buffers = HashSet::default();
15747 for buffer_id in buffer_ids {
15748 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15749 let buffer = buffer_entity.read(cx);
15750 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15751 {
15752 buffers.insert(buffer_entity);
15753 }
15754 }
15755 }
15756
15757 if let Some(project) = &self.project {
15758 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15759 } else {
15760 Task::ready(Ok(()))
15761 }
15762 }
15763
15764 fn do_stage_or_unstage_and_next(
15765 &mut self,
15766 stage: bool,
15767 window: &mut Window,
15768 cx: &mut Context<Self>,
15769 ) {
15770 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15771
15772 if ranges.iter().any(|range| range.start != range.end) {
15773 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15774 return;
15775 }
15776
15777 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15778 let snapshot = self.snapshot(window, cx);
15779 let position = self.selections.newest::<Point>(cx).head();
15780 let mut row = snapshot
15781 .buffer_snapshot
15782 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15783 .find(|hunk| hunk.row_range.start.0 > position.row)
15784 .map(|hunk| hunk.row_range.start);
15785
15786 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15787 // Outside of the project diff editor, wrap around to the beginning.
15788 if !all_diff_hunks_expanded {
15789 row = row.or_else(|| {
15790 snapshot
15791 .buffer_snapshot
15792 .diff_hunks_in_range(Point::zero()..position)
15793 .find(|hunk| hunk.row_range.end.0 < position.row)
15794 .map(|hunk| hunk.row_range.start)
15795 });
15796 }
15797
15798 if let Some(row) = row {
15799 let destination = Point::new(row.0, 0);
15800 let autoscroll = Autoscroll::center();
15801
15802 self.unfold_ranges(&[destination..destination], false, false, cx);
15803 self.change_selections(Some(autoscroll), window, cx, |s| {
15804 s.select_ranges([destination..destination]);
15805 });
15806 }
15807 }
15808
15809 fn do_stage_or_unstage(
15810 &self,
15811 stage: bool,
15812 buffer_id: BufferId,
15813 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15814 cx: &mut App,
15815 ) -> Option<()> {
15816 let project = self.project.as_ref()?;
15817 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15818 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15819 let buffer_snapshot = buffer.read(cx).snapshot();
15820 let file_exists = buffer_snapshot
15821 .file()
15822 .is_some_and(|file| file.disk_state().exists());
15823 diff.update(cx, |diff, cx| {
15824 diff.stage_or_unstage_hunks(
15825 stage,
15826 &hunks
15827 .map(|hunk| buffer_diff::DiffHunk {
15828 buffer_range: hunk.buffer_range,
15829 diff_base_byte_range: hunk.diff_base_byte_range,
15830 secondary_status: hunk.secondary_status,
15831 range: Point::zero()..Point::zero(), // unused
15832 })
15833 .collect::<Vec<_>>(),
15834 &buffer_snapshot,
15835 file_exists,
15836 cx,
15837 )
15838 });
15839 None
15840 }
15841
15842 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15843 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15844 self.buffer
15845 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15846 }
15847
15848 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15849 self.buffer.update(cx, |buffer, cx| {
15850 let ranges = vec![Anchor::min()..Anchor::max()];
15851 if !buffer.all_diff_hunks_expanded()
15852 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15853 {
15854 buffer.collapse_diff_hunks(ranges, cx);
15855 true
15856 } else {
15857 false
15858 }
15859 })
15860 }
15861
15862 fn toggle_diff_hunks_in_ranges(
15863 &mut self,
15864 ranges: Vec<Range<Anchor>>,
15865 cx: &mut Context<Editor>,
15866 ) {
15867 self.buffer.update(cx, |buffer, cx| {
15868 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15869 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15870 })
15871 }
15872
15873 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15874 self.buffer.update(cx, |buffer, cx| {
15875 let snapshot = buffer.snapshot(cx);
15876 let excerpt_id = range.end.excerpt_id;
15877 let point_range = range.to_point(&snapshot);
15878 let expand = !buffer.single_hunk_is_expanded(range, cx);
15879 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15880 })
15881 }
15882
15883 pub(crate) fn apply_all_diff_hunks(
15884 &mut self,
15885 _: &ApplyAllDiffHunks,
15886 window: &mut Window,
15887 cx: &mut Context<Self>,
15888 ) {
15889 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15890
15891 let buffers = self.buffer.read(cx).all_buffers();
15892 for branch_buffer in buffers {
15893 branch_buffer.update(cx, |branch_buffer, cx| {
15894 branch_buffer.merge_into_base(Vec::new(), cx);
15895 });
15896 }
15897
15898 if let Some(project) = self.project.clone() {
15899 self.save(true, project, window, cx).detach_and_log_err(cx);
15900 }
15901 }
15902
15903 pub(crate) fn apply_selected_diff_hunks(
15904 &mut self,
15905 _: &ApplyDiffHunk,
15906 window: &mut Window,
15907 cx: &mut Context<Self>,
15908 ) {
15909 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15910 let snapshot = self.snapshot(window, cx);
15911 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15912 let mut ranges_by_buffer = HashMap::default();
15913 self.transact(window, cx, |editor, _window, cx| {
15914 for hunk in hunks {
15915 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15916 ranges_by_buffer
15917 .entry(buffer.clone())
15918 .or_insert_with(Vec::new)
15919 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15920 }
15921 }
15922
15923 for (buffer, ranges) in ranges_by_buffer {
15924 buffer.update(cx, |buffer, cx| {
15925 buffer.merge_into_base(ranges, cx);
15926 });
15927 }
15928 });
15929
15930 if let Some(project) = self.project.clone() {
15931 self.save(true, project, window, cx).detach_and_log_err(cx);
15932 }
15933 }
15934
15935 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15936 if hovered != self.gutter_hovered {
15937 self.gutter_hovered = hovered;
15938 cx.notify();
15939 }
15940 }
15941
15942 pub fn insert_blocks(
15943 &mut self,
15944 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15945 autoscroll: Option<Autoscroll>,
15946 cx: &mut Context<Self>,
15947 ) -> Vec<CustomBlockId> {
15948 let blocks = self
15949 .display_map
15950 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15951 if let Some(autoscroll) = autoscroll {
15952 self.request_autoscroll(autoscroll, cx);
15953 }
15954 cx.notify();
15955 blocks
15956 }
15957
15958 pub fn resize_blocks(
15959 &mut self,
15960 heights: HashMap<CustomBlockId, u32>,
15961 autoscroll: Option<Autoscroll>,
15962 cx: &mut Context<Self>,
15963 ) {
15964 self.display_map
15965 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15966 if let Some(autoscroll) = autoscroll {
15967 self.request_autoscroll(autoscroll, cx);
15968 }
15969 cx.notify();
15970 }
15971
15972 pub fn replace_blocks(
15973 &mut self,
15974 renderers: HashMap<CustomBlockId, RenderBlock>,
15975 autoscroll: Option<Autoscroll>,
15976 cx: &mut Context<Self>,
15977 ) {
15978 self.display_map
15979 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15980 if let Some(autoscroll) = autoscroll {
15981 self.request_autoscroll(autoscroll, cx);
15982 }
15983 cx.notify();
15984 }
15985
15986 pub fn remove_blocks(
15987 &mut self,
15988 block_ids: HashSet<CustomBlockId>,
15989 autoscroll: Option<Autoscroll>,
15990 cx: &mut Context<Self>,
15991 ) {
15992 self.display_map.update(cx, |display_map, cx| {
15993 display_map.remove_blocks(block_ids, cx)
15994 });
15995 if let Some(autoscroll) = autoscroll {
15996 self.request_autoscroll(autoscroll, cx);
15997 }
15998 cx.notify();
15999 }
16000
16001 pub fn row_for_block(
16002 &self,
16003 block_id: CustomBlockId,
16004 cx: &mut Context<Self>,
16005 ) -> Option<DisplayRow> {
16006 self.display_map
16007 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16008 }
16009
16010 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16011 self.focused_block = Some(focused_block);
16012 }
16013
16014 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16015 self.focused_block.take()
16016 }
16017
16018 pub fn insert_creases(
16019 &mut self,
16020 creases: impl IntoIterator<Item = Crease<Anchor>>,
16021 cx: &mut Context<Self>,
16022 ) -> Vec<CreaseId> {
16023 self.display_map
16024 .update(cx, |map, cx| map.insert_creases(creases, cx))
16025 }
16026
16027 pub fn remove_creases(
16028 &mut self,
16029 ids: impl IntoIterator<Item = CreaseId>,
16030 cx: &mut Context<Self>,
16031 ) {
16032 self.display_map
16033 .update(cx, |map, cx| map.remove_creases(ids, cx));
16034 }
16035
16036 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16037 self.display_map
16038 .update(cx, |map, cx| map.snapshot(cx))
16039 .longest_row()
16040 }
16041
16042 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16043 self.display_map
16044 .update(cx, |map, cx| map.snapshot(cx))
16045 .max_point()
16046 }
16047
16048 pub fn text(&self, cx: &App) -> String {
16049 self.buffer.read(cx).read(cx).text()
16050 }
16051
16052 pub fn is_empty(&self, cx: &App) -> bool {
16053 self.buffer.read(cx).read(cx).is_empty()
16054 }
16055
16056 pub fn text_option(&self, cx: &App) -> Option<String> {
16057 let text = self.text(cx);
16058 let text = text.trim();
16059
16060 if text.is_empty() {
16061 return None;
16062 }
16063
16064 Some(text.to_string())
16065 }
16066
16067 pub fn set_text(
16068 &mut self,
16069 text: impl Into<Arc<str>>,
16070 window: &mut Window,
16071 cx: &mut Context<Self>,
16072 ) {
16073 self.transact(window, cx, |this, _, cx| {
16074 this.buffer
16075 .read(cx)
16076 .as_singleton()
16077 .expect("you can only call set_text on editors for singleton buffers")
16078 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16079 });
16080 }
16081
16082 pub fn display_text(&self, cx: &mut App) -> String {
16083 self.display_map
16084 .update(cx, |map, cx| map.snapshot(cx))
16085 .text()
16086 }
16087
16088 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16089 let mut wrap_guides = smallvec::smallvec![];
16090
16091 if self.show_wrap_guides == Some(false) {
16092 return wrap_guides;
16093 }
16094
16095 let settings = self.buffer.read(cx).language_settings(cx);
16096 if settings.show_wrap_guides {
16097 match self.soft_wrap_mode(cx) {
16098 SoftWrap::Column(soft_wrap) => {
16099 wrap_guides.push((soft_wrap as usize, true));
16100 }
16101 SoftWrap::Bounded(soft_wrap) => {
16102 wrap_guides.push((soft_wrap as usize, true));
16103 }
16104 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16105 }
16106 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16107 }
16108
16109 wrap_guides
16110 }
16111
16112 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16113 let settings = self.buffer.read(cx).language_settings(cx);
16114 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16115 match mode {
16116 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16117 SoftWrap::None
16118 }
16119 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16120 language_settings::SoftWrap::PreferredLineLength => {
16121 SoftWrap::Column(settings.preferred_line_length)
16122 }
16123 language_settings::SoftWrap::Bounded => {
16124 SoftWrap::Bounded(settings.preferred_line_length)
16125 }
16126 }
16127 }
16128
16129 pub fn set_soft_wrap_mode(
16130 &mut self,
16131 mode: language_settings::SoftWrap,
16132
16133 cx: &mut Context<Self>,
16134 ) {
16135 self.soft_wrap_mode_override = Some(mode);
16136 cx.notify();
16137 }
16138
16139 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16140 self.hard_wrap = hard_wrap;
16141 cx.notify();
16142 }
16143
16144 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16145 self.text_style_refinement = Some(style);
16146 }
16147
16148 /// called by the Element so we know what style we were most recently rendered with.
16149 pub(crate) fn set_style(
16150 &mut self,
16151 style: EditorStyle,
16152 window: &mut Window,
16153 cx: &mut Context<Self>,
16154 ) {
16155 let rem_size = window.rem_size();
16156 self.display_map.update(cx, |map, cx| {
16157 map.set_font(
16158 style.text.font(),
16159 style.text.font_size.to_pixels(rem_size),
16160 cx,
16161 )
16162 });
16163 self.style = Some(style);
16164 }
16165
16166 pub fn style(&self) -> Option<&EditorStyle> {
16167 self.style.as_ref()
16168 }
16169
16170 // Called by the element. This method is not designed to be called outside of the editor
16171 // element's layout code because it does not notify when rewrapping is computed synchronously.
16172 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16173 self.display_map
16174 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16175 }
16176
16177 pub fn set_soft_wrap(&mut self) {
16178 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16179 }
16180
16181 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16182 if self.soft_wrap_mode_override.is_some() {
16183 self.soft_wrap_mode_override.take();
16184 } else {
16185 let soft_wrap = match self.soft_wrap_mode(cx) {
16186 SoftWrap::GitDiff => return,
16187 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16188 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16189 language_settings::SoftWrap::None
16190 }
16191 };
16192 self.soft_wrap_mode_override = Some(soft_wrap);
16193 }
16194 cx.notify();
16195 }
16196
16197 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16198 let Some(workspace) = self.workspace() else {
16199 return;
16200 };
16201 let fs = workspace.read(cx).app_state().fs.clone();
16202 let current_show = TabBarSettings::get_global(cx).show;
16203 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16204 setting.show = Some(!current_show);
16205 });
16206 }
16207
16208 pub fn toggle_indent_guides(
16209 &mut self,
16210 _: &ToggleIndentGuides,
16211 _: &mut Window,
16212 cx: &mut Context<Self>,
16213 ) {
16214 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16215 self.buffer
16216 .read(cx)
16217 .language_settings(cx)
16218 .indent_guides
16219 .enabled
16220 });
16221 self.show_indent_guides = Some(!currently_enabled);
16222 cx.notify();
16223 }
16224
16225 fn should_show_indent_guides(&self) -> Option<bool> {
16226 self.show_indent_guides
16227 }
16228
16229 pub fn toggle_line_numbers(
16230 &mut self,
16231 _: &ToggleLineNumbers,
16232 _: &mut Window,
16233 cx: &mut Context<Self>,
16234 ) {
16235 let mut editor_settings = EditorSettings::get_global(cx).clone();
16236 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16237 EditorSettings::override_global(editor_settings, cx);
16238 }
16239
16240 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16241 if let Some(show_line_numbers) = self.show_line_numbers {
16242 return show_line_numbers;
16243 }
16244 EditorSettings::get_global(cx).gutter.line_numbers
16245 }
16246
16247 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16248 self.use_relative_line_numbers
16249 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16250 }
16251
16252 pub fn toggle_relative_line_numbers(
16253 &mut self,
16254 _: &ToggleRelativeLineNumbers,
16255 _: &mut Window,
16256 cx: &mut Context<Self>,
16257 ) {
16258 let is_relative = self.should_use_relative_line_numbers(cx);
16259 self.set_relative_line_number(Some(!is_relative), cx)
16260 }
16261
16262 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16263 self.use_relative_line_numbers = is_relative;
16264 cx.notify();
16265 }
16266
16267 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16268 self.show_gutter = show_gutter;
16269 cx.notify();
16270 }
16271
16272 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16273 self.show_scrollbars = show_scrollbars;
16274 cx.notify();
16275 }
16276
16277 pub fn disable_scrolling(&mut self, cx: &mut Context<Self>) {
16278 self.disable_scrolling = true;
16279 cx.notify();
16280 }
16281
16282 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16283 self.show_line_numbers = Some(show_line_numbers);
16284 cx.notify();
16285 }
16286
16287 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16288 self.disable_expand_excerpt_buttons = true;
16289 cx.notify();
16290 }
16291
16292 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16293 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16294 cx.notify();
16295 }
16296
16297 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16298 self.show_code_actions = Some(show_code_actions);
16299 cx.notify();
16300 }
16301
16302 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16303 self.show_runnables = Some(show_runnables);
16304 cx.notify();
16305 }
16306
16307 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16308 self.show_breakpoints = Some(show_breakpoints);
16309 cx.notify();
16310 }
16311
16312 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16313 if self.display_map.read(cx).masked != masked {
16314 self.display_map.update(cx, |map, _| map.masked = masked);
16315 }
16316 cx.notify()
16317 }
16318
16319 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16320 self.show_wrap_guides = Some(show_wrap_guides);
16321 cx.notify();
16322 }
16323
16324 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16325 self.show_indent_guides = Some(show_indent_guides);
16326 cx.notify();
16327 }
16328
16329 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16330 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16331 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16332 if let Some(dir) = file.abs_path(cx).parent() {
16333 return Some(dir.to_owned());
16334 }
16335 }
16336
16337 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16338 return Some(project_path.path.to_path_buf());
16339 }
16340 }
16341
16342 None
16343 }
16344
16345 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16346 self.active_excerpt(cx)?
16347 .1
16348 .read(cx)
16349 .file()
16350 .and_then(|f| f.as_local())
16351 }
16352
16353 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16354 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16355 let buffer = buffer.read(cx);
16356 if let Some(project_path) = buffer.project_path(cx) {
16357 let project = self.project.as_ref()?.read(cx);
16358 project.absolute_path(&project_path, cx)
16359 } else {
16360 buffer
16361 .file()
16362 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16363 }
16364 })
16365 }
16366
16367 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16368 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16369 let project_path = buffer.read(cx).project_path(cx)?;
16370 let project = self.project.as_ref()?.read(cx);
16371 let entry = project.entry_for_path(&project_path, cx)?;
16372 let path = entry.path.to_path_buf();
16373 Some(path)
16374 })
16375 }
16376
16377 pub fn reveal_in_finder(
16378 &mut self,
16379 _: &RevealInFileManager,
16380 _window: &mut Window,
16381 cx: &mut Context<Self>,
16382 ) {
16383 if let Some(target) = self.target_file(cx) {
16384 cx.reveal_path(&target.abs_path(cx));
16385 }
16386 }
16387
16388 pub fn copy_path(
16389 &mut self,
16390 _: &zed_actions::workspace::CopyPath,
16391 _window: &mut Window,
16392 cx: &mut Context<Self>,
16393 ) {
16394 if let Some(path) = self.target_file_abs_path(cx) {
16395 if let Some(path) = path.to_str() {
16396 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16397 }
16398 }
16399 }
16400
16401 pub fn copy_relative_path(
16402 &mut self,
16403 _: &zed_actions::workspace::CopyRelativePath,
16404 _window: &mut Window,
16405 cx: &mut Context<Self>,
16406 ) {
16407 if let Some(path) = self.target_file_path(cx) {
16408 if let Some(path) = path.to_str() {
16409 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16410 }
16411 }
16412 }
16413
16414 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16415 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16416 buffer.read(cx).project_path(cx)
16417 } else {
16418 None
16419 }
16420 }
16421
16422 // Returns true if the editor handled a go-to-line request
16423 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16424 maybe!({
16425 let breakpoint_store = self.breakpoint_store.as_ref()?;
16426
16427 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
16428 else {
16429 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16430 return None;
16431 };
16432
16433 let position = active_stack_frame.position;
16434 let buffer_id = position.buffer_id?;
16435 let snapshot = self
16436 .project
16437 .as_ref()?
16438 .read(cx)
16439 .buffer_for_id(buffer_id, cx)?
16440 .read(cx)
16441 .snapshot();
16442
16443 let mut handled = false;
16444 for (id, ExcerptRange { context, .. }) in
16445 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
16446 {
16447 if context.start.cmp(&position, &snapshot).is_ge()
16448 || context.end.cmp(&position, &snapshot).is_lt()
16449 {
16450 continue;
16451 }
16452 let snapshot = self.buffer.read(cx).snapshot(cx);
16453 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
16454
16455 handled = true;
16456 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16457 self.go_to_line::<DebugCurrentRowHighlight>(
16458 multibuffer_anchor,
16459 Some(cx.theme().colors().editor_debugger_active_line_background),
16460 window,
16461 cx,
16462 );
16463
16464 cx.notify();
16465 }
16466
16467 handled.then_some(())
16468 })
16469 .is_some()
16470 }
16471
16472 pub fn copy_file_name_without_extension(
16473 &mut self,
16474 _: &CopyFileNameWithoutExtension,
16475 _: &mut Window,
16476 cx: &mut Context<Self>,
16477 ) {
16478 if let Some(file) = self.target_file(cx) {
16479 if let Some(file_stem) = file.path().file_stem() {
16480 if let Some(name) = file_stem.to_str() {
16481 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16482 }
16483 }
16484 }
16485 }
16486
16487 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16488 if let Some(file) = self.target_file(cx) {
16489 if let Some(file_name) = file.path().file_name() {
16490 if let Some(name) = file_name.to_str() {
16491 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16492 }
16493 }
16494 }
16495 }
16496
16497 pub fn toggle_git_blame(
16498 &mut self,
16499 _: &::git::Blame,
16500 window: &mut Window,
16501 cx: &mut Context<Self>,
16502 ) {
16503 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16504
16505 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16506 self.start_git_blame(true, window, cx);
16507 }
16508
16509 cx.notify();
16510 }
16511
16512 pub fn toggle_git_blame_inline(
16513 &mut self,
16514 _: &ToggleGitBlameInline,
16515 window: &mut Window,
16516 cx: &mut Context<Self>,
16517 ) {
16518 self.toggle_git_blame_inline_internal(true, window, cx);
16519 cx.notify();
16520 }
16521
16522 pub fn open_git_blame_commit(
16523 &mut self,
16524 _: &OpenGitBlameCommit,
16525 window: &mut Window,
16526 cx: &mut Context<Self>,
16527 ) {
16528 self.open_git_blame_commit_internal(window, cx);
16529 }
16530
16531 fn open_git_blame_commit_internal(
16532 &mut self,
16533 window: &mut Window,
16534 cx: &mut Context<Self>,
16535 ) -> Option<()> {
16536 let blame = self.blame.as_ref()?;
16537 let snapshot = self.snapshot(window, cx);
16538 let cursor = self.selections.newest::<Point>(cx).head();
16539 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16540 let blame_entry = blame
16541 .update(cx, |blame, cx| {
16542 blame
16543 .blame_for_rows(
16544 &[RowInfo {
16545 buffer_id: Some(buffer.remote_id()),
16546 buffer_row: Some(point.row),
16547 ..Default::default()
16548 }],
16549 cx,
16550 )
16551 .next()
16552 })
16553 .flatten()?;
16554 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16555 let repo = blame.read(cx).repository(cx)?;
16556 let workspace = self.workspace()?.downgrade();
16557 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16558 None
16559 }
16560
16561 pub fn git_blame_inline_enabled(&self) -> bool {
16562 self.git_blame_inline_enabled
16563 }
16564
16565 pub fn toggle_selection_menu(
16566 &mut self,
16567 _: &ToggleSelectionMenu,
16568 _: &mut Window,
16569 cx: &mut Context<Self>,
16570 ) {
16571 self.show_selection_menu = self
16572 .show_selection_menu
16573 .map(|show_selections_menu| !show_selections_menu)
16574 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16575
16576 cx.notify();
16577 }
16578
16579 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16580 self.show_selection_menu
16581 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16582 }
16583
16584 fn start_git_blame(
16585 &mut self,
16586 user_triggered: bool,
16587 window: &mut Window,
16588 cx: &mut Context<Self>,
16589 ) {
16590 if let Some(project) = self.project.as_ref() {
16591 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16592 return;
16593 };
16594
16595 if buffer.read(cx).file().is_none() {
16596 return;
16597 }
16598
16599 let focused = self.focus_handle(cx).contains_focused(window, cx);
16600
16601 let project = project.clone();
16602 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16603 self.blame_subscription =
16604 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16605 self.blame = Some(blame);
16606 }
16607 }
16608
16609 fn toggle_git_blame_inline_internal(
16610 &mut self,
16611 user_triggered: bool,
16612 window: &mut Window,
16613 cx: &mut Context<Self>,
16614 ) {
16615 if self.git_blame_inline_enabled {
16616 self.git_blame_inline_enabled = false;
16617 self.show_git_blame_inline = false;
16618 self.show_git_blame_inline_delay_task.take();
16619 } else {
16620 self.git_blame_inline_enabled = true;
16621 self.start_git_blame_inline(user_triggered, window, cx);
16622 }
16623
16624 cx.notify();
16625 }
16626
16627 fn start_git_blame_inline(
16628 &mut self,
16629 user_triggered: bool,
16630 window: &mut Window,
16631 cx: &mut Context<Self>,
16632 ) {
16633 self.start_git_blame(user_triggered, window, cx);
16634
16635 if ProjectSettings::get_global(cx)
16636 .git
16637 .inline_blame_delay()
16638 .is_some()
16639 {
16640 self.start_inline_blame_timer(window, cx);
16641 } else {
16642 self.show_git_blame_inline = true
16643 }
16644 }
16645
16646 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16647 self.blame.as_ref()
16648 }
16649
16650 pub fn show_git_blame_gutter(&self) -> bool {
16651 self.show_git_blame_gutter
16652 }
16653
16654 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16655 self.show_git_blame_gutter && self.has_blame_entries(cx)
16656 }
16657
16658 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16659 self.show_git_blame_inline
16660 && (self.focus_handle.is_focused(window)
16661 || self
16662 .git_blame_inline_tooltip
16663 .as_ref()
16664 .and_then(|t| t.upgrade())
16665 .is_some())
16666 && !self.newest_selection_head_on_empty_line(cx)
16667 && self.has_blame_entries(cx)
16668 }
16669
16670 fn has_blame_entries(&self, cx: &App) -> bool {
16671 self.blame()
16672 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16673 }
16674
16675 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16676 let cursor_anchor = self.selections.newest_anchor().head();
16677
16678 let snapshot = self.buffer.read(cx).snapshot(cx);
16679 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16680
16681 snapshot.line_len(buffer_row) == 0
16682 }
16683
16684 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16685 let buffer_and_selection = maybe!({
16686 let selection = self.selections.newest::<Point>(cx);
16687 let selection_range = selection.range();
16688
16689 let multi_buffer = self.buffer().read(cx);
16690 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16691 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16692
16693 let (buffer, range, _) = if selection.reversed {
16694 buffer_ranges.first()
16695 } else {
16696 buffer_ranges.last()
16697 }?;
16698
16699 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16700 ..text::ToPoint::to_point(&range.end, &buffer).row;
16701 Some((
16702 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16703 selection,
16704 ))
16705 });
16706
16707 let Some((buffer, selection)) = buffer_and_selection else {
16708 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16709 };
16710
16711 let Some(project) = self.project.as_ref() else {
16712 return Task::ready(Err(anyhow!("editor does not have project")));
16713 };
16714
16715 project.update(cx, |project, cx| {
16716 project.get_permalink_to_line(&buffer, selection, cx)
16717 })
16718 }
16719
16720 pub fn copy_permalink_to_line(
16721 &mut self,
16722 _: &CopyPermalinkToLine,
16723 window: &mut Window,
16724 cx: &mut Context<Self>,
16725 ) {
16726 let permalink_task = self.get_permalink_to_line(cx);
16727 let workspace = self.workspace();
16728
16729 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16730 Ok(permalink) => {
16731 cx.update(|_, cx| {
16732 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16733 })
16734 .ok();
16735 }
16736 Err(err) => {
16737 let message = format!("Failed to copy permalink: {err}");
16738
16739 Err::<(), anyhow::Error>(err).log_err();
16740
16741 if let Some(workspace) = workspace {
16742 workspace
16743 .update_in(cx, |workspace, _, cx| {
16744 struct CopyPermalinkToLine;
16745
16746 workspace.show_toast(
16747 Toast::new(
16748 NotificationId::unique::<CopyPermalinkToLine>(),
16749 message,
16750 ),
16751 cx,
16752 )
16753 })
16754 .ok();
16755 }
16756 }
16757 })
16758 .detach();
16759 }
16760
16761 pub fn copy_file_location(
16762 &mut self,
16763 _: &CopyFileLocation,
16764 _: &mut Window,
16765 cx: &mut Context<Self>,
16766 ) {
16767 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16768 if let Some(file) = self.target_file(cx) {
16769 if let Some(path) = file.path().to_str() {
16770 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16771 }
16772 }
16773 }
16774
16775 pub fn open_permalink_to_line(
16776 &mut self,
16777 _: &OpenPermalinkToLine,
16778 window: &mut Window,
16779 cx: &mut Context<Self>,
16780 ) {
16781 let permalink_task = self.get_permalink_to_line(cx);
16782 let workspace = self.workspace();
16783
16784 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16785 Ok(permalink) => {
16786 cx.update(|_, cx| {
16787 cx.open_url(permalink.as_ref());
16788 })
16789 .ok();
16790 }
16791 Err(err) => {
16792 let message = format!("Failed to open permalink: {err}");
16793
16794 Err::<(), anyhow::Error>(err).log_err();
16795
16796 if let Some(workspace) = workspace {
16797 workspace
16798 .update(cx, |workspace, cx| {
16799 struct OpenPermalinkToLine;
16800
16801 workspace.show_toast(
16802 Toast::new(
16803 NotificationId::unique::<OpenPermalinkToLine>(),
16804 message,
16805 ),
16806 cx,
16807 )
16808 })
16809 .ok();
16810 }
16811 }
16812 })
16813 .detach();
16814 }
16815
16816 pub fn insert_uuid_v4(
16817 &mut self,
16818 _: &InsertUuidV4,
16819 window: &mut Window,
16820 cx: &mut Context<Self>,
16821 ) {
16822 self.insert_uuid(UuidVersion::V4, window, cx);
16823 }
16824
16825 pub fn insert_uuid_v7(
16826 &mut self,
16827 _: &InsertUuidV7,
16828 window: &mut Window,
16829 cx: &mut Context<Self>,
16830 ) {
16831 self.insert_uuid(UuidVersion::V7, window, cx);
16832 }
16833
16834 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16835 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16836 self.transact(window, cx, |this, window, cx| {
16837 let edits = this
16838 .selections
16839 .all::<Point>(cx)
16840 .into_iter()
16841 .map(|selection| {
16842 let uuid = match version {
16843 UuidVersion::V4 => uuid::Uuid::new_v4(),
16844 UuidVersion::V7 => uuid::Uuid::now_v7(),
16845 };
16846
16847 (selection.range(), uuid.to_string())
16848 });
16849 this.edit(edits, cx);
16850 this.refresh_inline_completion(true, false, window, cx);
16851 });
16852 }
16853
16854 pub fn open_selections_in_multibuffer(
16855 &mut self,
16856 _: &OpenSelectionsInMultibuffer,
16857 window: &mut Window,
16858 cx: &mut Context<Self>,
16859 ) {
16860 let multibuffer = self.buffer.read(cx);
16861
16862 let Some(buffer) = multibuffer.as_singleton() else {
16863 return;
16864 };
16865
16866 let Some(workspace) = self.workspace() else {
16867 return;
16868 };
16869
16870 let locations = self
16871 .selections
16872 .disjoint_anchors()
16873 .iter()
16874 .map(|range| Location {
16875 buffer: buffer.clone(),
16876 range: range.start.text_anchor..range.end.text_anchor,
16877 })
16878 .collect::<Vec<_>>();
16879
16880 let title = multibuffer.title(cx).to_string();
16881
16882 cx.spawn_in(window, async move |_, cx| {
16883 workspace.update_in(cx, |workspace, window, cx| {
16884 Self::open_locations_in_multibuffer(
16885 workspace,
16886 locations,
16887 format!("Selections for '{title}'"),
16888 false,
16889 MultibufferSelectionMode::All,
16890 window,
16891 cx,
16892 );
16893 })
16894 })
16895 .detach();
16896 }
16897
16898 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16899 /// last highlight added will be used.
16900 ///
16901 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16902 pub fn highlight_rows<T: 'static>(
16903 &mut self,
16904 range: Range<Anchor>,
16905 color: Hsla,
16906 options: RowHighlightOptions,
16907 cx: &mut Context<Self>,
16908 ) {
16909 let snapshot = self.buffer().read(cx).snapshot(cx);
16910 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16911 let ix = row_highlights.binary_search_by(|highlight| {
16912 Ordering::Equal
16913 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16914 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16915 });
16916
16917 if let Err(mut ix) = ix {
16918 let index = post_inc(&mut self.highlight_order);
16919
16920 // If this range intersects with the preceding highlight, then merge it with
16921 // the preceding highlight. Otherwise insert a new highlight.
16922 let mut merged = false;
16923 if ix > 0 {
16924 let prev_highlight = &mut row_highlights[ix - 1];
16925 if prev_highlight
16926 .range
16927 .end
16928 .cmp(&range.start, &snapshot)
16929 .is_ge()
16930 {
16931 ix -= 1;
16932 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16933 prev_highlight.range.end = range.end;
16934 }
16935 merged = true;
16936 prev_highlight.index = index;
16937 prev_highlight.color = color;
16938 prev_highlight.options = options;
16939 }
16940 }
16941
16942 if !merged {
16943 row_highlights.insert(
16944 ix,
16945 RowHighlight {
16946 range: range.clone(),
16947 index,
16948 color,
16949 options,
16950 type_id: TypeId::of::<T>(),
16951 },
16952 );
16953 }
16954
16955 // If any of the following highlights intersect with this one, merge them.
16956 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16957 let highlight = &row_highlights[ix];
16958 if next_highlight
16959 .range
16960 .start
16961 .cmp(&highlight.range.end, &snapshot)
16962 .is_le()
16963 {
16964 if next_highlight
16965 .range
16966 .end
16967 .cmp(&highlight.range.end, &snapshot)
16968 .is_gt()
16969 {
16970 row_highlights[ix].range.end = next_highlight.range.end;
16971 }
16972 row_highlights.remove(ix + 1);
16973 } else {
16974 break;
16975 }
16976 }
16977 }
16978 }
16979
16980 /// Remove any highlighted row ranges of the given type that intersect the
16981 /// given ranges.
16982 pub fn remove_highlighted_rows<T: 'static>(
16983 &mut self,
16984 ranges_to_remove: Vec<Range<Anchor>>,
16985 cx: &mut Context<Self>,
16986 ) {
16987 let snapshot = self.buffer().read(cx).snapshot(cx);
16988 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16989 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16990 row_highlights.retain(|highlight| {
16991 while let Some(range_to_remove) = ranges_to_remove.peek() {
16992 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16993 Ordering::Less | Ordering::Equal => {
16994 ranges_to_remove.next();
16995 }
16996 Ordering::Greater => {
16997 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16998 Ordering::Less | Ordering::Equal => {
16999 return false;
17000 }
17001 Ordering::Greater => break,
17002 }
17003 }
17004 }
17005 }
17006
17007 true
17008 })
17009 }
17010
17011 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17012 pub fn clear_row_highlights<T: 'static>(&mut self) {
17013 self.highlighted_rows.remove(&TypeId::of::<T>());
17014 }
17015
17016 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17017 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17018 self.highlighted_rows
17019 .get(&TypeId::of::<T>())
17020 .map_or(&[] as &[_], |vec| vec.as_slice())
17021 .iter()
17022 .map(|highlight| (highlight.range.clone(), highlight.color))
17023 }
17024
17025 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17026 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17027 /// Allows to ignore certain kinds of highlights.
17028 pub fn highlighted_display_rows(
17029 &self,
17030 window: &mut Window,
17031 cx: &mut App,
17032 ) -> BTreeMap<DisplayRow, LineHighlight> {
17033 let snapshot = self.snapshot(window, cx);
17034 let mut used_highlight_orders = HashMap::default();
17035 self.highlighted_rows
17036 .iter()
17037 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17038 .fold(
17039 BTreeMap::<DisplayRow, LineHighlight>::new(),
17040 |mut unique_rows, highlight| {
17041 let start = highlight.range.start.to_display_point(&snapshot);
17042 let end = highlight.range.end.to_display_point(&snapshot);
17043 let start_row = start.row().0;
17044 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17045 && end.column() == 0
17046 {
17047 end.row().0.saturating_sub(1)
17048 } else {
17049 end.row().0
17050 };
17051 for row in start_row..=end_row {
17052 let used_index =
17053 used_highlight_orders.entry(row).or_insert(highlight.index);
17054 if highlight.index >= *used_index {
17055 *used_index = highlight.index;
17056 unique_rows.insert(
17057 DisplayRow(row),
17058 LineHighlight {
17059 include_gutter: highlight.options.include_gutter,
17060 border: None,
17061 background: highlight.color.into(),
17062 type_id: Some(highlight.type_id),
17063 },
17064 );
17065 }
17066 }
17067 unique_rows
17068 },
17069 )
17070 }
17071
17072 pub fn highlighted_display_row_for_autoscroll(
17073 &self,
17074 snapshot: &DisplaySnapshot,
17075 ) -> Option<DisplayRow> {
17076 self.highlighted_rows
17077 .values()
17078 .flat_map(|highlighted_rows| highlighted_rows.iter())
17079 .filter_map(|highlight| {
17080 if highlight.options.autoscroll {
17081 Some(highlight.range.start.to_display_point(snapshot).row())
17082 } else {
17083 None
17084 }
17085 })
17086 .min()
17087 }
17088
17089 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17090 self.highlight_background::<SearchWithinRange>(
17091 ranges,
17092 |colors| colors.editor_document_highlight_read_background,
17093 cx,
17094 )
17095 }
17096
17097 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17098 self.breadcrumb_header = Some(new_header);
17099 }
17100
17101 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17102 self.clear_background_highlights::<SearchWithinRange>(cx);
17103 }
17104
17105 pub fn highlight_background<T: 'static>(
17106 &mut self,
17107 ranges: &[Range<Anchor>],
17108 color_fetcher: fn(&ThemeColors) -> Hsla,
17109 cx: &mut Context<Self>,
17110 ) {
17111 self.background_highlights
17112 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17113 self.scrollbar_marker_state.dirty = true;
17114 cx.notify();
17115 }
17116
17117 pub fn clear_background_highlights<T: 'static>(
17118 &mut self,
17119 cx: &mut Context<Self>,
17120 ) -> Option<BackgroundHighlight> {
17121 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17122 if !text_highlights.1.is_empty() {
17123 self.scrollbar_marker_state.dirty = true;
17124 cx.notify();
17125 }
17126 Some(text_highlights)
17127 }
17128
17129 pub fn highlight_gutter<T: 'static>(
17130 &mut self,
17131 ranges: &[Range<Anchor>],
17132 color_fetcher: fn(&App) -> Hsla,
17133 cx: &mut Context<Self>,
17134 ) {
17135 self.gutter_highlights
17136 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17137 cx.notify();
17138 }
17139
17140 pub fn clear_gutter_highlights<T: 'static>(
17141 &mut self,
17142 cx: &mut Context<Self>,
17143 ) -> Option<GutterHighlight> {
17144 cx.notify();
17145 self.gutter_highlights.remove(&TypeId::of::<T>())
17146 }
17147
17148 #[cfg(feature = "test-support")]
17149 pub fn all_text_background_highlights(
17150 &self,
17151 window: &mut Window,
17152 cx: &mut Context<Self>,
17153 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17154 let snapshot = self.snapshot(window, cx);
17155 let buffer = &snapshot.buffer_snapshot;
17156 let start = buffer.anchor_before(0);
17157 let end = buffer.anchor_after(buffer.len());
17158 let theme = cx.theme().colors();
17159 self.background_highlights_in_range(start..end, &snapshot, theme)
17160 }
17161
17162 #[cfg(feature = "test-support")]
17163 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17164 let snapshot = self.buffer().read(cx).snapshot(cx);
17165
17166 let highlights = self
17167 .background_highlights
17168 .get(&TypeId::of::<items::BufferSearchHighlights>());
17169
17170 if let Some((_color, ranges)) = highlights {
17171 ranges
17172 .iter()
17173 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17174 .collect_vec()
17175 } else {
17176 vec![]
17177 }
17178 }
17179
17180 fn document_highlights_for_position<'a>(
17181 &'a self,
17182 position: Anchor,
17183 buffer: &'a MultiBufferSnapshot,
17184 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17185 let read_highlights = self
17186 .background_highlights
17187 .get(&TypeId::of::<DocumentHighlightRead>())
17188 .map(|h| &h.1);
17189 let write_highlights = self
17190 .background_highlights
17191 .get(&TypeId::of::<DocumentHighlightWrite>())
17192 .map(|h| &h.1);
17193 let left_position = position.bias_left(buffer);
17194 let right_position = position.bias_right(buffer);
17195 read_highlights
17196 .into_iter()
17197 .chain(write_highlights)
17198 .flat_map(move |ranges| {
17199 let start_ix = match ranges.binary_search_by(|probe| {
17200 let cmp = probe.end.cmp(&left_position, buffer);
17201 if cmp.is_ge() {
17202 Ordering::Greater
17203 } else {
17204 Ordering::Less
17205 }
17206 }) {
17207 Ok(i) | Err(i) => i,
17208 };
17209
17210 ranges[start_ix..]
17211 .iter()
17212 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17213 })
17214 }
17215
17216 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17217 self.background_highlights
17218 .get(&TypeId::of::<T>())
17219 .map_or(false, |(_, highlights)| !highlights.is_empty())
17220 }
17221
17222 pub fn background_highlights_in_range(
17223 &self,
17224 search_range: Range<Anchor>,
17225 display_snapshot: &DisplaySnapshot,
17226 theme: &ThemeColors,
17227 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17228 let mut results = Vec::new();
17229 for (color_fetcher, ranges) in self.background_highlights.values() {
17230 let color = color_fetcher(theme);
17231 let start_ix = match ranges.binary_search_by(|probe| {
17232 let cmp = probe
17233 .end
17234 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17235 if cmp.is_gt() {
17236 Ordering::Greater
17237 } else {
17238 Ordering::Less
17239 }
17240 }) {
17241 Ok(i) | Err(i) => i,
17242 };
17243 for range in &ranges[start_ix..] {
17244 if range
17245 .start
17246 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17247 .is_ge()
17248 {
17249 break;
17250 }
17251
17252 let start = range.start.to_display_point(display_snapshot);
17253 let end = range.end.to_display_point(display_snapshot);
17254 results.push((start..end, color))
17255 }
17256 }
17257 results
17258 }
17259
17260 pub fn background_highlight_row_ranges<T: 'static>(
17261 &self,
17262 search_range: Range<Anchor>,
17263 display_snapshot: &DisplaySnapshot,
17264 count: usize,
17265 ) -> Vec<RangeInclusive<DisplayPoint>> {
17266 let mut results = Vec::new();
17267 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17268 return vec![];
17269 };
17270
17271 let start_ix = match ranges.binary_search_by(|probe| {
17272 let cmp = probe
17273 .end
17274 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17275 if cmp.is_gt() {
17276 Ordering::Greater
17277 } else {
17278 Ordering::Less
17279 }
17280 }) {
17281 Ok(i) | Err(i) => i,
17282 };
17283 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17284 if let (Some(start_display), Some(end_display)) = (start, end) {
17285 results.push(
17286 start_display.to_display_point(display_snapshot)
17287 ..=end_display.to_display_point(display_snapshot),
17288 );
17289 }
17290 };
17291 let mut start_row: Option<Point> = None;
17292 let mut end_row: Option<Point> = None;
17293 if ranges.len() > count {
17294 return Vec::new();
17295 }
17296 for range in &ranges[start_ix..] {
17297 if range
17298 .start
17299 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17300 .is_ge()
17301 {
17302 break;
17303 }
17304 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17305 if let Some(current_row) = &end_row {
17306 if end.row == current_row.row {
17307 continue;
17308 }
17309 }
17310 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17311 if start_row.is_none() {
17312 assert_eq!(end_row, None);
17313 start_row = Some(start);
17314 end_row = Some(end);
17315 continue;
17316 }
17317 if let Some(current_end) = end_row.as_mut() {
17318 if start.row > current_end.row + 1 {
17319 push_region(start_row, end_row);
17320 start_row = Some(start);
17321 end_row = Some(end);
17322 } else {
17323 // Merge two hunks.
17324 *current_end = end;
17325 }
17326 } else {
17327 unreachable!();
17328 }
17329 }
17330 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17331 push_region(start_row, end_row);
17332 results
17333 }
17334
17335 pub fn gutter_highlights_in_range(
17336 &self,
17337 search_range: Range<Anchor>,
17338 display_snapshot: &DisplaySnapshot,
17339 cx: &App,
17340 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17341 let mut results = Vec::new();
17342 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17343 let color = color_fetcher(cx);
17344 let start_ix = match ranges.binary_search_by(|probe| {
17345 let cmp = probe
17346 .end
17347 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17348 if cmp.is_gt() {
17349 Ordering::Greater
17350 } else {
17351 Ordering::Less
17352 }
17353 }) {
17354 Ok(i) | Err(i) => i,
17355 };
17356 for range in &ranges[start_ix..] {
17357 if range
17358 .start
17359 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17360 .is_ge()
17361 {
17362 break;
17363 }
17364
17365 let start = range.start.to_display_point(display_snapshot);
17366 let end = range.end.to_display_point(display_snapshot);
17367 results.push((start..end, color))
17368 }
17369 }
17370 results
17371 }
17372
17373 /// Get the text ranges corresponding to the redaction query
17374 pub fn redacted_ranges(
17375 &self,
17376 search_range: Range<Anchor>,
17377 display_snapshot: &DisplaySnapshot,
17378 cx: &App,
17379 ) -> Vec<Range<DisplayPoint>> {
17380 display_snapshot
17381 .buffer_snapshot
17382 .redacted_ranges(search_range, |file| {
17383 if let Some(file) = file {
17384 file.is_private()
17385 && EditorSettings::get(
17386 Some(SettingsLocation {
17387 worktree_id: file.worktree_id(cx),
17388 path: file.path().as_ref(),
17389 }),
17390 cx,
17391 )
17392 .redact_private_values
17393 } else {
17394 false
17395 }
17396 })
17397 .map(|range| {
17398 range.start.to_display_point(display_snapshot)
17399 ..range.end.to_display_point(display_snapshot)
17400 })
17401 .collect()
17402 }
17403
17404 pub fn highlight_text<T: 'static>(
17405 &mut self,
17406 ranges: Vec<Range<Anchor>>,
17407 style: HighlightStyle,
17408 cx: &mut Context<Self>,
17409 ) {
17410 self.display_map.update(cx, |map, _| {
17411 map.highlight_text(TypeId::of::<T>(), ranges, style)
17412 });
17413 cx.notify();
17414 }
17415
17416 pub(crate) fn highlight_inlays<T: 'static>(
17417 &mut self,
17418 highlights: Vec<InlayHighlight>,
17419 style: HighlightStyle,
17420 cx: &mut Context<Self>,
17421 ) {
17422 self.display_map.update(cx, |map, _| {
17423 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17424 });
17425 cx.notify();
17426 }
17427
17428 pub fn text_highlights<'a, T: 'static>(
17429 &'a self,
17430 cx: &'a App,
17431 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17432 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17433 }
17434
17435 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17436 let cleared = self
17437 .display_map
17438 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17439 if cleared {
17440 cx.notify();
17441 }
17442 }
17443
17444 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17445 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17446 && self.focus_handle.is_focused(window)
17447 }
17448
17449 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17450 self.show_cursor_when_unfocused = is_enabled;
17451 cx.notify();
17452 }
17453
17454 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17455 cx.notify();
17456 }
17457
17458 fn on_debug_session_event(
17459 &mut self,
17460 _session: Entity<Session>,
17461 event: &SessionEvent,
17462 cx: &mut Context<Self>,
17463 ) {
17464 match event {
17465 SessionEvent::InvalidateInlineValue => {
17466 self.refresh_inline_values(cx);
17467 }
17468 _ => {}
17469 }
17470 }
17471
17472 fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
17473 let Some(project) = self.project.clone() else {
17474 return;
17475 };
17476 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
17477 return;
17478 };
17479 if !self.inline_value_cache.enabled {
17480 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
17481 self.splice_inlays(&inlays, Vec::new(), cx);
17482 return;
17483 }
17484
17485 let current_execution_position = self
17486 .highlighted_rows
17487 .get(&TypeId::of::<DebugCurrentRowHighlight>())
17488 .and_then(|lines| lines.last().map(|line| line.range.start));
17489
17490 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
17491 let snapshot = editor
17492 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17493 .ok()?;
17494
17495 let inline_values = editor
17496 .update(cx, |_, cx| {
17497 let Some(current_execution_position) = current_execution_position else {
17498 return Some(Task::ready(Ok(Vec::new())));
17499 };
17500
17501 // todo(debugger) when introducing multi buffer inline values check execution position's buffer id to make sure the text
17502 // anchor is in the same buffer
17503 let range =
17504 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
17505 project.inline_values(buffer, range, cx)
17506 })
17507 .ok()
17508 .flatten()?
17509 .await
17510 .context("refreshing debugger inlays")
17511 .log_err()?;
17512
17513 let (excerpt_id, buffer_id) = snapshot
17514 .excerpts()
17515 .next()
17516 .map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?;
17517 editor
17518 .update(cx, |editor, cx| {
17519 let new_inlays = inline_values
17520 .into_iter()
17521 .map(|debugger_value| {
17522 Inlay::debugger_hint(
17523 post_inc(&mut editor.next_inlay_id),
17524 Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position),
17525 debugger_value.text(),
17526 )
17527 })
17528 .collect::<Vec<_>>();
17529 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
17530 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
17531
17532 editor.splice_inlays(&inlay_ids, new_inlays, cx);
17533 })
17534 .ok()?;
17535 Some(())
17536 });
17537 }
17538
17539 fn on_buffer_event(
17540 &mut self,
17541 multibuffer: &Entity<MultiBuffer>,
17542 event: &multi_buffer::Event,
17543 window: &mut Window,
17544 cx: &mut Context<Self>,
17545 ) {
17546 match event {
17547 multi_buffer::Event::Edited {
17548 singleton_buffer_edited,
17549 edited_buffer: buffer_edited,
17550 } => {
17551 self.scrollbar_marker_state.dirty = true;
17552 self.active_indent_guides_state.dirty = true;
17553 self.refresh_active_diagnostics(cx);
17554 self.refresh_code_actions(window, cx);
17555 if self.has_active_inline_completion() {
17556 self.update_visible_inline_completion(window, cx);
17557 }
17558 if let Some(buffer) = buffer_edited {
17559 let buffer_id = buffer.read(cx).remote_id();
17560 if !self.registered_buffers.contains_key(&buffer_id) {
17561 if let Some(project) = self.project.as_ref() {
17562 project.update(cx, |project, cx| {
17563 self.registered_buffers.insert(
17564 buffer_id,
17565 project.register_buffer_with_language_servers(&buffer, cx),
17566 );
17567 })
17568 }
17569 }
17570 }
17571 cx.emit(EditorEvent::BufferEdited);
17572 cx.emit(SearchEvent::MatchesInvalidated);
17573 if *singleton_buffer_edited {
17574 if let Some(project) = &self.project {
17575 #[allow(clippy::mutable_key_type)]
17576 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17577 multibuffer
17578 .all_buffers()
17579 .into_iter()
17580 .filter_map(|buffer| {
17581 buffer.update(cx, |buffer, cx| {
17582 let language = buffer.language()?;
17583 let should_discard = project.update(cx, |project, cx| {
17584 project.is_local()
17585 && !project.has_language_servers_for(buffer, cx)
17586 });
17587 should_discard.not().then_some(language.clone())
17588 })
17589 })
17590 .collect::<HashSet<_>>()
17591 });
17592 if !languages_affected.is_empty() {
17593 self.refresh_inlay_hints(
17594 InlayHintRefreshReason::BufferEdited(languages_affected),
17595 cx,
17596 );
17597 }
17598 }
17599 }
17600
17601 let Some(project) = &self.project else { return };
17602 let (telemetry, is_via_ssh) = {
17603 let project = project.read(cx);
17604 let telemetry = project.client().telemetry().clone();
17605 let is_via_ssh = project.is_via_ssh();
17606 (telemetry, is_via_ssh)
17607 };
17608 refresh_linked_ranges(self, window, cx);
17609 telemetry.log_edit_event("editor", is_via_ssh);
17610 }
17611 multi_buffer::Event::ExcerptsAdded {
17612 buffer,
17613 predecessor,
17614 excerpts,
17615 } => {
17616 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17617 let buffer_id = buffer.read(cx).remote_id();
17618 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17619 if let Some(project) = &self.project {
17620 get_uncommitted_diff_for_buffer(
17621 project,
17622 [buffer.clone()],
17623 self.buffer.clone(),
17624 cx,
17625 )
17626 .detach();
17627 }
17628 }
17629 cx.emit(EditorEvent::ExcerptsAdded {
17630 buffer: buffer.clone(),
17631 predecessor: *predecessor,
17632 excerpts: excerpts.clone(),
17633 });
17634 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17635 }
17636 multi_buffer::Event::ExcerptsRemoved {
17637 ids,
17638 removed_buffer_ids,
17639 } => {
17640 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17641 let buffer = self.buffer.read(cx);
17642 self.registered_buffers
17643 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17644 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17645 cx.emit(EditorEvent::ExcerptsRemoved {
17646 ids: ids.clone(),
17647 removed_buffer_ids: removed_buffer_ids.clone(),
17648 })
17649 }
17650 multi_buffer::Event::ExcerptsEdited {
17651 excerpt_ids,
17652 buffer_ids,
17653 } => {
17654 self.display_map.update(cx, |map, cx| {
17655 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17656 });
17657 cx.emit(EditorEvent::ExcerptsEdited {
17658 ids: excerpt_ids.clone(),
17659 })
17660 }
17661 multi_buffer::Event::ExcerptsExpanded { ids } => {
17662 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17663 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17664 }
17665 multi_buffer::Event::Reparsed(buffer_id) => {
17666 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17667 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17668
17669 cx.emit(EditorEvent::Reparsed(*buffer_id));
17670 }
17671 multi_buffer::Event::DiffHunksToggled => {
17672 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17673 }
17674 multi_buffer::Event::LanguageChanged(buffer_id) => {
17675 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17676 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17677 cx.emit(EditorEvent::Reparsed(*buffer_id));
17678 cx.notify();
17679 }
17680 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17681 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17682 multi_buffer::Event::FileHandleChanged
17683 | multi_buffer::Event::Reloaded
17684 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17685 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17686 multi_buffer::Event::DiagnosticsUpdated => {
17687 self.refresh_active_diagnostics(cx);
17688 self.refresh_inline_diagnostics(true, window, cx);
17689 self.scrollbar_marker_state.dirty = true;
17690 cx.notify();
17691 }
17692 _ => {}
17693 };
17694 }
17695
17696 fn on_display_map_changed(
17697 &mut self,
17698 _: Entity<DisplayMap>,
17699 _: &mut Window,
17700 cx: &mut Context<Self>,
17701 ) {
17702 cx.notify();
17703 }
17704
17705 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17706 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17707 self.update_edit_prediction_settings(cx);
17708 self.refresh_inline_completion(true, false, window, cx);
17709 self.refresh_inlay_hints(
17710 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17711 self.selections.newest_anchor().head(),
17712 &self.buffer.read(cx).snapshot(cx),
17713 cx,
17714 )),
17715 cx,
17716 );
17717
17718 let old_cursor_shape = self.cursor_shape;
17719
17720 {
17721 let editor_settings = EditorSettings::get_global(cx);
17722 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17723 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17724 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17725 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17726 }
17727
17728 if old_cursor_shape != self.cursor_shape {
17729 cx.emit(EditorEvent::CursorShapeChanged);
17730 }
17731
17732 let project_settings = ProjectSettings::get_global(cx);
17733 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17734
17735 if self.mode.is_full() {
17736 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17737 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17738 if self.show_inline_diagnostics != show_inline_diagnostics {
17739 self.show_inline_diagnostics = show_inline_diagnostics;
17740 self.refresh_inline_diagnostics(false, window, cx);
17741 }
17742
17743 if self.git_blame_inline_enabled != inline_blame_enabled {
17744 self.toggle_git_blame_inline_internal(false, window, cx);
17745 }
17746 }
17747
17748 cx.notify();
17749 }
17750
17751 pub fn set_searchable(&mut self, searchable: bool) {
17752 self.searchable = searchable;
17753 }
17754
17755 pub fn searchable(&self) -> bool {
17756 self.searchable
17757 }
17758
17759 fn open_proposed_changes_editor(
17760 &mut self,
17761 _: &OpenProposedChangesEditor,
17762 window: &mut Window,
17763 cx: &mut Context<Self>,
17764 ) {
17765 let Some(workspace) = self.workspace() else {
17766 cx.propagate();
17767 return;
17768 };
17769
17770 let selections = self.selections.all::<usize>(cx);
17771 let multi_buffer = self.buffer.read(cx);
17772 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17773 let mut new_selections_by_buffer = HashMap::default();
17774 for selection in selections {
17775 for (buffer, range, _) in
17776 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17777 {
17778 let mut range = range.to_point(buffer);
17779 range.start.column = 0;
17780 range.end.column = buffer.line_len(range.end.row);
17781 new_selections_by_buffer
17782 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17783 .or_insert(Vec::new())
17784 .push(range)
17785 }
17786 }
17787
17788 let proposed_changes_buffers = new_selections_by_buffer
17789 .into_iter()
17790 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17791 .collect::<Vec<_>>();
17792 let proposed_changes_editor = cx.new(|cx| {
17793 ProposedChangesEditor::new(
17794 "Proposed changes",
17795 proposed_changes_buffers,
17796 self.project.clone(),
17797 window,
17798 cx,
17799 )
17800 });
17801
17802 window.defer(cx, move |window, cx| {
17803 workspace.update(cx, |workspace, cx| {
17804 workspace.active_pane().update(cx, |pane, cx| {
17805 pane.add_item(
17806 Box::new(proposed_changes_editor),
17807 true,
17808 true,
17809 None,
17810 window,
17811 cx,
17812 );
17813 });
17814 });
17815 });
17816 }
17817
17818 pub fn open_excerpts_in_split(
17819 &mut self,
17820 _: &OpenExcerptsSplit,
17821 window: &mut Window,
17822 cx: &mut Context<Self>,
17823 ) {
17824 self.open_excerpts_common(None, true, window, cx)
17825 }
17826
17827 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17828 self.open_excerpts_common(None, false, window, cx)
17829 }
17830
17831 fn open_excerpts_common(
17832 &mut self,
17833 jump_data: Option<JumpData>,
17834 split: bool,
17835 window: &mut Window,
17836 cx: &mut Context<Self>,
17837 ) {
17838 let Some(workspace) = self.workspace() else {
17839 cx.propagate();
17840 return;
17841 };
17842
17843 if self.buffer.read(cx).is_singleton() {
17844 cx.propagate();
17845 return;
17846 }
17847
17848 let mut new_selections_by_buffer = HashMap::default();
17849 match &jump_data {
17850 Some(JumpData::MultiBufferPoint {
17851 excerpt_id,
17852 position,
17853 anchor,
17854 line_offset_from_top,
17855 }) => {
17856 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17857 if let Some(buffer) = multi_buffer_snapshot
17858 .buffer_id_for_excerpt(*excerpt_id)
17859 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17860 {
17861 let buffer_snapshot = buffer.read(cx).snapshot();
17862 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17863 language::ToPoint::to_point(anchor, &buffer_snapshot)
17864 } else {
17865 buffer_snapshot.clip_point(*position, Bias::Left)
17866 };
17867 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17868 new_selections_by_buffer.insert(
17869 buffer,
17870 (
17871 vec![jump_to_offset..jump_to_offset],
17872 Some(*line_offset_from_top),
17873 ),
17874 );
17875 }
17876 }
17877 Some(JumpData::MultiBufferRow {
17878 row,
17879 line_offset_from_top,
17880 }) => {
17881 let point = MultiBufferPoint::new(row.0, 0);
17882 if let Some((buffer, buffer_point, _)) =
17883 self.buffer.read(cx).point_to_buffer_point(point, cx)
17884 {
17885 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17886 new_selections_by_buffer
17887 .entry(buffer)
17888 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17889 .0
17890 .push(buffer_offset..buffer_offset)
17891 }
17892 }
17893 None => {
17894 let selections = self.selections.all::<usize>(cx);
17895 let multi_buffer = self.buffer.read(cx);
17896 for selection in selections {
17897 for (snapshot, range, _, anchor) in multi_buffer
17898 .snapshot(cx)
17899 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17900 {
17901 if let Some(anchor) = anchor {
17902 // selection is in a deleted hunk
17903 let Some(buffer_id) = anchor.buffer_id else {
17904 continue;
17905 };
17906 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17907 continue;
17908 };
17909 let offset = text::ToOffset::to_offset(
17910 &anchor.text_anchor,
17911 &buffer_handle.read(cx).snapshot(),
17912 );
17913 let range = offset..offset;
17914 new_selections_by_buffer
17915 .entry(buffer_handle)
17916 .or_insert((Vec::new(), None))
17917 .0
17918 .push(range)
17919 } else {
17920 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17921 else {
17922 continue;
17923 };
17924 new_selections_by_buffer
17925 .entry(buffer_handle)
17926 .or_insert((Vec::new(), None))
17927 .0
17928 .push(range)
17929 }
17930 }
17931 }
17932 }
17933 }
17934
17935 new_selections_by_buffer
17936 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
17937
17938 if new_selections_by_buffer.is_empty() {
17939 return;
17940 }
17941
17942 // We defer the pane interaction because we ourselves are a workspace item
17943 // and activating a new item causes the pane to call a method on us reentrantly,
17944 // which panics if we're on the stack.
17945 window.defer(cx, move |window, cx| {
17946 workspace.update(cx, |workspace, cx| {
17947 let pane = if split {
17948 workspace.adjacent_pane(window, cx)
17949 } else {
17950 workspace.active_pane().clone()
17951 };
17952
17953 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17954 let editor = buffer
17955 .read(cx)
17956 .file()
17957 .is_none()
17958 .then(|| {
17959 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17960 // so `workspace.open_project_item` will never find them, always opening a new editor.
17961 // Instead, we try to activate the existing editor in the pane first.
17962 let (editor, pane_item_index) =
17963 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17964 let editor = item.downcast::<Editor>()?;
17965 let singleton_buffer =
17966 editor.read(cx).buffer().read(cx).as_singleton()?;
17967 if singleton_buffer == buffer {
17968 Some((editor, i))
17969 } else {
17970 None
17971 }
17972 })?;
17973 pane.update(cx, |pane, cx| {
17974 pane.activate_item(pane_item_index, true, true, window, cx)
17975 });
17976 Some(editor)
17977 })
17978 .flatten()
17979 .unwrap_or_else(|| {
17980 workspace.open_project_item::<Self>(
17981 pane.clone(),
17982 buffer,
17983 true,
17984 true,
17985 window,
17986 cx,
17987 )
17988 });
17989
17990 editor.update(cx, |editor, cx| {
17991 let autoscroll = match scroll_offset {
17992 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17993 None => Autoscroll::newest(),
17994 };
17995 let nav_history = editor.nav_history.take();
17996 editor.change_selections(Some(autoscroll), window, cx, |s| {
17997 s.select_ranges(ranges);
17998 });
17999 editor.nav_history = nav_history;
18000 });
18001 }
18002 })
18003 });
18004 }
18005
18006 // For now, don't allow opening excerpts in buffers that aren't backed by
18007 // regular project files.
18008 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18009 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18010 }
18011
18012 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18013 let snapshot = self.buffer.read(cx).read(cx);
18014 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18015 Some(
18016 ranges
18017 .iter()
18018 .map(move |range| {
18019 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18020 })
18021 .collect(),
18022 )
18023 }
18024
18025 fn selection_replacement_ranges(
18026 &self,
18027 range: Range<OffsetUtf16>,
18028 cx: &mut App,
18029 ) -> Vec<Range<OffsetUtf16>> {
18030 let selections = self.selections.all::<OffsetUtf16>(cx);
18031 let newest_selection = selections
18032 .iter()
18033 .max_by_key(|selection| selection.id)
18034 .unwrap();
18035 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18036 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18037 let snapshot = self.buffer.read(cx).read(cx);
18038 selections
18039 .into_iter()
18040 .map(|mut selection| {
18041 selection.start.0 =
18042 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18043 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18044 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18045 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18046 })
18047 .collect()
18048 }
18049
18050 fn report_editor_event(
18051 &self,
18052 event_type: &'static str,
18053 file_extension: Option<String>,
18054 cx: &App,
18055 ) {
18056 if cfg!(any(test, feature = "test-support")) {
18057 return;
18058 }
18059
18060 let Some(project) = &self.project else { return };
18061
18062 // If None, we are in a file without an extension
18063 let file = self
18064 .buffer
18065 .read(cx)
18066 .as_singleton()
18067 .and_then(|b| b.read(cx).file());
18068 let file_extension = file_extension.or(file
18069 .as_ref()
18070 .and_then(|file| Path::new(file.file_name(cx)).extension())
18071 .and_then(|e| e.to_str())
18072 .map(|a| a.to_string()));
18073
18074 let vim_mode = vim_enabled(cx);
18075
18076 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18077 let copilot_enabled = edit_predictions_provider
18078 == language::language_settings::EditPredictionProvider::Copilot;
18079 let copilot_enabled_for_language = self
18080 .buffer
18081 .read(cx)
18082 .language_settings(cx)
18083 .show_edit_predictions;
18084
18085 let project = project.read(cx);
18086 telemetry::event!(
18087 event_type,
18088 file_extension,
18089 vim_mode,
18090 copilot_enabled,
18091 copilot_enabled_for_language,
18092 edit_predictions_provider,
18093 is_via_ssh = project.is_via_ssh(),
18094 );
18095 }
18096
18097 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18098 /// with each line being an array of {text, highlight} objects.
18099 fn copy_highlight_json(
18100 &mut self,
18101 _: &CopyHighlightJson,
18102 window: &mut Window,
18103 cx: &mut Context<Self>,
18104 ) {
18105 #[derive(Serialize)]
18106 struct Chunk<'a> {
18107 text: String,
18108 highlight: Option<&'a str>,
18109 }
18110
18111 let snapshot = self.buffer.read(cx).snapshot(cx);
18112 let range = self
18113 .selected_text_range(false, window, cx)
18114 .and_then(|selection| {
18115 if selection.range.is_empty() {
18116 None
18117 } else {
18118 Some(selection.range)
18119 }
18120 })
18121 .unwrap_or_else(|| 0..snapshot.len());
18122
18123 let chunks = snapshot.chunks(range, true);
18124 let mut lines = Vec::new();
18125 let mut line: VecDeque<Chunk> = VecDeque::new();
18126
18127 let Some(style) = self.style.as_ref() else {
18128 return;
18129 };
18130
18131 for chunk in chunks {
18132 let highlight = chunk
18133 .syntax_highlight_id
18134 .and_then(|id| id.name(&style.syntax));
18135 let mut chunk_lines = chunk.text.split('\n').peekable();
18136 while let Some(text) = chunk_lines.next() {
18137 let mut merged_with_last_token = false;
18138 if let Some(last_token) = line.back_mut() {
18139 if last_token.highlight == highlight {
18140 last_token.text.push_str(text);
18141 merged_with_last_token = true;
18142 }
18143 }
18144
18145 if !merged_with_last_token {
18146 line.push_back(Chunk {
18147 text: text.into(),
18148 highlight,
18149 });
18150 }
18151
18152 if chunk_lines.peek().is_some() {
18153 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18154 line.pop_front();
18155 }
18156 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18157 line.pop_back();
18158 }
18159
18160 lines.push(mem::take(&mut line));
18161 }
18162 }
18163 }
18164
18165 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18166 return;
18167 };
18168 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18169 }
18170
18171 pub fn open_context_menu(
18172 &mut self,
18173 _: &OpenContextMenu,
18174 window: &mut Window,
18175 cx: &mut Context<Self>,
18176 ) {
18177 self.request_autoscroll(Autoscroll::newest(), cx);
18178 let position = self.selections.newest_display(cx).start;
18179 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18180 }
18181
18182 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18183 &self.inlay_hint_cache
18184 }
18185
18186 pub fn replay_insert_event(
18187 &mut self,
18188 text: &str,
18189 relative_utf16_range: Option<Range<isize>>,
18190 window: &mut Window,
18191 cx: &mut Context<Self>,
18192 ) {
18193 if !self.input_enabled {
18194 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18195 return;
18196 }
18197 if let Some(relative_utf16_range) = relative_utf16_range {
18198 let selections = self.selections.all::<OffsetUtf16>(cx);
18199 self.change_selections(None, window, cx, |s| {
18200 let new_ranges = selections.into_iter().map(|range| {
18201 let start = OffsetUtf16(
18202 range
18203 .head()
18204 .0
18205 .saturating_add_signed(relative_utf16_range.start),
18206 );
18207 let end = OffsetUtf16(
18208 range
18209 .head()
18210 .0
18211 .saturating_add_signed(relative_utf16_range.end),
18212 );
18213 start..end
18214 });
18215 s.select_ranges(new_ranges);
18216 });
18217 }
18218
18219 self.handle_input(text, window, cx);
18220 }
18221
18222 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18223 let Some(provider) = self.semantics_provider.as_ref() else {
18224 return false;
18225 };
18226
18227 let mut supports = false;
18228 self.buffer().update(cx, |this, cx| {
18229 this.for_each_buffer(|buffer| {
18230 supports |= provider.supports_inlay_hints(buffer, cx);
18231 });
18232 });
18233
18234 supports
18235 }
18236
18237 pub fn is_focused(&self, window: &Window) -> bool {
18238 self.focus_handle.is_focused(window)
18239 }
18240
18241 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18242 cx.emit(EditorEvent::Focused);
18243
18244 if let Some(descendant) = self
18245 .last_focused_descendant
18246 .take()
18247 .and_then(|descendant| descendant.upgrade())
18248 {
18249 window.focus(&descendant);
18250 } else {
18251 if let Some(blame) = self.blame.as_ref() {
18252 blame.update(cx, GitBlame::focus)
18253 }
18254
18255 self.blink_manager.update(cx, BlinkManager::enable);
18256 self.show_cursor_names(window, cx);
18257 self.buffer.update(cx, |buffer, cx| {
18258 buffer.finalize_last_transaction(cx);
18259 if self.leader_peer_id.is_none() {
18260 buffer.set_active_selections(
18261 &self.selections.disjoint_anchors(),
18262 self.selections.line_mode,
18263 self.cursor_shape,
18264 cx,
18265 );
18266 }
18267 });
18268 }
18269 }
18270
18271 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18272 cx.emit(EditorEvent::FocusedIn)
18273 }
18274
18275 fn handle_focus_out(
18276 &mut self,
18277 event: FocusOutEvent,
18278 _window: &mut Window,
18279 cx: &mut Context<Self>,
18280 ) {
18281 if event.blurred != self.focus_handle {
18282 self.last_focused_descendant = Some(event.blurred);
18283 }
18284 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18285 }
18286
18287 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18288 self.blink_manager.update(cx, BlinkManager::disable);
18289 self.buffer
18290 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18291
18292 if let Some(blame) = self.blame.as_ref() {
18293 blame.update(cx, GitBlame::blur)
18294 }
18295 if !self.hover_state.focused(window, cx) {
18296 hide_hover(self, cx);
18297 }
18298 if !self
18299 .context_menu
18300 .borrow()
18301 .as_ref()
18302 .is_some_and(|context_menu| context_menu.focused(window, cx))
18303 {
18304 self.hide_context_menu(window, cx);
18305 }
18306 self.discard_inline_completion(false, cx);
18307 cx.emit(EditorEvent::Blurred);
18308 cx.notify();
18309 }
18310
18311 pub fn register_action<A: Action>(
18312 &mut self,
18313 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18314 ) -> Subscription {
18315 let id = self.next_editor_action_id.post_inc();
18316 let listener = Arc::new(listener);
18317 self.editor_actions.borrow_mut().insert(
18318 id,
18319 Box::new(move |window, _| {
18320 let listener = listener.clone();
18321 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18322 let action = action.downcast_ref().unwrap();
18323 if phase == DispatchPhase::Bubble {
18324 listener(action, window, cx)
18325 }
18326 })
18327 }),
18328 );
18329
18330 let editor_actions = self.editor_actions.clone();
18331 Subscription::new(move || {
18332 editor_actions.borrow_mut().remove(&id);
18333 })
18334 }
18335
18336 pub fn file_header_size(&self) -> u32 {
18337 FILE_HEADER_HEIGHT
18338 }
18339
18340 pub fn restore(
18341 &mut self,
18342 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18343 window: &mut Window,
18344 cx: &mut Context<Self>,
18345 ) {
18346 let workspace = self.workspace();
18347 let project = self.project.as_ref();
18348 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18349 let mut tasks = Vec::new();
18350 for (buffer_id, changes) in revert_changes {
18351 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18352 buffer.update(cx, |buffer, cx| {
18353 buffer.edit(
18354 changes
18355 .into_iter()
18356 .map(|(range, text)| (range, text.to_string())),
18357 None,
18358 cx,
18359 );
18360 });
18361
18362 if let Some(project) =
18363 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18364 {
18365 project.update(cx, |project, cx| {
18366 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18367 })
18368 }
18369 }
18370 }
18371 tasks
18372 });
18373 cx.spawn_in(window, async move |_, cx| {
18374 for (buffer, task) in save_tasks {
18375 let result = task.await;
18376 if result.is_err() {
18377 let Some(path) = buffer
18378 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18379 .ok()
18380 else {
18381 continue;
18382 };
18383 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18384 let Some(task) = cx
18385 .update_window_entity(&workspace, |workspace, window, cx| {
18386 workspace
18387 .open_path_preview(path, None, false, false, false, window, cx)
18388 })
18389 .ok()
18390 else {
18391 continue;
18392 };
18393 task.await.log_err();
18394 }
18395 }
18396 }
18397 })
18398 .detach();
18399 self.change_selections(None, window, cx, |selections| selections.refresh());
18400 }
18401
18402 pub fn to_pixel_point(
18403 &self,
18404 source: multi_buffer::Anchor,
18405 editor_snapshot: &EditorSnapshot,
18406 window: &mut Window,
18407 ) -> Option<gpui::Point<Pixels>> {
18408 let source_point = source.to_display_point(editor_snapshot);
18409 self.display_to_pixel_point(source_point, editor_snapshot, window)
18410 }
18411
18412 pub fn display_to_pixel_point(
18413 &self,
18414 source: DisplayPoint,
18415 editor_snapshot: &EditorSnapshot,
18416 window: &mut Window,
18417 ) -> Option<gpui::Point<Pixels>> {
18418 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18419 let text_layout_details = self.text_layout_details(window);
18420 let scroll_top = text_layout_details
18421 .scroll_anchor
18422 .scroll_position(editor_snapshot)
18423 .y;
18424
18425 if source.row().as_f32() < scroll_top.floor() {
18426 return None;
18427 }
18428 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18429 let source_y = line_height * (source.row().as_f32() - scroll_top);
18430 Some(gpui::Point::new(source_x, source_y))
18431 }
18432
18433 pub fn has_visible_completions_menu(&self) -> bool {
18434 !self.edit_prediction_preview_is_active()
18435 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18436 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18437 })
18438 }
18439
18440 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18441 self.addons
18442 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18443 }
18444
18445 pub fn unregister_addon<T: Addon>(&mut self) {
18446 self.addons.remove(&std::any::TypeId::of::<T>());
18447 }
18448
18449 pub fn addon<T: Addon>(&self) -> Option<&T> {
18450 let type_id = std::any::TypeId::of::<T>();
18451 self.addons
18452 .get(&type_id)
18453 .and_then(|item| item.to_any().downcast_ref::<T>())
18454 }
18455
18456 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
18457 let type_id = std::any::TypeId::of::<T>();
18458 self.addons
18459 .get_mut(&type_id)
18460 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
18461 }
18462
18463 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18464 let text_layout_details = self.text_layout_details(window);
18465 let style = &text_layout_details.editor_style;
18466 let font_id = window.text_system().resolve_font(&style.text.font());
18467 let font_size = style.text.font_size.to_pixels(window.rem_size());
18468 let line_height = style.text.line_height_in_pixels(window.rem_size());
18469 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18470
18471 gpui::Size::new(em_width, line_height)
18472 }
18473
18474 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18475 self.load_diff_task.clone()
18476 }
18477
18478 fn read_metadata_from_db(
18479 &mut self,
18480 item_id: u64,
18481 workspace_id: WorkspaceId,
18482 window: &mut Window,
18483 cx: &mut Context<Editor>,
18484 ) {
18485 if self.is_singleton(cx)
18486 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18487 {
18488 let buffer_snapshot = OnceCell::new();
18489
18490 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18491 if !folds.is_empty() {
18492 let snapshot =
18493 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18494 self.fold_ranges(
18495 folds
18496 .into_iter()
18497 .map(|(start, end)| {
18498 snapshot.clip_offset(start, Bias::Left)
18499 ..snapshot.clip_offset(end, Bias::Right)
18500 })
18501 .collect(),
18502 false,
18503 window,
18504 cx,
18505 );
18506 }
18507 }
18508
18509 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18510 if !selections.is_empty() {
18511 let snapshot =
18512 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18513 self.change_selections(None, window, cx, |s| {
18514 s.select_ranges(selections.into_iter().map(|(start, end)| {
18515 snapshot.clip_offset(start, Bias::Left)
18516 ..snapshot.clip_offset(end, Bias::Right)
18517 }));
18518 });
18519 }
18520 };
18521 }
18522
18523 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18524 }
18525}
18526
18527fn vim_enabled(cx: &App) -> bool {
18528 cx.global::<SettingsStore>()
18529 .raw_user_settings()
18530 .get("vim_mode")
18531 == Some(&serde_json::Value::Bool(true))
18532}
18533
18534// Consider user intent and default settings
18535fn choose_completion_range(
18536 completion: &Completion,
18537 intent: CompletionIntent,
18538 buffer: &Entity<Buffer>,
18539 cx: &mut Context<Editor>,
18540) -> Range<usize> {
18541 fn should_replace(
18542 completion: &Completion,
18543 insert_range: &Range<text::Anchor>,
18544 intent: CompletionIntent,
18545 completion_mode_setting: LspInsertMode,
18546 buffer: &Buffer,
18547 ) -> bool {
18548 // specific actions take precedence over settings
18549 match intent {
18550 CompletionIntent::CompleteWithInsert => return false,
18551 CompletionIntent::CompleteWithReplace => return true,
18552 CompletionIntent::Complete | CompletionIntent::Compose => {}
18553 }
18554
18555 match completion_mode_setting {
18556 LspInsertMode::Insert => false,
18557 LspInsertMode::Replace => true,
18558 LspInsertMode::ReplaceSubsequence => {
18559 let mut text_to_replace = buffer.chars_for_range(
18560 buffer.anchor_before(completion.replace_range.start)
18561 ..buffer.anchor_after(completion.replace_range.end),
18562 );
18563 let mut completion_text = completion.new_text.chars();
18564
18565 // is `text_to_replace` a subsequence of `completion_text`
18566 text_to_replace
18567 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18568 }
18569 LspInsertMode::ReplaceSuffix => {
18570 let range_after_cursor = insert_range.end..completion.replace_range.end;
18571
18572 let text_after_cursor = buffer
18573 .text_for_range(
18574 buffer.anchor_before(range_after_cursor.start)
18575 ..buffer.anchor_after(range_after_cursor.end),
18576 )
18577 .collect::<String>();
18578 completion.new_text.ends_with(&text_after_cursor)
18579 }
18580 }
18581 }
18582
18583 let buffer = buffer.read(cx);
18584
18585 if let CompletionSource::Lsp {
18586 insert_range: Some(insert_range),
18587 ..
18588 } = &completion.source
18589 {
18590 let completion_mode_setting =
18591 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18592 .completions
18593 .lsp_insert_mode;
18594
18595 if !should_replace(
18596 completion,
18597 &insert_range,
18598 intent,
18599 completion_mode_setting,
18600 buffer,
18601 ) {
18602 return insert_range.to_offset(buffer);
18603 }
18604 }
18605
18606 completion.replace_range.to_offset(buffer)
18607}
18608
18609fn insert_extra_newline_brackets(
18610 buffer: &MultiBufferSnapshot,
18611 range: Range<usize>,
18612 language: &language::LanguageScope,
18613) -> bool {
18614 let leading_whitespace_len = buffer
18615 .reversed_chars_at(range.start)
18616 .take_while(|c| c.is_whitespace() && *c != '\n')
18617 .map(|c| c.len_utf8())
18618 .sum::<usize>();
18619 let trailing_whitespace_len = buffer
18620 .chars_at(range.end)
18621 .take_while(|c| c.is_whitespace() && *c != '\n')
18622 .map(|c| c.len_utf8())
18623 .sum::<usize>();
18624 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18625
18626 language.brackets().any(|(pair, enabled)| {
18627 let pair_start = pair.start.trim_end();
18628 let pair_end = pair.end.trim_start();
18629
18630 enabled
18631 && pair.newline
18632 && buffer.contains_str_at(range.end, pair_end)
18633 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18634 })
18635}
18636
18637fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18638 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18639 [(buffer, range, _)] => (*buffer, range.clone()),
18640 _ => return false,
18641 };
18642 let pair = {
18643 let mut result: Option<BracketMatch> = None;
18644
18645 for pair in buffer
18646 .all_bracket_ranges(range.clone())
18647 .filter(move |pair| {
18648 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18649 })
18650 {
18651 let len = pair.close_range.end - pair.open_range.start;
18652
18653 if let Some(existing) = &result {
18654 let existing_len = existing.close_range.end - existing.open_range.start;
18655 if len > existing_len {
18656 continue;
18657 }
18658 }
18659
18660 result = Some(pair);
18661 }
18662
18663 result
18664 };
18665 let Some(pair) = pair else {
18666 return false;
18667 };
18668 pair.newline_only
18669 && buffer
18670 .chars_for_range(pair.open_range.end..range.start)
18671 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18672 .all(|c| c.is_whitespace() && c != '\n')
18673}
18674
18675fn get_uncommitted_diff_for_buffer(
18676 project: &Entity<Project>,
18677 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18678 buffer: Entity<MultiBuffer>,
18679 cx: &mut App,
18680) -> Task<()> {
18681 let mut tasks = Vec::new();
18682 project.update(cx, |project, cx| {
18683 for buffer in buffers {
18684 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18685 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18686 }
18687 }
18688 });
18689 cx.spawn(async move |cx| {
18690 let diffs = future::join_all(tasks).await;
18691 buffer
18692 .update(cx, |buffer, cx| {
18693 for diff in diffs.into_iter().flatten() {
18694 buffer.add_diff(diff, cx);
18695 }
18696 })
18697 .ok();
18698 })
18699}
18700
18701fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18702 let tab_size = tab_size.get() as usize;
18703 let mut width = offset;
18704
18705 for ch in text.chars() {
18706 width += if ch == '\t' {
18707 tab_size - (width % tab_size)
18708 } else {
18709 1
18710 };
18711 }
18712
18713 width - offset
18714}
18715
18716#[cfg(test)]
18717mod tests {
18718 use super::*;
18719
18720 #[test]
18721 fn test_string_size_with_expanded_tabs() {
18722 let nz = |val| NonZeroU32::new(val).unwrap();
18723 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18724 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18725 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18726 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18727 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18728 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18729 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18730 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18731 }
18732}
18733
18734/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18735struct WordBreakingTokenizer<'a> {
18736 input: &'a str,
18737}
18738
18739impl<'a> WordBreakingTokenizer<'a> {
18740 fn new(input: &'a str) -> Self {
18741 Self { input }
18742 }
18743}
18744
18745fn is_char_ideographic(ch: char) -> bool {
18746 use unicode_script::Script::*;
18747 use unicode_script::UnicodeScript;
18748 matches!(ch.script(), Han | Tangut | Yi)
18749}
18750
18751fn is_grapheme_ideographic(text: &str) -> bool {
18752 text.chars().any(is_char_ideographic)
18753}
18754
18755fn is_grapheme_whitespace(text: &str) -> bool {
18756 text.chars().any(|x| x.is_whitespace())
18757}
18758
18759fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18760 text.chars().next().map_or(false, |ch| {
18761 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18762 })
18763}
18764
18765#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18766enum WordBreakToken<'a> {
18767 Word { token: &'a str, grapheme_len: usize },
18768 InlineWhitespace { token: &'a str, grapheme_len: usize },
18769 Newline,
18770}
18771
18772impl<'a> Iterator for WordBreakingTokenizer<'a> {
18773 /// Yields a span, the count of graphemes in the token, and whether it was
18774 /// whitespace. Note that it also breaks at word boundaries.
18775 type Item = WordBreakToken<'a>;
18776
18777 fn next(&mut self) -> Option<Self::Item> {
18778 use unicode_segmentation::UnicodeSegmentation;
18779 if self.input.is_empty() {
18780 return None;
18781 }
18782
18783 let mut iter = self.input.graphemes(true).peekable();
18784 let mut offset = 0;
18785 let mut grapheme_len = 0;
18786 if let Some(first_grapheme) = iter.next() {
18787 let is_newline = first_grapheme == "\n";
18788 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18789 offset += first_grapheme.len();
18790 grapheme_len += 1;
18791 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18792 if let Some(grapheme) = iter.peek().copied() {
18793 if should_stay_with_preceding_ideograph(grapheme) {
18794 offset += grapheme.len();
18795 grapheme_len += 1;
18796 }
18797 }
18798 } else {
18799 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18800 let mut next_word_bound = words.peek().copied();
18801 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18802 next_word_bound = words.next();
18803 }
18804 while let Some(grapheme) = iter.peek().copied() {
18805 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18806 break;
18807 };
18808 if is_grapheme_whitespace(grapheme) != is_whitespace
18809 || (grapheme == "\n") != is_newline
18810 {
18811 break;
18812 };
18813 offset += grapheme.len();
18814 grapheme_len += 1;
18815 iter.next();
18816 }
18817 }
18818 let token = &self.input[..offset];
18819 self.input = &self.input[offset..];
18820 if token == "\n" {
18821 Some(WordBreakToken::Newline)
18822 } else if is_whitespace {
18823 Some(WordBreakToken::InlineWhitespace {
18824 token,
18825 grapheme_len,
18826 })
18827 } else {
18828 Some(WordBreakToken::Word {
18829 token,
18830 grapheme_len,
18831 })
18832 }
18833 } else {
18834 None
18835 }
18836 }
18837}
18838
18839#[test]
18840fn test_word_breaking_tokenizer() {
18841 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18842 ("", &[]),
18843 (" ", &[whitespace(" ", 2)]),
18844 ("Ʒ", &[word("Ʒ", 1)]),
18845 ("Ǽ", &[word("Ǽ", 1)]),
18846 ("⋑", &[word("⋑", 1)]),
18847 ("⋑⋑", &[word("⋑⋑", 2)]),
18848 (
18849 "原理,进而",
18850 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18851 ),
18852 (
18853 "hello world",
18854 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18855 ),
18856 (
18857 "hello, world",
18858 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18859 ),
18860 (
18861 " hello world",
18862 &[
18863 whitespace(" ", 2),
18864 word("hello", 5),
18865 whitespace(" ", 1),
18866 word("world", 5),
18867 ],
18868 ),
18869 (
18870 "这是什么 \n 钢笔",
18871 &[
18872 word("这", 1),
18873 word("是", 1),
18874 word("什", 1),
18875 word("么", 1),
18876 whitespace(" ", 1),
18877 newline(),
18878 whitespace(" ", 1),
18879 word("钢", 1),
18880 word("笔", 1),
18881 ],
18882 ),
18883 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
18884 ];
18885
18886 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18887 WordBreakToken::Word {
18888 token,
18889 grapheme_len,
18890 }
18891 }
18892
18893 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18894 WordBreakToken::InlineWhitespace {
18895 token,
18896 grapheme_len,
18897 }
18898 }
18899
18900 fn newline() -> WordBreakToken<'static> {
18901 WordBreakToken::Newline
18902 }
18903
18904 for (input, result) in tests {
18905 assert_eq!(
18906 WordBreakingTokenizer::new(input)
18907 .collect::<Vec<_>>()
18908 .as_slice(),
18909 *result,
18910 );
18911 }
18912}
18913
18914fn wrap_with_prefix(
18915 line_prefix: String,
18916 unwrapped_text: String,
18917 wrap_column: usize,
18918 tab_size: NonZeroU32,
18919 preserve_existing_whitespace: bool,
18920) -> String {
18921 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18922 let mut wrapped_text = String::new();
18923 let mut current_line = line_prefix.clone();
18924
18925 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18926 let mut current_line_len = line_prefix_len;
18927 let mut in_whitespace = false;
18928 for token in tokenizer {
18929 let have_preceding_whitespace = in_whitespace;
18930 match token {
18931 WordBreakToken::Word {
18932 token,
18933 grapheme_len,
18934 } => {
18935 in_whitespace = false;
18936 if current_line_len + grapheme_len > wrap_column
18937 && current_line_len != line_prefix_len
18938 {
18939 wrapped_text.push_str(current_line.trim_end());
18940 wrapped_text.push('\n');
18941 current_line.truncate(line_prefix.len());
18942 current_line_len = line_prefix_len;
18943 }
18944 current_line.push_str(token);
18945 current_line_len += grapheme_len;
18946 }
18947 WordBreakToken::InlineWhitespace {
18948 mut token,
18949 mut grapheme_len,
18950 } => {
18951 in_whitespace = true;
18952 if have_preceding_whitespace && !preserve_existing_whitespace {
18953 continue;
18954 }
18955 if !preserve_existing_whitespace {
18956 token = " ";
18957 grapheme_len = 1;
18958 }
18959 if current_line_len + grapheme_len > wrap_column {
18960 wrapped_text.push_str(current_line.trim_end());
18961 wrapped_text.push('\n');
18962 current_line.truncate(line_prefix.len());
18963 current_line_len = line_prefix_len;
18964 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18965 current_line.push_str(token);
18966 current_line_len += grapheme_len;
18967 }
18968 }
18969 WordBreakToken::Newline => {
18970 in_whitespace = true;
18971 if preserve_existing_whitespace {
18972 wrapped_text.push_str(current_line.trim_end());
18973 wrapped_text.push('\n');
18974 current_line.truncate(line_prefix.len());
18975 current_line_len = line_prefix_len;
18976 } else if have_preceding_whitespace {
18977 continue;
18978 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18979 {
18980 wrapped_text.push_str(current_line.trim_end());
18981 wrapped_text.push('\n');
18982 current_line.truncate(line_prefix.len());
18983 current_line_len = line_prefix_len;
18984 } else if current_line_len != line_prefix_len {
18985 current_line.push(' ');
18986 current_line_len += 1;
18987 }
18988 }
18989 }
18990 }
18991
18992 if !current_line.is_empty() {
18993 wrapped_text.push_str(¤t_line);
18994 }
18995 wrapped_text
18996}
18997
18998#[test]
18999fn test_wrap_with_prefix() {
19000 assert_eq!(
19001 wrap_with_prefix(
19002 "# ".to_string(),
19003 "abcdefg".to_string(),
19004 4,
19005 NonZeroU32::new(4).unwrap(),
19006 false,
19007 ),
19008 "# abcdefg"
19009 );
19010 assert_eq!(
19011 wrap_with_prefix(
19012 "".to_string(),
19013 "\thello world".to_string(),
19014 8,
19015 NonZeroU32::new(4).unwrap(),
19016 false,
19017 ),
19018 "hello\nworld"
19019 );
19020 assert_eq!(
19021 wrap_with_prefix(
19022 "// ".to_string(),
19023 "xx \nyy zz aa bb cc".to_string(),
19024 12,
19025 NonZeroU32::new(4).unwrap(),
19026 false,
19027 ),
19028 "// xx yy zz\n// aa bb cc"
19029 );
19030 assert_eq!(
19031 wrap_with_prefix(
19032 String::new(),
19033 "这是什么 \n 钢笔".to_string(),
19034 3,
19035 NonZeroU32::new(4).unwrap(),
19036 false,
19037 ),
19038 "这是什\n么 钢\n笔"
19039 );
19040}
19041
19042pub trait CollaborationHub {
19043 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19044 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19045 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19046}
19047
19048impl CollaborationHub for Entity<Project> {
19049 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19050 self.read(cx).collaborators()
19051 }
19052
19053 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19054 self.read(cx).user_store().read(cx).participant_indices()
19055 }
19056
19057 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19058 let this = self.read(cx);
19059 let user_ids = this.collaborators().values().map(|c| c.user_id);
19060 this.user_store().read_with(cx, |user_store, cx| {
19061 user_store.participant_names(user_ids, cx)
19062 })
19063 }
19064}
19065
19066pub trait SemanticsProvider {
19067 fn hover(
19068 &self,
19069 buffer: &Entity<Buffer>,
19070 position: text::Anchor,
19071 cx: &mut App,
19072 ) -> Option<Task<Vec<project::Hover>>>;
19073
19074 fn inline_values(
19075 &self,
19076 buffer_handle: Entity<Buffer>,
19077 range: Range<text::Anchor>,
19078 cx: &mut App,
19079 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19080
19081 fn inlay_hints(
19082 &self,
19083 buffer_handle: Entity<Buffer>,
19084 range: Range<text::Anchor>,
19085 cx: &mut App,
19086 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19087
19088 fn resolve_inlay_hint(
19089 &self,
19090 hint: InlayHint,
19091 buffer_handle: Entity<Buffer>,
19092 server_id: LanguageServerId,
19093 cx: &mut App,
19094 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19095
19096 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19097
19098 fn document_highlights(
19099 &self,
19100 buffer: &Entity<Buffer>,
19101 position: text::Anchor,
19102 cx: &mut App,
19103 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19104
19105 fn definitions(
19106 &self,
19107 buffer: &Entity<Buffer>,
19108 position: text::Anchor,
19109 kind: GotoDefinitionKind,
19110 cx: &mut App,
19111 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19112
19113 fn range_for_rename(
19114 &self,
19115 buffer: &Entity<Buffer>,
19116 position: text::Anchor,
19117 cx: &mut App,
19118 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19119
19120 fn perform_rename(
19121 &self,
19122 buffer: &Entity<Buffer>,
19123 position: text::Anchor,
19124 new_name: String,
19125 cx: &mut App,
19126 ) -> Option<Task<Result<ProjectTransaction>>>;
19127}
19128
19129pub trait CompletionProvider {
19130 fn completions(
19131 &self,
19132 excerpt_id: ExcerptId,
19133 buffer: &Entity<Buffer>,
19134 buffer_position: text::Anchor,
19135 trigger: CompletionContext,
19136 window: &mut Window,
19137 cx: &mut Context<Editor>,
19138 ) -> Task<Result<Option<Vec<Completion>>>>;
19139
19140 fn resolve_completions(
19141 &self,
19142 buffer: Entity<Buffer>,
19143 completion_indices: Vec<usize>,
19144 completions: Rc<RefCell<Box<[Completion]>>>,
19145 cx: &mut Context<Editor>,
19146 ) -> Task<Result<bool>>;
19147
19148 fn apply_additional_edits_for_completion(
19149 &self,
19150 _buffer: Entity<Buffer>,
19151 _completions: Rc<RefCell<Box<[Completion]>>>,
19152 _completion_index: usize,
19153 _push_to_history: bool,
19154 _cx: &mut Context<Editor>,
19155 ) -> Task<Result<Option<language::Transaction>>> {
19156 Task::ready(Ok(None))
19157 }
19158
19159 fn is_completion_trigger(
19160 &self,
19161 buffer: &Entity<Buffer>,
19162 position: language::Anchor,
19163 text: &str,
19164 trigger_in_words: bool,
19165 cx: &mut Context<Editor>,
19166 ) -> bool;
19167
19168 fn sort_completions(&self) -> bool {
19169 true
19170 }
19171
19172 fn filter_completions(&self) -> bool {
19173 true
19174 }
19175}
19176
19177pub trait CodeActionProvider {
19178 fn id(&self) -> Arc<str>;
19179
19180 fn code_actions(
19181 &self,
19182 buffer: &Entity<Buffer>,
19183 range: Range<text::Anchor>,
19184 window: &mut Window,
19185 cx: &mut App,
19186 ) -> Task<Result<Vec<CodeAction>>>;
19187
19188 fn apply_code_action(
19189 &self,
19190 buffer_handle: Entity<Buffer>,
19191 action: CodeAction,
19192 excerpt_id: ExcerptId,
19193 push_to_history: bool,
19194 window: &mut Window,
19195 cx: &mut App,
19196 ) -> Task<Result<ProjectTransaction>>;
19197}
19198
19199impl CodeActionProvider for Entity<Project> {
19200 fn id(&self) -> Arc<str> {
19201 "project".into()
19202 }
19203
19204 fn code_actions(
19205 &self,
19206 buffer: &Entity<Buffer>,
19207 range: Range<text::Anchor>,
19208 _window: &mut Window,
19209 cx: &mut App,
19210 ) -> Task<Result<Vec<CodeAction>>> {
19211 self.update(cx, |project, cx| {
19212 let code_lens = project.code_lens(buffer, range.clone(), cx);
19213 let code_actions = project.code_actions(buffer, range, None, cx);
19214 cx.background_spawn(async move {
19215 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19216 Ok(code_lens
19217 .context("code lens fetch")?
19218 .into_iter()
19219 .chain(code_actions.context("code action fetch")?)
19220 .collect())
19221 })
19222 })
19223 }
19224
19225 fn apply_code_action(
19226 &self,
19227 buffer_handle: Entity<Buffer>,
19228 action: CodeAction,
19229 _excerpt_id: ExcerptId,
19230 push_to_history: bool,
19231 _window: &mut Window,
19232 cx: &mut App,
19233 ) -> Task<Result<ProjectTransaction>> {
19234 self.update(cx, |project, cx| {
19235 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19236 })
19237 }
19238}
19239
19240fn snippet_completions(
19241 project: &Project,
19242 buffer: &Entity<Buffer>,
19243 buffer_position: text::Anchor,
19244 cx: &mut App,
19245) -> Task<Result<Vec<Completion>>> {
19246 let languages = buffer.read(cx).languages_at(buffer_position);
19247 let snippet_store = project.snippets().read(cx);
19248
19249 let scopes: Vec<_> = languages
19250 .iter()
19251 .filter_map(|language| {
19252 let language_name = language.lsp_id();
19253 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19254
19255 if snippets.is_empty() {
19256 None
19257 } else {
19258 Some((language.default_scope(), snippets))
19259 }
19260 })
19261 .collect();
19262
19263 if scopes.is_empty() {
19264 return Task::ready(Ok(vec![]));
19265 }
19266
19267 let snapshot = buffer.read(cx).text_snapshot();
19268 let chars: String = snapshot
19269 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19270 .collect();
19271 let executor = cx.background_executor().clone();
19272
19273 cx.background_spawn(async move {
19274 let mut all_results: Vec<Completion> = Vec::new();
19275 for (scope, snippets) in scopes.into_iter() {
19276 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19277 let mut last_word = chars
19278 .chars()
19279 .take_while(|c| classifier.is_word(*c))
19280 .collect::<String>();
19281 last_word = last_word.chars().rev().collect();
19282
19283 if last_word.is_empty() {
19284 return Ok(vec![]);
19285 }
19286
19287 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19288 let to_lsp = |point: &text::Anchor| {
19289 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19290 point_to_lsp(end)
19291 };
19292 let lsp_end = to_lsp(&buffer_position);
19293
19294 let candidates = snippets
19295 .iter()
19296 .enumerate()
19297 .flat_map(|(ix, snippet)| {
19298 snippet
19299 .prefix
19300 .iter()
19301 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19302 })
19303 .collect::<Vec<StringMatchCandidate>>();
19304
19305 let mut matches = fuzzy::match_strings(
19306 &candidates,
19307 &last_word,
19308 last_word.chars().any(|c| c.is_uppercase()),
19309 100,
19310 &Default::default(),
19311 executor.clone(),
19312 )
19313 .await;
19314
19315 // Remove all candidates where the query's start does not match the start of any word in the candidate
19316 if let Some(query_start) = last_word.chars().next() {
19317 matches.retain(|string_match| {
19318 split_words(&string_match.string).any(|word| {
19319 // Check that the first codepoint of the word as lowercase matches the first
19320 // codepoint of the query as lowercase
19321 word.chars()
19322 .flat_map(|codepoint| codepoint.to_lowercase())
19323 .zip(query_start.to_lowercase())
19324 .all(|(word_cp, query_cp)| word_cp == query_cp)
19325 })
19326 });
19327 }
19328
19329 let matched_strings = matches
19330 .into_iter()
19331 .map(|m| m.string)
19332 .collect::<HashSet<_>>();
19333
19334 let mut result: Vec<Completion> = snippets
19335 .iter()
19336 .filter_map(|snippet| {
19337 let matching_prefix = snippet
19338 .prefix
19339 .iter()
19340 .find(|prefix| matched_strings.contains(*prefix))?;
19341 let start = as_offset - last_word.len();
19342 let start = snapshot.anchor_before(start);
19343 let range = start..buffer_position;
19344 let lsp_start = to_lsp(&start);
19345 let lsp_range = lsp::Range {
19346 start: lsp_start,
19347 end: lsp_end,
19348 };
19349 Some(Completion {
19350 replace_range: range,
19351 new_text: snippet.body.clone(),
19352 source: CompletionSource::Lsp {
19353 insert_range: None,
19354 server_id: LanguageServerId(usize::MAX),
19355 resolved: true,
19356 lsp_completion: Box::new(lsp::CompletionItem {
19357 label: snippet.prefix.first().unwrap().clone(),
19358 kind: Some(CompletionItemKind::SNIPPET),
19359 label_details: snippet.description.as_ref().map(|description| {
19360 lsp::CompletionItemLabelDetails {
19361 detail: Some(description.clone()),
19362 description: None,
19363 }
19364 }),
19365 insert_text_format: Some(InsertTextFormat::SNIPPET),
19366 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19367 lsp::InsertReplaceEdit {
19368 new_text: snippet.body.clone(),
19369 insert: lsp_range,
19370 replace: lsp_range,
19371 },
19372 )),
19373 filter_text: Some(snippet.body.clone()),
19374 sort_text: Some(char::MAX.to_string()),
19375 ..lsp::CompletionItem::default()
19376 }),
19377 lsp_defaults: None,
19378 },
19379 label: CodeLabel {
19380 text: matching_prefix.clone(),
19381 runs: Vec::new(),
19382 filter_range: 0..matching_prefix.len(),
19383 },
19384 icon_path: None,
19385 documentation: snippet.description.clone().map(|description| {
19386 CompletionDocumentation::SingleLine(description.into())
19387 }),
19388 insert_text_mode: None,
19389 confirm: None,
19390 })
19391 })
19392 .collect();
19393
19394 all_results.append(&mut result);
19395 }
19396
19397 Ok(all_results)
19398 })
19399}
19400
19401impl CompletionProvider for Entity<Project> {
19402 fn completions(
19403 &self,
19404 _excerpt_id: ExcerptId,
19405 buffer: &Entity<Buffer>,
19406 buffer_position: text::Anchor,
19407 options: CompletionContext,
19408 _window: &mut Window,
19409 cx: &mut Context<Editor>,
19410 ) -> Task<Result<Option<Vec<Completion>>>> {
19411 self.update(cx, |project, cx| {
19412 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19413 let project_completions = project.completions(buffer, buffer_position, options, cx);
19414 cx.background_spawn(async move {
19415 let snippets_completions = snippets.await?;
19416 match project_completions.await? {
19417 Some(mut completions) => {
19418 completions.extend(snippets_completions);
19419 Ok(Some(completions))
19420 }
19421 None => {
19422 if snippets_completions.is_empty() {
19423 Ok(None)
19424 } else {
19425 Ok(Some(snippets_completions))
19426 }
19427 }
19428 }
19429 })
19430 })
19431 }
19432
19433 fn resolve_completions(
19434 &self,
19435 buffer: Entity<Buffer>,
19436 completion_indices: Vec<usize>,
19437 completions: Rc<RefCell<Box<[Completion]>>>,
19438 cx: &mut Context<Editor>,
19439 ) -> Task<Result<bool>> {
19440 self.update(cx, |project, cx| {
19441 project.lsp_store().update(cx, |lsp_store, cx| {
19442 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19443 })
19444 })
19445 }
19446
19447 fn apply_additional_edits_for_completion(
19448 &self,
19449 buffer: Entity<Buffer>,
19450 completions: Rc<RefCell<Box<[Completion]>>>,
19451 completion_index: usize,
19452 push_to_history: bool,
19453 cx: &mut Context<Editor>,
19454 ) -> Task<Result<Option<language::Transaction>>> {
19455 self.update(cx, |project, cx| {
19456 project.lsp_store().update(cx, |lsp_store, cx| {
19457 lsp_store.apply_additional_edits_for_completion(
19458 buffer,
19459 completions,
19460 completion_index,
19461 push_to_history,
19462 cx,
19463 )
19464 })
19465 })
19466 }
19467
19468 fn is_completion_trigger(
19469 &self,
19470 buffer: &Entity<Buffer>,
19471 position: language::Anchor,
19472 text: &str,
19473 trigger_in_words: bool,
19474 cx: &mut Context<Editor>,
19475 ) -> bool {
19476 let mut chars = text.chars();
19477 let char = if let Some(char) = chars.next() {
19478 char
19479 } else {
19480 return false;
19481 };
19482 if chars.next().is_some() {
19483 return false;
19484 }
19485
19486 let buffer = buffer.read(cx);
19487 let snapshot = buffer.snapshot();
19488 if !snapshot.settings_at(position, cx).show_completions_on_input {
19489 return false;
19490 }
19491 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19492 if trigger_in_words && classifier.is_word(char) {
19493 return true;
19494 }
19495
19496 buffer.completion_triggers().contains(text)
19497 }
19498}
19499
19500impl SemanticsProvider for Entity<Project> {
19501 fn hover(
19502 &self,
19503 buffer: &Entity<Buffer>,
19504 position: text::Anchor,
19505 cx: &mut App,
19506 ) -> Option<Task<Vec<project::Hover>>> {
19507 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19508 }
19509
19510 fn document_highlights(
19511 &self,
19512 buffer: &Entity<Buffer>,
19513 position: text::Anchor,
19514 cx: &mut App,
19515 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19516 Some(self.update(cx, |project, cx| {
19517 project.document_highlights(buffer, position, cx)
19518 }))
19519 }
19520
19521 fn definitions(
19522 &self,
19523 buffer: &Entity<Buffer>,
19524 position: text::Anchor,
19525 kind: GotoDefinitionKind,
19526 cx: &mut App,
19527 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19528 Some(self.update(cx, |project, cx| match kind {
19529 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19530 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19531 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19532 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19533 }))
19534 }
19535
19536 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19537 // TODO: make this work for remote projects
19538 self.update(cx, |project, cx| {
19539 if project
19540 .active_debug_session(cx)
19541 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
19542 {
19543 return true;
19544 }
19545
19546 buffer.update(cx, |buffer, cx| {
19547 project.any_language_server_supports_inlay_hints(buffer, cx)
19548 })
19549 })
19550 }
19551
19552 fn inline_values(
19553 &self,
19554 buffer_handle: Entity<Buffer>,
19555 range: Range<text::Anchor>,
19556 cx: &mut App,
19557 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19558 self.update(cx, |project, cx| {
19559 let (session, active_stack_frame) = project.active_debug_session(cx)?;
19560
19561 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
19562 })
19563 }
19564
19565 fn inlay_hints(
19566 &self,
19567 buffer_handle: Entity<Buffer>,
19568 range: Range<text::Anchor>,
19569 cx: &mut App,
19570 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19571 Some(self.update(cx, |project, cx| {
19572 project.inlay_hints(buffer_handle, range, cx)
19573 }))
19574 }
19575
19576 fn resolve_inlay_hint(
19577 &self,
19578 hint: InlayHint,
19579 buffer_handle: Entity<Buffer>,
19580 server_id: LanguageServerId,
19581 cx: &mut App,
19582 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19583 Some(self.update(cx, |project, cx| {
19584 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19585 }))
19586 }
19587
19588 fn range_for_rename(
19589 &self,
19590 buffer: &Entity<Buffer>,
19591 position: text::Anchor,
19592 cx: &mut App,
19593 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19594 Some(self.update(cx, |project, cx| {
19595 let buffer = buffer.clone();
19596 let task = project.prepare_rename(buffer.clone(), position, cx);
19597 cx.spawn(async move |_, cx| {
19598 Ok(match task.await? {
19599 PrepareRenameResponse::Success(range) => Some(range),
19600 PrepareRenameResponse::InvalidPosition => None,
19601 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19602 // Fallback on using TreeSitter info to determine identifier range
19603 buffer.update(cx, |buffer, _| {
19604 let snapshot = buffer.snapshot();
19605 let (range, kind) = snapshot.surrounding_word(position);
19606 if kind != Some(CharKind::Word) {
19607 return None;
19608 }
19609 Some(
19610 snapshot.anchor_before(range.start)
19611 ..snapshot.anchor_after(range.end),
19612 )
19613 })?
19614 }
19615 })
19616 })
19617 }))
19618 }
19619
19620 fn perform_rename(
19621 &self,
19622 buffer: &Entity<Buffer>,
19623 position: text::Anchor,
19624 new_name: String,
19625 cx: &mut App,
19626 ) -> Option<Task<Result<ProjectTransaction>>> {
19627 Some(self.update(cx, |project, cx| {
19628 project.perform_rename(buffer.clone(), position, new_name, cx)
19629 }))
19630 }
19631}
19632
19633fn inlay_hint_settings(
19634 location: Anchor,
19635 snapshot: &MultiBufferSnapshot,
19636 cx: &mut Context<Editor>,
19637) -> InlayHintSettings {
19638 let file = snapshot.file_at(location);
19639 let language = snapshot.language_at(location).map(|l| l.name());
19640 language_settings(language, file, cx).inlay_hints
19641}
19642
19643fn consume_contiguous_rows(
19644 contiguous_row_selections: &mut Vec<Selection<Point>>,
19645 selection: &Selection<Point>,
19646 display_map: &DisplaySnapshot,
19647 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19648) -> (MultiBufferRow, MultiBufferRow) {
19649 contiguous_row_selections.push(selection.clone());
19650 let start_row = MultiBufferRow(selection.start.row);
19651 let mut end_row = ending_row(selection, display_map);
19652
19653 while let Some(next_selection) = selections.peek() {
19654 if next_selection.start.row <= end_row.0 {
19655 end_row = ending_row(next_selection, display_map);
19656 contiguous_row_selections.push(selections.next().unwrap().clone());
19657 } else {
19658 break;
19659 }
19660 }
19661 (start_row, end_row)
19662}
19663
19664fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19665 if next_selection.end.column > 0 || next_selection.is_empty() {
19666 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19667 } else {
19668 MultiBufferRow(next_selection.end.row)
19669 }
19670}
19671
19672impl EditorSnapshot {
19673 pub fn remote_selections_in_range<'a>(
19674 &'a self,
19675 range: &'a Range<Anchor>,
19676 collaboration_hub: &dyn CollaborationHub,
19677 cx: &'a App,
19678 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19679 let participant_names = collaboration_hub.user_names(cx);
19680 let participant_indices = collaboration_hub.user_participant_indices(cx);
19681 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19682 let collaborators_by_replica_id = collaborators_by_peer_id
19683 .iter()
19684 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19685 .collect::<HashMap<_, _>>();
19686 self.buffer_snapshot
19687 .selections_in_range(range, false)
19688 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19689 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19690 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19691 let user_name = participant_names.get(&collaborator.user_id).cloned();
19692 Some(RemoteSelection {
19693 replica_id,
19694 selection,
19695 cursor_shape,
19696 line_mode,
19697 participant_index,
19698 peer_id: collaborator.peer_id,
19699 user_name,
19700 })
19701 })
19702 }
19703
19704 pub fn hunks_for_ranges(
19705 &self,
19706 ranges: impl IntoIterator<Item = Range<Point>>,
19707 ) -> Vec<MultiBufferDiffHunk> {
19708 let mut hunks = Vec::new();
19709 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19710 HashMap::default();
19711 for query_range in ranges {
19712 let query_rows =
19713 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19714 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19715 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19716 ) {
19717 // Include deleted hunks that are adjacent to the query range, because
19718 // otherwise they would be missed.
19719 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19720 if hunk.status().is_deleted() {
19721 intersects_range |= hunk.row_range.start == query_rows.end;
19722 intersects_range |= hunk.row_range.end == query_rows.start;
19723 }
19724 if intersects_range {
19725 if !processed_buffer_rows
19726 .entry(hunk.buffer_id)
19727 .or_default()
19728 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19729 {
19730 continue;
19731 }
19732 hunks.push(hunk);
19733 }
19734 }
19735 }
19736
19737 hunks
19738 }
19739
19740 fn display_diff_hunks_for_rows<'a>(
19741 &'a self,
19742 display_rows: Range<DisplayRow>,
19743 folded_buffers: &'a HashSet<BufferId>,
19744 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19745 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19746 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19747
19748 self.buffer_snapshot
19749 .diff_hunks_in_range(buffer_start..buffer_end)
19750 .filter_map(|hunk| {
19751 if folded_buffers.contains(&hunk.buffer_id) {
19752 return None;
19753 }
19754
19755 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19756 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19757
19758 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19759 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19760
19761 let display_hunk = if hunk_display_start.column() != 0 {
19762 DisplayDiffHunk::Folded {
19763 display_row: hunk_display_start.row(),
19764 }
19765 } else {
19766 let mut end_row = hunk_display_end.row();
19767 if hunk_display_end.column() > 0 {
19768 end_row.0 += 1;
19769 }
19770 let is_created_file = hunk.is_created_file();
19771 DisplayDiffHunk::Unfolded {
19772 status: hunk.status(),
19773 diff_base_byte_range: hunk.diff_base_byte_range,
19774 display_row_range: hunk_display_start.row()..end_row,
19775 multi_buffer_range: Anchor::range_in_buffer(
19776 hunk.excerpt_id,
19777 hunk.buffer_id,
19778 hunk.buffer_range,
19779 ),
19780 is_created_file,
19781 }
19782 };
19783
19784 Some(display_hunk)
19785 })
19786 }
19787
19788 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19789 self.display_snapshot.buffer_snapshot.language_at(position)
19790 }
19791
19792 pub fn is_focused(&self) -> bool {
19793 self.is_focused
19794 }
19795
19796 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19797 self.placeholder_text.as_ref()
19798 }
19799
19800 pub fn scroll_position(&self) -> gpui::Point<f32> {
19801 self.scroll_anchor.scroll_position(&self.display_snapshot)
19802 }
19803
19804 fn gutter_dimensions(
19805 &self,
19806 font_id: FontId,
19807 font_size: Pixels,
19808 max_line_number_width: Pixels,
19809 cx: &App,
19810 ) -> Option<GutterDimensions> {
19811 if !self.show_gutter {
19812 return None;
19813 }
19814
19815 let descent = cx.text_system().descent(font_id, font_size);
19816 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19817 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19818
19819 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19820 matches!(
19821 ProjectSettings::get_global(cx).git.git_gutter,
19822 Some(GitGutterSetting::TrackedFiles)
19823 )
19824 });
19825 let gutter_settings = EditorSettings::get_global(cx).gutter;
19826 let show_line_numbers = self
19827 .show_line_numbers
19828 .unwrap_or(gutter_settings.line_numbers);
19829 let line_gutter_width = if show_line_numbers {
19830 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19831 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19832 max_line_number_width.max(min_width_for_number_on_gutter)
19833 } else {
19834 0.0.into()
19835 };
19836
19837 let show_code_actions = self
19838 .show_code_actions
19839 .unwrap_or(gutter_settings.code_actions);
19840
19841 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19842 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19843
19844 let git_blame_entries_width =
19845 self.git_blame_gutter_max_author_length
19846 .map(|max_author_length| {
19847 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19848 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19849
19850 /// The number of characters to dedicate to gaps and margins.
19851 const SPACING_WIDTH: usize = 4;
19852
19853 let max_char_count = max_author_length.min(renderer.max_author_length())
19854 + ::git::SHORT_SHA_LENGTH
19855 + MAX_RELATIVE_TIMESTAMP.len()
19856 + SPACING_WIDTH;
19857
19858 em_advance * max_char_count
19859 });
19860
19861 let is_singleton = self.buffer_snapshot.is_singleton();
19862
19863 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19864 left_padding += if !is_singleton {
19865 em_width * 4.0
19866 } else if show_code_actions || show_runnables || show_breakpoints {
19867 em_width * 3.0
19868 } else if show_git_gutter && show_line_numbers {
19869 em_width * 2.0
19870 } else if show_git_gutter || show_line_numbers {
19871 em_width
19872 } else {
19873 px(0.)
19874 };
19875
19876 let shows_folds = is_singleton && gutter_settings.folds;
19877
19878 let right_padding = if shows_folds && show_line_numbers {
19879 em_width * 4.0
19880 } else if shows_folds || (!is_singleton && show_line_numbers) {
19881 em_width * 3.0
19882 } else if show_line_numbers {
19883 em_width
19884 } else {
19885 px(0.)
19886 };
19887
19888 Some(GutterDimensions {
19889 left_padding,
19890 right_padding,
19891 width: line_gutter_width + left_padding + right_padding,
19892 margin: -descent,
19893 git_blame_entries_width,
19894 })
19895 }
19896
19897 pub fn render_crease_toggle(
19898 &self,
19899 buffer_row: MultiBufferRow,
19900 row_contains_cursor: bool,
19901 editor: Entity<Editor>,
19902 window: &mut Window,
19903 cx: &mut App,
19904 ) -> Option<AnyElement> {
19905 let folded = self.is_line_folded(buffer_row);
19906 let mut is_foldable = false;
19907
19908 if let Some(crease) = self
19909 .crease_snapshot
19910 .query_row(buffer_row, &self.buffer_snapshot)
19911 {
19912 is_foldable = true;
19913 match crease {
19914 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
19915 if let Some(render_toggle) = render_toggle {
19916 let toggle_callback =
19917 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
19918 if folded {
19919 editor.update(cx, |editor, cx| {
19920 editor.fold_at(buffer_row, window, cx)
19921 });
19922 } else {
19923 editor.update(cx, |editor, cx| {
19924 editor.unfold_at(buffer_row, window, cx)
19925 });
19926 }
19927 });
19928 return Some((render_toggle)(
19929 buffer_row,
19930 folded,
19931 toggle_callback,
19932 window,
19933 cx,
19934 ));
19935 }
19936 }
19937 }
19938 }
19939
19940 is_foldable |= self.starts_indent(buffer_row);
19941
19942 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19943 Some(
19944 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19945 .toggle_state(folded)
19946 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19947 if folded {
19948 this.unfold_at(buffer_row, window, cx);
19949 } else {
19950 this.fold_at(buffer_row, window, cx);
19951 }
19952 }))
19953 .into_any_element(),
19954 )
19955 } else {
19956 None
19957 }
19958 }
19959
19960 pub fn render_crease_trailer(
19961 &self,
19962 buffer_row: MultiBufferRow,
19963 window: &mut Window,
19964 cx: &mut App,
19965 ) -> Option<AnyElement> {
19966 let folded = self.is_line_folded(buffer_row);
19967 if let Crease::Inline { render_trailer, .. } = self
19968 .crease_snapshot
19969 .query_row(buffer_row, &self.buffer_snapshot)?
19970 {
19971 let render_trailer = render_trailer.as_ref()?;
19972 Some(render_trailer(buffer_row, folded, window, cx))
19973 } else {
19974 None
19975 }
19976 }
19977}
19978
19979impl Deref for EditorSnapshot {
19980 type Target = DisplaySnapshot;
19981
19982 fn deref(&self) -> &Self::Target {
19983 &self.display_snapshot
19984 }
19985}
19986
19987#[derive(Clone, Debug, PartialEq, Eq)]
19988pub enum EditorEvent {
19989 InputIgnored {
19990 text: Arc<str>,
19991 },
19992 InputHandled {
19993 utf16_range_to_replace: Option<Range<isize>>,
19994 text: Arc<str>,
19995 },
19996 ExcerptsAdded {
19997 buffer: Entity<Buffer>,
19998 predecessor: ExcerptId,
19999 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20000 },
20001 ExcerptsRemoved {
20002 ids: Vec<ExcerptId>,
20003 removed_buffer_ids: Vec<BufferId>,
20004 },
20005 BufferFoldToggled {
20006 ids: Vec<ExcerptId>,
20007 folded: bool,
20008 },
20009 ExcerptsEdited {
20010 ids: Vec<ExcerptId>,
20011 },
20012 ExcerptsExpanded {
20013 ids: Vec<ExcerptId>,
20014 },
20015 BufferEdited,
20016 Edited {
20017 transaction_id: clock::Lamport,
20018 },
20019 Reparsed(BufferId),
20020 Focused,
20021 FocusedIn,
20022 Blurred,
20023 DirtyChanged,
20024 Saved,
20025 TitleChanged,
20026 DiffBaseChanged,
20027 SelectionsChanged {
20028 local: bool,
20029 },
20030 ScrollPositionChanged {
20031 local: bool,
20032 autoscroll: bool,
20033 },
20034 Closed,
20035 TransactionUndone {
20036 transaction_id: clock::Lamport,
20037 },
20038 TransactionBegun {
20039 transaction_id: clock::Lamport,
20040 },
20041 Reloaded,
20042 CursorShapeChanged,
20043 PushedToNavHistory {
20044 anchor: Anchor,
20045 is_deactivate: bool,
20046 },
20047}
20048
20049impl EventEmitter<EditorEvent> for Editor {}
20050
20051impl Focusable for Editor {
20052 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20053 self.focus_handle.clone()
20054 }
20055}
20056
20057impl Render for Editor {
20058 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20059 let settings = ThemeSettings::get_global(cx);
20060
20061 let mut text_style = match self.mode {
20062 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20063 color: cx.theme().colors().editor_foreground,
20064 font_family: settings.ui_font.family.clone(),
20065 font_features: settings.ui_font.features.clone(),
20066 font_fallbacks: settings.ui_font.fallbacks.clone(),
20067 font_size: rems(0.875).into(),
20068 font_weight: settings.ui_font.weight,
20069 line_height: relative(settings.buffer_line_height.value()),
20070 ..Default::default()
20071 },
20072 EditorMode::Full { .. } => TextStyle {
20073 color: cx.theme().colors().editor_foreground,
20074 font_family: settings.buffer_font.family.clone(),
20075 font_features: settings.buffer_font.features.clone(),
20076 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20077 font_size: settings.buffer_font_size(cx).into(),
20078 font_weight: settings.buffer_font.weight,
20079 line_height: relative(settings.buffer_line_height.value()),
20080 ..Default::default()
20081 },
20082 };
20083 if let Some(text_style_refinement) = &self.text_style_refinement {
20084 text_style.refine(text_style_refinement)
20085 }
20086
20087 let background = match self.mode {
20088 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20089 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20090 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20091 };
20092
20093 EditorElement::new(
20094 &cx.entity(),
20095 EditorStyle {
20096 background,
20097 local_player: cx.theme().players().local(),
20098 text: text_style,
20099 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20100 syntax: cx.theme().syntax().clone(),
20101 status: cx.theme().status().clone(),
20102 inlay_hints_style: make_inlay_hints_style(cx),
20103 inline_completion_styles: make_suggestion_styles(cx),
20104 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20105 },
20106 )
20107 }
20108}
20109
20110impl EntityInputHandler for Editor {
20111 fn text_for_range(
20112 &mut self,
20113 range_utf16: Range<usize>,
20114 adjusted_range: &mut Option<Range<usize>>,
20115 _: &mut Window,
20116 cx: &mut Context<Self>,
20117 ) -> Option<String> {
20118 let snapshot = self.buffer.read(cx).read(cx);
20119 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20120 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20121 if (start.0..end.0) != range_utf16 {
20122 adjusted_range.replace(start.0..end.0);
20123 }
20124 Some(snapshot.text_for_range(start..end).collect())
20125 }
20126
20127 fn selected_text_range(
20128 &mut self,
20129 ignore_disabled_input: bool,
20130 _: &mut Window,
20131 cx: &mut Context<Self>,
20132 ) -> Option<UTF16Selection> {
20133 // Prevent the IME menu from appearing when holding down an alphabetic key
20134 // while input is disabled.
20135 if !ignore_disabled_input && !self.input_enabled {
20136 return None;
20137 }
20138
20139 let selection = self.selections.newest::<OffsetUtf16>(cx);
20140 let range = selection.range();
20141
20142 Some(UTF16Selection {
20143 range: range.start.0..range.end.0,
20144 reversed: selection.reversed,
20145 })
20146 }
20147
20148 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20149 let snapshot = self.buffer.read(cx).read(cx);
20150 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20151 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20152 }
20153
20154 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20155 self.clear_highlights::<InputComposition>(cx);
20156 self.ime_transaction.take();
20157 }
20158
20159 fn replace_text_in_range(
20160 &mut self,
20161 range_utf16: Option<Range<usize>>,
20162 text: &str,
20163 window: &mut Window,
20164 cx: &mut Context<Self>,
20165 ) {
20166 if !self.input_enabled {
20167 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20168 return;
20169 }
20170
20171 self.transact(window, cx, |this, window, cx| {
20172 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20173 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20174 Some(this.selection_replacement_ranges(range_utf16, cx))
20175 } else {
20176 this.marked_text_ranges(cx)
20177 };
20178
20179 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20180 let newest_selection_id = this.selections.newest_anchor().id;
20181 this.selections
20182 .all::<OffsetUtf16>(cx)
20183 .iter()
20184 .zip(ranges_to_replace.iter())
20185 .find_map(|(selection, range)| {
20186 if selection.id == newest_selection_id {
20187 Some(
20188 (range.start.0 as isize - selection.head().0 as isize)
20189 ..(range.end.0 as isize - selection.head().0 as isize),
20190 )
20191 } else {
20192 None
20193 }
20194 })
20195 });
20196
20197 cx.emit(EditorEvent::InputHandled {
20198 utf16_range_to_replace: range_to_replace,
20199 text: text.into(),
20200 });
20201
20202 if let Some(new_selected_ranges) = new_selected_ranges {
20203 this.change_selections(None, window, cx, |selections| {
20204 selections.select_ranges(new_selected_ranges)
20205 });
20206 this.backspace(&Default::default(), window, cx);
20207 }
20208
20209 this.handle_input(text, window, cx);
20210 });
20211
20212 if let Some(transaction) = self.ime_transaction {
20213 self.buffer.update(cx, |buffer, cx| {
20214 buffer.group_until_transaction(transaction, cx);
20215 });
20216 }
20217
20218 self.unmark_text(window, cx);
20219 }
20220
20221 fn replace_and_mark_text_in_range(
20222 &mut self,
20223 range_utf16: Option<Range<usize>>,
20224 text: &str,
20225 new_selected_range_utf16: Option<Range<usize>>,
20226 window: &mut Window,
20227 cx: &mut Context<Self>,
20228 ) {
20229 if !self.input_enabled {
20230 return;
20231 }
20232
20233 let transaction = self.transact(window, cx, |this, window, cx| {
20234 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20235 let snapshot = this.buffer.read(cx).read(cx);
20236 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20237 for marked_range in &mut marked_ranges {
20238 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20239 marked_range.start.0 += relative_range_utf16.start;
20240 marked_range.start =
20241 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20242 marked_range.end =
20243 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20244 }
20245 }
20246 Some(marked_ranges)
20247 } else if let Some(range_utf16) = range_utf16 {
20248 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20249 Some(this.selection_replacement_ranges(range_utf16, cx))
20250 } else {
20251 None
20252 };
20253
20254 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20255 let newest_selection_id = this.selections.newest_anchor().id;
20256 this.selections
20257 .all::<OffsetUtf16>(cx)
20258 .iter()
20259 .zip(ranges_to_replace.iter())
20260 .find_map(|(selection, range)| {
20261 if selection.id == newest_selection_id {
20262 Some(
20263 (range.start.0 as isize - selection.head().0 as isize)
20264 ..(range.end.0 as isize - selection.head().0 as isize),
20265 )
20266 } else {
20267 None
20268 }
20269 })
20270 });
20271
20272 cx.emit(EditorEvent::InputHandled {
20273 utf16_range_to_replace: range_to_replace,
20274 text: text.into(),
20275 });
20276
20277 if let Some(ranges) = ranges_to_replace {
20278 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20279 }
20280
20281 let marked_ranges = {
20282 let snapshot = this.buffer.read(cx).read(cx);
20283 this.selections
20284 .disjoint_anchors()
20285 .iter()
20286 .map(|selection| {
20287 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20288 })
20289 .collect::<Vec<_>>()
20290 };
20291
20292 if text.is_empty() {
20293 this.unmark_text(window, cx);
20294 } else {
20295 this.highlight_text::<InputComposition>(
20296 marked_ranges.clone(),
20297 HighlightStyle {
20298 underline: Some(UnderlineStyle {
20299 thickness: px(1.),
20300 color: None,
20301 wavy: false,
20302 }),
20303 ..Default::default()
20304 },
20305 cx,
20306 );
20307 }
20308
20309 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20310 let use_autoclose = this.use_autoclose;
20311 let use_auto_surround = this.use_auto_surround;
20312 this.set_use_autoclose(false);
20313 this.set_use_auto_surround(false);
20314 this.handle_input(text, window, cx);
20315 this.set_use_autoclose(use_autoclose);
20316 this.set_use_auto_surround(use_auto_surround);
20317
20318 if let Some(new_selected_range) = new_selected_range_utf16 {
20319 let snapshot = this.buffer.read(cx).read(cx);
20320 let new_selected_ranges = marked_ranges
20321 .into_iter()
20322 .map(|marked_range| {
20323 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20324 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20325 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20326 snapshot.clip_offset_utf16(new_start, Bias::Left)
20327 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20328 })
20329 .collect::<Vec<_>>();
20330
20331 drop(snapshot);
20332 this.change_selections(None, window, cx, |selections| {
20333 selections.select_ranges(new_selected_ranges)
20334 });
20335 }
20336 });
20337
20338 self.ime_transaction = self.ime_transaction.or(transaction);
20339 if let Some(transaction) = self.ime_transaction {
20340 self.buffer.update(cx, |buffer, cx| {
20341 buffer.group_until_transaction(transaction, cx);
20342 });
20343 }
20344
20345 if self.text_highlights::<InputComposition>(cx).is_none() {
20346 self.ime_transaction.take();
20347 }
20348 }
20349
20350 fn bounds_for_range(
20351 &mut self,
20352 range_utf16: Range<usize>,
20353 element_bounds: gpui::Bounds<Pixels>,
20354 window: &mut Window,
20355 cx: &mut Context<Self>,
20356 ) -> Option<gpui::Bounds<Pixels>> {
20357 let text_layout_details = self.text_layout_details(window);
20358 let gpui::Size {
20359 width: em_width,
20360 height: line_height,
20361 } = self.character_size(window);
20362
20363 let snapshot = self.snapshot(window, cx);
20364 let scroll_position = snapshot.scroll_position();
20365 let scroll_left = scroll_position.x * em_width;
20366
20367 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20368 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20369 + self.gutter_dimensions.width
20370 + self.gutter_dimensions.margin;
20371 let y = line_height * (start.row().as_f32() - scroll_position.y);
20372
20373 Some(Bounds {
20374 origin: element_bounds.origin + point(x, y),
20375 size: size(em_width, line_height),
20376 })
20377 }
20378
20379 fn character_index_for_point(
20380 &mut self,
20381 point: gpui::Point<Pixels>,
20382 _window: &mut Window,
20383 _cx: &mut Context<Self>,
20384 ) -> Option<usize> {
20385 let position_map = self.last_position_map.as_ref()?;
20386 if !position_map.text_hitbox.contains(&point) {
20387 return None;
20388 }
20389 let display_point = position_map.point_for_position(point).previous_valid;
20390 let anchor = position_map
20391 .snapshot
20392 .display_point_to_anchor(display_point, Bias::Left);
20393 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20394 Some(utf16_offset.0)
20395 }
20396}
20397
20398trait SelectionExt {
20399 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20400 fn spanned_rows(
20401 &self,
20402 include_end_if_at_line_start: bool,
20403 map: &DisplaySnapshot,
20404 ) -> Range<MultiBufferRow>;
20405}
20406
20407impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20408 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20409 let start = self
20410 .start
20411 .to_point(&map.buffer_snapshot)
20412 .to_display_point(map);
20413 let end = self
20414 .end
20415 .to_point(&map.buffer_snapshot)
20416 .to_display_point(map);
20417 if self.reversed {
20418 end..start
20419 } else {
20420 start..end
20421 }
20422 }
20423
20424 fn spanned_rows(
20425 &self,
20426 include_end_if_at_line_start: bool,
20427 map: &DisplaySnapshot,
20428 ) -> Range<MultiBufferRow> {
20429 let start = self.start.to_point(&map.buffer_snapshot);
20430 let mut end = self.end.to_point(&map.buffer_snapshot);
20431 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20432 end.row -= 1;
20433 }
20434
20435 let buffer_start = map.prev_line_boundary(start).0;
20436 let buffer_end = map.next_line_boundary(end).0;
20437 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20438 }
20439}
20440
20441impl<T: InvalidationRegion> InvalidationStack<T> {
20442 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20443 where
20444 S: Clone + ToOffset,
20445 {
20446 while let Some(region) = self.last() {
20447 let all_selections_inside_invalidation_ranges =
20448 if selections.len() == region.ranges().len() {
20449 selections
20450 .iter()
20451 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20452 .all(|(selection, invalidation_range)| {
20453 let head = selection.head().to_offset(buffer);
20454 invalidation_range.start <= head && invalidation_range.end >= head
20455 })
20456 } else {
20457 false
20458 };
20459
20460 if all_selections_inside_invalidation_ranges {
20461 break;
20462 } else {
20463 self.pop();
20464 }
20465 }
20466 }
20467}
20468
20469impl<T> Default for InvalidationStack<T> {
20470 fn default() -> Self {
20471 Self(Default::default())
20472 }
20473}
20474
20475impl<T> Deref for InvalidationStack<T> {
20476 type Target = Vec<T>;
20477
20478 fn deref(&self) -> &Self::Target {
20479 &self.0
20480 }
20481}
20482
20483impl<T> DerefMut for InvalidationStack<T> {
20484 fn deref_mut(&mut self) -> &mut Self::Target {
20485 &mut self.0
20486 }
20487}
20488
20489impl InvalidationRegion for SnippetState {
20490 fn ranges(&self) -> &[Range<Anchor>] {
20491 &self.ranges[self.active_index]
20492 }
20493}
20494
20495fn inline_completion_edit_text(
20496 current_snapshot: &BufferSnapshot,
20497 edits: &[(Range<Anchor>, String)],
20498 edit_preview: &EditPreview,
20499 include_deletions: bool,
20500 cx: &App,
20501) -> HighlightedText {
20502 let edits = edits
20503 .iter()
20504 .map(|(anchor, text)| {
20505 (
20506 anchor.start.text_anchor..anchor.end.text_anchor,
20507 text.clone(),
20508 )
20509 })
20510 .collect::<Vec<_>>();
20511
20512 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20513}
20514
20515pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20516 match severity {
20517 DiagnosticSeverity::ERROR => colors.error,
20518 DiagnosticSeverity::WARNING => colors.warning,
20519 DiagnosticSeverity::INFORMATION => colors.info,
20520 DiagnosticSeverity::HINT => colors.info,
20521 _ => colors.ignored,
20522 }
20523}
20524
20525pub fn styled_runs_for_code_label<'a>(
20526 label: &'a CodeLabel,
20527 syntax_theme: &'a theme::SyntaxTheme,
20528) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20529 let fade_out = HighlightStyle {
20530 fade_out: Some(0.35),
20531 ..Default::default()
20532 };
20533
20534 let mut prev_end = label.filter_range.end;
20535 label
20536 .runs
20537 .iter()
20538 .enumerate()
20539 .flat_map(move |(ix, (range, highlight_id))| {
20540 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20541 style
20542 } else {
20543 return Default::default();
20544 };
20545 let mut muted_style = style;
20546 muted_style.highlight(fade_out);
20547
20548 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20549 if range.start >= label.filter_range.end {
20550 if range.start > prev_end {
20551 runs.push((prev_end..range.start, fade_out));
20552 }
20553 runs.push((range.clone(), muted_style));
20554 } else if range.end <= label.filter_range.end {
20555 runs.push((range.clone(), style));
20556 } else {
20557 runs.push((range.start..label.filter_range.end, style));
20558 runs.push((label.filter_range.end..range.end, muted_style));
20559 }
20560 prev_end = cmp::max(prev_end, range.end);
20561
20562 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20563 runs.push((prev_end..label.text.len(), fade_out));
20564 }
20565
20566 runs
20567 })
20568}
20569
20570pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20571 let mut prev_index = 0;
20572 let mut prev_codepoint: Option<char> = None;
20573 text.char_indices()
20574 .chain([(text.len(), '\0')])
20575 .filter_map(move |(index, codepoint)| {
20576 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20577 let is_boundary = index == text.len()
20578 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20579 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20580 if is_boundary {
20581 let chunk = &text[prev_index..index];
20582 prev_index = index;
20583 Some(chunk)
20584 } else {
20585 None
20586 }
20587 })
20588}
20589
20590pub trait RangeToAnchorExt: Sized {
20591 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20592
20593 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20594 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20595 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20596 }
20597}
20598
20599impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20600 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20601 let start_offset = self.start.to_offset(snapshot);
20602 let end_offset = self.end.to_offset(snapshot);
20603 if start_offset == end_offset {
20604 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20605 } else {
20606 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20607 }
20608 }
20609}
20610
20611pub trait RowExt {
20612 fn as_f32(&self) -> f32;
20613
20614 fn next_row(&self) -> Self;
20615
20616 fn previous_row(&self) -> Self;
20617
20618 fn minus(&self, other: Self) -> u32;
20619}
20620
20621impl RowExt for DisplayRow {
20622 fn as_f32(&self) -> f32 {
20623 self.0 as f32
20624 }
20625
20626 fn next_row(&self) -> Self {
20627 Self(self.0 + 1)
20628 }
20629
20630 fn previous_row(&self) -> Self {
20631 Self(self.0.saturating_sub(1))
20632 }
20633
20634 fn minus(&self, other: Self) -> u32 {
20635 self.0 - other.0
20636 }
20637}
20638
20639impl RowExt for MultiBufferRow {
20640 fn as_f32(&self) -> f32 {
20641 self.0 as f32
20642 }
20643
20644 fn next_row(&self) -> Self {
20645 Self(self.0 + 1)
20646 }
20647
20648 fn previous_row(&self) -> Self {
20649 Self(self.0.saturating_sub(1))
20650 }
20651
20652 fn minus(&self, other: Self) -> u32 {
20653 self.0 - other.0
20654 }
20655}
20656
20657trait RowRangeExt {
20658 type Row;
20659
20660 fn len(&self) -> usize;
20661
20662 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20663}
20664
20665impl RowRangeExt for Range<MultiBufferRow> {
20666 type Row = MultiBufferRow;
20667
20668 fn len(&self) -> usize {
20669 (self.end.0 - self.start.0) as usize
20670 }
20671
20672 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20673 (self.start.0..self.end.0).map(MultiBufferRow)
20674 }
20675}
20676
20677impl RowRangeExt for Range<DisplayRow> {
20678 type Row = DisplayRow;
20679
20680 fn len(&self) -> usize {
20681 (self.end.0 - self.start.0) as usize
20682 }
20683
20684 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20685 (self.start.0..self.end.0).map(DisplayRow)
20686 }
20687}
20688
20689/// If select range has more than one line, we
20690/// just point the cursor to range.start.
20691fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20692 if range.start.row == range.end.row {
20693 range
20694 } else {
20695 range.start..range.start
20696 }
20697}
20698pub struct KillRing(ClipboardItem);
20699impl Global for KillRing {}
20700
20701const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20702
20703enum BreakpointPromptEditAction {
20704 Log,
20705 Condition,
20706 HitCondition,
20707}
20708
20709struct BreakpointPromptEditor {
20710 pub(crate) prompt: Entity<Editor>,
20711 editor: WeakEntity<Editor>,
20712 breakpoint_anchor: Anchor,
20713 breakpoint: Breakpoint,
20714 edit_action: BreakpointPromptEditAction,
20715 block_ids: HashSet<CustomBlockId>,
20716 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20717 _subscriptions: Vec<Subscription>,
20718}
20719
20720impl BreakpointPromptEditor {
20721 const MAX_LINES: u8 = 4;
20722
20723 fn new(
20724 editor: WeakEntity<Editor>,
20725 breakpoint_anchor: Anchor,
20726 breakpoint: Breakpoint,
20727 edit_action: BreakpointPromptEditAction,
20728 window: &mut Window,
20729 cx: &mut Context<Self>,
20730 ) -> Self {
20731 let base_text = match edit_action {
20732 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20733 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20734 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20735 }
20736 .map(|msg| msg.to_string())
20737 .unwrap_or_default();
20738
20739 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20740 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20741
20742 let prompt = cx.new(|cx| {
20743 let mut prompt = Editor::new(
20744 EditorMode::AutoHeight {
20745 max_lines: Self::MAX_LINES as usize,
20746 },
20747 buffer,
20748 None,
20749 window,
20750 cx,
20751 );
20752 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20753 prompt.set_show_cursor_when_unfocused(false, cx);
20754 prompt.set_placeholder_text(
20755 match edit_action {
20756 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20757 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20758 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20759 },
20760 cx,
20761 );
20762
20763 prompt
20764 });
20765
20766 Self {
20767 prompt,
20768 editor,
20769 breakpoint_anchor,
20770 breakpoint,
20771 edit_action,
20772 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20773 block_ids: Default::default(),
20774 _subscriptions: vec![],
20775 }
20776 }
20777
20778 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20779 self.block_ids.extend(block_ids)
20780 }
20781
20782 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20783 if let Some(editor) = self.editor.upgrade() {
20784 let message = self
20785 .prompt
20786 .read(cx)
20787 .buffer
20788 .read(cx)
20789 .as_singleton()
20790 .expect("A multi buffer in breakpoint prompt isn't possible")
20791 .read(cx)
20792 .as_rope()
20793 .to_string();
20794
20795 editor.update(cx, |editor, cx| {
20796 editor.edit_breakpoint_at_anchor(
20797 self.breakpoint_anchor,
20798 self.breakpoint.clone(),
20799 match self.edit_action {
20800 BreakpointPromptEditAction::Log => {
20801 BreakpointEditAction::EditLogMessage(message.into())
20802 }
20803 BreakpointPromptEditAction::Condition => {
20804 BreakpointEditAction::EditCondition(message.into())
20805 }
20806 BreakpointPromptEditAction::HitCondition => {
20807 BreakpointEditAction::EditHitCondition(message.into())
20808 }
20809 },
20810 cx,
20811 );
20812
20813 editor.remove_blocks(self.block_ids.clone(), None, cx);
20814 cx.focus_self(window);
20815 });
20816 }
20817 }
20818
20819 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20820 self.editor
20821 .update(cx, |editor, cx| {
20822 editor.remove_blocks(self.block_ids.clone(), None, cx);
20823 window.focus(&editor.focus_handle);
20824 })
20825 .log_err();
20826 }
20827
20828 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20829 let settings = ThemeSettings::get_global(cx);
20830 let text_style = TextStyle {
20831 color: if self.prompt.read(cx).read_only(cx) {
20832 cx.theme().colors().text_disabled
20833 } else {
20834 cx.theme().colors().text
20835 },
20836 font_family: settings.buffer_font.family.clone(),
20837 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20838 font_size: settings.buffer_font_size(cx).into(),
20839 font_weight: settings.buffer_font.weight,
20840 line_height: relative(settings.buffer_line_height.value()),
20841 ..Default::default()
20842 };
20843 EditorElement::new(
20844 &self.prompt,
20845 EditorStyle {
20846 background: cx.theme().colors().editor_background,
20847 local_player: cx.theme().players().local(),
20848 text: text_style,
20849 ..Default::default()
20850 },
20851 )
20852 }
20853}
20854
20855impl Render for BreakpointPromptEditor {
20856 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20857 let gutter_dimensions = *self.gutter_dimensions.lock();
20858 h_flex()
20859 .key_context("Editor")
20860 .bg(cx.theme().colors().editor_background)
20861 .border_y_1()
20862 .border_color(cx.theme().status().info_border)
20863 .size_full()
20864 .py(window.line_height() / 2.5)
20865 .on_action(cx.listener(Self::confirm))
20866 .on_action(cx.listener(Self::cancel))
20867 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20868 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20869 }
20870}
20871
20872impl Focusable for BreakpointPromptEditor {
20873 fn focus_handle(&self, cx: &App) -> FocusHandle {
20874 self.prompt.focus_handle(cx)
20875 }
20876}
20877
20878fn all_edits_insertions_or_deletions(
20879 edits: &Vec<(Range<Anchor>, String)>,
20880 snapshot: &MultiBufferSnapshot,
20881) -> bool {
20882 let mut all_insertions = true;
20883 let mut all_deletions = true;
20884
20885 for (range, new_text) in edits.iter() {
20886 let range_is_empty = range.to_offset(&snapshot).is_empty();
20887 let text_is_empty = new_text.is_empty();
20888
20889 if range_is_empty != text_is_empty {
20890 if range_is_empty {
20891 all_deletions = false;
20892 } else {
20893 all_insertions = false;
20894 }
20895 } else {
20896 return false;
20897 }
20898
20899 if !all_insertions && !all_deletions {
20900 return false;
20901 }
20902 }
20903 all_insertions || all_deletions
20904}
20905
20906struct MissingEditPredictionKeybindingTooltip;
20907
20908impl Render for MissingEditPredictionKeybindingTooltip {
20909 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20910 ui::tooltip_container(window, cx, |container, _, cx| {
20911 container
20912 .flex_shrink_0()
20913 .max_w_80()
20914 .min_h(rems_from_px(124.))
20915 .justify_between()
20916 .child(
20917 v_flex()
20918 .flex_1()
20919 .text_ui_sm(cx)
20920 .child(Label::new("Conflict with Accept Keybinding"))
20921 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20922 )
20923 .child(
20924 h_flex()
20925 .pb_1()
20926 .gap_1()
20927 .items_end()
20928 .w_full()
20929 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20930 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20931 }))
20932 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20933 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20934 })),
20935 )
20936 })
20937 }
20938}
20939
20940#[derive(Debug, Clone, Copy, PartialEq)]
20941pub struct LineHighlight {
20942 pub background: Background,
20943 pub border: Option<gpui::Hsla>,
20944 pub include_gutter: bool,
20945 pub type_id: Option<TypeId>,
20946}
20947
20948fn render_diff_hunk_controls(
20949 row: u32,
20950 status: &DiffHunkStatus,
20951 hunk_range: Range<Anchor>,
20952 is_created_file: bool,
20953 line_height: Pixels,
20954 editor: &Entity<Editor>,
20955 _window: &mut Window,
20956 cx: &mut App,
20957) -> AnyElement {
20958 h_flex()
20959 .h(line_height)
20960 .mr_1()
20961 .gap_1()
20962 .px_0p5()
20963 .pb_1()
20964 .border_x_1()
20965 .border_b_1()
20966 .border_color(cx.theme().colors().border_variant)
20967 .rounded_b_lg()
20968 .bg(cx.theme().colors().editor_background)
20969 .gap_1()
20970 .occlude()
20971 .shadow_md()
20972 .child(if status.has_secondary_hunk() {
20973 Button::new(("stage", row as u64), "Stage")
20974 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20975 .tooltip({
20976 let focus_handle = editor.focus_handle(cx);
20977 move |window, cx| {
20978 Tooltip::for_action_in(
20979 "Stage Hunk",
20980 &::git::ToggleStaged,
20981 &focus_handle,
20982 window,
20983 cx,
20984 )
20985 }
20986 })
20987 .on_click({
20988 let editor = editor.clone();
20989 move |_event, _window, cx| {
20990 editor.update(cx, |editor, cx| {
20991 editor.stage_or_unstage_diff_hunks(
20992 true,
20993 vec![hunk_range.start..hunk_range.start],
20994 cx,
20995 );
20996 });
20997 }
20998 })
20999 } else {
21000 Button::new(("unstage", row as u64), "Unstage")
21001 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21002 .tooltip({
21003 let focus_handle = editor.focus_handle(cx);
21004 move |window, cx| {
21005 Tooltip::for_action_in(
21006 "Unstage Hunk",
21007 &::git::ToggleStaged,
21008 &focus_handle,
21009 window,
21010 cx,
21011 )
21012 }
21013 })
21014 .on_click({
21015 let editor = editor.clone();
21016 move |_event, _window, cx| {
21017 editor.update(cx, |editor, cx| {
21018 editor.stage_or_unstage_diff_hunks(
21019 false,
21020 vec![hunk_range.start..hunk_range.start],
21021 cx,
21022 );
21023 });
21024 }
21025 })
21026 })
21027 .child(
21028 Button::new(("restore", row as u64), "Restore")
21029 .tooltip({
21030 let focus_handle = editor.focus_handle(cx);
21031 move |window, cx| {
21032 Tooltip::for_action_in(
21033 "Restore Hunk",
21034 &::git::Restore,
21035 &focus_handle,
21036 window,
21037 cx,
21038 )
21039 }
21040 })
21041 .on_click({
21042 let editor = editor.clone();
21043 move |_event, window, cx| {
21044 editor.update(cx, |editor, cx| {
21045 let snapshot = editor.snapshot(window, cx);
21046 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21047 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21048 });
21049 }
21050 })
21051 .disabled(is_created_file),
21052 )
21053 .when(
21054 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21055 |el| {
21056 el.child(
21057 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21058 .shape(IconButtonShape::Square)
21059 .icon_size(IconSize::Small)
21060 // .disabled(!has_multiple_hunks)
21061 .tooltip({
21062 let focus_handle = editor.focus_handle(cx);
21063 move |window, cx| {
21064 Tooltip::for_action_in(
21065 "Next Hunk",
21066 &GoToHunk,
21067 &focus_handle,
21068 window,
21069 cx,
21070 )
21071 }
21072 })
21073 .on_click({
21074 let editor = editor.clone();
21075 move |_event, window, cx| {
21076 editor.update(cx, |editor, cx| {
21077 let snapshot = editor.snapshot(window, cx);
21078 let position =
21079 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21080 editor.go_to_hunk_before_or_after_position(
21081 &snapshot,
21082 position,
21083 Direction::Next,
21084 window,
21085 cx,
21086 );
21087 editor.expand_selected_diff_hunks(cx);
21088 });
21089 }
21090 }),
21091 )
21092 .child(
21093 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21094 .shape(IconButtonShape::Square)
21095 .icon_size(IconSize::Small)
21096 // .disabled(!has_multiple_hunks)
21097 .tooltip({
21098 let focus_handle = editor.focus_handle(cx);
21099 move |window, cx| {
21100 Tooltip::for_action_in(
21101 "Previous Hunk",
21102 &GoToPreviousHunk,
21103 &focus_handle,
21104 window,
21105 cx,
21106 )
21107 }
21108 })
21109 .on_click({
21110 let editor = editor.clone();
21111 move |_event, window, cx| {
21112 editor.update(cx, |editor, cx| {
21113 let snapshot = editor.snapshot(window, cx);
21114 let point =
21115 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21116 editor.go_to_hunk_before_or_after_position(
21117 &snapshot,
21118 point,
21119 Direction::Prev,
21120 window,
21121 cx,
21122 );
21123 editor.expand_selected_diff_hunks(cx);
21124 });
21125 }
21126 }),
21127 )
21128 },
21129 )
21130 .into_any_element()
21131}