1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::ReplicaId;
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use display_map::*;
63pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
64use editor_settings::GoToDefinitionFallback;
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
67 ShowScrollbar,
68};
69pub use editor_settings_controls::*;
70use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
71pub use element::{
72 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
73};
74use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
75use futures::{
76 FutureExt,
77 future::{self, Shared, join},
78};
79use fuzzy::StringMatchCandidate;
80
81use ::git::blame::BlameEntry;
82use ::git::{Restore, blame::ParsedCommitMessage};
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use git::blame::{GitBlame, GlobalBlameRenderer};
88use gpui::{
89 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
90 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
91 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
92 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
93 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
94 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
95 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
96 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
97};
98use highlight_matching_bracket::refresh_matching_bracket_highlights;
99use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
100pub use hover_popover::hover_markdown_style;
101use hover_popover::{HoverState, hide_hover};
102use indent_guides::ActiveIndentGuidesState;
103use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
104pub use inline_completion::Direction;
105use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
106pub use items::MAX_TAB_TITLE_LEN;
107use itertools::Itertools;
108use language::{
109 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
110 CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
111 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
112 TransactionId, TreeSitterOptions, WordsQuery,
113 language_settings::{
114 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
115 all_language_settings, language_settings,
116 },
117 point_from_lsp, text_diff_with_options,
118};
119use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
120use linked_editing_ranges::refresh_linked_ranges;
121use markdown::Markdown;
122use mouse_context_menu::MouseContextMenu;
123use persistence::DB;
124use project::{
125 ProjectPath,
126 debugger::{
127 breakpoint_store::{
128 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
129 },
130 session::{Session, SessionEvent},
131 },
132};
133
134pub use git::blame::BlameRenderer;
135pub use proposed_changes_editor::{
136 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
137};
138use smallvec::smallvec;
139use std::{cell::OnceCell, iter::Peekable};
140use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
141
142pub use lsp::CompletionContext;
143use lsp::{
144 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
145 InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
146};
147
148use language::BufferSnapshot;
149pub use lsp_ext::lsp_tasks;
150use movement::TextLayoutDetails;
151pub use multi_buffer::{
152 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
153 RowInfo, ToOffset, ToPoint,
154};
155use multi_buffer::{
156 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
157 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
158};
159use parking_lot::Mutex;
160use project::{
161 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
162 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
163 TaskSourceKind,
164 debugger::breakpoint_store::Breakpoint,
165 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
166 project_settings::{GitGutterSetting, ProjectSettings},
167};
168use rand::prelude::*;
169use rpc::{ErrorExt, proto::*};
170use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
171use selections_collection::{
172 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
173};
174use serde::{Deserialize, Serialize};
175use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
176use smallvec::SmallVec;
177use snippet::Snippet;
178use std::sync::Arc;
179use std::{
180 any::TypeId,
181 borrow::Cow,
182 cell::RefCell,
183 cmp::{self, Ordering, Reverse},
184 mem,
185 num::NonZeroU32,
186 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 time::{Duration, Instant},
190};
191pub use sum_tree::Bias;
192use sum_tree::TreeMap;
193use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
194use theme::{
195 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
196 observe_buffer_font_size_adjustment,
197};
198use ui::{
199 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
200 IconSize, Key, Tooltip, h_flex, prelude::*,
201};
202use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
203use workspace::{
204 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
205 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
206 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
207 item::{ItemHandle, PreviewTabsSettings},
208 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
209 searchable::SearchEvent,
210};
211
212use crate::hover_links::{find_url, find_url_from_range};
213use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
214
215pub const FILE_HEADER_HEIGHT: u32 = 2;
216pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
217pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
218const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
219const MAX_LINE_LEN: usize = 1024;
220const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
221const MAX_SELECTION_HISTORY_LEN: usize = 1024;
222pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
223#[doc(hidden)]
224pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
225const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
226
227pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
249 alt: true,
250 shift: true,
251 control: false,
252 platform: false,
253 function: false,
254};
255
256struct InlineValueCache {
257 enabled: bool,
258 inlays: Vec<InlayId>,
259 refresh_task: Task<Option<()>>,
260}
261
262impl InlineValueCache {
263 fn new(enabled: bool) -> Self {
264 Self {
265 enabled,
266 inlays: Vec::new(),
267 refresh_task: Task::ready(None),
268 }
269 }
270}
271
272#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
273pub enum InlayId {
274 InlineCompletion(usize),
275 Hint(usize),
276 DebuggerValue(usize),
277}
278
279impl InlayId {
280 fn id(&self) -> usize {
281 match self {
282 Self::InlineCompletion(id) => *id,
283 Self::Hint(id) => *id,
284 Self::DebuggerValue(id) => *id,
285 }
286 }
287}
288
289pub enum DebugCurrentRowHighlight {}
290enum DocumentHighlightRead {}
291enum DocumentHighlightWrite {}
292enum InputComposition {}
293enum SelectedTextHighlight {}
294
295pub enum ConflictsOuter {}
296pub enum ConflictsOurs {}
297pub enum ConflictsTheirs {}
298pub enum ConflictsOursMarker {}
299pub enum ConflictsTheirsMarker {}
300
301#[derive(Debug, Copy, Clone, PartialEq, Eq)]
302pub enum Navigated {
303 Yes,
304 No,
305}
306
307impl Navigated {
308 pub fn from_bool(yes: bool) -> Navigated {
309 if yes { Navigated::Yes } else { Navigated::No }
310 }
311}
312
313#[derive(Debug, Clone, PartialEq, Eq)]
314enum DisplayDiffHunk {
315 Folded {
316 display_row: DisplayRow,
317 },
318 Unfolded {
319 is_created_file: bool,
320 diff_base_byte_range: Range<usize>,
321 display_row_range: Range<DisplayRow>,
322 multi_buffer_range: Range<Anchor>,
323 status: DiffHunkStatus,
324 },
325}
326
327pub enum HideMouseCursorOrigin {
328 TypingAction,
329 MovementAction,
330}
331
332pub fn init_settings(cx: &mut App) {
333 EditorSettings::register(cx);
334}
335
336pub fn init(cx: &mut App) {
337 init_settings(cx);
338
339 cx.set_global(GlobalBlameRenderer(Arc::new(())));
340
341 workspace::register_project_item::<Editor>(cx);
342 workspace::FollowableViewRegistry::register::<Editor>(cx);
343 workspace::register_serializable_item::<Editor>(cx);
344
345 cx.observe_new(
346 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
347 workspace.register_action(Editor::new_file);
348 workspace.register_action(Editor::new_file_vertical);
349 workspace.register_action(Editor::new_file_horizontal);
350 workspace.register_action(Editor::cancel_language_server_work);
351 },
352 )
353 .detach();
354
355 cx.on_action(move |_: &workspace::NewFile, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 Editor::new_file(workspace, &Default::default(), window, cx)
364 },
365 )
366 .detach();
367 }
368 });
369 cx.on_action(move |_: &workspace::NewWindow, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 cx.activate(true);
378 Editor::new_file(workspace, &Default::default(), window, cx)
379 },
380 )
381 .detach();
382 }
383 });
384}
385
386pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
387 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
388}
389
390pub trait DiagnosticRenderer {
391 fn render_group(
392 &self,
393 diagnostic_group: Vec<DiagnosticEntry<Point>>,
394 buffer_id: BufferId,
395 snapshot: EditorSnapshot,
396 editor: WeakEntity<Editor>,
397 cx: &mut App,
398 ) -> Vec<BlockProperties<Anchor>>;
399
400 fn render_hover(
401 &self,
402 diagnostic_group: Vec<DiagnosticEntry<Point>>,
403 range: Range<Point>,
404 buffer_id: BufferId,
405 cx: &mut App,
406 ) -> Option<Entity<markdown::Markdown>>;
407
408 fn open_link(
409 &self,
410 editor: &mut Editor,
411 link: SharedString,
412 window: &mut Window,
413 cx: &mut Context<Editor>,
414 );
415}
416
417pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
418
419impl GlobalDiagnosticRenderer {
420 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
421 cx.try_global::<Self>().map(|g| g.0.clone())
422 }
423}
424
425impl gpui::Global for GlobalDiagnosticRenderer {}
426pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
427 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
428}
429
430pub struct SearchWithinRange;
431
432trait InvalidationRegion {
433 fn ranges(&self) -> &[Range<Anchor>];
434}
435
436#[derive(Clone, Debug, PartialEq)]
437pub enum SelectPhase {
438 Begin {
439 position: DisplayPoint,
440 add: bool,
441 click_count: usize,
442 },
443 BeginColumnar {
444 position: DisplayPoint,
445 reset: bool,
446 goal_column: u32,
447 },
448 Extend {
449 position: DisplayPoint,
450 click_count: usize,
451 },
452 Update {
453 position: DisplayPoint,
454 goal_column: u32,
455 scroll_delta: gpui::Point<f32>,
456 },
457 End,
458}
459
460#[derive(Clone, Debug)]
461pub enum SelectMode {
462 Character,
463 Word(Range<Anchor>),
464 Line(Range<Anchor>),
465 All,
466}
467
468#[derive(Copy, Clone, PartialEq, Eq, Debug)]
469pub enum EditorMode {
470 SingleLine {
471 auto_width: bool,
472 },
473 AutoHeight {
474 max_lines: usize,
475 },
476 Full {
477 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
478 scale_ui_elements_with_buffer_font_size: bool,
479 /// When set to `true`, the editor will render a background for the active line.
480 show_active_line_background: bool,
481 /// When set to `true`, the editor's height will be determined by its content.
482 sized_by_content: bool,
483 },
484}
485
486impl EditorMode {
487 pub fn full() -> Self {
488 Self::Full {
489 scale_ui_elements_with_buffer_font_size: true,
490 show_active_line_background: true,
491 sized_by_content: false,
492 }
493 }
494
495 pub fn is_full(&self) -> bool {
496 matches!(self, Self::Full { .. })
497 }
498}
499
500#[derive(Copy, Clone, Debug)]
501pub enum SoftWrap {
502 /// Prefer not to wrap at all.
503 ///
504 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
505 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
506 GitDiff,
507 /// Prefer a single line generally, unless an overly long line is encountered.
508 None,
509 /// Soft wrap lines that exceed the editor width.
510 EditorWidth,
511 /// Soft wrap lines at the preferred line length.
512 Column(u32),
513 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
514 Bounded(u32),
515}
516
517#[derive(Clone)]
518pub struct EditorStyle {
519 pub background: Hsla,
520 pub local_player: PlayerColor,
521 pub text: TextStyle,
522 pub scrollbar_width: Pixels,
523 pub syntax: Arc<SyntaxTheme>,
524 pub status: StatusColors,
525 pub inlay_hints_style: HighlightStyle,
526 pub inline_completion_styles: InlineCompletionStyles,
527 pub unnecessary_code_fade: f32,
528}
529
530impl Default for EditorStyle {
531 fn default() -> Self {
532 Self {
533 background: Hsla::default(),
534 local_player: PlayerColor::default(),
535 text: TextStyle::default(),
536 scrollbar_width: Pixels::default(),
537 syntax: Default::default(),
538 // HACK: Status colors don't have a real default.
539 // We should look into removing the status colors from the editor
540 // style and retrieve them directly from the theme.
541 status: StatusColors::dark(),
542 inlay_hints_style: HighlightStyle::default(),
543 inline_completion_styles: InlineCompletionStyles {
544 insertion: HighlightStyle::default(),
545 whitespace: HighlightStyle::default(),
546 },
547 unnecessary_code_fade: Default::default(),
548 }
549 }
550}
551
552pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
553 let show_background = language_settings::language_settings(None, None, cx)
554 .inlay_hints
555 .show_background;
556
557 HighlightStyle {
558 color: Some(cx.theme().status().hint),
559 background_color: show_background.then(|| cx.theme().status().hint_background),
560 ..HighlightStyle::default()
561 }
562}
563
564pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
565 InlineCompletionStyles {
566 insertion: HighlightStyle {
567 color: Some(cx.theme().status().predictive),
568 ..HighlightStyle::default()
569 },
570 whitespace: HighlightStyle {
571 background_color: Some(cx.theme().status().created_background),
572 ..HighlightStyle::default()
573 },
574 }
575}
576
577type CompletionId = usize;
578
579pub(crate) enum EditDisplayMode {
580 TabAccept,
581 DiffPopover,
582 Inline,
583}
584
585enum InlineCompletion {
586 Edit {
587 edits: Vec<(Range<Anchor>, String)>,
588 edit_preview: Option<EditPreview>,
589 display_mode: EditDisplayMode,
590 snapshot: BufferSnapshot,
591 },
592 Move {
593 target: Anchor,
594 snapshot: BufferSnapshot,
595 },
596}
597
598struct InlineCompletionState {
599 inlay_ids: Vec<InlayId>,
600 completion: InlineCompletion,
601 completion_id: Option<SharedString>,
602 invalidation_range: Range<Anchor>,
603}
604
605enum EditPredictionSettings {
606 Disabled,
607 Enabled {
608 show_in_menu: bool,
609 preview_requires_modifier: bool,
610 },
611}
612
613enum InlineCompletionHighlight {}
614
615#[derive(Debug, Clone)]
616struct InlineDiagnostic {
617 message: SharedString,
618 group_id: usize,
619 is_primary: bool,
620 start: Point,
621 severity: DiagnosticSeverity,
622}
623
624pub enum MenuInlineCompletionsPolicy {
625 Never,
626 ByProvider,
627}
628
629pub enum EditPredictionPreview {
630 /// Modifier is not pressed
631 Inactive { released_too_fast: bool },
632 /// Modifier pressed
633 Active {
634 since: Instant,
635 previous_scroll_position: Option<ScrollAnchor>,
636 },
637}
638
639impl EditPredictionPreview {
640 pub fn released_too_fast(&self) -> bool {
641 match self {
642 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
643 EditPredictionPreview::Active { .. } => false,
644 }
645 }
646
647 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
648 if let EditPredictionPreview::Active {
649 previous_scroll_position,
650 ..
651 } = self
652 {
653 *previous_scroll_position = scroll_position;
654 }
655 }
656}
657
658pub struct ContextMenuOptions {
659 pub min_entries_visible: usize,
660 pub max_entries_visible: usize,
661 pub placement: Option<ContextMenuPlacement>,
662}
663
664#[derive(Debug, Clone, PartialEq, Eq)]
665pub enum ContextMenuPlacement {
666 Above,
667 Below,
668}
669
670#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
671struct EditorActionId(usize);
672
673impl EditorActionId {
674 pub fn post_inc(&mut self) -> Self {
675 let answer = self.0;
676
677 *self = Self(answer + 1);
678
679 Self(answer)
680 }
681}
682
683// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
684// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
685
686type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
687type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
688
689#[derive(Default)]
690struct ScrollbarMarkerState {
691 scrollbar_size: Size<Pixels>,
692 dirty: bool,
693 markers: Arc<[PaintQuad]>,
694 pending_refresh: Option<Task<Result<()>>>,
695}
696
697impl ScrollbarMarkerState {
698 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
699 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
700 }
701}
702
703#[derive(Clone, Debug)]
704struct RunnableTasks {
705 templates: Vec<(TaskSourceKind, TaskTemplate)>,
706 offset: multi_buffer::Anchor,
707 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
708 column: u32,
709 // Values of all named captures, including those starting with '_'
710 extra_variables: HashMap<String, String>,
711 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
712 context_range: Range<BufferOffset>,
713}
714
715impl RunnableTasks {
716 fn resolve<'a>(
717 &'a self,
718 cx: &'a task::TaskContext,
719 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
720 self.templates.iter().filter_map(|(kind, template)| {
721 template
722 .resolve_task(&kind.to_id_base(), cx)
723 .map(|task| (kind.clone(), task))
724 })
725 }
726}
727
728#[derive(Clone)]
729struct ResolvedTasks {
730 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
731 position: Anchor,
732}
733
734#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
735struct BufferOffset(usize);
736
737// Addons allow storing per-editor state in other crates (e.g. Vim)
738pub trait Addon: 'static {
739 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
740
741 fn render_buffer_header_controls(
742 &self,
743 _: &ExcerptInfo,
744 _: &Window,
745 _: &App,
746 ) -> Option<AnyElement> {
747 None
748 }
749
750 fn to_any(&self) -> &dyn std::any::Any;
751
752 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
753 None
754 }
755}
756
757/// A set of caret positions, registered when the editor was edited.
758pub struct ChangeList {
759 changes: Vec<Vec<Anchor>>,
760 /// Currently "selected" change.
761 position: Option<usize>,
762}
763
764impl ChangeList {
765 pub fn new() -> Self {
766 Self {
767 changes: Vec::new(),
768 position: None,
769 }
770 }
771
772 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
773 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
774 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
775 if self.changes.is_empty() {
776 return None;
777 }
778
779 let prev = self.position.unwrap_or(self.changes.len());
780 let next = if direction == Direction::Prev {
781 prev.saturating_sub(count)
782 } else {
783 (prev + count).min(self.changes.len() - 1)
784 };
785 self.position = Some(next);
786 self.changes.get(next).map(|anchors| anchors.as_slice())
787 }
788
789 /// Adds a new change to the list, resetting the change list position.
790 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
791 self.position.take();
792 if pop_state {
793 self.changes.pop();
794 }
795 self.changes.push(new_positions.clone());
796 }
797
798 pub fn last(&self) -> Option<&[Anchor]> {
799 self.changes.last().map(|anchors| anchors.as_slice())
800 }
801}
802
803#[derive(Clone)]
804struct InlineBlamePopoverState {
805 scroll_handle: ScrollHandle,
806 commit_message: Option<ParsedCommitMessage>,
807 markdown: Entity<Markdown>,
808}
809
810struct InlineBlamePopover {
811 position: gpui::Point<Pixels>,
812 show_task: Option<Task<()>>,
813 hide_task: Option<Task<()>>,
814 popover_bounds: Option<Bounds<Pixels>>,
815 popover_state: InlineBlamePopoverState,
816}
817
818/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
819///
820/// See the [module level documentation](self) for more information.
821pub struct Editor {
822 focus_handle: FocusHandle,
823 last_focused_descendant: Option<WeakFocusHandle>,
824 /// The text buffer being edited
825 buffer: Entity<MultiBuffer>,
826 /// Map of how text in the buffer should be displayed.
827 /// Handles soft wraps, folds, fake inlay text insertions, etc.
828 pub display_map: Entity<DisplayMap>,
829 pub selections: SelectionsCollection,
830 pub scroll_manager: ScrollManager,
831 /// When inline assist editors are linked, they all render cursors because
832 /// typing enters text into each of them, even the ones that aren't focused.
833 pub(crate) show_cursor_when_unfocused: bool,
834 columnar_selection_tail: Option<Anchor>,
835 add_selections_state: Option<AddSelectionsState>,
836 select_next_state: Option<SelectNextState>,
837 select_prev_state: Option<SelectNextState>,
838 selection_history: SelectionHistory,
839 autoclose_regions: Vec<AutocloseRegion>,
840 snippet_stack: InvalidationStack<SnippetState>,
841 select_syntax_node_history: SelectSyntaxNodeHistory,
842 ime_transaction: Option<TransactionId>,
843 active_diagnostics: ActiveDiagnostic,
844 show_inline_diagnostics: bool,
845 inline_diagnostics_update: Task<()>,
846 inline_diagnostics_enabled: bool,
847 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
848 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
849 hard_wrap: Option<usize>,
850
851 // TODO: make this a access method
852 pub project: Option<Entity<Project>>,
853 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
854 completion_provider: Option<Box<dyn CompletionProvider>>,
855 collaboration_hub: Option<Box<dyn CollaborationHub>>,
856 blink_manager: Entity<BlinkManager>,
857 show_cursor_names: bool,
858 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
859 pub show_local_selections: bool,
860 mode: EditorMode,
861 show_breadcrumbs: bool,
862 show_gutter: bool,
863 show_scrollbars: bool,
864 disable_scrolling: bool,
865 disable_expand_excerpt_buttons: bool,
866 show_line_numbers: Option<bool>,
867 use_relative_line_numbers: Option<bool>,
868 show_git_diff_gutter: Option<bool>,
869 show_code_actions: Option<bool>,
870 show_runnables: Option<bool>,
871 show_breakpoints: Option<bool>,
872 show_wrap_guides: Option<bool>,
873 show_indent_guides: Option<bool>,
874 placeholder_text: Option<Arc<str>>,
875 highlight_order: usize,
876 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
877 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
878 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
879 scrollbar_marker_state: ScrollbarMarkerState,
880 active_indent_guides_state: ActiveIndentGuidesState,
881 nav_history: Option<ItemNavHistory>,
882 context_menu: RefCell<Option<CodeContextMenu>>,
883 context_menu_options: Option<ContextMenuOptions>,
884 mouse_context_menu: Option<MouseContextMenu>,
885 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
886 inline_blame_popover: Option<InlineBlamePopover>,
887 signature_help_state: SignatureHelpState,
888 auto_signature_help: Option<bool>,
889 find_all_references_task_sources: Vec<Anchor>,
890 next_completion_id: CompletionId,
891 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
892 code_actions_task: Option<Task<Result<()>>>,
893 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
894 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
895 document_highlights_task: Option<Task<()>>,
896 linked_editing_range_task: Option<Task<Option<()>>>,
897 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
898 pending_rename: Option<RenameState>,
899 searchable: bool,
900 cursor_shape: CursorShape,
901 current_line_highlight: Option<CurrentLineHighlight>,
902 collapse_matches: bool,
903 autoindent_mode: Option<AutoindentMode>,
904 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
905 input_enabled: bool,
906 use_modal_editing: bool,
907 read_only: bool,
908 leader_peer_id: Option<PeerId>,
909 remote_id: Option<ViewId>,
910 pub hover_state: HoverState,
911 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
912 gutter_hovered: bool,
913 hovered_link_state: Option<HoveredLinkState>,
914 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
915 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
916 active_inline_completion: Option<InlineCompletionState>,
917 /// Used to prevent flickering as the user types while the menu is open
918 stale_inline_completion_in_menu: Option<InlineCompletionState>,
919 edit_prediction_settings: EditPredictionSettings,
920 inline_completions_hidden_for_vim_mode: bool,
921 show_inline_completions_override: Option<bool>,
922 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
923 edit_prediction_preview: EditPredictionPreview,
924 edit_prediction_indent_conflict: bool,
925 edit_prediction_requires_modifier_in_indent_conflict: bool,
926 inlay_hint_cache: InlayHintCache,
927 next_inlay_id: usize,
928 _subscriptions: Vec<Subscription>,
929 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
930 gutter_dimensions: GutterDimensions,
931 style: Option<EditorStyle>,
932 text_style_refinement: Option<TextStyleRefinement>,
933 next_editor_action_id: EditorActionId,
934 editor_actions:
935 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
936 use_autoclose: bool,
937 use_auto_surround: bool,
938 auto_replace_emoji_shortcode: bool,
939 jsx_tag_auto_close_enabled_in_any_buffer: bool,
940 show_git_blame_gutter: bool,
941 show_git_blame_inline: bool,
942 show_git_blame_inline_delay_task: Option<Task<()>>,
943 git_blame_inline_enabled: bool,
944 render_diff_hunk_controls: RenderDiffHunkControlsFn,
945 serialize_dirty_buffers: bool,
946 show_selection_menu: Option<bool>,
947 blame: Option<Entity<GitBlame>>,
948 blame_subscription: Option<Subscription>,
949 custom_context_menu: Option<
950 Box<
951 dyn 'static
952 + Fn(
953 &mut Self,
954 DisplayPoint,
955 &mut Window,
956 &mut Context<Self>,
957 ) -> Option<Entity<ui::ContextMenu>>,
958 >,
959 >,
960 last_bounds: Option<Bounds<Pixels>>,
961 last_position_map: Option<Rc<PositionMap>>,
962 expect_bounds_change: Option<Bounds<Pixels>>,
963 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
964 tasks_update_task: Option<Task<()>>,
965 breakpoint_store: Option<Entity<BreakpointStore>>,
966 /// Allow's a user to create a breakpoint by selecting this indicator
967 /// It should be None while a user is not hovering over the gutter
968 /// Otherwise it represents the point that the breakpoint will be shown
969 gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
970 in_project_search: bool,
971 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
972 breadcrumb_header: Option<String>,
973 focused_block: Option<FocusedBlock>,
974 next_scroll_position: NextScrollCursorCenterTopBottom,
975 addons: HashMap<TypeId, Box<dyn Addon>>,
976 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
977 load_diff_task: Option<Shared<Task<()>>>,
978 selection_mark_mode: bool,
979 toggle_fold_multiple_buffers: Task<()>,
980 _scroll_cursor_center_top_bottom_task: Task<()>,
981 serialize_selections: Task<()>,
982 serialize_folds: Task<()>,
983 mouse_cursor_hidden: bool,
984 hide_mouse_mode: HideMouseMode,
985 pub change_list: ChangeList,
986 inline_value_cache: InlineValueCache,
987}
988
989#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
990enum NextScrollCursorCenterTopBottom {
991 #[default]
992 Center,
993 Top,
994 Bottom,
995}
996
997impl NextScrollCursorCenterTopBottom {
998 fn next(&self) -> Self {
999 match self {
1000 Self::Center => Self::Top,
1001 Self::Top => Self::Bottom,
1002 Self::Bottom => Self::Center,
1003 }
1004 }
1005}
1006
1007#[derive(Clone)]
1008pub struct EditorSnapshot {
1009 pub mode: EditorMode,
1010 show_gutter: bool,
1011 show_line_numbers: Option<bool>,
1012 show_git_diff_gutter: Option<bool>,
1013 show_code_actions: Option<bool>,
1014 show_runnables: Option<bool>,
1015 show_breakpoints: Option<bool>,
1016 git_blame_gutter_max_author_length: Option<usize>,
1017 pub display_snapshot: DisplaySnapshot,
1018 pub placeholder_text: Option<Arc<str>>,
1019 is_focused: bool,
1020 scroll_anchor: ScrollAnchor,
1021 ongoing_scroll: OngoingScroll,
1022 current_line_highlight: CurrentLineHighlight,
1023 gutter_hovered: bool,
1024}
1025
1026#[derive(Default, Debug, Clone, Copy)]
1027pub struct GutterDimensions {
1028 pub left_padding: Pixels,
1029 pub right_padding: Pixels,
1030 pub width: Pixels,
1031 pub margin: Pixels,
1032 pub git_blame_entries_width: Option<Pixels>,
1033}
1034
1035impl GutterDimensions {
1036 /// The full width of the space taken up by the gutter.
1037 pub fn full_width(&self) -> Pixels {
1038 self.margin + self.width
1039 }
1040
1041 /// The width of the space reserved for the fold indicators,
1042 /// use alongside 'justify_end' and `gutter_width` to
1043 /// right align content with the line numbers
1044 pub fn fold_area_width(&self) -> Pixels {
1045 self.margin + self.right_padding
1046 }
1047}
1048
1049#[derive(Debug)]
1050pub struct RemoteSelection {
1051 pub replica_id: ReplicaId,
1052 pub selection: Selection<Anchor>,
1053 pub cursor_shape: CursorShape,
1054 pub peer_id: PeerId,
1055 pub line_mode: bool,
1056 pub participant_index: Option<ParticipantIndex>,
1057 pub user_name: Option<SharedString>,
1058}
1059
1060#[derive(Clone, Debug)]
1061struct SelectionHistoryEntry {
1062 selections: Arc<[Selection<Anchor>]>,
1063 select_next_state: Option<SelectNextState>,
1064 select_prev_state: Option<SelectNextState>,
1065 add_selections_state: Option<AddSelectionsState>,
1066}
1067
1068enum SelectionHistoryMode {
1069 Normal,
1070 Undoing,
1071 Redoing,
1072}
1073
1074#[derive(Clone, PartialEq, Eq, Hash)]
1075struct HoveredCursor {
1076 replica_id: u16,
1077 selection_id: usize,
1078}
1079
1080impl Default for SelectionHistoryMode {
1081 fn default() -> Self {
1082 Self::Normal
1083 }
1084}
1085
1086#[derive(Default)]
1087struct SelectionHistory {
1088 #[allow(clippy::type_complexity)]
1089 selections_by_transaction:
1090 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1091 mode: SelectionHistoryMode,
1092 undo_stack: VecDeque<SelectionHistoryEntry>,
1093 redo_stack: VecDeque<SelectionHistoryEntry>,
1094}
1095
1096impl SelectionHistory {
1097 fn insert_transaction(
1098 &mut self,
1099 transaction_id: TransactionId,
1100 selections: Arc<[Selection<Anchor>]>,
1101 ) {
1102 self.selections_by_transaction
1103 .insert(transaction_id, (selections, None));
1104 }
1105
1106 #[allow(clippy::type_complexity)]
1107 fn transaction(
1108 &self,
1109 transaction_id: TransactionId,
1110 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1111 self.selections_by_transaction.get(&transaction_id)
1112 }
1113
1114 #[allow(clippy::type_complexity)]
1115 fn transaction_mut(
1116 &mut self,
1117 transaction_id: TransactionId,
1118 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1119 self.selections_by_transaction.get_mut(&transaction_id)
1120 }
1121
1122 fn push(&mut self, entry: SelectionHistoryEntry) {
1123 if !entry.selections.is_empty() {
1124 match self.mode {
1125 SelectionHistoryMode::Normal => {
1126 self.push_undo(entry);
1127 self.redo_stack.clear();
1128 }
1129 SelectionHistoryMode::Undoing => self.push_redo(entry),
1130 SelectionHistoryMode::Redoing => self.push_undo(entry),
1131 }
1132 }
1133 }
1134
1135 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1136 if self
1137 .undo_stack
1138 .back()
1139 .map_or(true, |e| e.selections != entry.selections)
1140 {
1141 self.undo_stack.push_back(entry);
1142 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1143 self.undo_stack.pop_front();
1144 }
1145 }
1146 }
1147
1148 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1149 if self
1150 .redo_stack
1151 .back()
1152 .map_or(true, |e| e.selections != entry.selections)
1153 {
1154 self.redo_stack.push_back(entry);
1155 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1156 self.redo_stack.pop_front();
1157 }
1158 }
1159 }
1160}
1161
1162#[derive(Clone, Copy)]
1163pub struct RowHighlightOptions {
1164 pub autoscroll: bool,
1165 pub include_gutter: bool,
1166}
1167
1168impl Default for RowHighlightOptions {
1169 fn default() -> Self {
1170 Self {
1171 autoscroll: Default::default(),
1172 include_gutter: true,
1173 }
1174 }
1175}
1176
1177struct RowHighlight {
1178 index: usize,
1179 range: Range<Anchor>,
1180 color: Hsla,
1181 options: RowHighlightOptions,
1182 type_id: TypeId,
1183}
1184
1185#[derive(Clone, Debug)]
1186struct AddSelectionsState {
1187 above: bool,
1188 stack: Vec<usize>,
1189}
1190
1191#[derive(Clone)]
1192struct SelectNextState {
1193 query: AhoCorasick,
1194 wordwise: bool,
1195 done: bool,
1196}
1197
1198impl std::fmt::Debug for SelectNextState {
1199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1200 f.debug_struct(std::any::type_name::<Self>())
1201 .field("wordwise", &self.wordwise)
1202 .field("done", &self.done)
1203 .finish()
1204 }
1205}
1206
1207#[derive(Debug)]
1208struct AutocloseRegion {
1209 selection_id: usize,
1210 range: Range<Anchor>,
1211 pair: BracketPair,
1212}
1213
1214#[derive(Debug)]
1215struct SnippetState {
1216 ranges: Vec<Vec<Range<Anchor>>>,
1217 active_index: usize,
1218 choices: Vec<Option<Vec<String>>>,
1219}
1220
1221#[doc(hidden)]
1222pub struct RenameState {
1223 pub range: Range<Anchor>,
1224 pub old_name: Arc<str>,
1225 pub editor: Entity<Editor>,
1226 block_id: CustomBlockId,
1227}
1228
1229struct InvalidationStack<T>(Vec<T>);
1230
1231struct RegisteredInlineCompletionProvider {
1232 provider: Arc<dyn InlineCompletionProviderHandle>,
1233 _subscription: Subscription,
1234}
1235
1236#[derive(Debug, PartialEq, Eq)]
1237pub struct ActiveDiagnosticGroup {
1238 pub active_range: Range<Anchor>,
1239 pub active_message: String,
1240 pub group_id: usize,
1241 pub blocks: HashSet<CustomBlockId>,
1242}
1243
1244#[derive(Debug, PartialEq, Eq)]
1245#[allow(clippy::large_enum_variant)]
1246pub(crate) enum ActiveDiagnostic {
1247 None,
1248 All,
1249 Group(ActiveDiagnosticGroup),
1250}
1251
1252#[derive(Serialize, Deserialize, Clone, Debug)]
1253pub struct ClipboardSelection {
1254 /// The number of bytes in this selection.
1255 pub len: usize,
1256 /// Whether this was a full-line selection.
1257 pub is_entire_line: bool,
1258 /// The indentation of the first line when this content was originally copied.
1259 pub first_line_indent: u32,
1260}
1261
1262// selections, scroll behavior, was newest selection reversed
1263type SelectSyntaxNodeHistoryState = (
1264 Box<[Selection<usize>]>,
1265 SelectSyntaxNodeScrollBehavior,
1266 bool,
1267);
1268
1269#[derive(Default)]
1270struct SelectSyntaxNodeHistory {
1271 stack: Vec<SelectSyntaxNodeHistoryState>,
1272 // disable temporarily to allow changing selections without losing the stack
1273 pub disable_clearing: bool,
1274}
1275
1276impl SelectSyntaxNodeHistory {
1277 pub fn try_clear(&mut self) {
1278 if !self.disable_clearing {
1279 self.stack.clear();
1280 }
1281 }
1282
1283 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1284 self.stack.push(selection);
1285 }
1286
1287 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1288 self.stack.pop()
1289 }
1290}
1291
1292enum SelectSyntaxNodeScrollBehavior {
1293 CursorTop,
1294 FitSelection,
1295 CursorBottom,
1296}
1297
1298#[derive(Debug)]
1299pub(crate) struct NavigationData {
1300 cursor_anchor: Anchor,
1301 cursor_position: Point,
1302 scroll_anchor: ScrollAnchor,
1303 scroll_top_row: u32,
1304}
1305
1306#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1307pub enum GotoDefinitionKind {
1308 Symbol,
1309 Declaration,
1310 Type,
1311 Implementation,
1312}
1313
1314#[derive(Debug, Clone)]
1315enum InlayHintRefreshReason {
1316 ModifiersChanged(bool),
1317 Toggle(bool),
1318 SettingsChange(InlayHintSettings),
1319 NewLinesShown,
1320 BufferEdited(HashSet<Arc<Language>>),
1321 RefreshRequested,
1322 ExcerptsRemoved(Vec<ExcerptId>),
1323}
1324
1325impl InlayHintRefreshReason {
1326 fn description(&self) -> &'static str {
1327 match self {
1328 Self::ModifiersChanged(_) => "modifiers changed",
1329 Self::Toggle(_) => "toggle",
1330 Self::SettingsChange(_) => "settings change",
1331 Self::NewLinesShown => "new lines shown",
1332 Self::BufferEdited(_) => "buffer edited",
1333 Self::RefreshRequested => "refresh requested",
1334 Self::ExcerptsRemoved(_) => "excerpts removed",
1335 }
1336 }
1337}
1338
1339pub enum FormatTarget {
1340 Buffers,
1341 Ranges(Vec<Range<MultiBufferPoint>>),
1342}
1343
1344pub(crate) struct FocusedBlock {
1345 id: BlockId,
1346 focus_handle: WeakFocusHandle,
1347}
1348
1349#[derive(Clone)]
1350enum JumpData {
1351 MultiBufferRow {
1352 row: MultiBufferRow,
1353 line_offset_from_top: u32,
1354 },
1355 MultiBufferPoint {
1356 excerpt_id: ExcerptId,
1357 position: Point,
1358 anchor: text::Anchor,
1359 line_offset_from_top: u32,
1360 },
1361}
1362
1363pub enum MultibufferSelectionMode {
1364 First,
1365 All,
1366}
1367
1368#[derive(Clone, Copy, Debug, Default)]
1369pub struct RewrapOptions {
1370 pub override_language_settings: bool,
1371 pub preserve_existing_whitespace: bool,
1372}
1373
1374impl Editor {
1375 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1376 let buffer = cx.new(|cx| Buffer::local("", cx));
1377 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1378 Self::new(
1379 EditorMode::SingleLine { auto_width: false },
1380 buffer,
1381 None,
1382 window,
1383 cx,
1384 )
1385 }
1386
1387 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1388 let buffer = cx.new(|cx| Buffer::local("", cx));
1389 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1390 Self::new(EditorMode::full(), buffer, None, window, cx)
1391 }
1392
1393 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1394 let buffer = cx.new(|cx| Buffer::local("", cx));
1395 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1396 Self::new(
1397 EditorMode::SingleLine { auto_width: true },
1398 buffer,
1399 None,
1400 window,
1401 cx,
1402 )
1403 }
1404
1405 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1406 let buffer = cx.new(|cx| Buffer::local("", cx));
1407 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1408 Self::new(
1409 EditorMode::AutoHeight { max_lines },
1410 buffer,
1411 None,
1412 window,
1413 cx,
1414 )
1415 }
1416
1417 pub fn for_buffer(
1418 buffer: Entity<Buffer>,
1419 project: Option<Entity<Project>>,
1420 window: &mut Window,
1421 cx: &mut Context<Self>,
1422 ) -> Self {
1423 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1424 Self::new(EditorMode::full(), buffer, project, window, cx)
1425 }
1426
1427 pub fn for_multibuffer(
1428 buffer: Entity<MultiBuffer>,
1429 project: Option<Entity<Project>>,
1430 window: &mut Window,
1431 cx: &mut Context<Self>,
1432 ) -> Self {
1433 Self::new(EditorMode::full(), buffer, project, window, cx)
1434 }
1435
1436 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1437 let mut clone = Self::new(
1438 self.mode,
1439 self.buffer.clone(),
1440 self.project.clone(),
1441 window,
1442 cx,
1443 );
1444 self.display_map.update(cx, |display_map, cx| {
1445 let snapshot = display_map.snapshot(cx);
1446 clone.display_map.update(cx, |display_map, cx| {
1447 display_map.set_state(&snapshot, cx);
1448 });
1449 });
1450 clone.folds_did_change(cx);
1451 clone.selections.clone_state(&self.selections);
1452 clone.scroll_manager.clone_state(&self.scroll_manager);
1453 clone.searchable = self.searchable;
1454 clone.read_only = self.read_only;
1455 clone
1456 }
1457
1458 pub fn new(
1459 mode: EditorMode,
1460 buffer: Entity<MultiBuffer>,
1461 project: Option<Entity<Project>>,
1462 window: &mut Window,
1463 cx: &mut Context<Self>,
1464 ) -> Self {
1465 let style = window.text_style();
1466 let font_size = style.font_size.to_pixels(window.rem_size());
1467 let editor = cx.entity().downgrade();
1468 let fold_placeholder = FoldPlaceholder {
1469 constrain_width: true,
1470 render: Arc::new(move |fold_id, fold_range, cx| {
1471 let editor = editor.clone();
1472 div()
1473 .id(fold_id)
1474 .bg(cx.theme().colors().ghost_element_background)
1475 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1476 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1477 .rounded_xs()
1478 .size_full()
1479 .cursor_pointer()
1480 .child("⋯")
1481 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1482 .on_click(move |_, _window, cx| {
1483 editor
1484 .update(cx, |editor, cx| {
1485 editor.unfold_ranges(
1486 &[fold_range.start..fold_range.end],
1487 true,
1488 false,
1489 cx,
1490 );
1491 cx.stop_propagation();
1492 })
1493 .ok();
1494 })
1495 .into_any()
1496 }),
1497 merge_adjacent: true,
1498 ..Default::default()
1499 };
1500 let display_map = cx.new(|cx| {
1501 DisplayMap::new(
1502 buffer.clone(),
1503 style.font(),
1504 font_size,
1505 None,
1506 FILE_HEADER_HEIGHT,
1507 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1508 fold_placeholder,
1509 cx,
1510 )
1511 });
1512
1513 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1514
1515 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1516
1517 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1518 .then(|| language_settings::SoftWrap::None);
1519
1520 let mut project_subscriptions = Vec::new();
1521 if mode.is_full() {
1522 if let Some(project) = project.as_ref() {
1523 project_subscriptions.push(cx.subscribe_in(
1524 project,
1525 window,
1526 |editor, _, event, window, cx| match event {
1527 project::Event::RefreshCodeLens => {
1528 // we always query lens with actions, without storing them, always refreshing them
1529 }
1530 project::Event::RefreshInlayHints => {
1531 editor
1532 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1533 }
1534 project::Event::SnippetEdit(id, snippet_edits) => {
1535 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1536 let focus_handle = editor.focus_handle(cx);
1537 if focus_handle.is_focused(window) {
1538 let snapshot = buffer.read(cx).snapshot();
1539 for (range, snippet) in snippet_edits {
1540 let editor_range =
1541 language::range_from_lsp(*range).to_offset(&snapshot);
1542 editor
1543 .insert_snippet(
1544 &[editor_range],
1545 snippet.clone(),
1546 window,
1547 cx,
1548 )
1549 .ok();
1550 }
1551 }
1552 }
1553 }
1554 _ => {}
1555 },
1556 ));
1557 if let Some(task_inventory) = project
1558 .read(cx)
1559 .task_store()
1560 .read(cx)
1561 .task_inventory()
1562 .cloned()
1563 {
1564 project_subscriptions.push(cx.observe_in(
1565 &task_inventory,
1566 window,
1567 |editor, _, window, cx| {
1568 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1569 },
1570 ));
1571 };
1572
1573 project_subscriptions.push(cx.subscribe_in(
1574 &project.read(cx).breakpoint_store(),
1575 window,
1576 |editor, _, event, window, cx| match event {
1577 BreakpointStoreEvent::ActiveDebugLineChanged => {
1578 if editor.go_to_active_debug_line(window, cx) {
1579 cx.stop_propagation();
1580 }
1581
1582 editor.refresh_inline_values(cx);
1583 }
1584 _ => {}
1585 },
1586 ));
1587 }
1588 }
1589
1590 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1591
1592 let inlay_hint_settings =
1593 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1594 let focus_handle = cx.focus_handle();
1595 cx.on_focus(&focus_handle, window, Self::handle_focus)
1596 .detach();
1597 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1598 .detach();
1599 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1600 .detach();
1601 cx.on_blur(&focus_handle, window, Self::handle_blur)
1602 .detach();
1603
1604 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1605 Some(false)
1606 } else {
1607 None
1608 };
1609
1610 let breakpoint_store = match (mode, project.as_ref()) {
1611 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1612 _ => None,
1613 };
1614
1615 let mut code_action_providers = Vec::new();
1616 let mut load_uncommitted_diff = None;
1617 if let Some(project) = project.clone() {
1618 load_uncommitted_diff = Some(
1619 get_uncommitted_diff_for_buffer(
1620 &project,
1621 buffer.read(cx).all_buffers(),
1622 buffer.clone(),
1623 cx,
1624 )
1625 .shared(),
1626 );
1627 code_action_providers.push(Rc::new(project) as Rc<_>);
1628 }
1629
1630 let mut this = Self {
1631 focus_handle,
1632 show_cursor_when_unfocused: false,
1633 last_focused_descendant: None,
1634 buffer: buffer.clone(),
1635 display_map: display_map.clone(),
1636 selections,
1637 scroll_manager: ScrollManager::new(cx),
1638 columnar_selection_tail: None,
1639 add_selections_state: None,
1640 select_next_state: None,
1641 select_prev_state: None,
1642 selection_history: Default::default(),
1643 autoclose_regions: Default::default(),
1644 snippet_stack: Default::default(),
1645 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1646 ime_transaction: Default::default(),
1647 active_diagnostics: ActiveDiagnostic::None,
1648 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1649 inline_diagnostics_update: Task::ready(()),
1650 inline_diagnostics: Vec::new(),
1651 soft_wrap_mode_override,
1652 hard_wrap: None,
1653 completion_provider: project.clone().map(|project| Box::new(project) as _),
1654 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1655 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1656 project,
1657 blink_manager: blink_manager.clone(),
1658 show_local_selections: true,
1659 show_scrollbars: true,
1660 disable_scrolling: false,
1661 mode,
1662 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1663 show_gutter: mode.is_full(),
1664 show_line_numbers: None,
1665 use_relative_line_numbers: None,
1666 disable_expand_excerpt_buttons: false,
1667 show_git_diff_gutter: None,
1668 show_code_actions: None,
1669 show_runnables: None,
1670 show_breakpoints: None,
1671 show_wrap_guides: None,
1672 show_indent_guides,
1673 placeholder_text: None,
1674 highlight_order: 0,
1675 highlighted_rows: HashMap::default(),
1676 background_highlights: Default::default(),
1677 gutter_highlights: TreeMap::default(),
1678 scrollbar_marker_state: ScrollbarMarkerState::default(),
1679 active_indent_guides_state: ActiveIndentGuidesState::default(),
1680 nav_history: None,
1681 context_menu: RefCell::new(None),
1682 context_menu_options: None,
1683 mouse_context_menu: None,
1684 completion_tasks: Default::default(),
1685 inline_blame_popover: Default::default(),
1686 signature_help_state: SignatureHelpState::default(),
1687 auto_signature_help: None,
1688 find_all_references_task_sources: Vec::new(),
1689 next_completion_id: 0,
1690 next_inlay_id: 0,
1691 code_action_providers,
1692 available_code_actions: Default::default(),
1693 code_actions_task: Default::default(),
1694 quick_selection_highlight_task: Default::default(),
1695 debounced_selection_highlight_task: Default::default(),
1696 document_highlights_task: Default::default(),
1697 linked_editing_range_task: Default::default(),
1698 pending_rename: Default::default(),
1699 searchable: true,
1700 cursor_shape: EditorSettings::get_global(cx)
1701 .cursor_shape
1702 .unwrap_or_default(),
1703 current_line_highlight: None,
1704 autoindent_mode: Some(AutoindentMode::EachLine),
1705 collapse_matches: false,
1706 workspace: None,
1707 input_enabled: true,
1708 use_modal_editing: mode.is_full(),
1709 read_only: false,
1710 use_autoclose: true,
1711 use_auto_surround: true,
1712 auto_replace_emoji_shortcode: false,
1713 jsx_tag_auto_close_enabled_in_any_buffer: false,
1714 leader_peer_id: None,
1715 remote_id: None,
1716 hover_state: Default::default(),
1717 pending_mouse_down: None,
1718 hovered_link_state: Default::default(),
1719 edit_prediction_provider: None,
1720 active_inline_completion: None,
1721 stale_inline_completion_in_menu: None,
1722 edit_prediction_preview: EditPredictionPreview::Inactive {
1723 released_too_fast: false,
1724 },
1725 inline_diagnostics_enabled: mode.is_full(),
1726 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1727 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1728
1729 gutter_hovered: false,
1730 pixel_position_of_newest_cursor: None,
1731 last_bounds: None,
1732 last_position_map: None,
1733 expect_bounds_change: None,
1734 gutter_dimensions: GutterDimensions::default(),
1735 style: None,
1736 show_cursor_names: false,
1737 hovered_cursors: Default::default(),
1738 next_editor_action_id: EditorActionId::default(),
1739 editor_actions: Rc::default(),
1740 inline_completions_hidden_for_vim_mode: false,
1741 show_inline_completions_override: None,
1742 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1743 edit_prediction_settings: EditPredictionSettings::Disabled,
1744 edit_prediction_indent_conflict: false,
1745 edit_prediction_requires_modifier_in_indent_conflict: true,
1746 custom_context_menu: None,
1747 show_git_blame_gutter: false,
1748 show_git_blame_inline: false,
1749 show_selection_menu: None,
1750 show_git_blame_inline_delay_task: None,
1751 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1752 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1753 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1754 .session
1755 .restore_unsaved_buffers,
1756 blame: None,
1757 blame_subscription: None,
1758 tasks: Default::default(),
1759
1760 breakpoint_store,
1761 gutter_breakpoint_indicator: (None, None),
1762 _subscriptions: vec![
1763 cx.observe(&buffer, Self::on_buffer_changed),
1764 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1765 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1766 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1767 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1768 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1769 cx.observe_window_activation(window, |editor, window, cx| {
1770 let active = window.is_window_active();
1771 editor.blink_manager.update(cx, |blink_manager, cx| {
1772 if active {
1773 blink_manager.enable(cx);
1774 } else {
1775 blink_manager.disable(cx);
1776 }
1777 });
1778 }),
1779 ],
1780 tasks_update_task: None,
1781 linked_edit_ranges: Default::default(),
1782 in_project_search: false,
1783 previous_search_ranges: None,
1784 breadcrumb_header: None,
1785 focused_block: None,
1786 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1787 addons: HashMap::default(),
1788 registered_buffers: HashMap::default(),
1789 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1790 selection_mark_mode: false,
1791 toggle_fold_multiple_buffers: Task::ready(()),
1792 serialize_selections: Task::ready(()),
1793 serialize_folds: Task::ready(()),
1794 text_style_refinement: None,
1795 load_diff_task: load_uncommitted_diff,
1796 mouse_cursor_hidden: false,
1797 hide_mouse_mode: EditorSettings::get_global(cx)
1798 .hide_mouse
1799 .unwrap_or_default(),
1800 change_list: ChangeList::new(),
1801 };
1802 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1803 this._subscriptions
1804 .push(cx.observe(breakpoints, |_, _, cx| {
1805 cx.notify();
1806 }));
1807 }
1808 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1809 this._subscriptions.extend(project_subscriptions);
1810
1811 this._subscriptions.push(cx.subscribe_in(
1812 &cx.entity(),
1813 window,
1814 |editor, _, e: &EditorEvent, window, cx| match e {
1815 EditorEvent::ScrollPositionChanged { local, .. } => {
1816 if *local {
1817 let new_anchor = editor.scroll_manager.anchor();
1818 let snapshot = editor.snapshot(window, cx);
1819 editor.update_restoration_data(cx, move |data| {
1820 data.scroll_position = (
1821 new_anchor.top_row(&snapshot.buffer_snapshot),
1822 new_anchor.offset,
1823 );
1824 });
1825 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1826 editor.inline_blame_popover.take();
1827 }
1828 }
1829 EditorEvent::Edited { .. } => {
1830 if !vim_enabled(cx) {
1831 let (map, selections) = editor.selections.all_adjusted_display(cx);
1832 let pop_state = editor
1833 .change_list
1834 .last()
1835 .map(|previous| {
1836 previous.len() == selections.len()
1837 && previous.iter().enumerate().all(|(ix, p)| {
1838 p.to_display_point(&map).row()
1839 == selections[ix].head().row()
1840 })
1841 })
1842 .unwrap_or(false);
1843 let new_positions = selections
1844 .into_iter()
1845 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1846 .collect();
1847 editor
1848 .change_list
1849 .push_to_change_list(pop_state, new_positions);
1850 }
1851 }
1852 _ => (),
1853 },
1854 ));
1855
1856 if let Some(dap_store) = this
1857 .project
1858 .as_ref()
1859 .map(|project| project.read(cx).dap_store())
1860 {
1861 let weak_editor = cx.weak_entity();
1862
1863 this._subscriptions
1864 .push(
1865 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1866 let session_entity = cx.entity();
1867 weak_editor
1868 .update(cx, |editor, cx| {
1869 editor._subscriptions.push(
1870 cx.subscribe(&session_entity, Self::on_debug_session_event),
1871 );
1872 })
1873 .ok();
1874 }),
1875 );
1876
1877 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1878 this._subscriptions
1879 .push(cx.subscribe(&session, Self::on_debug_session_event));
1880 }
1881 }
1882
1883 this.end_selection(window, cx);
1884 this.scroll_manager.show_scrollbars(window, cx);
1885 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1886
1887 if mode.is_full() {
1888 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1889 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1890
1891 if this.git_blame_inline_enabled {
1892 this.git_blame_inline_enabled = true;
1893 this.start_git_blame_inline(false, window, cx);
1894 }
1895
1896 this.go_to_active_debug_line(window, cx);
1897
1898 if let Some(buffer) = buffer.read(cx).as_singleton() {
1899 if let Some(project) = this.project.as_ref() {
1900 let handle = project.update(cx, |project, cx| {
1901 project.register_buffer_with_language_servers(&buffer, cx)
1902 });
1903 this.registered_buffers
1904 .insert(buffer.read(cx).remote_id(), handle);
1905 }
1906 }
1907 }
1908
1909 this.report_editor_event("Editor Opened", None, cx);
1910 this
1911 }
1912
1913 pub fn deploy_mouse_context_menu(
1914 &mut self,
1915 position: gpui::Point<Pixels>,
1916 context_menu: Entity<ContextMenu>,
1917 window: &mut Window,
1918 cx: &mut Context<Self>,
1919 ) {
1920 self.mouse_context_menu = Some(MouseContextMenu::new(
1921 self,
1922 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1923 context_menu,
1924 window,
1925 cx,
1926 ));
1927 }
1928
1929 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1930 self.mouse_context_menu
1931 .as_ref()
1932 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1933 }
1934
1935 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1936 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1937 }
1938
1939 fn key_context_internal(
1940 &self,
1941 has_active_edit_prediction: bool,
1942 window: &Window,
1943 cx: &App,
1944 ) -> KeyContext {
1945 let mut key_context = KeyContext::new_with_defaults();
1946 key_context.add("Editor");
1947 let mode = match self.mode {
1948 EditorMode::SingleLine { .. } => "single_line",
1949 EditorMode::AutoHeight { .. } => "auto_height",
1950 EditorMode::Full { .. } => "full",
1951 };
1952
1953 if EditorSettings::jupyter_enabled(cx) {
1954 key_context.add("jupyter");
1955 }
1956
1957 key_context.set("mode", mode);
1958 if self.pending_rename.is_some() {
1959 key_context.add("renaming");
1960 }
1961
1962 match self.context_menu.borrow().as_ref() {
1963 Some(CodeContextMenu::Completions(_)) => {
1964 key_context.add("menu");
1965 key_context.add("showing_completions");
1966 }
1967 Some(CodeContextMenu::CodeActions(_)) => {
1968 key_context.add("menu");
1969 key_context.add("showing_code_actions")
1970 }
1971 None => {}
1972 }
1973
1974 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1975 if !self.focus_handle(cx).contains_focused(window, cx)
1976 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1977 {
1978 for addon in self.addons.values() {
1979 addon.extend_key_context(&mut key_context, cx)
1980 }
1981 }
1982
1983 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1984 if let Some(extension) = singleton_buffer
1985 .read(cx)
1986 .file()
1987 .and_then(|file| file.path().extension()?.to_str())
1988 {
1989 key_context.set("extension", extension.to_string());
1990 }
1991 } else {
1992 key_context.add("multibuffer");
1993 }
1994
1995 if has_active_edit_prediction {
1996 if self.edit_prediction_in_conflict() {
1997 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1998 } else {
1999 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2000 key_context.add("copilot_suggestion");
2001 }
2002 }
2003
2004 if self.selection_mark_mode {
2005 key_context.add("selection_mode");
2006 }
2007
2008 key_context
2009 }
2010
2011 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2012 self.mouse_cursor_hidden = match origin {
2013 HideMouseCursorOrigin::TypingAction => {
2014 matches!(
2015 self.hide_mouse_mode,
2016 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2017 )
2018 }
2019 HideMouseCursorOrigin::MovementAction => {
2020 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2021 }
2022 };
2023 }
2024
2025 pub fn edit_prediction_in_conflict(&self) -> bool {
2026 if !self.show_edit_predictions_in_menu() {
2027 return false;
2028 }
2029
2030 let showing_completions = self
2031 .context_menu
2032 .borrow()
2033 .as_ref()
2034 .map_or(false, |context| {
2035 matches!(context, CodeContextMenu::Completions(_))
2036 });
2037
2038 showing_completions
2039 || self.edit_prediction_requires_modifier()
2040 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2041 // bindings to insert tab characters.
2042 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2043 }
2044
2045 pub fn accept_edit_prediction_keybind(
2046 &self,
2047 window: &Window,
2048 cx: &App,
2049 ) -> AcceptEditPredictionBinding {
2050 let key_context = self.key_context_internal(true, window, cx);
2051 let in_conflict = self.edit_prediction_in_conflict();
2052
2053 AcceptEditPredictionBinding(
2054 window
2055 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2056 .into_iter()
2057 .filter(|binding| {
2058 !in_conflict
2059 || binding
2060 .keystrokes()
2061 .first()
2062 .map_or(false, |keystroke| keystroke.modifiers.modified())
2063 })
2064 .rev()
2065 .min_by_key(|binding| {
2066 binding
2067 .keystrokes()
2068 .first()
2069 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2070 }),
2071 )
2072 }
2073
2074 pub fn new_file(
2075 workspace: &mut Workspace,
2076 _: &workspace::NewFile,
2077 window: &mut Window,
2078 cx: &mut Context<Workspace>,
2079 ) {
2080 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2081 "Failed to create buffer",
2082 window,
2083 cx,
2084 |e, _, _| match e.error_code() {
2085 ErrorCode::RemoteUpgradeRequired => Some(format!(
2086 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2087 e.error_tag("required").unwrap_or("the latest version")
2088 )),
2089 _ => None,
2090 },
2091 );
2092 }
2093
2094 pub fn new_in_workspace(
2095 workspace: &mut Workspace,
2096 window: &mut Window,
2097 cx: &mut Context<Workspace>,
2098 ) -> Task<Result<Entity<Editor>>> {
2099 let project = workspace.project().clone();
2100 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2101
2102 cx.spawn_in(window, async move |workspace, cx| {
2103 let buffer = create.await?;
2104 workspace.update_in(cx, |workspace, window, cx| {
2105 let editor =
2106 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2107 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2108 editor
2109 })
2110 })
2111 }
2112
2113 fn new_file_vertical(
2114 workspace: &mut Workspace,
2115 _: &workspace::NewFileSplitVertical,
2116 window: &mut Window,
2117 cx: &mut Context<Workspace>,
2118 ) {
2119 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2120 }
2121
2122 fn new_file_horizontal(
2123 workspace: &mut Workspace,
2124 _: &workspace::NewFileSplitHorizontal,
2125 window: &mut Window,
2126 cx: &mut Context<Workspace>,
2127 ) {
2128 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2129 }
2130
2131 fn new_file_in_direction(
2132 workspace: &mut Workspace,
2133 direction: SplitDirection,
2134 window: &mut Window,
2135 cx: &mut Context<Workspace>,
2136 ) {
2137 let project = workspace.project().clone();
2138 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2139
2140 cx.spawn_in(window, async move |workspace, cx| {
2141 let buffer = create.await?;
2142 workspace.update_in(cx, move |workspace, window, cx| {
2143 workspace.split_item(
2144 direction,
2145 Box::new(
2146 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2147 ),
2148 window,
2149 cx,
2150 )
2151 })?;
2152 anyhow::Ok(())
2153 })
2154 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2155 match e.error_code() {
2156 ErrorCode::RemoteUpgradeRequired => Some(format!(
2157 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2158 e.error_tag("required").unwrap_or("the latest version")
2159 )),
2160 _ => None,
2161 }
2162 });
2163 }
2164
2165 pub fn leader_peer_id(&self) -> Option<PeerId> {
2166 self.leader_peer_id
2167 }
2168
2169 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2170 &self.buffer
2171 }
2172
2173 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2174 self.workspace.as_ref()?.0.upgrade()
2175 }
2176
2177 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2178 self.buffer().read(cx).title(cx)
2179 }
2180
2181 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2182 let git_blame_gutter_max_author_length = self
2183 .render_git_blame_gutter(cx)
2184 .then(|| {
2185 if let Some(blame) = self.blame.as_ref() {
2186 let max_author_length =
2187 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2188 Some(max_author_length)
2189 } else {
2190 None
2191 }
2192 })
2193 .flatten();
2194
2195 EditorSnapshot {
2196 mode: self.mode,
2197 show_gutter: self.show_gutter,
2198 show_line_numbers: self.show_line_numbers,
2199 show_git_diff_gutter: self.show_git_diff_gutter,
2200 show_code_actions: self.show_code_actions,
2201 show_runnables: self.show_runnables,
2202 show_breakpoints: self.show_breakpoints,
2203 git_blame_gutter_max_author_length,
2204 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2205 scroll_anchor: self.scroll_manager.anchor(),
2206 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2207 placeholder_text: self.placeholder_text.clone(),
2208 is_focused: self.focus_handle.is_focused(window),
2209 current_line_highlight: self
2210 .current_line_highlight
2211 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2212 gutter_hovered: self.gutter_hovered,
2213 }
2214 }
2215
2216 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2217 self.buffer.read(cx).language_at(point, cx)
2218 }
2219
2220 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2221 self.buffer.read(cx).read(cx).file_at(point).cloned()
2222 }
2223
2224 pub fn active_excerpt(
2225 &self,
2226 cx: &App,
2227 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2228 self.buffer
2229 .read(cx)
2230 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2231 }
2232
2233 pub fn mode(&self) -> EditorMode {
2234 self.mode
2235 }
2236
2237 pub fn set_mode(&mut self, mode: EditorMode) {
2238 self.mode = mode;
2239 }
2240
2241 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2242 self.collaboration_hub.as_deref()
2243 }
2244
2245 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2246 self.collaboration_hub = Some(hub);
2247 }
2248
2249 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2250 self.in_project_search = in_project_search;
2251 }
2252
2253 pub fn set_custom_context_menu(
2254 &mut self,
2255 f: impl 'static
2256 + Fn(
2257 &mut Self,
2258 DisplayPoint,
2259 &mut Window,
2260 &mut Context<Self>,
2261 ) -> Option<Entity<ui::ContextMenu>>,
2262 ) {
2263 self.custom_context_menu = Some(Box::new(f))
2264 }
2265
2266 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2267 self.completion_provider = provider;
2268 }
2269
2270 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2271 self.semantics_provider.clone()
2272 }
2273
2274 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2275 self.semantics_provider = provider;
2276 }
2277
2278 pub fn set_edit_prediction_provider<T>(
2279 &mut self,
2280 provider: Option<Entity<T>>,
2281 window: &mut Window,
2282 cx: &mut Context<Self>,
2283 ) where
2284 T: EditPredictionProvider,
2285 {
2286 self.edit_prediction_provider =
2287 provider.map(|provider| RegisteredInlineCompletionProvider {
2288 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2289 if this.focus_handle.is_focused(window) {
2290 this.update_visible_inline_completion(window, cx);
2291 }
2292 }),
2293 provider: Arc::new(provider),
2294 });
2295 self.update_edit_prediction_settings(cx);
2296 self.refresh_inline_completion(false, false, window, cx);
2297 }
2298
2299 pub fn placeholder_text(&self) -> Option<&str> {
2300 self.placeholder_text.as_deref()
2301 }
2302
2303 pub fn set_placeholder_text(
2304 &mut self,
2305 placeholder_text: impl Into<Arc<str>>,
2306 cx: &mut Context<Self>,
2307 ) {
2308 let placeholder_text = Some(placeholder_text.into());
2309 if self.placeholder_text != placeholder_text {
2310 self.placeholder_text = placeholder_text;
2311 cx.notify();
2312 }
2313 }
2314
2315 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2316 self.cursor_shape = cursor_shape;
2317
2318 // Disrupt blink for immediate user feedback that the cursor shape has changed
2319 self.blink_manager.update(cx, BlinkManager::show_cursor);
2320
2321 cx.notify();
2322 }
2323
2324 pub fn set_current_line_highlight(
2325 &mut self,
2326 current_line_highlight: Option<CurrentLineHighlight>,
2327 ) {
2328 self.current_line_highlight = current_line_highlight;
2329 }
2330
2331 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2332 self.collapse_matches = collapse_matches;
2333 }
2334
2335 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2336 let buffers = self.buffer.read(cx).all_buffers();
2337 let Some(project) = self.project.as_ref() else {
2338 return;
2339 };
2340 project.update(cx, |project, cx| {
2341 for buffer in buffers {
2342 self.registered_buffers
2343 .entry(buffer.read(cx).remote_id())
2344 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2345 }
2346 })
2347 }
2348
2349 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2350 if self.collapse_matches {
2351 return range.start..range.start;
2352 }
2353 range.clone()
2354 }
2355
2356 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2357 if self.display_map.read(cx).clip_at_line_ends != clip {
2358 self.display_map
2359 .update(cx, |map, _| map.clip_at_line_ends = clip);
2360 }
2361 }
2362
2363 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2364 self.input_enabled = input_enabled;
2365 }
2366
2367 pub fn set_inline_completions_hidden_for_vim_mode(
2368 &mut self,
2369 hidden: bool,
2370 window: &mut Window,
2371 cx: &mut Context<Self>,
2372 ) {
2373 if hidden != self.inline_completions_hidden_for_vim_mode {
2374 self.inline_completions_hidden_for_vim_mode = hidden;
2375 if hidden {
2376 self.update_visible_inline_completion(window, cx);
2377 } else {
2378 self.refresh_inline_completion(true, false, window, cx);
2379 }
2380 }
2381 }
2382
2383 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2384 self.menu_inline_completions_policy = value;
2385 }
2386
2387 pub fn set_autoindent(&mut self, autoindent: bool) {
2388 if autoindent {
2389 self.autoindent_mode = Some(AutoindentMode::EachLine);
2390 } else {
2391 self.autoindent_mode = None;
2392 }
2393 }
2394
2395 pub fn read_only(&self, cx: &App) -> bool {
2396 self.read_only || self.buffer.read(cx).read_only()
2397 }
2398
2399 pub fn set_read_only(&mut self, read_only: bool) {
2400 self.read_only = read_only;
2401 }
2402
2403 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2404 self.use_autoclose = autoclose;
2405 }
2406
2407 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2408 self.use_auto_surround = auto_surround;
2409 }
2410
2411 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2412 self.auto_replace_emoji_shortcode = auto_replace;
2413 }
2414
2415 pub fn toggle_edit_predictions(
2416 &mut self,
2417 _: &ToggleEditPrediction,
2418 window: &mut Window,
2419 cx: &mut Context<Self>,
2420 ) {
2421 if self.show_inline_completions_override.is_some() {
2422 self.set_show_edit_predictions(None, window, cx);
2423 } else {
2424 let show_edit_predictions = !self.edit_predictions_enabled();
2425 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2426 }
2427 }
2428
2429 pub fn set_show_edit_predictions(
2430 &mut self,
2431 show_edit_predictions: Option<bool>,
2432 window: &mut Window,
2433 cx: &mut Context<Self>,
2434 ) {
2435 self.show_inline_completions_override = show_edit_predictions;
2436 self.update_edit_prediction_settings(cx);
2437
2438 if let Some(false) = show_edit_predictions {
2439 self.discard_inline_completion(false, cx);
2440 } else {
2441 self.refresh_inline_completion(false, true, window, cx);
2442 }
2443 }
2444
2445 fn inline_completions_disabled_in_scope(
2446 &self,
2447 buffer: &Entity<Buffer>,
2448 buffer_position: language::Anchor,
2449 cx: &App,
2450 ) -> bool {
2451 let snapshot = buffer.read(cx).snapshot();
2452 let settings = snapshot.settings_at(buffer_position, cx);
2453
2454 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2455 return false;
2456 };
2457
2458 scope.override_name().map_or(false, |scope_name| {
2459 settings
2460 .edit_predictions_disabled_in
2461 .iter()
2462 .any(|s| s == scope_name)
2463 })
2464 }
2465
2466 pub fn set_use_modal_editing(&mut self, to: bool) {
2467 self.use_modal_editing = to;
2468 }
2469
2470 pub fn use_modal_editing(&self) -> bool {
2471 self.use_modal_editing
2472 }
2473
2474 fn selections_did_change(
2475 &mut self,
2476 local: bool,
2477 old_cursor_position: &Anchor,
2478 show_completions: bool,
2479 window: &mut Window,
2480 cx: &mut Context<Self>,
2481 ) {
2482 window.invalidate_character_coordinates();
2483
2484 // Copy selections to primary selection buffer
2485 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2486 if local {
2487 let selections = self.selections.all::<usize>(cx);
2488 let buffer_handle = self.buffer.read(cx).read(cx);
2489
2490 let mut text = String::new();
2491 for (index, selection) in selections.iter().enumerate() {
2492 let text_for_selection = buffer_handle
2493 .text_for_range(selection.start..selection.end)
2494 .collect::<String>();
2495
2496 text.push_str(&text_for_selection);
2497 if index != selections.len() - 1 {
2498 text.push('\n');
2499 }
2500 }
2501
2502 if !text.is_empty() {
2503 cx.write_to_primary(ClipboardItem::new_string(text));
2504 }
2505 }
2506
2507 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2508 self.buffer.update(cx, |buffer, cx| {
2509 buffer.set_active_selections(
2510 &self.selections.disjoint_anchors(),
2511 self.selections.line_mode,
2512 self.cursor_shape,
2513 cx,
2514 )
2515 });
2516 }
2517 let display_map = self
2518 .display_map
2519 .update(cx, |display_map, cx| display_map.snapshot(cx));
2520 let buffer = &display_map.buffer_snapshot;
2521 self.add_selections_state = None;
2522 self.select_next_state = None;
2523 self.select_prev_state = None;
2524 self.select_syntax_node_history.try_clear();
2525 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2526 self.snippet_stack
2527 .invalidate(&self.selections.disjoint_anchors(), buffer);
2528 self.take_rename(false, window, cx);
2529
2530 let new_cursor_position = self.selections.newest_anchor().head();
2531
2532 self.push_to_nav_history(
2533 *old_cursor_position,
2534 Some(new_cursor_position.to_point(buffer)),
2535 false,
2536 cx,
2537 );
2538
2539 if local {
2540 let new_cursor_position = self.selections.newest_anchor().head();
2541 let mut context_menu = self.context_menu.borrow_mut();
2542 let completion_menu = match context_menu.as_ref() {
2543 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2544 _ => {
2545 *context_menu = None;
2546 None
2547 }
2548 };
2549 if let Some(buffer_id) = new_cursor_position.buffer_id {
2550 if !self.registered_buffers.contains_key(&buffer_id) {
2551 if let Some(project) = self.project.as_ref() {
2552 project.update(cx, |project, cx| {
2553 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2554 return;
2555 };
2556 self.registered_buffers.insert(
2557 buffer_id,
2558 project.register_buffer_with_language_servers(&buffer, cx),
2559 );
2560 })
2561 }
2562 }
2563 }
2564
2565 if let Some(completion_menu) = completion_menu {
2566 let cursor_position = new_cursor_position.to_offset(buffer);
2567 let (word_range, kind) =
2568 buffer.surrounding_word(completion_menu.initial_position, true);
2569 if kind == Some(CharKind::Word)
2570 && word_range.to_inclusive().contains(&cursor_position)
2571 {
2572 let mut completion_menu = completion_menu.clone();
2573 drop(context_menu);
2574
2575 let query = Self::completion_query(buffer, cursor_position);
2576 cx.spawn(async move |this, cx| {
2577 completion_menu
2578 .filter(query.as_deref(), cx.background_executor().clone())
2579 .await;
2580
2581 this.update(cx, |this, cx| {
2582 let mut context_menu = this.context_menu.borrow_mut();
2583 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2584 else {
2585 return;
2586 };
2587
2588 if menu.id > completion_menu.id {
2589 return;
2590 }
2591
2592 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2593 drop(context_menu);
2594 cx.notify();
2595 })
2596 })
2597 .detach();
2598
2599 if show_completions {
2600 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2601 }
2602 } else {
2603 drop(context_menu);
2604 self.hide_context_menu(window, cx);
2605 }
2606 } else {
2607 drop(context_menu);
2608 }
2609
2610 hide_hover(self, cx);
2611
2612 if old_cursor_position.to_display_point(&display_map).row()
2613 != new_cursor_position.to_display_point(&display_map).row()
2614 {
2615 self.available_code_actions.take();
2616 }
2617 self.refresh_code_actions(window, cx);
2618 self.refresh_document_highlights(cx);
2619 self.refresh_selected_text_highlights(window, cx);
2620 refresh_matching_bracket_highlights(self, window, cx);
2621 self.update_visible_inline_completion(window, cx);
2622 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2623 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2624 self.inline_blame_popover.take();
2625 if self.git_blame_inline_enabled {
2626 self.start_inline_blame_timer(window, cx);
2627 }
2628 }
2629
2630 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2631 cx.emit(EditorEvent::SelectionsChanged { local });
2632
2633 let selections = &self.selections.disjoint;
2634 if selections.len() == 1 {
2635 cx.emit(SearchEvent::ActiveMatchChanged)
2636 }
2637 if local {
2638 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2639 let inmemory_selections = selections
2640 .iter()
2641 .map(|s| {
2642 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2643 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2644 })
2645 .collect();
2646 self.update_restoration_data(cx, |data| {
2647 data.selections = inmemory_selections;
2648 });
2649
2650 if WorkspaceSettings::get(None, cx).restore_on_startup
2651 != RestoreOnStartupBehavior::None
2652 {
2653 if let Some(workspace_id) =
2654 self.workspace.as_ref().and_then(|workspace| workspace.1)
2655 {
2656 let snapshot = self.buffer().read(cx).snapshot(cx);
2657 let selections = selections.clone();
2658 let background_executor = cx.background_executor().clone();
2659 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2660 self.serialize_selections = cx.background_spawn(async move {
2661 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2662 let db_selections = selections
2663 .iter()
2664 .map(|selection| {
2665 (
2666 selection.start.to_offset(&snapshot),
2667 selection.end.to_offset(&snapshot),
2668 )
2669 })
2670 .collect();
2671
2672 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2673 .await
2674 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2675 .log_err();
2676 });
2677 }
2678 }
2679 }
2680 }
2681
2682 cx.notify();
2683 }
2684
2685 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2686 use text::ToOffset as _;
2687 use text::ToPoint as _;
2688
2689 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2690 return;
2691 }
2692
2693 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2694 return;
2695 };
2696
2697 let snapshot = singleton.read(cx).snapshot();
2698 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2699 let display_snapshot = display_map.snapshot(cx);
2700
2701 display_snapshot
2702 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2703 .map(|fold| {
2704 fold.range.start.text_anchor.to_point(&snapshot)
2705 ..fold.range.end.text_anchor.to_point(&snapshot)
2706 })
2707 .collect()
2708 });
2709 self.update_restoration_data(cx, |data| {
2710 data.folds = inmemory_folds;
2711 });
2712
2713 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2714 return;
2715 };
2716 let background_executor = cx.background_executor().clone();
2717 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2718 let db_folds = self.display_map.update(cx, |display_map, cx| {
2719 display_map
2720 .snapshot(cx)
2721 .folds_in_range(0..snapshot.len())
2722 .map(|fold| {
2723 (
2724 fold.range.start.text_anchor.to_offset(&snapshot),
2725 fold.range.end.text_anchor.to_offset(&snapshot),
2726 )
2727 })
2728 .collect()
2729 });
2730 self.serialize_folds = cx.background_spawn(async move {
2731 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2732 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2733 .await
2734 .with_context(|| {
2735 format!(
2736 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2737 )
2738 })
2739 .log_err();
2740 });
2741 }
2742
2743 pub fn sync_selections(
2744 &mut self,
2745 other: Entity<Editor>,
2746 cx: &mut Context<Self>,
2747 ) -> gpui::Subscription {
2748 let other_selections = other.read(cx).selections.disjoint.to_vec();
2749 self.selections.change_with(cx, |selections| {
2750 selections.select_anchors(other_selections);
2751 });
2752
2753 let other_subscription =
2754 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2755 EditorEvent::SelectionsChanged { local: true } => {
2756 let other_selections = other.read(cx).selections.disjoint.to_vec();
2757 if other_selections.is_empty() {
2758 return;
2759 }
2760 this.selections.change_with(cx, |selections| {
2761 selections.select_anchors(other_selections);
2762 });
2763 }
2764 _ => {}
2765 });
2766
2767 let this_subscription =
2768 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2769 EditorEvent::SelectionsChanged { local: true } => {
2770 let these_selections = this.selections.disjoint.to_vec();
2771 if these_selections.is_empty() {
2772 return;
2773 }
2774 other.update(cx, |other_editor, cx| {
2775 other_editor.selections.change_with(cx, |selections| {
2776 selections.select_anchors(these_selections);
2777 })
2778 });
2779 }
2780 _ => {}
2781 });
2782
2783 Subscription::join(other_subscription, this_subscription)
2784 }
2785
2786 pub fn change_selections<R>(
2787 &mut self,
2788 autoscroll: Option<Autoscroll>,
2789 window: &mut Window,
2790 cx: &mut Context<Self>,
2791 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2792 ) -> R {
2793 self.change_selections_inner(autoscroll, true, window, cx, change)
2794 }
2795
2796 fn change_selections_inner<R>(
2797 &mut self,
2798 autoscroll: Option<Autoscroll>,
2799 request_completions: bool,
2800 window: &mut Window,
2801 cx: &mut Context<Self>,
2802 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2803 ) -> R {
2804 let old_cursor_position = self.selections.newest_anchor().head();
2805 self.push_to_selection_history();
2806
2807 let (changed, result) = self.selections.change_with(cx, change);
2808
2809 if changed {
2810 if let Some(autoscroll) = autoscroll {
2811 self.request_autoscroll(autoscroll, cx);
2812 }
2813 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2814
2815 if self.should_open_signature_help_automatically(
2816 &old_cursor_position,
2817 self.signature_help_state.backspace_pressed(),
2818 cx,
2819 ) {
2820 self.show_signature_help(&ShowSignatureHelp, window, cx);
2821 }
2822 self.signature_help_state.set_backspace_pressed(false);
2823 }
2824
2825 result
2826 }
2827
2828 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2829 where
2830 I: IntoIterator<Item = (Range<S>, T)>,
2831 S: ToOffset,
2832 T: Into<Arc<str>>,
2833 {
2834 if self.read_only(cx) {
2835 return;
2836 }
2837
2838 self.buffer
2839 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2840 }
2841
2842 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, 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(edits, self.autoindent_mode.clone(), cx)
2854 });
2855 }
2856
2857 pub fn edit_with_block_indent<I, S, T>(
2858 &mut self,
2859 edits: I,
2860 original_indent_columns: Vec<Option<u32>>,
2861 cx: &mut Context<Self>,
2862 ) where
2863 I: IntoIterator<Item = (Range<S>, T)>,
2864 S: ToOffset,
2865 T: Into<Arc<str>>,
2866 {
2867 if self.read_only(cx) {
2868 return;
2869 }
2870
2871 self.buffer.update(cx, |buffer, cx| {
2872 buffer.edit(
2873 edits,
2874 Some(AutoindentMode::Block {
2875 original_indent_columns,
2876 }),
2877 cx,
2878 )
2879 });
2880 }
2881
2882 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2883 self.hide_context_menu(window, cx);
2884
2885 match phase {
2886 SelectPhase::Begin {
2887 position,
2888 add,
2889 click_count,
2890 } => self.begin_selection(position, add, click_count, window, cx),
2891 SelectPhase::BeginColumnar {
2892 position,
2893 goal_column,
2894 reset,
2895 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2896 SelectPhase::Extend {
2897 position,
2898 click_count,
2899 } => self.extend_selection(position, click_count, window, cx),
2900 SelectPhase::Update {
2901 position,
2902 goal_column,
2903 scroll_delta,
2904 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2905 SelectPhase::End => self.end_selection(window, cx),
2906 }
2907 }
2908
2909 fn extend_selection(
2910 &mut self,
2911 position: DisplayPoint,
2912 click_count: usize,
2913 window: &mut Window,
2914 cx: &mut Context<Self>,
2915 ) {
2916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2917 let tail = self.selections.newest::<usize>(cx).tail();
2918 self.begin_selection(position, false, click_count, window, cx);
2919
2920 let position = position.to_offset(&display_map, Bias::Left);
2921 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2922
2923 let mut pending_selection = self
2924 .selections
2925 .pending_anchor()
2926 .expect("extend_selection not called with pending selection");
2927 if position >= tail {
2928 pending_selection.start = tail_anchor;
2929 } else {
2930 pending_selection.end = tail_anchor;
2931 pending_selection.reversed = true;
2932 }
2933
2934 let mut pending_mode = self.selections.pending_mode().unwrap();
2935 match &mut pending_mode {
2936 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2937 _ => {}
2938 }
2939
2940 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2941 s.set_pending(pending_selection, pending_mode)
2942 });
2943 }
2944
2945 fn begin_selection(
2946 &mut self,
2947 position: DisplayPoint,
2948 add: bool,
2949 click_count: usize,
2950 window: &mut Window,
2951 cx: &mut Context<Self>,
2952 ) {
2953 if !self.focus_handle.is_focused(window) {
2954 self.last_focused_descendant = None;
2955 window.focus(&self.focus_handle);
2956 }
2957
2958 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2959 let buffer = &display_map.buffer_snapshot;
2960 let newest_selection = self.selections.newest_anchor().clone();
2961 let position = display_map.clip_point(position, Bias::Left);
2962
2963 let start;
2964 let end;
2965 let mode;
2966 let mut auto_scroll;
2967 match click_count {
2968 1 => {
2969 start = buffer.anchor_before(position.to_point(&display_map));
2970 end = start;
2971 mode = SelectMode::Character;
2972 auto_scroll = true;
2973 }
2974 2 => {
2975 let range = movement::surrounding_word(&display_map, position);
2976 start = buffer.anchor_before(range.start.to_point(&display_map));
2977 end = buffer.anchor_before(range.end.to_point(&display_map));
2978 mode = SelectMode::Word(start..end);
2979 auto_scroll = true;
2980 }
2981 3 => {
2982 let position = display_map
2983 .clip_point(position, Bias::Left)
2984 .to_point(&display_map);
2985 let line_start = display_map.prev_line_boundary(position).0;
2986 let next_line_start = buffer.clip_point(
2987 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2988 Bias::Left,
2989 );
2990 start = buffer.anchor_before(line_start);
2991 end = buffer.anchor_before(next_line_start);
2992 mode = SelectMode::Line(start..end);
2993 auto_scroll = true;
2994 }
2995 _ => {
2996 start = buffer.anchor_before(0);
2997 end = buffer.anchor_before(buffer.len());
2998 mode = SelectMode::All;
2999 auto_scroll = false;
3000 }
3001 }
3002 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3003
3004 let point_to_delete: Option<usize> = {
3005 let selected_points: Vec<Selection<Point>> =
3006 self.selections.disjoint_in_range(start..end, cx);
3007
3008 if !add || click_count > 1 {
3009 None
3010 } else if !selected_points.is_empty() {
3011 Some(selected_points[0].id)
3012 } else {
3013 let clicked_point_already_selected =
3014 self.selections.disjoint.iter().find(|selection| {
3015 selection.start.to_point(buffer) == start.to_point(buffer)
3016 || selection.end.to_point(buffer) == end.to_point(buffer)
3017 });
3018
3019 clicked_point_already_selected.map(|selection| selection.id)
3020 }
3021 };
3022
3023 let selections_count = self.selections.count();
3024
3025 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3026 if let Some(point_to_delete) = point_to_delete {
3027 s.delete(point_to_delete);
3028
3029 if selections_count == 1 {
3030 s.set_pending_anchor_range(start..end, mode);
3031 }
3032 } else {
3033 if !add {
3034 s.clear_disjoint();
3035 } else if click_count > 1 {
3036 s.delete(newest_selection.id)
3037 }
3038
3039 s.set_pending_anchor_range(start..end, mode);
3040 }
3041 });
3042 }
3043
3044 fn begin_columnar_selection(
3045 &mut self,
3046 position: DisplayPoint,
3047 goal_column: u32,
3048 reset: bool,
3049 window: &mut Window,
3050 cx: &mut Context<Self>,
3051 ) {
3052 if !self.focus_handle.is_focused(window) {
3053 self.last_focused_descendant = None;
3054 window.focus(&self.focus_handle);
3055 }
3056
3057 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3058
3059 if reset {
3060 let pointer_position = display_map
3061 .buffer_snapshot
3062 .anchor_before(position.to_point(&display_map));
3063
3064 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3065 s.clear_disjoint();
3066 s.set_pending_anchor_range(
3067 pointer_position..pointer_position,
3068 SelectMode::Character,
3069 );
3070 });
3071 }
3072
3073 let tail = self.selections.newest::<Point>(cx).tail();
3074 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3075
3076 if !reset {
3077 self.select_columns(
3078 tail.to_display_point(&display_map),
3079 position,
3080 goal_column,
3081 &display_map,
3082 window,
3083 cx,
3084 );
3085 }
3086 }
3087
3088 fn update_selection(
3089 &mut self,
3090 position: DisplayPoint,
3091 goal_column: u32,
3092 scroll_delta: gpui::Point<f32>,
3093 window: &mut Window,
3094 cx: &mut Context<Self>,
3095 ) {
3096 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3097
3098 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3099 let tail = tail.to_display_point(&display_map);
3100 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3101 } else if let Some(mut pending) = self.selections.pending_anchor() {
3102 let buffer = self.buffer.read(cx).snapshot(cx);
3103 let head;
3104 let tail;
3105 let mode = self.selections.pending_mode().unwrap();
3106 match &mode {
3107 SelectMode::Character => {
3108 head = position.to_point(&display_map);
3109 tail = pending.tail().to_point(&buffer);
3110 }
3111 SelectMode::Word(original_range) => {
3112 let original_display_range = original_range.start.to_display_point(&display_map)
3113 ..original_range.end.to_display_point(&display_map);
3114 let original_buffer_range = original_display_range.start.to_point(&display_map)
3115 ..original_display_range.end.to_point(&display_map);
3116 if movement::is_inside_word(&display_map, position)
3117 || original_display_range.contains(&position)
3118 {
3119 let word_range = movement::surrounding_word(&display_map, position);
3120 if word_range.start < original_display_range.start {
3121 head = word_range.start.to_point(&display_map);
3122 } else {
3123 head = word_range.end.to_point(&display_map);
3124 }
3125 } else {
3126 head = position.to_point(&display_map);
3127 }
3128
3129 if head <= original_buffer_range.start {
3130 tail = original_buffer_range.end;
3131 } else {
3132 tail = original_buffer_range.start;
3133 }
3134 }
3135 SelectMode::Line(original_range) => {
3136 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3137
3138 let position = display_map
3139 .clip_point(position, Bias::Left)
3140 .to_point(&display_map);
3141 let line_start = display_map.prev_line_boundary(position).0;
3142 let next_line_start = buffer.clip_point(
3143 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3144 Bias::Left,
3145 );
3146
3147 if line_start < original_range.start {
3148 head = line_start
3149 } else {
3150 head = next_line_start
3151 }
3152
3153 if head <= original_range.start {
3154 tail = original_range.end;
3155 } else {
3156 tail = original_range.start;
3157 }
3158 }
3159 SelectMode::All => {
3160 return;
3161 }
3162 };
3163
3164 if head < tail {
3165 pending.start = buffer.anchor_before(head);
3166 pending.end = buffer.anchor_before(tail);
3167 pending.reversed = true;
3168 } else {
3169 pending.start = buffer.anchor_before(tail);
3170 pending.end = buffer.anchor_before(head);
3171 pending.reversed = false;
3172 }
3173
3174 self.change_selections(None, window, cx, |s| {
3175 s.set_pending(pending, mode);
3176 });
3177 } else {
3178 log::error!("update_selection dispatched with no pending selection");
3179 return;
3180 }
3181
3182 self.apply_scroll_delta(scroll_delta, window, cx);
3183 cx.notify();
3184 }
3185
3186 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3187 self.columnar_selection_tail.take();
3188 if self.selections.pending_anchor().is_some() {
3189 let selections = self.selections.all::<usize>(cx);
3190 self.change_selections(None, window, cx, |s| {
3191 s.select(selections);
3192 s.clear_pending();
3193 });
3194 }
3195 }
3196
3197 fn select_columns(
3198 &mut self,
3199 tail: DisplayPoint,
3200 head: DisplayPoint,
3201 goal_column: u32,
3202 display_map: &DisplaySnapshot,
3203 window: &mut Window,
3204 cx: &mut Context<Self>,
3205 ) {
3206 let start_row = cmp::min(tail.row(), head.row());
3207 let end_row = cmp::max(tail.row(), head.row());
3208 let start_column = cmp::min(tail.column(), goal_column);
3209 let end_column = cmp::max(tail.column(), goal_column);
3210 let reversed = start_column < tail.column();
3211
3212 let selection_ranges = (start_row.0..=end_row.0)
3213 .map(DisplayRow)
3214 .filter_map(|row| {
3215 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3216 let start = display_map
3217 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3218 .to_point(display_map);
3219 let end = display_map
3220 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3221 .to_point(display_map);
3222 if reversed {
3223 Some(end..start)
3224 } else {
3225 Some(start..end)
3226 }
3227 } else {
3228 None
3229 }
3230 })
3231 .collect::<Vec<_>>();
3232
3233 self.change_selections(None, window, cx, |s| {
3234 s.select_ranges(selection_ranges);
3235 });
3236 cx.notify();
3237 }
3238
3239 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3240 self.selections
3241 .all_adjusted(cx)
3242 .iter()
3243 .any(|selection| !selection.is_empty())
3244 }
3245
3246 pub fn has_pending_nonempty_selection(&self) -> bool {
3247 let pending_nonempty_selection = match self.selections.pending_anchor() {
3248 Some(Selection { start, end, .. }) => start != end,
3249 None => false,
3250 };
3251
3252 pending_nonempty_selection
3253 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3254 }
3255
3256 pub fn has_pending_selection(&self) -> bool {
3257 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3258 }
3259
3260 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3261 self.selection_mark_mode = false;
3262
3263 if self.clear_expanded_diff_hunks(cx) {
3264 cx.notify();
3265 return;
3266 }
3267 if self.dismiss_menus_and_popups(true, window, cx) {
3268 return;
3269 }
3270
3271 if self.mode.is_full()
3272 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3273 {
3274 return;
3275 }
3276
3277 cx.propagate();
3278 }
3279
3280 pub fn dismiss_menus_and_popups(
3281 &mut self,
3282 is_user_requested: bool,
3283 window: &mut Window,
3284 cx: &mut Context<Self>,
3285 ) -> bool {
3286 if self.take_rename(false, window, cx).is_some() {
3287 return true;
3288 }
3289
3290 if hide_hover(self, cx) {
3291 return true;
3292 }
3293
3294 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3295 return true;
3296 }
3297
3298 if self.hide_context_menu(window, cx).is_some() {
3299 return true;
3300 }
3301
3302 if self.mouse_context_menu.take().is_some() {
3303 return true;
3304 }
3305
3306 if is_user_requested && self.discard_inline_completion(true, cx) {
3307 return true;
3308 }
3309
3310 if self.snippet_stack.pop().is_some() {
3311 return true;
3312 }
3313
3314 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3315 self.dismiss_diagnostics(cx);
3316 return true;
3317 }
3318
3319 false
3320 }
3321
3322 fn linked_editing_ranges_for(
3323 &self,
3324 selection: Range<text::Anchor>,
3325 cx: &App,
3326 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3327 if self.linked_edit_ranges.is_empty() {
3328 return None;
3329 }
3330 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3331 selection.end.buffer_id.and_then(|end_buffer_id| {
3332 if selection.start.buffer_id != Some(end_buffer_id) {
3333 return None;
3334 }
3335 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3336 let snapshot = buffer.read(cx).snapshot();
3337 self.linked_edit_ranges
3338 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3339 .map(|ranges| (ranges, snapshot, buffer))
3340 })?;
3341 use text::ToOffset as TO;
3342 // find offset from the start of current range to current cursor position
3343 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3344
3345 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3346 let start_difference = start_offset - start_byte_offset;
3347 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3348 let end_difference = end_offset - start_byte_offset;
3349 // Current range has associated linked ranges.
3350 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3351 for range in linked_ranges.iter() {
3352 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3353 let end_offset = start_offset + end_difference;
3354 let start_offset = start_offset + start_difference;
3355 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3356 continue;
3357 }
3358 if self.selections.disjoint_anchor_ranges().any(|s| {
3359 if s.start.buffer_id != selection.start.buffer_id
3360 || s.end.buffer_id != selection.end.buffer_id
3361 {
3362 return false;
3363 }
3364 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3365 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3366 }) {
3367 continue;
3368 }
3369 let start = buffer_snapshot.anchor_after(start_offset);
3370 let end = buffer_snapshot.anchor_after(end_offset);
3371 linked_edits
3372 .entry(buffer.clone())
3373 .or_default()
3374 .push(start..end);
3375 }
3376 Some(linked_edits)
3377 }
3378
3379 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3380 let text: Arc<str> = text.into();
3381
3382 if self.read_only(cx) {
3383 return;
3384 }
3385
3386 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3387
3388 let selections = self.selections.all_adjusted(cx);
3389 let mut bracket_inserted = false;
3390 let mut edits = Vec::new();
3391 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3392 let mut new_selections = Vec::with_capacity(selections.len());
3393 let mut new_autoclose_regions = Vec::new();
3394 let snapshot = self.buffer.read(cx).read(cx);
3395 let mut clear_linked_edit_ranges = false;
3396
3397 for (selection, autoclose_region) in
3398 self.selections_with_autoclose_regions(selections, &snapshot)
3399 {
3400 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3401 // Determine if the inserted text matches the opening or closing
3402 // bracket of any of this language's bracket pairs.
3403 let mut bracket_pair = None;
3404 let mut is_bracket_pair_start = false;
3405 let mut is_bracket_pair_end = false;
3406 if !text.is_empty() {
3407 let mut bracket_pair_matching_end = None;
3408 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3409 // and they are removing the character that triggered IME popup.
3410 for (pair, enabled) in scope.brackets() {
3411 if !pair.close && !pair.surround {
3412 continue;
3413 }
3414
3415 if enabled && pair.start.ends_with(text.as_ref()) {
3416 let prefix_len = pair.start.len() - text.len();
3417 let preceding_text_matches_prefix = prefix_len == 0
3418 || (selection.start.column >= (prefix_len as u32)
3419 && snapshot.contains_str_at(
3420 Point::new(
3421 selection.start.row,
3422 selection.start.column - (prefix_len as u32),
3423 ),
3424 &pair.start[..prefix_len],
3425 ));
3426 if preceding_text_matches_prefix {
3427 bracket_pair = Some(pair.clone());
3428 is_bracket_pair_start = true;
3429 break;
3430 }
3431 }
3432 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3433 {
3434 // take first bracket pair matching end, but don't break in case a later bracket
3435 // pair matches start
3436 bracket_pair_matching_end = Some(pair.clone());
3437 }
3438 }
3439 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3440 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3441 is_bracket_pair_end = true;
3442 }
3443 }
3444
3445 if let Some(bracket_pair) = bracket_pair {
3446 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3447 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3448 let auto_surround =
3449 self.use_auto_surround && snapshot_settings.use_auto_surround;
3450 if selection.is_empty() {
3451 if is_bracket_pair_start {
3452 // If the inserted text is a suffix of an opening bracket and the
3453 // selection is preceded by the rest of the opening bracket, then
3454 // insert the closing bracket.
3455 let following_text_allows_autoclose = snapshot
3456 .chars_at(selection.start)
3457 .next()
3458 .map_or(true, |c| scope.should_autoclose_before(c));
3459
3460 let preceding_text_allows_autoclose = selection.start.column == 0
3461 || snapshot.reversed_chars_at(selection.start).next().map_or(
3462 true,
3463 |c| {
3464 bracket_pair.start != bracket_pair.end
3465 || !snapshot
3466 .char_classifier_at(selection.start)
3467 .is_word(c)
3468 },
3469 );
3470
3471 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3472 && bracket_pair.start.len() == 1
3473 {
3474 let target = bracket_pair.start.chars().next().unwrap();
3475 let current_line_count = snapshot
3476 .reversed_chars_at(selection.start)
3477 .take_while(|&c| c != '\n')
3478 .filter(|&c| c == target)
3479 .count();
3480 current_line_count % 2 == 1
3481 } else {
3482 false
3483 };
3484
3485 if autoclose
3486 && bracket_pair.close
3487 && following_text_allows_autoclose
3488 && preceding_text_allows_autoclose
3489 && !is_closing_quote
3490 {
3491 let anchor = snapshot.anchor_before(selection.end);
3492 new_selections.push((selection.map(|_| anchor), text.len()));
3493 new_autoclose_regions.push((
3494 anchor,
3495 text.len(),
3496 selection.id,
3497 bracket_pair.clone(),
3498 ));
3499 edits.push((
3500 selection.range(),
3501 format!("{}{}", text, bracket_pair.end).into(),
3502 ));
3503 bracket_inserted = true;
3504 continue;
3505 }
3506 }
3507
3508 if let Some(region) = autoclose_region {
3509 // If the selection is followed by an auto-inserted closing bracket,
3510 // then don't insert that closing bracket again; just move the selection
3511 // past the closing bracket.
3512 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3513 && text.as_ref() == region.pair.end.as_str();
3514 if should_skip {
3515 let anchor = snapshot.anchor_after(selection.end);
3516 new_selections
3517 .push((selection.map(|_| anchor), region.pair.end.len()));
3518 continue;
3519 }
3520 }
3521
3522 let always_treat_brackets_as_autoclosed = snapshot
3523 .language_settings_at(selection.start, cx)
3524 .always_treat_brackets_as_autoclosed;
3525 if always_treat_brackets_as_autoclosed
3526 && is_bracket_pair_end
3527 && snapshot.contains_str_at(selection.end, text.as_ref())
3528 {
3529 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3530 // and the inserted text is a closing bracket and the selection is followed
3531 // by the closing bracket then move the selection past the closing bracket.
3532 let anchor = snapshot.anchor_after(selection.end);
3533 new_selections.push((selection.map(|_| anchor), text.len()));
3534 continue;
3535 }
3536 }
3537 // If an opening bracket is 1 character long and is typed while
3538 // text is selected, then surround that text with the bracket pair.
3539 else if auto_surround
3540 && bracket_pair.surround
3541 && is_bracket_pair_start
3542 && bracket_pair.start.chars().count() == 1
3543 {
3544 edits.push((selection.start..selection.start, text.clone()));
3545 edits.push((
3546 selection.end..selection.end,
3547 bracket_pair.end.as_str().into(),
3548 ));
3549 bracket_inserted = true;
3550 new_selections.push((
3551 Selection {
3552 id: selection.id,
3553 start: snapshot.anchor_after(selection.start),
3554 end: snapshot.anchor_before(selection.end),
3555 reversed: selection.reversed,
3556 goal: selection.goal,
3557 },
3558 0,
3559 ));
3560 continue;
3561 }
3562 }
3563 }
3564
3565 if self.auto_replace_emoji_shortcode
3566 && selection.is_empty()
3567 && text.as_ref().ends_with(':')
3568 {
3569 if let Some(possible_emoji_short_code) =
3570 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3571 {
3572 if !possible_emoji_short_code.is_empty() {
3573 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3574 let emoji_shortcode_start = Point::new(
3575 selection.start.row,
3576 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3577 );
3578
3579 // Remove shortcode from buffer
3580 edits.push((
3581 emoji_shortcode_start..selection.start,
3582 "".to_string().into(),
3583 ));
3584 new_selections.push((
3585 Selection {
3586 id: selection.id,
3587 start: snapshot.anchor_after(emoji_shortcode_start),
3588 end: snapshot.anchor_before(selection.start),
3589 reversed: selection.reversed,
3590 goal: selection.goal,
3591 },
3592 0,
3593 ));
3594
3595 // Insert emoji
3596 let selection_start_anchor = snapshot.anchor_after(selection.start);
3597 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3598 edits.push((selection.start..selection.end, emoji.to_string().into()));
3599
3600 continue;
3601 }
3602 }
3603 }
3604 }
3605
3606 // If not handling any auto-close operation, then just replace the selected
3607 // text with the given input and move the selection to the end of the
3608 // newly inserted text.
3609 let anchor = snapshot.anchor_after(selection.end);
3610 if !self.linked_edit_ranges.is_empty() {
3611 let start_anchor = snapshot.anchor_before(selection.start);
3612
3613 let is_word_char = text.chars().next().map_or(true, |char| {
3614 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3615 classifier.is_word(char)
3616 });
3617
3618 if is_word_char {
3619 if let Some(ranges) = self
3620 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3621 {
3622 for (buffer, edits) in ranges {
3623 linked_edits
3624 .entry(buffer.clone())
3625 .or_default()
3626 .extend(edits.into_iter().map(|range| (range, text.clone())));
3627 }
3628 }
3629 } else {
3630 clear_linked_edit_ranges = true;
3631 }
3632 }
3633
3634 new_selections.push((selection.map(|_| anchor), 0));
3635 edits.push((selection.start..selection.end, text.clone()));
3636 }
3637
3638 drop(snapshot);
3639
3640 self.transact(window, cx, |this, window, cx| {
3641 if clear_linked_edit_ranges {
3642 this.linked_edit_ranges.clear();
3643 }
3644 let initial_buffer_versions =
3645 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3646
3647 this.buffer.update(cx, |buffer, cx| {
3648 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3649 });
3650 for (buffer, edits) in linked_edits {
3651 buffer.update(cx, |buffer, cx| {
3652 let snapshot = buffer.snapshot();
3653 let edits = edits
3654 .into_iter()
3655 .map(|(range, text)| {
3656 use text::ToPoint as TP;
3657 let end_point = TP::to_point(&range.end, &snapshot);
3658 let start_point = TP::to_point(&range.start, &snapshot);
3659 (start_point..end_point, text)
3660 })
3661 .sorted_by_key(|(range, _)| range.start);
3662 buffer.edit(edits, None, cx);
3663 })
3664 }
3665 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3666 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3667 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3668 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3669 .zip(new_selection_deltas)
3670 .map(|(selection, delta)| Selection {
3671 id: selection.id,
3672 start: selection.start + delta,
3673 end: selection.end + delta,
3674 reversed: selection.reversed,
3675 goal: SelectionGoal::None,
3676 })
3677 .collect::<Vec<_>>();
3678
3679 let mut i = 0;
3680 for (position, delta, selection_id, pair) in new_autoclose_regions {
3681 let position = position.to_offset(&map.buffer_snapshot) + delta;
3682 let start = map.buffer_snapshot.anchor_before(position);
3683 let end = map.buffer_snapshot.anchor_after(position);
3684 while let Some(existing_state) = this.autoclose_regions.get(i) {
3685 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3686 Ordering::Less => i += 1,
3687 Ordering::Greater => break,
3688 Ordering::Equal => {
3689 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3690 Ordering::Less => i += 1,
3691 Ordering::Equal => break,
3692 Ordering::Greater => break,
3693 }
3694 }
3695 }
3696 }
3697 this.autoclose_regions.insert(
3698 i,
3699 AutocloseRegion {
3700 selection_id,
3701 range: start..end,
3702 pair,
3703 },
3704 );
3705 }
3706
3707 let had_active_inline_completion = this.has_active_inline_completion();
3708 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3709 s.select(new_selections)
3710 });
3711
3712 if !bracket_inserted {
3713 if let Some(on_type_format_task) =
3714 this.trigger_on_type_formatting(text.to_string(), window, cx)
3715 {
3716 on_type_format_task.detach_and_log_err(cx);
3717 }
3718 }
3719
3720 let editor_settings = EditorSettings::get_global(cx);
3721 if bracket_inserted
3722 && (editor_settings.auto_signature_help
3723 || editor_settings.show_signature_help_after_edits)
3724 {
3725 this.show_signature_help(&ShowSignatureHelp, window, cx);
3726 }
3727
3728 let trigger_in_words =
3729 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3730 if this.hard_wrap.is_some() {
3731 let latest: Range<Point> = this.selections.newest(cx).range();
3732 if latest.is_empty()
3733 && this
3734 .buffer()
3735 .read(cx)
3736 .snapshot(cx)
3737 .line_len(MultiBufferRow(latest.start.row))
3738 == latest.start.column
3739 {
3740 this.rewrap_impl(
3741 RewrapOptions {
3742 override_language_settings: true,
3743 preserve_existing_whitespace: true,
3744 },
3745 cx,
3746 )
3747 }
3748 }
3749 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3750 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3751 this.refresh_inline_completion(true, false, window, cx);
3752 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3753 });
3754 }
3755
3756 fn find_possible_emoji_shortcode_at_position(
3757 snapshot: &MultiBufferSnapshot,
3758 position: Point,
3759 ) -> Option<String> {
3760 let mut chars = Vec::new();
3761 let mut found_colon = false;
3762 for char in snapshot.reversed_chars_at(position).take(100) {
3763 // Found a possible emoji shortcode in the middle of the buffer
3764 if found_colon {
3765 if char.is_whitespace() {
3766 chars.reverse();
3767 return Some(chars.iter().collect());
3768 }
3769 // If the previous character is not a whitespace, we are in the middle of a word
3770 // and we only want to complete the shortcode if the word is made up of other emojis
3771 let mut containing_word = String::new();
3772 for ch in snapshot
3773 .reversed_chars_at(position)
3774 .skip(chars.len() + 1)
3775 .take(100)
3776 {
3777 if ch.is_whitespace() {
3778 break;
3779 }
3780 containing_word.push(ch);
3781 }
3782 let containing_word = containing_word.chars().rev().collect::<String>();
3783 if util::word_consists_of_emojis(containing_word.as_str()) {
3784 chars.reverse();
3785 return Some(chars.iter().collect());
3786 }
3787 }
3788
3789 if char.is_whitespace() || !char.is_ascii() {
3790 return None;
3791 }
3792 if char == ':' {
3793 found_colon = true;
3794 } else {
3795 chars.push(char);
3796 }
3797 }
3798 // Found a possible emoji shortcode at the beginning of the buffer
3799 chars.reverse();
3800 Some(chars.iter().collect())
3801 }
3802
3803 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3804 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3805 self.transact(window, cx, |this, window, cx| {
3806 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3807 let selections = this.selections.all::<usize>(cx);
3808 let multi_buffer = this.buffer.read(cx);
3809 let buffer = multi_buffer.snapshot(cx);
3810 selections
3811 .iter()
3812 .map(|selection| {
3813 let start_point = selection.start.to_point(&buffer);
3814 let mut indent =
3815 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3816 indent.len = cmp::min(indent.len, start_point.column);
3817 let start = selection.start;
3818 let end = selection.end;
3819 let selection_is_empty = start == end;
3820 let language_scope = buffer.language_scope_at(start);
3821 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3822 &language_scope
3823 {
3824 let insert_extra_newline =
3825 insert_extra_newline_brackets(&buffer, start..end, language)
3826 || insert_extra_newline_tree_sitter(&buffer, start..end);
3827
3828 // Comment extension on newline is allowed only for cursor selections
3829 let comment_delimiter = maybe!({
3830 if !selection_is_empty {
3831 return None;
3832 }
3833
3834 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3835 return None;
3836 }
3837
3838 let delimiters = language.line_comment_prefixes();
3839 let max_len_of_delimiter =
3840 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3841 let (snapshot, range) =
3842 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3843
3844 let mut index_of_first_non_whitespace = 0;
3845 let comment_candidate = snapshot
3846 .chars_for_range(range)
3847 .skip_while(|c| {
3848 let should_skip = c.is_whitespace();
3849 if should_skip {
3850 index_of_first_non_whitespace += 1;
3851 }
3852 should_skip
3853 })
3854 .take(max_len_of_delimiter)
3855 .collect::<String>();
3856 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3857 comment_candidate.starts_with(comment_prefix.as_ref())
3858 })?;
3859 let cursor_is_placed_after_comment_marker =
3860 index_of_first_non_whitespace + comment_prefix.len()
3861 <= start_point.column as usize;
3862 if cursor_is_placed_after_comment_marker {
3863 Some(comment_prefix.clone())
3864 } else {
3865 None
3866 }
3867 });
3868 (comment_delimiter, insert_extra_newline)
3869 } else {
3870 (None, false)
3871 };
3872
3873 let capacity_for_delimiter = comment_delimiter
3874 .as_deref()
3875 .map(str::len)
3876 .unwrap_or_default();
3877 let mut new_text =
3878 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3879 new_text.push('\n');
3880 new_text.extend(indent.chars());
3881 if let Some(delimiter) = &comment_delimiter {
3882 new_text.push_str(delimiter);
3883 }
3884 if insert_extra_newline {
3885 new_text = new_text.repeat(2);
3886 }
3887
3888 let anchor = buffer.anchor_after(end);
3889 let new_selection = selection.map(|_| anchor);
3890 (
3891 (start..end, new_text),
3892 (insert_extra_newline, new_selection),
3893 )
3894 })
3895 .unzip()
3896 };
3897
3898 this.edit_with_autoindent(edits, cx);
3899 let buffer = this.buffer.read(cx).snapshot(cx);
3900 let new_selections = selection_fixup_info
3901 .into_iter()
3902 .map(|(extra_newline_inserted, new_selection)| {
3903 let mut cursor = new_selection.end.to_point(&buffer);
3904 if extra_newline_inserted {
3905 cursor.row -= 1;
3906 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3907 }
3908 new_selection.map(|_| cursor)
3909 })
3910 .collect();
3911
3912 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3913 s.select(new_selections)
3914 });
3915 this.refresh_inline_completion(true, false, window, cx);
3916 });
3917 }
3918
3919 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3920 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3921
3922 let buffer = self.buffer.read(cx);
3923 let snapshot = buffer.snapshot(cx);
3924
3925 let mut edits = Vec::new();
3926 let mut rows = Vec::new();
3927
3928 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3929 let cursor = selection.head();
3930 let row = cursor.row;
3931
3932 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3933
3934 let newline = "\n".to_string();
3935 edits.push((start_of_line..start_of_line, newline));
3936
3937 rows.push(row + rows_inserted as u32);
3938 }
3939
3940 self.transact(window, cx, |editor, window, cx| {
3941 editor.edit(edits, cx);
3942
3943 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3944 let mut index = 0;
3945 s.move_cursors_with(|map, _, _| {
3946 let row = rows[index];
3947 index += 1;
3948
3949 let point = Point::new(row, 0);
3950 let boundary = map.next_line_boundary(point).1;
3951 let clipped = map.clip_point(boundary, Bias::Left);
3952
3953 (clipped, SelectionGoal::None)
3954 });
3955 });
3956
3957 let mut indent_edits = Vec::new();
3958 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3959 for row in rows {
3960 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3961 for (row, indent) in indents {
3962 if indent.len == 0 {
3963 continue;
3964 }
3965
3966 let text = match indent.kind {
3967 IndentKind::Space => " ".repeat(indent.len as usize),
3968 IndentKind::Tab => "\t".repeat(indent.len as usize),
3969 };
3970 let point = Point::new(row.0, 0);
3971 indent_edits.push((point..point, text));
3972 }
3973 }
3974 editor.edit(indent_edits, cx);
3975 });
3976 }
3977
3978 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3979 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3980
3981 let buffer = self.buffer.read(cx);
3982 let snapshot = buffer.snapshot(cx);
3983
3984 let mut edits = Vec::new();
3985 let mut rows = Vec::new();
3986 let mut rows_inserted = 0;
3987
3988 for selection in self.selections.all_adjusted(cx) {
3989 let cursor = selection.head();
3990 let row = cursor.row;
3991
3992 let point = Point::new(row + 1, 0);
3993 let start_of_line = snapshot.clip_point(point, Bias::Left);
3994
3995 let newline = "\n".to_string();
3996 edits.push((start_of_line..start_of_line, newline));
3997
3998 rows_inserted += 1;
3999 rows.push(row + rows_inserted);
4000 }
4001
4002 self.transact(window, cx, |editor, window, cx| {
4003 editor.edit(edits, cx);
4004
4005 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4006 let mut index = 0;
4007 s.move_cursors_with(|map, _, _| {
4008 let row = rows[index];
4009 index += 1;
4010
4011 let point = Point::new(row, 0);
4012 let boundary = map.next_line_boundary(point).1;
4013 let clipped = map.clip_point(boundary, Bias::Left);
4014
4015 (clipped, SelectionGoal::None)
4016 });
4017 });
4018
4019 let mut indent_edits = Vec::new();
4020 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4021 for row in rows {
4022 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4023 for (row, indent) in indents {
4024 if indent.len == 0 {
4025 continue;
4026 }
4027
4028 let text = match indent.kind {
4029 IndentKind::Space => " ".repeat(indent.len as usize),
4030 IndentKind::Tab => "\t".repeat(indent.len as usize),
4031 };
4032 let point = Point::new(row.0, 0);
4033 indent_edits.push((point..point, text));
4034 }
4035 }
4036 editor.edit(indent_edits, cx);
4037 });
4038 }
4039
4040 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4041 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4042 original_indent_columns: Vec::new(),
4043 });
4044 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4045 }
4046
4047 fn insert_with_autoindent_mode(
4048 &mut self,
4049 text: &str,
4050 autoindent_mode: Option<AutoindentMode>,
4051 window: &mut Window,
4052 cx: &mut Context<Self>,
4053 ) {
4054 if self.read_only(cx) {
4055 return;
4056 }
4057
4058 let text: Arc<str> = text.into();
4059 self.transact(window, cx, |this, window, cx| {
4060 let old_selections = this.selections.all_adjusted(cx);
4061 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4062 let anchors = {
4063 let snapshot = buffer.read(cx);
4064 old_selections
4065 .iter()
4066 .map(|s| {
4067 let anchor = snapshot.anchor_after(s.head());
4068 s.map(|_| anchor)
4069 })
4070 .collect::<Vec<_>>()
4071 };
4072 buffer.edit(
4073 old_selections
4074 .iter()
4075 .map(|s| (s.start..s.end, text.clone())),
4076 autoindent_mode,
4077 cx,
4078 );
4079 anchors
4080 });
4081
4082 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4083 s.select_anchors(selection_anchors);
4084 });
4085
4086 cx.notify();
4087 });
4088 }
4089
4090 fn trigger_completion_on_input(
4091 &mut self,
4092 text: &str,
4093 trigger_in_words: bool,
4094 window: &mut Window,
4095 cx: &mut Context<Self>,
4096 ) {
4097 let ignore_completion_provider = self
4098 .context_menu
4099 .borrow()
4100 .as_ref()
4101 .map(|menu| match menu {
4102 CodeContextMenu::Completions(completions_menu) => {
4103 completions_menu.ignore_completion_provider
4104 }
4105 CodeContextMenu::CodeActions(_) => false,
4106 })
4107 .unwrap_or(false);
4108
4109 if ignore_completion_provider {
4110 self.show_word_completions(&ShowWordCompletions, window, cx);
4111 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4112 self.show_completions(
4113 &ShowCompletions {
4114 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4115 },
4116 window,
4117 cx,
4118 );
4119 } else {
4120 self.hide_context_menu(window, cx);
4121 }
4122 }
4123
4124 fn is_completion_trigger(
4125 &self,
4126 text: &str,
4127 trigger_in_words: bool,
4128 cx: &mut Context<Self>,
4129 ) -> bool {
4130 let position = self.selections.newest_anchor().head();
4131 let multibuffer = self.buffer.read(cx);
4132 let Some(buffer) = position
4133 .buffer_id
4134 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4135 else {
4136 return false;
4137 };
4138
4139 if let Some(completion_provider) = &self.completion_provider {
4140 completion_provider.is_completion_trigger(
4141 &buffer,
4142 position.text_anchor,
4143 text,
4144 trigger_in_words,
4145 cx,
4146 )
4147 } else {
4148 false
4149 }
4150 }
4151
4152 /// If any empty selections is touching the start of its innermost containing autoclose
4153 /// region, expand it to select the brackets.
4154 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4155 let selections = self.selections.all::<usize>(cx);
4156 let buffer = self.buffer.read(cx).read(cx);
4157 let new_selections = self
4158 .selections_with_autoclose_regions(selections, &buffer)
4159 .map(|(mut selection, region)| {
4160 if !selection.is_empty() {
4161 return selection;
4162 }
4163
4164 if let Some(region) = region {
4165 let mut range = region.range.to_offset(&buffer);
4166 if selection.start == range.start && range.start >= region.pair.start.len() {
4167 range.start -= region.pair.start.len();
4168 if buffer.contains_str_at(range.start, ®ion.pair.start)
4169 && buffer.contains_str_at(range.end, ®ion.pair.end)
4170 {
4171 range.end += region.pair.end.len();
4172 selection.start = range.start;
4173 selection.end = range.end;
4174
4175 return selection;
4176 }
4177 }
4178 }
4179
4180 let always_treat_brackets_as_autoclosed = buffer
4181 .language_settings_at(selection.start, cx)
4182 .always_treat_brackets_as_autoclosed;
4183
4184 if !always_treat_brackets_as_autoclosed {
4185 return selection;
4186 }
4187
4188 if let Some(scope) = buffer.language_scope_at(selection.start) {
4189 for (pair, enabled) in scope.brackets() {
4190 if !enabled || !pair.close {
4191 continue;
4192 }
4193
4194 if buffer.contains_str_at(selection.start, &pair.end) {
4195 let pair_start_len = pair.start.len();
4196 if buffer.contains_str_at(
4197 selection.start.saturating_sub(pair_start_len),
4198 &pair.start,
4199 ) {
4200 selection.start -= pair_start_len;
4201 selection.end += pair.end.len();
4202
4203 return selection;
4204 }
4205 }
4206 }
4207 }
4208
4209 selection
4210 })
4211 .collect();
4212
4213 drop(buffer);
4214 self.change_selections(None, window, cx, |selections| {
4215 selections.select(new_selections)
4216 });
4217 }
4218
4219 /// Iterate the given selections, and for each one, find the smallest surrounding
4220 /// autoclose region. This uses the ordering of the selections and the autoclose
4221 /// regions to avoid repeated comparisons.
4222 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4223 &'a self,
4224 selections: impl IntoIterator<Item = Selection<D>>,
4225 buffer: &'a MultiBufferSnapshot,
4226 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4227 let mut i = 0;
4228 let mut regions = self.autoclose_regions.as_slice();
4229 selections.into_iter().map(move |selection| {
4230 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4231
4232 let mut enclosing = None;
4233 while let Some(pair_state) = regions.get(i) {
4234 if pair_state.range.end.to_offset(buffer) < range.start {
4235 regions = ®ions[i + 1..];
4236 i = 0;
4237 } else if pair_state.range.start.to_offset(buffer) > range.end {
4238 break;
4239 } else {
4240 if pair_state.selection_id == selection.id {
4241 enclosing = Some(pair_state);
4242 }
4243 i += 1;
4244 }
4245 }
4246
4247 (selection, enclosing)
4248 })
4249 }
4250
4251 /// Remove any autoclose regions that no longer contain their selection.
4252 fn invalidate_autoclose_regions(
4253 &mut self,
4254 mut selections: &[Selection<Anchor>],
4255 buffer: &MultiBufferSnapshot,
4256 ) {
4257 self.autoclose_regions.retain(|state| {
4258 let mut i = 0;
4259 while let Some(selection) = selections.get(i) {
4260 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4261 selections = &selections[1..];
4262 continue;
4263 }
4264 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4265 break;
4266 }
4267 if selection.id == state.selection_id {
4268 return true;
4269 } else {
4270 i += 1;
4271 }
4272 }
4273 false
4274 });
4275 }
4276
4277 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4278 let offset = position.to_offset(buffer);
4279 let (word_range, kind) = buffer.surrounding_word(offset, true);
4280 if offset > word_range.start && kind == Some(CharKind::Word) {
4281 Some(
4282 buffer
4283 .text_for_range(word_range.start..offset)
4284 .collect::<String>(),
4285 )
4286 } else {
4287 None
4288 }
4289 }
4290
4291 pub fn toggle_inline_values(
4292 &mut self,
4293 _: &ToggleInlineValues,
4294 _: &mut Window,
4295 cx: &mut Context<Self>,
4296 ) {
4297 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4298
4299 self.refresh_inline_values(cx);
4300 }
4301
4302 pub fn toggle_inlay_hints(
4303 &mut self,
4304 _: &ToggleInlayHints,
4305 _: &mut Window,
4306 cx: &mut Context<Self>,
4307 ) {
4308 self.refresh_inlay_hints(
4309 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4310 cx,
4311 );
4312 }
4313
4314 pub fn inlay_hints_enabled(&self) -> bool {
4315 self.inlay_hint_cache.enabled
4316 }
4317
4318 pub fn inline_values_enabled(&self) -> bool {
4319 self.inline_value_cache.enabled
4320 }
4321
4322 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4323 if self.semantics_provider.is_none() || !self.mode.is_full() {
4324 return;
4325 }
4326
4327 let reason_description = reason.description();
4328 let ignore_debounce = matches!(
4329 reason,
4330 InlayHintRefreshReason::SettingsChange(_)
4331 | InlayHintRefreshReason::Toggle(_)
4332 | InlayHintRefreshReason::ExcerptsRemoved(_)
4333 | InlayHintRefreshReason::ModifiersChanged(_)
4334 );
4335 let (invalidate_cache, required_languages) = match reason {
4336 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4337 match self.inlay_hint_cache.modifiers_override(enabled) {
4338 Some(enabled) => {
4339 if enabled {
4340 (InvalidationStrategy::RefreshRequested, None)
4341 } else {
4342 self.splice_inlays(
4343 &self
4344 .visible_inlay_hints(cx)
4345 .iter()
4346 .map(|inlay| inlay.id)
4347 .collect::<Vec<InlayId>>(),
4348 Vec::new(),
4349 cx,
4350 );
4351 return;
4352 }
4353 }
4354 None => return,
4355 }
4356 }
4357 InlayHintRefreshReason::Toggle(enabled) => {
4358 if self.inlay_hint_cache.toggle(enabled) {
4359 if enabled {
4360 (InvalidationStrategy::RefreshRequested, None)
4361 } else {
4362 self.splice_inlays(
4363 &self
4364 .visible_inlay_hints(cx)
4365 .iter()
4366 .map(|inlay| inlay.id)
4367 .collect::<Vec<InlayId>>(),
4368 Vec::new(),
4369 cx,
4370 );
4371 return;
4372 }
4373 } else {
4374 return;
4375 }
4376 }
4377 InlayHintRefreshReason::SettingsChange(new_settings) => {
4378 match self.inlay_hint_cache.update_settings(
4379 &self.buffer,
4380 new_settings,
4381 self.visible_inlay_hints(cx),
4382 cx,
4383 ) {
4384 ControlFlow::Break(Some(InlaySplice {
4385 to_remove,
4386 to_insert,
4387 })) => {
4388 self.splice_inlays(&to_remove, to_insert, cx);
4389 return;
4390 }
4391 ControlFlow::Break(None) => return,
4392 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4393 }
4394 }
4395 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4396 if let Some(InlaySplice {
4397 to_remove,
4398 to_insert,
4399 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4400 {
4401 self.splice_inlays(&to_remove, to_insert, cx);
4402 }
4403 self.display_map.update(cx, |display_map, _| {
4404 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4405 });
4406 return;
4407 }
4408 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4409 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4410 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4411 }
4412 InlayHintRefreshReason::RefreshRequested => {
4413 (InvalidationStrategy::RefreshRequested, None)
4414 }
4415 };
4416
4417 if let Some(InlaySplice {
4418 to_remove,
4419 to_insert,
4420 }) = self.inlay_hint_cache.spawn_hint_refresh(
4421 reason_description,
4422 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4423 invalidate_cache,
4424 ignore_debounce,
4425 cx,
4426 ) {
4427 self.splice_inlays(&to_remove, to_insert, cx);
4428 }
4429 }
4430
4431 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4432 self.display_map
4433 .read(cx)
4434 .current_inlays()
4435 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4436 .cloned()
4437 .collect()
4438 }
4439
4440 pub fn excerpts_for_inlay_hints_query(
4441 &self,
4442 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4443 cx: &mut Context<Editor>,
4444 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4445 let Some(project) = self.project.as_ref() else {
4446 return HashMap::default();
4447 };
4448 let project = project.read(cx);
4449 let multi_buffer = self.buffer().read(cx);
4450 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4451 let multi_buffer_visible_start = self
4452 .scroll_manager
4453 .anchor()
4454 .anchor
4455 .to_point(&multi_buffer_snapshot);
4456 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4457 multi_buffer_visible_start
4458 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4459 Bias::Left,
4460 );
4461 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4462 multi_buffer_snapshot
4463 .range_to_buffer_ranges(multi_buffer_visible_range)
4464 .into_iter()
4465 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4466 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4467 let buffer_file = project::File::from_dyn(buffer.file())?;
4468 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4469 let worktree_entry = buffer_worktree
4470 .read(cx)
4471 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4472 if worktree_entry.is_ignored {
4473 return None;
4474 }
4475
4476 let language = buffer.language()?;
4477 if let Some(restrict_to_languages) = restrict_to_languages {
4478 if !restrict_to_languages.contains(language) {
4479 return None;
4480 }
4481 }
4482 Some((
4483 excerpt_id,
4484 (
4485 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4486 buffer.version().clone(),
4487 excerpt_visible_range,
4488 ),
4489 ))
4490 })
4491 .collect()
4492 }
4493
4494 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4495 TextLayoutDetails {
4496 text_system: window.text_system().clone(),
4497 editor_style: self.style.clone().unwrap(),
4498 rem_size: window.rem_size(),
4499 scroll_anchor: self.scroll_manager.anchor(),
4500 visible_rows: self.visible_line_count(),
4501 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4502 }
4503 }
4504
4505 pub fn splice_inlays(
4506 &self,
4507 to_remove: &[InlayId],
4508 to_insert: Vec<Inlay>,
4509 cx: &mut Context<Self>,
4510 ) {
4511 self.display_map.update(cx, |display_map, cx| {
4512 display_map.splice_inlays(to_remove, to_insert, cx)
4513 });
4514 cx.notify();
4515 }
4516
4517 fn trigger_on_type_formatting(
4518 &self,
4519 input: String,
4520 window: &mut Window,
4521 cx: &mut Context<Self>,
4522 ) -> Option<Task<Result<()>>> {
4523 if input.len() != 1 {
4524 return None;
4525 }
4526
4527 let project = self.project.as_ref()?;
4528 let position = self.selections.newest_anchor().head();
4529 let (buffer, buffer_position) = self
4530 .buffer
4531 .read(cx)
4532 .text_anchor_for_position(position, cx)?;
4533
4534 let settings = language_settings::language_settings(
4535 buffer
4536 .read(cx)
4537 .language_at(buffer_position)
4538 .map(|l| l.name()),
4539 buffer.read(cx).file(),
4540 cx,
4541 );
4542 if !settings.use_on_type_format {
4543 return None;
4544 }
4545
4546 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4547 // hence we do LSP request & edit on host side only — add formats to host's history.
4548 let push_to_lsp_host_history = true;
4549 // If this is not the host, append its history with new edits.
4550 let push_to_client_history = project.read(cx).is_via_collab();
4551
4552 let on_type_formatting = project.update(cx, |project, cx| {
4553 project.on_type_format(
4554 buffer.clone(),
4555 buffer_position,
4556 input,
4557 push_to_lsp_host_history,
4558 cx,
4559 )
4560 });
4561 Some(cx.spawn_in(window, async move |editor, cx| {
4562 if let Some(transaction) = on_type_formatting.await? {
4563 if push_to_client_history {
4564 buffer
4565 .update(cx, |buffer, _| {
4566 buffer.push_transaction(transaction, Instant::now());
4567 buffer.finalize_last_transaction();
4568 })
4569 .ok();
4570 }
4571 editor.update(cx, |editor, cx| {
4572 editor.refresh_document_highlights(cx);
4573 })?;
4574 }
4575 Ok(())
4576 }))
4577 }
4578
4579 pub fn show_word_completions(
4580 &mut self,
4581 _: &ShowWordCompletions,
4582 window: &mut Window,
4583 cx: &mut Context<Self>,
4584 ) {
4585 self.open_completions_menu(true, None, window, cx);
4586 }
4587
4588 pub fn show_completions(
4589 &mut self,
4590 options: &ShowCompletions,
4591 window: &mut Window,
4592 cx: &mut Context<Self>,
4593 ) {
4594 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4595 }
4596
4597 fn open_completions_menu(
4598 &mut self,
4599 ignore_completion_provider: bool,
4600 trigger: Option<&str>,
4601 window: &mut Window,
4602 cx: &mut Context<Self>,
4603 ) {
4604 if self.pending_rename.is_some() {
4605 return;
4606 }
4607 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4608 return;
4609 }
4610
4611 let position = self.selections.newest_anchor().head();
4612 if position.diff_base_anchor.is_some() {
4613 return;
4614 }
4615 let (buffer, buffer_position) =
4616 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4617 output
4618 } else {
4619 return;
4620 };
4621 let buffer_snapshot = buffer.read(cx).snapshot();
4622 let show_completion_documentation = buffer_snapshot
4623 .settings_at(buffer_position, cx)
4624 .show_completion_documentation;
4625
4626 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4627
4628 let trigger_kind = match trigger {
4629 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4630 CompletionTriggerKind::TRIGGER_CHARACTER
4631 }
4632 _ => CompletionTriggerKind::INVOKED,
4633 };
4634 let completion_context = CompletionContext {
4635 trigger_character: trigger.and_then(|trigger| {
4636 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4637 Some(String::from(trigger))
4638 } else {
4639 None
4640 }
4641 }),
4642 trigger_kind,
4643 };
4644
4645 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4646 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4647 let word_to_exclude = buffer_snapshot
4648 .text_for_range(old_range.clone())
4649 .collect::<String>();
4650 (
4651 buffer_snapshot.anchor_before(old_range.start)
4652 ..buffer_snapshot.anchor_after(old_range.end),
4653 Some(word_to_exclude),
4654 )
4655 } else {
4656 (buffer_position..buffer_position, None)
4657 };
4658
4659 let completion_settings = language_settings(
4660 buffer_snapshot
4661 .language_at(buffer_position)
4662 .map(|language| language.name()),
4663 buffer_snapshot.file(),
4664 cx,
4665 )
4666 .completions;
4667
4668 // The document can be large, so stay in reasonable bounds when searching for words,
4669 // otherwise completion pop-up might be slow to appear.
4670 const WORD_LOOKUP_ROWS: u32 = 5_000;
4671 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4672 let min_word_search = buffer_snapshot.clip_point(
4673 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4674 Bias::Left,
4675 );
4676 let max_word_search = buffer_snapshot.clip_point(
4677 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4678 Bias::Right,
4679 );
4680 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4681 ..buffer_snapshot.point_to_offset(max_word_search);
4682
4683 let provider = self
4684 .completion_provider
4685 .as_ref()
4686 .filter(|_| !ignore_completion_provider);
4687 let skip_digits = query
4688 .as_ref()
4689 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4690
4691 let (mut words, provided_completions) = match provider {
4692 Some(provider) => {
4693 let completions = provider.completions(
4694 position.excerpt_id,
4695 &buffer,
4696 buffer_position,
4697 completion_context,
4698 window,
4699 cx,
4700 );
4701
4702 let words = match completion_settings.words {
4703 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4704 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4705 .background_spawn(async move {
4706 buffer_snapshot.words_in_range(WordsQuery {
4707 fuzzy_contents: None,
4708 range: word_search_range,
4709 skip_digits,
4710 })
4711 }),
4712 };
4713
4714 (words, completions)
4715 }
4716 None => (
4717 cx.background_spawn(async move {
4718 buffer_snapshot.words_in_range(WordsQuery {
4719 fuzzy_contents: None,
4720 range: word_search_range,
4721 skip_digits,
4722 })
4723 }),
4724 Task::ready(Ok(None)),
4725 ),
4726 };
4727
4728 let sort_completions = provider
4729 .as_ref()
4730 .map_or(false, |provider| provider.sort_completions());
4731
4732 let filter_completions = provider
4733 .as_ref()
4734 .map_or(true, |provider| provider.filter_completions());
4735
4736 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
4737
4738 let id = post_inc(&mut self.next_completion_id);
4739 let task = cx.spawn_in(window, async move |editor, cx| {
4740 async move {
4741 editor.update(cx, |this, _| {
4742 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4743 })?;
4744
4745 let mut completions = Vec::new();
4746 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4747 completions.extend(provided_completions);
4748 if completion_settings.words == WordsCompletionMode::Fallback {
4749 words = Task::ready(BTreeMap::default());
4750 }
4751 }
4752
4753 let mut words = words.await;
4754 if let Some(word_to_exclude) = &word_to_exclude {
4755 words.remove(word_to_exclude);
4756 }
4757 for lsp_completion in &completions {
4758 words.remove(&lsp_completion.new_text);
4759 }
4760 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4761 replace_range: old_range.clone(),
4762 new_text: word.clone(),
4763 label: CodeLabel::plain(word, None),
4764 icon_path: None,
4765 documentation: None,
4766 source: CompletionSource::BufferWord {
4767 word_range,
4768 resolved: false,
4769 },
4770 insert_text_mode: Some(InsertTextMode::AS_IS),
4771 confirm: None,
4772 }));
4773
4774 let menu = if completions.is_empty() {
4775 None
4776 } else {
4777 let mut menu = CompletionsMenu::new(
4778 id,
4779 sort_completions,
4780 show_completion_documentation,
4781 ignore_completion_provider,
4782 position,
4783 buffer.clone(),
4784 completions.into(),
4785 snippet_sort_order,
4786 );
4787
4788 menu.filter(
4789 if filter_completions {
4790 query.as_deref()
4791 } else {
4792 None
4793 },
4794 cx.background_executor().clone(),
4795 )
4796 .await;
4797
4798 menu.visible().then_some(menu)
4799 };
4800
4801 editor.update_in(cx, |editor, window, cx| {
4802 match editor.context_menu.borrow().as_ref() {
4803 None => {}
4804 Some(CodeContextMenu::Completions(prev_menu)) => {
4805 if prev_menu.id > id {
4806 return;
4807 }
4808 }
4809 _ => return,
4810 }
4811
4812 if editor.focus_handle.is_focused(window) && menu.is_some() {
4813 let mut menu = menu.unwrap();
4814 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4815
4816 *editor.context_menu.borrow_mut() =
4817 Some(CodeContextMenu::Completions(menu));
4818
4819 if editor.show_edit_predictions_in_menu() {
4820 editor.update_visible_inline_completion(window, cx);
4821 } else {
4822 editor.discard_inline_completion(false, cx);
4823 }
4824
4825 cx.notify();
4826 } else if editor.completion_tasks.len() <= 1 {
4827 // If there are no more completion tasks and the last menu was
4828 // empty, we should hide it.
4829 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4830 // If it was already hidden and we don't show inline
4831 // completions in the menu, we should also show the
4832 // inline-completion when available.
4833 if was_hidden && editor.show_edit_predictions_in_menu() {
4834 editor.update_visible_inline_completion(window, cx);
4835 }
4836 }
4837 })?;
4838
4839 anyhow::Ok(())
4840 }
4841 .log_err()
4842 .await
4843 });
4844
4845 self.completion_tasks.push((id, task));
4846 }
4847
4848 #[cfg(feature = "test-support")]
4849 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4850 let menu = self.context_menu.borrow();
4851 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4852 let completions = menu.completions.borrow();
4853 Some(completions.to_vec())
4854 } else {
4855 None
4856 }
4857 }
4858
4859 pub fn confirm_completion(
4860 &mut self,
4861 action: &ConfirmCompletion,
4862 window: &mut Window,
4863 cx: &mut Context<Self>,
4864 ) -> Option<Task<Result<()>>> {
4865 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4866 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4867 }
4868
4869 pub fn confirm_completion_insert(
4870 &mut self,
4871 _: &ConfirmCompletionInsert,
4872 window: &mut Window,
4873 cx: &mut Context<Self>,
4874 ) -> Option<Task<Result<()>>> {
4875 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4876 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4877 }
4878
4879 pub fn confirm_completion_replace(
4880 &mut self,
4881 _: &ConfirmCompletionReplace,
4882 window: &mut Window,
4883 cx: &mut Context<Self>,
4884 ) -> Option<Task<Result<()>>> {
4885 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4886 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4887 }
4888
4889 pub fn compose_completion(
4890 &mut self,
4891 action: &ComposeCompletion,
4892 window: &mut Window,
4893 cx: &mut Context<Self>,
4894 ) -> Option<Task<Result<()>>> {
4895 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4896 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4897 }
4898
4899 fn do_completion(
4900 &mut self,
4901 item_ix: Option<usize>,
4902 intent: CompletionIntent,
4903 window: &mut Window,
4904 cx: &mut Context<Editor>,
4905 ) -> Option<Task<Result<()>>> {
4906 use language::ToOffset as _;
4907
4908 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4909 else {
4910 return None;
4911 };
4912
4913 let candidate_id = {
4914 let entries = completions_menu.entries.borrow();
4915 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4916 if self.show_edit_predictions_in_menu() {
4917 self.discard_inline_completion(true, cx);
4918 }
4919 mat.candidate_id
4920 };
4921
4922 let buffer_handle = completions_menu.buffer;
4923 let completion = completions_menu
4924 .completions
4925 .borrow()
4926 .get(candidate_id)?
4927 .clone();
4928 cx.stop_propagation();
4929
4930 let snippet;
4931 let new_text;
4932 if completion.is_snippet() {
4933 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4934 new_text = snippet.as_ref().unwrap().text.clone();
4935 } else {
4936 snippet = None;
4937 new_text = completion.new_text.clone();
4938 };
4939
4940 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4941 let buffer = buffer_handle.read(cx);
4942 let snapshot = self.buffer.read(cx).snapshot(cx);
4943 let replace_range_multibuffer = {
4944 let excerpt = snapshot
4945 .excerpt_containing(self.selections.newest_anchor().range())
4946 .unwrap();
4947 let multibuffer_anchor = snapshot
4948 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
4949 .unwrap()
4950 ..snapshot
4951 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
4952 .unwrap();
4953 multibuffer_anchor.start.to_offset(&snapshot)
4954 ..multibuffer_anchor.end.to_offset(&snapshot)
4955 };
4956 let newest_anchor = self.selections.newest_anchor();
4957 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
4958 return None;
4959 }
4960
4961 let old_text = buffer
4962 .text_for_range(replace_range.clone())
4963 .collect::<String>();
4964 let lookbehind = newest_anchor
4965 .start
4966 .text_anchor
4967 .to_offset(buffer)
4968 .saturating_sub(replace_range.start);
4969 let lookahead = replace_range
4970 .end
4971 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
4972 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
4973 let suffix = &old_text[lookbehind.min(old_text.len())..];
4974
4975 let selections = self.selections.all::<usize>(cx);
4976 let mut ranges = Vec::new();
4977 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4978
4979 for selection in &selections {
4980 let range = if selection.id == newest_anchor.id {
4981 replace_range_multibuffer.clone()
4982 } else {
4983 let mut range = selection.range();
4984
4985 // if prefix is present, don't duplicate it
4986 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
4987 range.start = range.start.saturating_sub(lookbehind);
4988
4989 // if suffix is also present, mimic the newest cursor and replace it
4990 if selection.id != newest_anchor.id
4991 && snapshot.contains_str_at(range.end, suffix)
4992 {
4993 range.end += lookahead;
4994 }
4995 }
4996 range
4997 };
4998
4999 ranges.push(range);
5000
5001 if !self.linked_edit_ranges.is_empty() {
5002 let start_anchor = snapshot.anchor_before(selection.head());
5003 let end_anchor = snapshot.anchor_after(selection.tail());
5004 if let Some(ranges) = self
5005 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5006 {
5007 for (buffer, edits) in ranges {
5008 linked_edits
5009 .entry(buffer.clone())
5010 .or_default()
5011 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5012 }
5013 }
5014 }
5015 }
5016
5017 cx.emit(EditorEvent::InputHandled {
5018 utf16_range_to_replace: None,
5019 text: new_text.clone().into(),
5020 });
5021
5022 self.transact(window, cx, |this, window, cx| {
5023 if let Some(mut snippet) = snippet {
5024 snippet.text = new_text.to_string();
5025 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5026 } else {
5027 this.buffer.update(cx, |buffer, cx| {
5028 let auto_indent = match completion.insert_text_mode {
5029 Some(InsertTextMode::AS_IS) => None,
5030 _ => this.autoindent_mode.clone(),
5031 };
5032 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5033 buffer.edit(edits, auto_indent, cx);
5034 });
5035 }
5036 for (buffer, edits) in linked_edits {
5037 buffer.update(cx, |buffer, cx| {
5038 let snapshot = buffer.snapshot();
5039 let edits = edits
5040 .into_iter()
5041 .map(|(range, text)| {
5042 use text::ToPoint as TP;
5043 let end_point = TP::to_point(&range.end, &snapshot);
5044 let start_point = TP::to_point(&range.start, &snapshot);
5045 (start_point..end_point, text)
5046 })
5047 .sorted_by_key(|(range, _)| range.start);
5048 buffer.edit(edits, None, cx);
5049 })
5050 }
5051
5052 this.refresh_inline_completion(true, false, window, cx);
5053 });
5054
5055 let show_new_completions_on_confirm = completion
5056 .confirm
5057 .as_ref()
5058 .map_or(false, |confirm| confirm(intent, window, cx));
5059 if show_new_completions_on_confirm {
5060 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5061 }
5062
5063 let provider = self.completion_provider.as_ref()?;
5064 drop(completion);
5065 let apply_edits = provider.apply_additional_edits_for_completion(
5066 buffer_handle,
5067 completions_menu.completions.clone(),
5068 candidate_id,
5069 true,
5070 cx,
5071 );
5072
5073 let editor_settings = EditorSettings::get_global(cx);
5074 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5075 // After the code completion is finished, users often want to know what signatures are needed.
5076 // so we should automatically call signature_help
5077 self.show_signature_help(&ShowSignatureHelp, window, cx);
5078 }
5079
5080 Some(cx.foreground_executor().spawn(async move {
5081 apply_edits.await?;
5082 Ok(())
5083 }))
5084 }
5085
5086 pub fn toggle_code_actions(
5087 &mut self,
5088 action: &ToggleCodeActions,
5089 window: &mut Window,
5090 cx: &mut Context<Self>,
5091 ) {
5092 let quick_launch = action.quick_launch;
5093 let mut context_menu = self.context_menu.borrow_mut();
5094 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5095 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5096 // Toggle if we're selecting the same one
5097 *context_menu = None;
5098 cx.notify();
5099 return;
5100 } else {
5101 // Otherwise, clear it and start a new one
5102 *context_menu = None;
5103 cx.notify();
5104 }
5105 }
5106 drop(context_menu);
5107 let snapshot = self.snapshot(window, cx);
5108 let deployed_from_indicator = action.deployed_from_indicator;
5109 let mut task = self.code_actions_task.take();
5110 let action = action.clone();
5111 cx.spawn_in(window, async move |editor, cx| {
5112 while let Some(prev_task) = task {
5113 prev_task.await.log_err();
5114 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5115 }
5116
5117 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5118 if editor.focus_handle.is_focused(window) {
5119 let multibuffer_point = action
5120 .deployed_from_indicator
5121 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5122 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5123 let (buffer, buffer_row) = snapshot
5124 .buffer_snapshot
5125 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5126 .and_then(|(buffer_snapshot, range)| {
5127 editor
5128 .buffer
5129 .read(cx)
5130 .buffer(buffer_snapshot.remote_id())
5131 .map(|buffer| (buffer, range.start.row))
5132 })?;
5133 let (_, code_actions) = editor
5134 .available_code_actions
5135 .clone()
5136 .and_then(|(location, code_actions)| {
5137 let snapshot = location.buffer.read(cx).snapshot();
5138 let point_range = location.range.to_point(&snapshot);
5139 let point_range = point_range.start.row..=point_range.end.row;
5140 if point_range.contains(&buffer_row) {
5141 Some((location, code_actions))
5142 } else {
5143 None
5144 }
5145 })
5146 .unzip();
5147 let buffer_id = buffer.read(cx).remote_id();
5148 let tasks = editor
5149 .tasks
5150 .get(&(buffer_id, buffer_row))
5151 .map(|t| Arc::new(t.to_owned()));
5152 if tasks.is_none() && code_actions.is_none() {
5153 return None;
5154 }
5155
5156 editor.completion_tasks.clear();
5157 editor.discard_inline_completion(false, cx);
5158 let task_context =
5159 tasks
5160 .as_ref()
5161 .zip(editor.project.clone())
5162 .map(|(tasks, project)| {
5163 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5164 });
5165
5166 Some(cx.spawn_in(window, async move |editor, cx| {
5167 let task_context = match task_context {
5168 Some(task_context) => task_context.await,
5169 None => None,
5170 };
5171 let resolved_tasks =
5172 tasks
5173 .zip(task_context.clone())
5174 .map(|(tasks, task_context)| ResolvedTasks {
5175 templates: tasks.resolve(&task_context).collect(),
5176 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5177 multibuffer_point.row,
5178 tasks.column,
5179 )),
5180 });
5181 let spawn_straight_away = quick_launch
5182 && resolved_tasks
5183 .as_ref()
5184 .map_or(false, |tasks| tasks.templates.len() == 1)
5185 && code_actions
5186 .as_ref()
5187 .map_or(true, |actions| actions.is_empty());
5188 let debug_scenarios = editor.update(cx, |editor, cx| {
5189 if cx.has_flag::<DebuggerFeatureFlag>() {
5190 maybe!({
5191 let project = editor.project.as_ref()?;
5192 let dap_store = project.read(cx).dap_store();
5193 let mut scenarios = vec![];
5194 let resolved_tasks = resolved_tasks.as_ref()?;
5195 let debug_adapter: SharedString = buffer
5196 .read(cx)
5197 .language()?
5198 .context_provider()?
5199 .debug_adapter()?
5200 .into();
5201 dap_store.update(cx, |this, cx| {
5202 for (_, task) in &resolved_tasks.templates {
5203 if let Some(scenario) = this
5204 .debug_scenario_for_build_task(
5205 task.resolved.clone(),
5206 SharedString::from(
5207 task.original_task().label.clone(),
5208 ),
5209 debug_adapter.clone(),
5210 cx,
5211 )
5212 {
5213 scenarios.push(scenario);
5214 }
5215 }
5216 });
5217 Some(scenarios)
5218 })
5219 .unwrap_or_default()
5220 } else {
5221 vec![]
5222 }
5223 })?;
5224 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5225 *editor.context_menu.borrow_mut() =
5226 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5227 buffer,
5228 actions: CodeActionContents::new(
5229 resolved_tasks,
5230 code_actions,
5231 debug_scenarios,
5232 task_context.unwrap_or_default(),
5233 ),
5234 selected_item: Default::default(),
5235 scroll_handle: UniformListScrollHandle::default(),
5236 deployed_from_indicator,
5237 }));
5238 if spawn_straight_away {
5239 if let Some(task) = editor.confirm_code_action(
5240 &ConfirmCodeAction { item_ix: Some(0) },
5241 window,
5242 cx,
5243 ) {
5244 cx.notify();
5245 return task;
5246 }
5247 }
5248 cx.notify();
5249 Task::ready(Ok(()))
5250 }) {
5251 task.await
5252 } else {
5253 Ok(())
5254 }
5255 }))
5256 } else {
5257 Some(Task::ready(Ok(())))
5258 }
5259 })?;
5260 if let Some(task) = spawned_test_task {
5261 task.await?;
5262 }
5263
5264 Ok::<_, anyhow::Error>(())
5265 })
5266 .detach_and_log_err(cx);
5267 }
5268
5269 pub fn confirm_code_action(
5270 &mut self,
5271 action: &ConfirmCodeAction,
5272 window: &mut Window,
5273 cx: &mut Context<Self>,
5274 ) -> Option<Task<Result<()>>> {
5275 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5276
5277 let actions_menu =
5278 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5279 menu
5280 } else {
5281 return None;
5282 };
5283
5284 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5285 let action = actions_menu.actions.get(action_ix)?;
5286 let title = action.label();
5287 let buffer = actions_menu.buffer;
5288 let workspace = self.workspace()?;
5289
5290 match action {
5291 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5292 workspace.update(cx, |workspace, cx| {
5293 workspace.schedule_resolved_task(
5294 task_source_kind,
5295 resolved_task,
5296 false,
5297 window,
5298 cx,
5299 );
5300
5301 Some(Task::ready(Ok(())))
5302 })
5303 }
5304 CodeActionsItem::CodeAction {
5305 excerpt_id,
5306 action,
5307 provider,
5308 } => {
5309 let apply_code_action =
5310 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5311 let workspace = workspace.downgrade();
5312 Some(cx.spawn_in(window, async move |editor, cx| {
5313 let project_transaction = apply_code_action.await?;
5314 Self::open_project_transaction(
5315 &editor,
5316 workspace,
5317 project_transaction,
5318 title,
5319 cx,
5320 )
5321 .await
5322 }))
5323 }
5324 CodeActionsItem::DebugScenario(scenario) => {
5325 let context = actions_menu.actions.context.clone();
5326
5327 workspace.update(cx, |workspace, cx| {
5328 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5329 });
5330 Some(Task::ready(Ok(())))
5331 }
5332 }
5333 }
5334
5335 pub async fn open_project_transaction(
5336 this: &WeakEntity<Editor>,
5337 workspace: WeakEntity<Workspace>,
5338 transaction: ProjectTransaction,
5339 title: String,
5340 cx: &mut AsyncWindowContext,
5341 ) -> Result<()> {
5342 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5343 cx.update(|_, cx| {
5344 entries.sort_unstable_by_key(|(buffer, _)| {
5345 buffer.read(cx).file().map(|f| f.path().clone())
5346 });
5347 })?;
5348
5349 // If the project transaction's edits are all contained within this editor, then
5350 // avoid opening a new editor to display them.
5351
5352 if let Some((buffer, transaction)) = entries.first() {
5353 if entries.len() == 1 {
5354 let excerpt = this.update(cx, |editor, cx| {
5355 editor
5356 .buffer()
5357 .read(cx)
5358 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5359 })?;
5360 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5361 if excerpted_buffer == *buffer {
5362 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5363 let excerpt_range = excerpt_range.to_offset(buffer);
5364 buffer
5365 .edited_ranges_for_transaction::<usize>(transaction)
5366 .all(|range| {
5367 excerpt_range.start <= range.start
5368 && excerpt_range.end >= range.end
5369 })
5370 })?;
5371
5372 if all_edits_within_excerpt {
5373 return Ok(());
5374 }
5375 }
5376 }
5377 }
5378 } else {
5379 return Ok(());
5380 }
5381
5382 let mut ranges_to_highlight = Vec::new();
5383 let excerpt_buffer = cx.new(|cx| {
5384 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5385 for (buffer_handle, transaction) in &entries {
5386 let edited_ranges = buffer_handle
5387 .read(cx)
5388 .edited_ranges_for_transaction::<Point>(transaction)
5389 .collect::<Vec<_>>();
5390 let (ranges, _) = multibuffer.set_excerpts_for_path(
5391 PathKey::for_buffer(buffer_handle, cx),
5392 buffer_handle.clone(),
5393 edited_ranges,
5394 DEFAULT_MULTIBUFFER_CONTEXT,
5395 cx,
5396 );
5397
5398 ranges_to_highlight.extend(ranges);
5399 }
5400 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5401 multibuffer
5402 })?;
5403
5404 workspace.update_in(cx, |workspace, window, cx| {
5405 let project = workspace.project().clone();
5406 let editor =
5407 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5408 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5409 editor.update(cx, |editor, cx| {
5410 editor.highlight_background::<Self>(
5411 &ranges_to_highlight,
5412 |theme| theme.editor_highlighted_line_background,
5413 cx,
5414 );
5415 });
5416 })?;
5417
5418 Ok(())
5419 }
5420
5421 pub fn clear_code_action_providers(&mut self) {
5422 self.code_action_providers.clear();
5423 self.available_code_actions.take();
5424 }
5425
5426 pub fn add_code_action_provider(
5427 &mut self,
5428 provider: Rc<dyn CodeActionProvider>,
5429 window: &mut Window,
5430 cx: &mut Context<Self>,
5431 ) {
5432 if self
5433 .code_action_providers
5434 .iter()
5435 .any(|existing_provider| existing_provider.id() == provider.id())
5436 {
5437 return;
5438 }
5439
5440 self.code_action_providers.push(provider);
5441 self.refresh_code_actions(window, cx);
5442 }
5443
5444 pub fn remove_code_action_provider(
5445 &mut self,
5446 id: Arc<str>,
5447 window: &mut Window,
5448 cx: &mut Context<Self>,
5449 ) {
5450 self.code_action_providers
5451 .retain(|provider| provider.id() != id);
5452 self.refresh_code_actions(window, cx);
5453 }
5454
5455 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5456 let newest_selection = self.selections.newest_anchor().clone();
5457 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5458 let buffer = self.buffer.read(cx);
5459 if newest_selection.head().diff_base_anchor.is_some() {
5460 return None;
5461 }
5462 let (start_buffer, start) =
5463 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5464 let (end_buffer, end) =
5465 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5466 if start_buffer != end_buffer {
5467 return None;
5468 }
5469
5470 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5471 cx.background_executor()
5472 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5473 .await;
5474
5475 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5476 let providers = this.code_action_providers.clone();
5477 let tasks = this
5478 .code_action_providers
5479 .iter()
5480 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5481 .collect::<Vec<_>>();
5482 (providers, tasks)
5483 })?;
5484
5485 let mut actions = Vec::new();
5486 for (provider, provider_actions) in
5487 providers.into_iter().zip(future::join_all(tasks).await)
5488 {
5489 if let Some(provider_actions) = provider_actions.log_err() {
5490 actions.extend(provider_actions.into_iter().map(|action| {
5491 AvailableCodeAction {
5492 excerpt_id: newest_selection.start.excerpt_id,
5493 action,
5494 provider: provider.clone(),
5495 }
5496 }));
5497 }
5498 }
5499
5500 this.update(cx, |this, cx| {
5501 this.available_code_actions = if actions.is_empty() {
5502 None
5503 } else {
5504 Some((
5505 Location {
5506 buffer: start_buffer,
5507 range: start..end,
5508 },
5509 actions.into(),
5510 ))
5511 };
5512 cx.notify();
5513 })
5514 }));
5515 None
5516 }
5517
5518 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5519 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5520 self.show_git_blame_inline = false;
5521
5522 self.show_git_blame_inline_delay_task =
5523 Some(cx.spawn_in(window, async move |this, cx| {
5524 cx.background_executor().timer(delay).await;
5525
5526 this.update(cx, |this, cx| {
5527 this.show_git_blame_inline = true;
5528 cx.notify();
5529 })
5530 .log_err();
5531 }));
5532 }
5533 }
5534
5535 fn show_blame_popover(
5536 &mut self,
5537 blame_entry: &BlameEntry,
5538 position: gpui::Point<Pixels>,
5539 cx: &mut Context<Self>,
5540 ) {
5541 if let Some(state) = &mut self.inline_blame_popover {
5542 state.hide_task.take();
5543 cx.notify();
5544 } else {
5545 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5546 let show_task = cx.spawn(async move |editor, cx| {
5547 cx.background_executor()
5548 .timer(std::time::Duration::from_millis(delay))
5549 .await;
5550 editor
5551 .update(cx, |editor, cx| {
5552 if let Some(state) = &mut editor.inline_blame_popover {
5553 state.show_task = None;
5554 cx.notify();
5555 }
5556 })
5557 .ok();
5558 });
5559 let Some(blame) = self.blame.as_ref() else {
5560 return;
5561 };
5562 let blame = blame.read(cx);
5563 let details = blame.details_for_entry(&blame_entry);
5564 let markdown = cx.new(|cx| {
5565 Markdown::new(
5566 details
5567 .as_ref()
5568 .map(|message| message.message.clone())
5569 .unwrap_or_default(),
5570 None,
5571 None,
5572 cx,
5573 )
5574 });
5575 self.inline_blame_popover = Some(InlineBlamePopover {
5576 position,
5577 show_task: Some(show_task),
5578 hide_task: None,
5579 popover_bounds: None,
5580 popover_state: InlineBlamePopoverState {
5581 scroll_handle: ScrollHandle::new(),
5582 commit_message: details,
5583 markdown,
5584 },
5585 });
5586 }
5587 }
5588
5589 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5590 if let Some(state) = &mut self.inline_blame_popover {
5591 if state.show_task.is_some() {
5592 self.inline_blame_popover.take();
5593 cx.notify();
5594 } else {
5595 let hide_task = cx.spawn(async move |editor, cx| {
5596 cx.background_executor()
5597 .timer(std::time::Duration::from_millis(100))
5598 .await;
5599 editor
5600 .update(cx, |editor, cx| {
5601 editor.inline_blame_popover.take();
5602 cx.notify();
5603 })
5604 .ok();
5605 });
5606 state.hide_task = Some(hide_task);
5607 }
5608 }
5609 }
5610
5611 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5612 if self.pending_rename.is_some() {
5613 return None;
5614 }
5615
5616 let provider = self.semantics_provider.clone()?;
5617 let buffer = self.buffer.read(cx);
5618 let newest_selection = self.selections.newest_anchor().clone();
5619 let cursor_position = newest_selection.head();
5620 let (cursor_buffer, cursor_buffer_position) =
5621 buffer.text_anchor_for_position(cursor_position, cx)?;
5622 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5623 if cursor_buffer != tail_buffer {
5624 return None;
5625 }
5626 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5627 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5628 cx.background_executor()
5629 .timer(Duration::from_millis(debounce))
5630 .await;
5631
5632 let highlights = if let Some(highlights) = cx
5633 .update(|cx| {
5634 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5635 })
5636 .ok()
5637 .flatten()
5638 {
5639 highlights.await.log_err()
5640 } else {
5641 None
5642 };
5643
5644 if let Some(highlights) = highlights {
5645 this.update(cx, |this, cx| {
5646 if this.pending_rename.is_some() {
5647 return;
5648 }
5649
5650 let buffer_id = cursor_position.buffer_id;
5651 let buffer = this.buffer.read(cx);
5652 if !buffer
5653 .text_anchor_for_position(cursor_position, cx)
5654 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5655 {
5656 return;
5657 }
5658
5659 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5660 let mut write_ranges = Vec::new();
5661 let mut read_ranges = Vec::new();
5662 for highlight in highlights {
5663 for (excerpt_id, excerpt_range) in
5664 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5665 {
5666 let start = highlight
5667 .range
5668 .start
5669 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5670 let end = highlight
5671 .range
5672 .end
5673 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5674 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5675 continue;
5676 }
5677
5678 let range = Anchor {
5679 buffer_id,
5680 excerpt_id,
5681 text_anchor: start,
5682 diff_base_anchor: None,
5683 }..Anchor {
5684 buffer_id,
5685 excerpt_id,
5686 text_anchor: end,
5687 diff_base_anchor: None,
5688 };
5689 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5690 write_ranges.push(range);
5691 } else {
5692 read_ranges.push(range);
5693 }
5694 }
5695 }
5696
5697 this.highlight_background::<DocumentHighlightRead>(
5698 &read_ranges,
5699 |theme| theme.editor_document_highlight_read_background,
5700 cx,
5701 );
5702 this.highlight_background::<DocumentHighlightWrite>(
5703 &write_ranges,
5704 |theme| theme.editor_document_highlight_write_background,
5705 cx,
5706 );
5707 cx.notify();
5708 })
5709 .log_err();
5710 }
5711 }));
5712 None
5713 }
5714
5715 fn prepare_highlight_query_from_selection(
5716 &mut self,
5717 cx: &mut Context<Editor>,
5718 ) -> Option<(String, Range<Anchor>)> {
5719 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5720 return None;
5721 }
5722 if !EditorSettings::get_global(cx).selection_highlight {
5723 return None;
5724 }
5725 if self.selections.count() != 1 || self.selections.line_mode {
5726 return None;
5727 }
5728 let selection = self.selections.newest::<Point>(cx);
5729 if selection.is_empty() || selection.start.row != selection.end.row {
5730 return None;
5731 }
5732 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5733 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5734 let query = multi_buffer_snapshot
5735 .text_for_range(selection_anchor_range.clone())
5736 .collect::<String>();
5737 if query.trim().is_empty() {
5738 return None;
5739 }
5740 Some((query, selection_anchor_range))
5741 }
5742
5743 fn update_selection_occurrence_highlights(
5744 &mut self,
5745 query_text: String,
5746 query_range: Range<Anchor>,
5747 multi_buffer_range_to_query: Range<Point>,
5748 use_debounce: bool,
5749 window: &mut Window,
5750 cx: &mut Context<Editor>,
5751 ) -> Task<()> {
5752 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5753 cx.spawn_in(window, async move |editor, cx| {
5754 if use_debounce {
5755 cx.background_executor()
5756 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5757 .await;
5758 }
5759 let match_task = cx.background_spawn(async move {
5760 let buffer_ranges = multi_buffer_snapshot
5761 .range_to_buffer_ranges(multi_buffer_range_to_query)
5762 .into_iter()
5763 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5764 let mut match_ranges = Vec::new();
5765 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5766 match_ranges.extend(
5767 project::search::SearchQuery::text(
5768 query_text.clone(),
5769 false,
5770 false,
5771 false,
5772 Default::default(),
5773 Default::default(),
5774 false,
5775 None,
5776 )
5777 .unwrap()
5778 .search(&buffer_snapshot, Some(search_range.clone()))
5779 .await
5780 .into_iter()
5781 .filter_map(|match_range| {
5782 let match_start = buffer_snapshot
5783 .anchor_after(search_range.start + match_range.start);
5784 let match_end =
5785 buffer_snapshot.anchor_before(search_range.start + match_range.end);
5786 let match_anchor_range = Anchor::range_in_buffer(
5787 excerpt_id,
5788 buffer_snapshot.remote_id(),
5789 match_start..match_end,
5790 );
5791 (match_anchor_range != query_range).then_some(match_anchor_range)
5792 }),
5793 );
5794 }
5795 match_ranges
5796 });
5797 let match_ranges = match_task.await;
5798 editor
5799 .update_in(cx, |editor, _, cx| {
5800 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5801 if !match_ranges.is_empty() {
5802 editor.highlight_background::<SelectedTextHighlight>(
5803 &match_ranges,
5804 |theme| theme.editor_document_highlight_bracket_background,
5805 cx,
5806 )
5807 }
5808 })
5809 .log_err();
5810 })
5811 }
5812
5813 fn refresh_selected_text_highlights(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
5814 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5815 else {
5816 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5817 self.quick_selection_highlight_task.take();
5818 self.debounced_selection_highlight_task.take();
5819 return;
5820 };
5821 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5822 if self
5823 .quick_selection_highlight_task
5824 .as_ref()
5825 .map_or(true, |(prev_anchor_range, _)| {
5826 prev_anchor_range != &query_range
5827 })
5828 {
5829 let multi_buffer_visible_start = self
5830 .scroll_manager
5831 .anchor()
5832 .anchor
5833 .to_point(&multi_buffer_snapshot);
5834 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5835 multi_buffer_visible_start
5836 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5837 Bias::Left,
5838 );
5839 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5840 self.quick_selection_highlight_task = Some((
5841 query_range.clone(),
5842 self.update_selection_occurrence_highlights(
5843 query_text.clone(),
5844 query_range.clone(),
5845 multi_buffer_visible_range,
5846 false,
5847 window,
5848 cx,
5849 ),
5850 ));
5851 }
5852 if self
5853 .debounced_selection_highlight_task
5854 .as_ref()
5855 .map_or(true, |(prev_anchor_range, _)| {
5856 prev_anchor_range != &query_range
5857 })
5858 {
5859 let multi_buffer_start = multi_buffer_snapshot
5860 .anchor_before(0)
5861 .to_point(&multi_buffer_snapshot);
5862 let multi_buffer_end = multi_buffer_snapshot
5863 .anchor_after(multi_buffer_snapshot.len())
5864 .to_point(&multi_buffer_snapshot);
5865 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
5866 self.debounced_selection_highlight_task = Some((
5867 query_range.clone(),
5868 self.update_selection_occurrence_highlights(
5869 query_text,
5870 query_range,
5871 multi_buffer_full_range,
5872 true,
5873 window,
5874 cx,
5875 ),
5876 ));
5877 }
5878 }
5879
5880 pub fn refresh_inline_completion(
5881 &mut self,
5882 debounce: bool,
5883 user_requested: bool,
5884 window: &mut Window,
5885 cx: &mut Context<Self>,
5886 ) -> Option<()> {
5887 let provider = self.edit_prediction_provider()?;
5888 let cursor = self.selections.newest_anchor().head();
5889 let (buffer, cursor_buffer_position) =
5890 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5891
5892 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5893 self.discard_inline_completion(false, cx);
5894 return None;
5895 }
5896
5897 if !user_requested
5898 && (!self.should_show_edit_predictions()
5899 || !self.is_focused(window)
5900 || buffer.read(cx).is_empty())
5901 {
5902 self.discard_inline_completion(false, cx);
5903 return None;
5904 }
5905
5906 self.update_visible_inline_completion(window, cx);
5907 provider.refresh(
5908 self.project.clone(),
5909 buffer,
5910 cursor_buffer_position,
5911 debounce,
5912 cx,
5913 );
5914 Some(())
5915 }
5916
5917 fn show_edit_predictions_in_menu(&self) -> bool {
5918 match self.edit_prediction_settings {
5919 EditPredictionSettings::Disabled => false,
5920 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5921 }
5922 }
5923
5924 pub fn edit_predictions_enabled(&self) -> bool {
5925 match self.edit_prediction_settings {
5926 EditPredictionSettings::Disabled => false,
5927 EditPredictionSettings::Enabled { .. } => true,
5928 }
5929 }
5930
5931 fn edit_prediction_requires_modifier(&self) -> bool {
5932 match self.edit_prediction_settings {
5933 EditPredictionSettings::Disabled => false,
5934 EditPredictionSettings::Enabled {
5935 preview_requires_modifier,
5936 ..
5937 } => preview_requires_modifier,
5938 }
5939 }
5940
5941 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5942 if self.edit_prediction_provider.is_none() {
5943 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5944 } else {
5945 let selection = self.selections.newest_anchor();
5946 let cursor = selection.head();
5947
5948 if let Some((buffer, cursor_buffer_position)) =
5949 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5950 {
5951 self.edit_prediction_settings =
5952 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5953 }
5954 }
5955 }
5956
5957 fn edit_prediction_settings_at_position(
5958 &self,
5959 buffer: &Entity<Buffer>,
5960 buffer_position: language::Anchor,
5961 cx: &App,
5962 ) -> EditPredictionSettings {
5963 if !self.mode.is_full()
5964 || !self.show_inline_completions_override.unwrap_or(true)
5965 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5966 {
5967 return EditPredictionSettings::Disabled;
5968 }
5969
5970 let buffer = buffer.read(cx);
5971
5972 let file = buffer.file();
5973
5974 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5975 return EditPredictionSettings::Disabled;
5976 };
5977
5978 let by_provider = matches!(
5979 self.menu_inline_completions_policy,
5980 MenuInlineCompletionsPolicy::ByProvider
5981 );
5982
5983 let show_in_menu = by_provider
5984 && self
5985 .edit_prediction_provider
5986 .as_ref()
5987 .map_or(false, |provider| {
5988 provider.provider.show_completions_in_menu()
5989 });
5990
5991 let preview_requires_modifier =
5992 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5993
5994 EditPredictionSettings::Enabled {
5995 show_in_menu,
5996 preview_requires_modifier,
5997 }
5998 }
5999
6000 fn should_show_edit_predictions(&self) -> bool {
6001 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6002 }
6003
6004 pub fn edit_prediction_preview_is_active(&self) -> bool {
6005 matches!(
6006 self.edit_prediction_preview,
6007 EditPredictionPreview::Active { .. }
6008 )
6009 }
6010
6011 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6012 let cursor = self.selections.newest_anchor().head();
6013 if let Some((buffer, cursor_position)) =
6014 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6015 {
6016 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6017 } else {
6018 false
6019 }
6020 }
6021
6022 fn edit_predictions_enabled_in_buffer(
6023 &self,
6024 buffer: &Entity<Buffer>,
6025 buffer_position: language::Anchor,
6026 cx: &App,
6027 ) -> bool {
6028 maybe!({
6029 if self.read_only(cx) {
6030 return Some(false);
6031 }
6032 let provider = self.edit_prediction_provider()?;
6033 if !provider.is_enabled(&buffer, buffer_position, cx) {
6034 return Some(false);
6035 }
6036 let buffer = buffer.read(cx);
6037 let Some(file) = buffer.file() else {
6038 return Some(true);
6039 };
6040 let settings = all_language_settings(Some(file), cx);
6041 Some(settings.edit_predictions_enabled_for_file(file, cx))
6042 })
6043 .unwrap_or(false)
6044 }
6045
6046 fn cycle_inline_completion(
6047 &mut self,
6048 direction: Direction,
6049 window: &mut Window,
6050 cx: &mut Context<Self>,
6051 ) -> Option<()> {
6052 let provider = self.edit_prediction_provider()?;
6053 let cursor = self.selections.newest_anchor().head();
6054 let (buffer, cursor_buffer_position) =
6055 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6056 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6057 return None;
6058 }
6059
6060 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6061 self.update_visible_inline_completion(window, cx);
6062
6063 Some(())
6064 }
6065
6066 pub fn show_inline_completion(
6067 &mut self,
6068 _: &ShowEditPrediction,
6069 window: &mut Window,
6070 cx: &mut Context<Self>,
6071 ) {
6072 if !self.has_active_inline_completion() {
6073 self.refresh_inline_completion(false, true, window, cx);
6074 return;
6075 }
6076
6077 self.update_visible_inline_completion(window, cx);
6078 }
6079
6080 pub fn display_cursor_names(
6081 &mut self,
6082 _: &DisplayCursorNames,
6083 window: &mut Window,
6084 cx: &mut Context<Self>,
6085 ) {
6086 self.show_cursor_names(window, cx);
6087 }
6088
6089 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6090 self.show_cursor_names = true;
6091 cx.notify();
6092 cx.spawn_in(window, async move |this, cx| {
6093 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6094 this.update(cx, |this, cx| {
6095 this.show_cursor_names = false;
6096 cx.notify()
6097 })
6098 .ok()
6099 })
6100 .detach();
6101 }
6102
6103 pub fn next_edit_prediction(
6104 &mut self,
6105 _: &NextEditPrediction,
6106 window: &mut Window,
6107 cx: &mut Context<Self>,
6108 ) {
6109 if self.has_active_inline_completion() {
6110 self.cycle_inline_completion(Direction::Next, window, cx);
6111 } else {
6112 let is_copilot_disabled = self
6113 .refresh_inline_completion(false, true, window, cx)
6114 .is_none();
6115 if is_copilot_disabled {
6116 cx.propagate();
6117 }
6118 }
6119 }
6120
6121 pub fn previous_edit_prediction(
6122 &mut self,
6123 _: &PreviousEditPrediction,
6124 window: &mut Window,
6125 cx: &mut Context<Self>,
6126 ) {
6127 if self.has_active_inline_completion() {
6128 self.cycle_inline_completion(Direction::Prev, window, cx);
6129 } else {
6130 let is_copilot_disabled = self
6131 .refresh_inline_completion(false, true, window, cx)
6132 .is_none();
6133 if is_copilot_disabled {
6134 cx.propagate();
6135 }
6136 }
6137 }
6138
6139 pub fn accept_edit_prediction(
6140 &mut self,
6141 _: &AcceptEditPrediction,
6142 window: &mut Window,
6143 cx: &mut Context<Self>,
6144 ) {
6145 if self.show_edit_predictions_in_menu() {
6146 self.hide_context_menu(window, cx);
6147 }
6148
6149 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6150 return;
6151 };
6152
6153 self.report_inline_completion_event(
6154 active_inline_completion.completion_id.clone(),
6155 true,
6156 cx,
6157 );
6158
6159 match &active_inline_completion.completion {
6160 InlineCompletion::Move { target, .. } => {
6161 let target = *target;
6162
6163 if let Some(position_map) = &self.last_position_map {
6164 if position_map
6165 .visible_row_range
6166 .contains(&target.to_display_point(&position_map.snapshot).row())
6167 || !self.edit_prediction_requires_modifier()
6168 {
6169 self.unfold_ranges(&[target..target], true, false, cx);
6170 // Note that this is also done in vim's handler of the Tab action.
6171 self.change_selections(
6172 Some(Autoscroll::newest()),
6173 window,
6174 cx,
6175 |selections| {
6176 selections.select_anchor_ranges([target..target]);
6177 },
6178 );
6179 self.clear_row_highlights::<EditPredictionPreview>();
6180
6181 self.edit_prediction_preview
6182 .set_previous_scroll_position(None);
6183 } else {
6184 self.edit_prediction_preview
6185 .set_previous_scroll_position(Some(
6186 position_map.snapshot.scroll_anchor,
6187 ));
6188
6189 self.highlight_rows::<EditPredictionPreview>(
6190 target..target,
6191 cx.theme().colors().editor_highlighted_line_background,
6192 RowHighlightOptions {
6193 autoscroll: true,
6194 ..Default::default()
6195 },
6196 cx,
6197 );
6198 self.request_autoscroll(Autoscroll::fit(), cx);
6199 }
6200 }
6201 }
6202 InlineCompletion::Edit { edits, .. } => {
6203 if let Some(provider) = self.edit_prediction_provider() {
6204 provider.accept(cx);
6205 }
6206
6207 let snapshot = self.buffer.read(cx).snapshot(cx);
6208 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6209
6210 self.buffer.update(cx, |buffer, cx| {
6211 buffer.edit(edits.iter().cloned(), None, cx)
6212 });
6213
6214 self.change_selections(None, window, cx, |s| {
6215 s.select_anchor_ranges([last_edit_end..last_edit_end])
6216 });
6217
6218 self.update_visible_inline_completion(window, cx);
6219 if self.active_inline_completion.is_none() {
6220 self.refresh_inline_completion(true, true, window, cx);
6221 }
6222
6223 cx.notify();
6224 }
6225 }
6226
6227 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6228 }
6229
6230 pub fn accept_partial_inline_completion(
6231 &mut self,
6232 _: &AcceptPartialEditPrediction,
6233 window: &mut Window,
6234 cx: &mut Context<Self>,
6235 ) {
6236 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6237 return;
6238 };
6239 if self.selections.count() != 1 {
6240 return;
6241 }
6242
6243 self.report_inline_completion_event(
6244 active_inline_completion.completion_id.clone(),
6245 true,
6246 cx,
6247 );
6248
6249 match &active_inline_completion.completion {
6250 InlineCompletion::Move { target, .. } => {
6251 let target = *target;
6252 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6253 selections.select_anchor_ranges([target..target]);
6254 });
6255 }
6256 InlineCompletion::Edit { edits, .. } => {
6257 // Find an insertion that starts at the cursor position.
6258 let snapshot = self.buffer.read(cx).snapshot(cx);
6259 let cursor_offset = self.selections.newest::<usize>(cx).head();
6260 let insertion = edits.iter().find_map(|(range, text)| {
6261 let range = range.to_offset(&snapshot);
6262 if range.is_empty() && range.start == cursor_offset {
6263 Some(text)
6264 } else {
6265 None
6266 }
6267 });
6268
6269 if let Some(text) = insertion {
6270 let mut partial_completion = text
6271 .chars()
6272 .by_ref()
6273 .take_while(|c| c.is_alphabetic())
6274 .collect::<String>();
6275 if partial_completion.is_empty() {
6276 partial_completion = text
6277 .chars()
6278 .by_ref()
6279 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6280 .collect::<String>();
6281 }
6282
6283 cx.emit(EditorEvent::InputHandled {
6284 utf16_range_to_replace: None,
6285 text: partial_completion.clone().into(),
6286 });
6287
6288 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6289
6290 self.refresh_inline_completion(true, true, window, cx);
6291 cx.notify();
6292 } else {
6293 self.accept_edit_prediction(&Default::default(), window, cx);
6294 }
6295 }
6296 }
6297 }
6298
6299 fn discard_inline_completion(
6300 &mut self,
6301 should_report_inline_completion_event: bool,
6302 cx: &mut Context<Self>,
6303 ) -> bool {
6304 if should_report_inline_completion_event {
6305 let completion_id = self
6306 .active_inline_completion
6307 .as_ref()
6308 .and_then(|active_completion| active_completion.completion_id.clone());
6309
6310 self.report_inline_completion_event(completion_id, false, cx);
6311 }
6312
6313 if let Some(provider) = self.edit_prediction_provider() {
6314 provider.discard(cx);
6315 }
6316
6317 self.take_active_inline_completion(cx)
6318 }
6319
6320 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6321 let Some(provider) = self.edit_prediction_provider() else {
6322 return;
6323 };
6324
6325 let Some((_, buffer, _)) = self
6326 .buffer
6327 .read(cx)
6328 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6329 else {
6330 return;
6331 };
6332
6333 let extension = buffer
6334 .read(cx)
6335 .file()
6336 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6337
6338 let event_type = match accepted {
6339 true => "Edit Prediction Accepted",
6340 false => "Edit Prediction Discarded",
6341 };
6342 telemetry::event!(
6343 event_type,
6344 provider = provider.name(),
6345 prediction_id = id,
6346 suggestion_accepted = accepted,
6347 file_extension = extension,
6348 );
6349 }
6350
6351 pub fn has_active_inline_completion(&self) -> bool {
6352 self.active_inline_completion.is_some()
6353 }
6354
6355 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6356 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6357 return false;
6358 };
6359
6360 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6361 self.clear_highlights::<InlineCompletionHighlight>(cx);
6362 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6363 true
6364 }
6365
6366 /// Returns true when we're displaying the edit prediction popover below the cursor
6367 /// like we are not previewing and the LSP autocomplete menu is visible
6368 /// or we are in `when_holding_modifier` mode.
6369 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6370 if self.edit_prediction_preview_is_active()
6371 || !self.show_edit_predictions_in_menu()
6372 || !self.edit_predictions_enabled()
6373 {
6374 return false;
6375 }
6376
6377 if self.has_visible_completions_menu() {
6378 return true;
6379 }
6380
6381 has_completion && self.edit_prediction_requires_modifier()
6382 }
6383
6384 fn handle_modifiers_changed(
6385 &mut self,
6386 modifiers: Modifiers,
6387 position_map: &PositionMap,
6388 window: &mut Window,
6389 cx: &mut Context<Self>,
6390 ) {
6391 if self.show_edit_predictions_in_menu() {
6392 self.update_edit_prediction_preview(&modifiers, window, cx);
6393 }
6394
6395 self.update_selection_mode(&modifiers, position_map, window, cx);
6396
6397 let mouse_position = window.mouse_position();
6398 if !position_map.text_hitbox.is_hovered(window) {
6399 return;
6400 }
6401
6402 self.update_hovered_link(
6403 position_map.point_for_position(mouse_position),
6404 &position_map.snapshot,
6405 modifiers,
6406 window,
6407 cx,
6408 )
6409 }
6410
6411 fn update_selection_mode(
6412 &mut self,
6413 modifiers: &Modifiers,
6414 position_map: &PositionMap,
6415 window: &mut Window,
6416 cx: &mut Context<Self>,
6417 ) {
6418 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6419 return;
6420 }
6421
6422 let mouse_position = window.mouse_position();
6423 let point_for_position = position_map.point_for_position(mouse_position);
6424 let position = point_for_position.previous_valid;
6425
6426 self.select(
6427 SelectPhase::BeginColumnar {
6428 position,
6429 reset: false,
6430 goal_column: point_for_position.exact_unclipped.column(),
6431 },
6432 window,
6433 cx,
6434 );
6435 }
6436
6437 fn update_edit_prediction_preview(
6438 &mut self,
6439 modifiers: &Modifiers,
6440 window: &mut Window,
6441 cx: &mut Context<Self>,
6442 ) {
6443 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6444 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6445 return;
6446 };
6447
6448 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6449 if matches!(
6450 self.edit_prediction_preview,
6451 EditPredictionPreview::Inactive { .. }
6452 ) {
6453 self.edit_prediction_preview = EditPredictionPreview::Active {
6454 previous_scroll_position: None,
6455 since: Instant::now(),
6456 };
6457
6458 self.update_visible_inline_completion(window, cx);
6459 cx.notify();
6460 }
6461 } else if let EditPredictionPreview::Active {
6462 previous_scroll_position,
6463 since,
6464 } = self.edit_prediction_preview
6465 {
6466 if let (Some(previous_scroll_position), Some(position_map)) =
6467 (previous_scroll_position, self.last_position_map.as_ref())
6468 {
6469 self.set_scroll_position(
6470 previous_scroll_position
6471 .scroll_position(&position_map.snapshot.display_snapshot),
6472 window,
6473 cx,
6474 );
6475 }
6476
6477 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6478 released_too_fast: since.elapsed() < Duration::from_millis(200),
6479 };
6480 self.clear_row_highlights::<EditPredictionPreview>();
6481 self.update_visible_inline_completion(window, cx);
6482 cx.notify();
6483 }
6484 }
6485
6486 fn update_visible_inline_completion(
6487 &mut self,
6488 _window: &mut Window,
6489 cx: &mut Context<Self>,
6490 ) -> Option<()> {
6491 let selection = self.selections.newest_anchor();
6492 let cursor = selection.head();
6493 let multibuffer = self.buffer.read(cx).snapshot(cx);
6494 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6495 let excerpt_id = cursor.excerpt_id;
6496
6497 let show_in_menu = self.show_edit_predictions_in_menu();
6498 let completions_menu_has_precedence = !show_in_menu
6499 && (self.context_menu.borrow().is_some()
6500 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6501
6502 if completions_menu_has_precedence
6503 || !offset_selection.is_empty()
6504 || self
6505 .active_inline_completion
6506 .as_ref()
6507 .map_or(false, |completion| {
6508 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6509 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6510 !invalidation_range.contains(&offset_selection.head())
6511 })
6512 {
6513 self.discard_inline_completion(false, cx);
6514 return None;
6515 }
6516
6517 self.take_active_inline_completion(cx);
6518 let Some(provider) = self.edit_prediction_provider() else {
6519 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6520 return None;
6521 };
6522
6523 let (buffer, cursor_buffer_position) =
6524 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6525
6526 self.edit_prediction_settings =
6527 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6528
6529 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6530
6531 if self.edit_prediction_indent_conflict {
6532 let cursor_point = cursor.to_point(&multibuffer);
6533
6534 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6535
6536 if let Some((_, indent)) = indents.iter().next() {
6537 if indent.len == cursor_point.column {
6538 self.edit_prediction_indent_conflict = false;
6539 }
6540 }
6541 }
6542
6543 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6544 let edits = inline_completion
6545 .edits
6546 .into_iter()
6547 .flat_map(|(range, new_text)| {
6548 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6549 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6550 Some((start..end, new_text))
6551 })
6552 .collect::<Vec<_>>();
6553 if edits.is_empty() {
6554 return None;
6555 }
6556
6557 let first_edit_start = edits.first().unwrap().0.start;
6558 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6559 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6560
6561 let last_edit_end = edits.last().unwrap().0.end;
6562 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6563 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6564
6565 let cursor_row = cursor.to_point(&multibuffer).row;
6566
6567 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6568
6569 let mut inlay_ids = Vec::new();
6570 let invalidation_row_range;
6571 let move_invalidation_row_range = if cursor_row < edit_start_row {
6572 Some(cursor_row..edit_end_row)
6573 } else if cursor_row > edit_end_row {
6574 Some(edit_start_row..cursor_row)
6575 } else {
6576 None
6577 };
6578 let is_move =
6579 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6580 let completion = if is_move {
6581 invalidation_row_range =
6582 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6583 let target = first_edit_start;
6584 InlineCompletion::Move { target, snapshot }
6585 } else {
6586 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6587 && !self.inline_completions_hidden_for_vim_mode;
6588
6589 if show_completions_in_buffer {
6590 if edits
6591 .iter()
6592 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6593 {
6594 let mut inlays = Vec::new();
6595 for (range, new_text) in &edits {
6596 let inlay = Inlay::inline_completion(
6597 post_inc(&mut self.next_inlay_id),
6598 range.start,
6599 new_text.as_str(),
6600 );
6601 inlay_ids.push(inlay.id);
6602 inlays.push(inlay);
6603 }
6604
6605 self.splice_inlays(&[], inlays, cx);
6606 } else {
6607 let background_color = cx.theme().status().deleted_background;
6608 self.highlight_text::<InlineCompletionHighlight>(
6609 edits.iter().map(|(range, _)| range.clone()).collect(),
6610 HighlightStyle {
6611 background_color: Some(background_color),
6612 ..Default::default()
6613 },
6614 cx,
6615 );
6616 }
6617 }
6618
6619 invalidation_row_range = edit_start_row..edit_end_row;
6620
6621 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6622 if provider.show_tab_accept_marker() {
6623 EditDisplayMode::TabAccept
6624 } else {
6625 EditDisplayMode::Inline
6626 }
6627 } else {
6628 EditDisplayMode::DiffPopover
6629 };
6630
6631 InlineCompletion::Edit {
6632 edits,
6633 edit_preview: inline_completion.edit_preview,
6634 display_mode,
6635 snapshot,
6636 }
6637 };
6638
6639 let invalidation_range = multibuffer
6640 .anchor_before(Point::new(invalidation_row_range.start, 0))
6641 ..multibuffer.anchor_after(Point::new(
6642 invalidation_row_range.end,
6643 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6644 ));
6645
6646 self.stale_inline_completion_in_menu = None;
6647 self.active_inline_completion = Some(InlineCompletionState {
6648 inlay_ids,
6649 completion,
6650 completion_id: inline_completion.id,
6651 invalidation_range,
6652 });
6653
6654 cx.notify();
6655
6656 Some(())
6657 }
6658
6659 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6660 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6661 }
6662
6663 fn render_code_actions_indicator(
6664 &self,
6665 _style: &EditorStyle,
6666 row: DisplayRow,
6667 is_active: bool,
6668 breakpoint: Option<&(Anchor, Breakpoint)>,
6669 cx: &mut Context<Self>,
6670 ) -> Option<IconButton> {
6671 let color = Color::Muted;
6672 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6673 let show_tooltip = !self.context_menu_visible();
6674
6675 if self.available_code_actions.is_some() {
6676 Some(
6677 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6678 .shape(ui::IconButtonShape::Square)
6679 .icon_size(IconSize::XSmall)
6680 .icon_color(color)
6681 .toggle_state(is_active)
6682 .when(show_tooltip, |this| {
6683 this.tooltip({
6684 let focus_handle = self.focus_handle.clone();
6685 move |window, cx| {
6686 Tooltip::for_action_in(
6687 "Toggle Code Actions",
6688 &ToggleCodeActions {
6689 deployed_from_indicator: None,
6690 quick_launch: false,
6691 },
6692 &focus_handle,
6693 window,
6694 cx,
6695 )
6696 }
6697 })
6698 })
6699 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
6700 let quick_launch = e.down.button == MouseButton::Left;
6701 window.focus(&editor.focus_handle(cx));
6702 editor.toggle_code_actions(
6703 &ToggleCodeActions {
6704 deployed_from_indicator: Some(row),
6705 quick_launch,
6706 },
6707 window,
6708 cx,
6709 );
6710 }))
6711 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6712 editor.set_breakpoint_context_menu(
6713 row,
6714 position,
6715 event.down.position,
6716 window,
6717 cx,
6718 );
6719 })),
6720 )
6721 } else {
6722 None
6723 }
6724 }
6725
6726 fn clear_tasks(&mut self) {
6727 self.tasks.clear()
6728 }
6729
6730 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6731 if self.tasks.insert(key, value).is_some() {
6732 // This case should hopefully be rare, but just in case...
6733 log::error!(
6734 "multiple different run targets found on a single line, only the last target will be rendered"
6735 )
6736 }
6737 }
6738
6739 /// Get all display points of breakpoints that will be rendered within editor
6740 ///
6741 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6742 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6743 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6744 fn active_breakpoints(
6745 &self,
6746 range: Range<DisplayRow>,
6747 window: &mut Window,
6748 cx: &mut Context<Self>,
6749 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6750 let mut breakpoint_display_points = HashMap::default();
6751
6752 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6753 return breakpoint_display_points;
6754 };
6755
6756 let snapshot = self.snapshot(window, cx);
6757
6758 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6759 let Some(project) = self.project.as_ref() else {
6760 return breakpoint_display_points;
6761 };
6762
6763 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6764 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6765
6766 for (buffer_snapshot, range, excerpt_id) in
6767 multi_buffer_snapshot.range_to_buffer_ranges(range)
6768 {
6769 let Some(buffer) = project.read_with(cx, |this, cx| {
6770 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6771 }) else {
6772 continue;
6773 };
6774 let breakpoints = breakpoint_store.read(cx).breakpoints(
6775 &buffer,
6776 Some(
6777 buffer_snapshot.anchor_before(range.start)
6778 ..buffer_snapshot.anchor_after(range.end),
6779 ),
6780 buffer_snapshot,
6781 cx,
6782 );
6783 for (anchor, breakpoint) in breakpoints {
6784 let multi_buffer_anchor =
6785 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6786 let position = multi_buffer_anchor
6787 .to_point(&multi_buffer_snapshot)
6788 .to_display_point(&snapshot);
6789
6790 breakpoint_display_points
6791 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6792 }
6793 }
6794
6795 breakpoint_display_points
6796 }
6797
6798 fn breakpoint_context_menu(
6799 &self,
6800 anchor: Anchor,
6801 window: &mut Window,
6802 cx: &mut Context<Self>,
6803 ) -> Entity<ui::ContextMenu> {
6804 let weak_editor = cx.weak_entity();
6805 let focus_handle = self.focus_handle(cx);
6806
6807 let row = self
6808 .buffer
6809 .read(cx)
6810 .snapshot(cx)
6811 .summary_for_anchor::<Point>(&anchor)
6812 .row;
6813
6814 let breakpoint = self
6815 .breakpoint_at_row(row, window, cx)
6816 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6817
6818 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6819 "Edit Log Breakpoint"
6820 } else {
6821 "Set Log Breakpoint"
6822 };
6823
6824 let condition_breakpoint_msg = if breakpoint
6825 .as_ref()
6826 .is_some_and(|bp| bp.1.condition.is_some())
6827 {
6828 "Edit Condition Breakpoint"
6829 } else {
6830 "Set Condition Breakpoint"
6831 };
6832
6833 let hit_condition_breakpoint_msg = if breakpoint
6834 .as_ref()
6835 .is_some_and(|bp| bp.1.hit_condition.is_some())
6836 {
6837 "Edit Hit Condition Breakpoint"
6838 } else {
6839 "Set Hit Condition Breakpoint"
6840 };
6841
6842 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6843 "Unset Breakpoint"
6844 } else {
6845 "Set Breakpoint"
6846 };
6847
6848 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6849 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6850
6851 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6852 BreakpointState::Enabled => Some("Disable"),
6853 BreakpointState::Disabled => Some("Enable"),
6854 });
6855
6856 let (anchor, breakpoint) =
6857 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6858
6859 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6860 menu.on_blur_subscription(Subscription::new(|| {}))
6861 .context(focus_handle)
6862 .when(run_to_cursor, |this| {
6863 let weak_editor = weak_editor.clone();
6864 this.entry("Run to cursor", None, move |window, cx| {
6865 weak_editor
6866 .update(cx, |editor, cx| {
6867 editor.change_selections(None, window, cx, |s| {
6868 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6869 });
6870 })
6871 .ok();
6872
6873 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6874 })
6875 .separator()
6876 })
6877 .when_some(toggle_state_msg, |this, msg| {
6878 this.entry(msg, None, {
6879 let weak_editor = weak_editor.clone();
6880 let breakpoint = breakpoint.clone();
6881 move |_window, cx| {
6882 weak_editor
6883 .update(cx, |this, cx| {
6884 this.edit_breakpoint_at_anchor(
6885 anchor,
6886 breakpoint.as_ref().clone(),
6887 BreakpointEditAction::InvertState,
6888 cx,
6889 );
6890 })
6891 .log_err();
6892 }
6893 })
6894 })
6895 .entry(set_breakpoint_msg, None, {
6896 let weak_editor = weak_editor.clone();
6897 let breakpoint = breakpoint.clone();
6898 move |_window, cx| {
6899 weak_editor
6900 .update(cx, |this, cx| {
6901 this.edit_breakpoint_at_anchor(
6902 anchor,
6903 breakpoint.as_ref().clone(),
6904 BreakpointEditAction::Toggle,
6905 cx,
6906 );
6907 })
6908 .log_err();
6909 }
6910 })
6911 .entry(log_breakpoint_msg, None, {
6912 let breakpoint = breakpoint.clone();
6913 let weak_editor = weak_editor.clone();
6914 move |window, cx| {
6915 weak_editor
6916 .update(cx, |this, cx| {
6917 this.add_edit_breakpoint_block(
6918 anchor,
6919 breakpoint.as_ref(),
6920 BreakpointPromptEditAction::Log,
6921 window,
6922 cx,
6923 );
6924 })
6925 .log_err();
6926 }
6927 })
6928 .entry(condition_breakpoint_msg, None, {
6929 let breakpoint = breakpoint.clone();
6930 let weak_editor = weak_editor.clone();
6931 move |window, cx| {
6932 weak_editor
6933 .update(cx, |this, cx| {
6934 this.add_edit_breakpoint_block(
6935 anchor,
6936 breakpoint.as_ref(),
6937 BreakpointPromptEditAction::Condition,
6938 window,
6939 cx,
6940 );
6941 })
6942 .log_err();
6943 }
6944 })
6945 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6946 weak_editor
6947 .update(cx, |this, cx| {
6948 this.add_edit_breakpoint_block(
6949 anchor,
6950 breakpoint.as_ref(),
6951 BreakpointPromptEditAction::HitCondition,
6952 window,
6953 cx,
6954 );
6955 })
6956 .log_err();
6957 })
6958 })
6959 }
6960
6961 fn render_breakpoint(
6962 &self,
6963 position: Anchor,
6964 row: DisplayRow,
6965 breakpoint: &Breakpoint,
6966 cx: &mut Context<Self>,
6967 ) -> IconButton {
6968 let (color, icon) = {
6969 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6970 (false, false) => ui::IconName::DebugBreakpoint,
6971 (true, false) => ui::IconName::DebugLogBreakpoint,
6972 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6973 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6974 };
6975
6976 let color = if self
6977 .gutter_breakpoint_indicator
6978 .0
6979 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6980 {
6981 Color::Hint
6982 } else {
6983 Color::Debugger
6984 };
6985
6986 (color, icon)
6987 };
6988
6989 let breakpoint = Arc::from(breakpoint.clone());
6990
6991 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6992 .icon_size(IconSize::XSmall)
6993 .size(ui::ButtonSize::None)
6994 .icon_color(color)
6995 .style(ButtonStyle::Transparent)
6996 .on_click(cx.listener({
6997 let breakpoint = breakpoint.clone();
6998
6999 move |editor, event: &ClickEvent, window, cx| {
7000 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7001 BreakpointEditAction::InvertState
7002 } else {
7003 BreakpointEditAction::Toggle
7004 };
7005
7006 window.focus(&editor.focus_handle(cx));
7007 editor.edit_breakpoint_at_anchor(
7008 position,
7009 breakpoint.as_ref().clone(),
7010 edit_action,
7011 cx,
7012 );
7013 }
7014 }))
7015 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7016 editor.set_breakpoint_context_menu(
7017 row,
7018 Some(position),
7019 event.down.position,
7020 window,
7021 cx,
7022 );
7023 }))
7024 }
7025
7026 fn build_tasks_context(
7027 project: &Entity<Project>,
7028 buffer: &Entity<Buffer>,
7029 buffer_row: u32,
7030 tasks: &Arc<RunnableTasks>,
7031 cx: &mut Context<Self>,
7032 ) -> Task<Option<task::TaskContext>> {
7033 let position = Point::new(buffer_row, tasks.column);
7034 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7035 let location = Location {
7036 buffer: buffer.clone(),
7037 range: range_start..range_start,
7038 };
7039 // Fill in the environmental variables from the tree-sitter captures
7040 let mut captured_task_variables = TaskVariables::default();
7041 for (capture_name, value) in tasks.extra_variables.clone() {
7042 captured_task_variables.insert(
7043 task::VariableName::Custom(capture_name.into()),
7044 value.clone(),
7045 );
7046 }
7047 project.update(cx, |project, cx| {
7048 project.task_store().update(cx, |task_store, cx| {
7049 task_store.task_context_for_location(captured_task_variables, location, cx)
7050 })
7051 })
7052 }
7053
7054 pub fn spawn_nearest_task(
7055 &mut self,
7056 action: &SpawnNearestTask,
7057 window: &mut Window,
7058 cx: &mut Context<Self>,
7059 ) {
7060 let Some((workspace, _)) = self.workspace.clone() else {
7061 return;
7062 };
7063 let Some(project) = self.project.clone() else {
7064 return;
7065 };
7066
7067 // Try to find a closest, enclosing node using tree-sitter that has a
7068 // task
7069 let Some((buffer, buffer_row, tasks)) = self
7070 .find_enclosing_node_task(cx)
7071 // Or find the task that's closest in row-distance.
7072 .or_else(|| self.find_closest_task(cx))
7073 else {
7074 return;
7075 };
7076
7077 let reveal_strategy = action.reveal;
7078 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7079 cx.spawn_in(window, async move |_, cx| {
7080 let context = task_context.await?;
7081 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7082
7083 let resolved = &mut resolved_task.resolved;
7084 resolved.reveal = reveal_strategy;
7085
7086 workspace
7087 .update_in(cx, |workspace, window, cx| {
7088 workspace.schedule_resolved_task(
7089 task_source_kind,
7090 resolved_task,
7091 false,
7092 window,
7093 cx,
7094 );
7095 })
7096 .ok()
7097 })
7098 .detach();
7099 }
7100
7101 fn find_closest_task(
7102 &mut self,
7103 cx: &mut Context<Self>,
7104 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7105 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7106
7107 let ((buffer_id, row), tasks) = self
7108 .tasks
7109 .iter()
7110 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7111
7112 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7113 let tasks = Arc::new(tasks.to_owned());
7114 Some((buffer, *row, tasks))
7115 }
7116
7117 fn find_enclosing_node_task(
7118 &mut self,
7119 cx: &mut Context<Self>,
7120 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7121 let snapshot = self.buffer.read(cx).snapshot(cx);
7122 let offset = self.selections.newest::<usize>(cx).head();
7123 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7124 let buffer_id = excerpt.buffer().remote_id();
7125
7126 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7127 let mut cursor = layer.node().walk();
7128
7129 while cursor.goto_first_child_for_byte(offset).is_some() {
7130 if cursor.node().end_byte() == offset {
7131 cursor.goto_next_sibling();
7132 }
7133 }
7134
7135 // Ascend to the smallest ancestor that contains the range and has a task.
7136 loop {
7137 let node = cursor.node();
7138 let node_range = node.byte_range();
7139 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7140
7141 // Check if this node contains our offset
7142 if node_range.start <= offset && node_range.end >= offset {
7143 // If it contains offset, check for task
7144 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7145 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7146 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7147 }
7148 }
7149
7150 if !cursor.goto_parent() {
7151 break;
7152 }
7153 }
7154 None
7155 }
7156
7157 fn render_run_indicator(
7158 &self,
7159 _style: &EditorStyle,
7160 is_active: bool,
7161 row: DisplayRow,
7162 breakpoint: Option<(Anchor, Breakpoint)>,
7163 cx: &mut Context<Self>,
7164 ) -> IconButton {
7165 let color = Color::Muted;
7166 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
7167
7168 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7169 .shape(ui::IconButtonShape::Square)
7170 .icon_size(IconSize::XSmall)
7171 .icon_color(color)
7172 .toggle_state(is_active)
7173 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7174 let quick_launch = e.down.button == MouseButton::Left;
7175 window.focus(&editor.focus_handle(cx));
7176 editor.toggle_code_actions(
7177 &ToggleCodeActions {
7178 deployed_from_indicator: Some(row),
7179 quick_launch,
7180 },
7181 window,
7182 cx,
7183 );
7184 }))
7185 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7186 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7187 }))
7188 }
7189
7190 pub fn context_menu_visible(&self) -> bool {
7191 !self.edit_prediction_preview_is_active()
7192 && self
7193 .context_menu
7194 .borrow()
7195 .as_ref()
7196 .map_or(false, |menu| menu.visible())
7197 }
7198
7199 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7200 self.context_menu
7201 .borrow()
7202 .as_ref()
7203 .map(|menu| menu.origin())
7204 }
7205
7206 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7207 self.context_menu_options = Some(options);
7208 }
7209
7210 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7211 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7212
7213 fn render_edit_prediction_popover(
7214 &mut self,
7215 text_bounds: &Bounds<Pixels>,
7216 content_origin: gpui::Point<Pixels>,
7217 editor_snapshot: &EditorSnapshot,
7218 visible_row_range: Range<DisplayRow>,
7219 scroll_top: f32,
7220 scroll_bottom: f32,
7221 line_layouts: &[LineWithInvisibles],
7222 line_height: Pixels,
7223 scroll_pixel_position: gpui::Point<Pixels>,
7224 newest_selection_head: Option<DisplayPoint>,
7225 editor_width: Pixels,
7226 style: &EditorStyle,
7227 window: &mut Window,
7228 cx: &mut App,
7229 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7230 let active_inline_completion = self.active_inline_completion.as_ref()?;
7231
7232 if self.edit_prediction_visible_in_cursor_popover(true) {
7233 return None;
7234 }
7235
7236 match &active_inline_completion.completion {
7237 InlineCompletion::Move { target, .. } => {
7238 let target_display_point = target.to_display_point(editor_snapshot);
7239
7240 if self.edit_prediction_requires_modifier() {
7241 if !self.edit_prediction_preview_is_active() {
7242 return None;
7243 }
7244
7245 self.render_edit_prediction_modifier_jump_popover(
7246 text_bounds,
7247 content_origin,
7248 visible_row_range,
7249 line_layouts,
7250 line_height,
7251 scroll_pixel_position,
7252 newest_selection_head,
7253 target_display_point,
7254 window,
7255 cx,
7256 )
7257 } else {
7258 self.render_edit_prediction_eager_jump_popover(
7259 text_bounds,
7260 content_origin,
7261 editor_snapshot,
7262 visible_row_range,
7263 scroll_top,
7264 scroll_bottom,
7265 line_height,
7266 scroll_pixel_position,
7267 target_display_point,
7268 editor_width,
7269 window,
7270 cx,
7271 )
7272 }
7273 }
7274 InlineCompletion::Edit {
7275 display_mode: EditDisplayMode::Inline,
7276 ..
7277 } => None,
7278 InlineCompletion::Edit {
7279 display_mode: EditDisplayMode::TabAccept,
7280 edits,
7281 ..
7282 } => {
7283 let range = &edits.first()?.0;
7284 let target_display_point = range.end.to_display_point(editor_snapshot);
7285
7286 self.render_edit_prediction_end_of_line_popover(
7287 "Accept",
7288 editor_snapshot,
7289 visible_row_range,
7290 target_display_point,
7291 line_height,
7292 scroll_pixel_position,
7293 content_origin,
7294 editor_width,
7295 window,
7296 cx,
7297 )
7298 }
7299 InlineCompletion::Edit {
7300 edits,
7301 edit_preview,
7302 display_mode: EditDisplayMode::DiffPopover,
7303 snapshot,
7304 } => self.render_edit_prediction_diff_popover(
7305 text_bounds,
7306 content_origin,
7307 editor_snapshot,
7308 visible_row_range,
7309 line_layouts,
7310 line_height,
7311 scroll_pixel_position,
7312 newest_selection_head,
7313 editor_width,
7314 style,
7315 edits,
7316 edit_preview,
7317 snapshot,
7318 window,
7319 cx,
7320 ),
7321 }
7322 }
7323
7324 fn render_edit_prediction_modifier_jump_popover(
7325 &mut self,
7326 text_bounds: &Bounds<Pixels>,
7327 content_origin: gpui::Point<Pixels>,
7328 visible_row_range: Range<DisplayRow>,
7329 line_layouts: &[LineWithInvisibles],
7330 line_height: Pixels,
7331 scroll_pixel_position: gpui::Point<Pixels>,
7332 newest_selection_head: Option<DisplayPoint>,
7333 target_display_point: DisplayPoint,
7334 window: &mut Window,
7335 cx: &mut App,
7336 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7337 let scrolled_content_origin =
7338 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7339
7340 const SCROLL_PADDING_Y: Pixels = px(12.);
7341
7342 if target_display_point.row() < visible_row_range.start {
7343 return self.render_edit_prediction_scroll_popover(
7344 |_| SCROLL_PADDING_Y,
7345 IconName::ArrowUp,
7346 visible_row_range,
7347 line_layouts,
7348 newest_selection_head,
7349 scrolled_content_origin,
7350 window,
7351 cx,
7352 );
7353 } else if target_display_point.row() >= visible_row_range.end {
7354 return self.render_edit_prediction_scroll_popover(
7355 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7356 IconName::ArrowDown,
7357 visible_row_range,
7358 line_layouts,
7359 newest_selection_head,
7360 scrolled_content_origin,
7361 window,
7362 cx,
7363 );
7364 }
7365
7366 const POLE_WIDTH: Pixels = px(2.);
7367
7368 let line_layout =
7369 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7370 let target_column = target_display_point.column() as usize;
7371
7372 let target_x = line_layout.x_for_index(target_column);
7373 let target_y =
7374 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7375
7376 let flag_on_right = target_x < text_bounds.size.width / 2.;
7377
7378 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7379 border_color.l += 0.001;
7380
7381 let mut element = v_flex()
7382 .items_end()
7383 .when(flag_on_right, |el| el.items_start())
7384 .child(if flag_on_right {
7385 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7386 .rounded_bl(px(0.))
7387 .rounded_tl(px(0.))
7388 .border_l_2()
7389 .border_color(border_color)
7390 } else {
7391 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7392 .rounded_br(px(0.))
7393 .rounded_tr(px(0.))
7394 .border_r_2()
7395 .border_color(border_color)
7396 })
7397 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7398 .into_any();
7399
7400 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7401
7402 let mut origin = scrolled_content_origin + point(target_x, target_y)
7403 - point(
7404 if flag_on_right {
7405 POLE_WIDTH
7406 } else {
7407 size.width - POLE_WIDTH
7408 },
7409 size.height - line_height,
7410 );
7411
7412 origin.x = origin.x.max(content_origin.x);
7413
7414 element.prepaint_at(origin, window, cx);
7415
7416 Some((element, origin))
7417 }
7418
7419 fn render_edit_prediction_scroll_popover(
7420 &mut self,
7421 to_y: impl Fn(Size<Pixels>) -> Pixels,
7422 scroll_icon: IconName,
7423 visible_row_range: Range<DisplayRow>,
7424 line_layouts: &[LineWithInvisibles],
7425 newest_selection_head: Option<DisplayPoint>,
7426 scrolled_content_origin: gpui::Point<Pixels>,
7427 window: &mut Window,
7428 cx: &mut App,
7429 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7430 let mut element = self
7431 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7432 .into_any();
7433
7434 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7435
7436 let cursor = newest_selection_head?;
7437 let cursor_row_layout =
7438 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7439 let cursor_column = cursor.column() as usize;
7440
7441 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7442
7443 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7444
7445 element.prepaint_at(origin, window, cx);
7446 Some((element, origin))
7447 }
7448
7449 fn render_edit_prediction_eager_jump_popover(
7450 &mut self,
7451 text_bounds: &Bounds<Pixels>,
7452 content_origin: gpui::Point<Pixels>,
7453 editor_snapshot: &EditorSnapshot,
7454 visible_row_range: Range<DisplayRow>,
7455 scroll_top: f32,
7456 scroll_bottom: f32,
7457 line_height: Pixels,
7458 scroll_pixel_position: gpui::Point<Pixels>,
7459 target_display_point: DisplayPoint,
7460 editor_width: Pixels,
7461 window: &mut Window,
7462 cx: &mut App,
7463 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7464 if target_display_point.row().as_f32() < scroll_top {
7465 let mut element = self
7466 .render_edit_prediction_line_popover(
7467 "Jump to Edit",
7468 Some(IconName::ArrowUp),
7469 window,
7470 cx,
7471 )?
7472 .into_any();
7473
7474 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7475 let offset = point(
7476 (text_bounds.size.width - size.width) / 2.,
7477 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7478 );
7479
7480 let origin = text_bounds.origin + offset;
7481 element.prepaint_at(origin, window, cx);
7482 Some((element, origin))
7483 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7484 let mut element = self
7485 .render_edit_prediction_line_popover(
7486 "Jump to Edit",
7487 Some(IconName::ArrowDown),
7488 window,
7489 cx,
7490 )?
7491 .into_any();
7492
7493 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7494 let offset = point(
7495 (text_bounds.size.width - size.width) / 2.,
7496 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7497 );
7498
7499 let origin = text_bounds.origin + offset;
7500 element.prepaint_at(origin, window, cx);
7501 Some((element, origin))
7502 } else {
7503 self.render_edit_prediction_end_of_line_popover(
7504 "Jump to Edit",
7505 editor_snapshot,
7506 visible_row_range,
7507 target_display_point,
7508 line_height,
7509 scroll_pixel_position,
7510 content_origin,
7511 editor_width,
7512 window,
7513 cx,
7514 )
7515 }
7516 }
7517
7518 fn render_edit_prediction_end_of_line_popover(
7519 self: &mut Editor,
7520 label: &'static str,
7521 editor_snapshot: &EditorSnapshot,
7522 visible_row_range: Range<DisplayRow>,
7523 target_display_point: DisplayPoint,
7524 line_height: Pixels,
7525 scroll_pixel_position: gpui::Point<Pixels>,
7526 content_origin: gpui::Point<Pixels>,
7527 editor_width: Pixels,
7528 window: &mut Window,
7529 cx: &mut App,
7530 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7531 let target_line_end = DisplayPoint::new(
7532 target_display_point.row(),
7533 editor_snapshot.line_len(target_display_point.row()),
7534 );
7535
7536 let mut element = self
7537 .render_edit_prediction_line_popover(label, None, window, cx)?
7538 .into_any();
7539
7540 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7541
7542 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7543
7544 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7545 let mut origin = start_point
7546 + line_origin
7547 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7548 origin.x = origin.x.max(content_origin.x);
7549
7550 let max_x = content_origin.x + editor_width - size.width;
7551
7552 if origin.x > max_x {
7553 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7554
7555 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7556 origin.y += offset;
7557 IconName::ArrowUp
7558 } else {
7559 origin.y -= offset;
7560 IconName::ArrowDown
7561 };
7562
7563 element = self
7564 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7565 .into_any();
7566
7567 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7568
7569 origin.x = content_origin.x + editor_width - size.width - px(2.);
7570 }
7571
7572 element.prepaint_at(origin, window, cx);
7573 Some((element, origin))
7574 }
7575
7576 fn render_edit_prediction_diff_popover(
7577 self: &Editor,
7578 text_bounds: &Bounds<Pixels>,
7579 content_origin: gpui::Point<Pixels>,
7580 editor_snapshot: &EditorSnapshot,
7581 visible_row_range: Range<DisplayRow>,
7582 line_layouts: &[LineWithInvisibles],
7583 line_height: Pixels,
7584 scroll_pixel_position: gpui::Point<Pixels>,
7585 newest_selection_head: Option<DisplayPoint>,
7586 editor_width: Pixels,
7587 style: &EditorStyle,
7588 edits: &Vec<(Range<Anchor>, String)>,
7589 edit_preview: &Option<language::EditPreview>,
7590 snapshot: &language::BufferSnapshot,
7591 window: &mut Window,
7592 cx: &mut App,
7593 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7594 let edit_start = edits
7595 .first()
7596 .unwrap()
7597 .0
7598 .start
7599 .to_display_point(editor_snapshot);
7600 let edit_end = edits
7601 .last()
7602 .unwrap()
7603 .0
7604 .end
7605 .to_display_point(editor_snapshot);
7606
7607 let is_visible = visible_row_range.contains(&edit_start.row())
7608 || visible_row_range.contains(&edit_end.row());
7609 if !is_visible {
7610 return None;
7611 }
7612
7613 let highlighted_edits =
7614 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7615
7616 let styled_text = highlighted_edits.to_styled_text(&style.text);
7617 let line_count = highlighted_edits.text.lines().count();
7618
7619 const BORDER_WIDTH: Pixels = px(1.);
7620
7621 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7622 let has_keybind = keybind.is_some();
7623
7624 let mut element = h_flex()
7625 .items_start()
7626 .child(
7627 h_flex()
7628 .bg(cx.theme().colors().editor_background)
7629 .border(BORDER_WIDTH)
7630 .shadow_sm()
7631 .border_color(cx.theme().colors().border)
7632 .rounded_l_lg()
7633 .when(line_count > 1, |el| el.rounded_br_lg())
7634 .pr_1()
7635 .child(styled_text),
7636 )
7637 .child(
7638 h_flex()
7639 .h(line_height + BORDER_WIDTH * 2.)
7640 .px_1p5()
7641 .gap_1()
7642 // Workaround: For some reason, there's a gap if we don't do this
7643 .ml(-BORDER_WIDTH)
7644 .shadow(smallvec![gpui::BoxShadow {
7645 color: gpui::black().opacity(0.05),
7646 offset: point(px(1.), px(1.)),
7647 blur_radius: px(2.),
7648 spread_radius: px(0.),
7649 }])
7650 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7651 .border(BORDER_WIDTH)
7652 .border_color(cx.theme().colors().border)
7653 .rounded_r_lg()
7654 .id("edit_prediction_diff_popover_keybind")
7655 .when(!has_keybind, |el| {
7656 let status_colors = cx.theme().status();
7657
7658 el.bg(status_colors.error_background)
7659 .border_color(status_colors.error.opacity(0.6))
7660 .child(Icon::new(IconName::Info).color(Color::Error))
7661 .cursor_default()
7662 .hoverable_tooltip(move |_window, cx| {
7663 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7664 })
7665 })
7666 .children(keybind),
7667 )
7668 .into_any();
7669
7670 let longest_row =
7671 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7672 let longest_line_width = if visible_row_range.contains(&longest_row) {
7673 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7674 } else {
7675 layout_line(
7676 longest_row,
7677 editor_snapshot,
7678 style,
7679 editor_width,
7680 |_| false,
7681 window,
7682 cx,
7683 )
7684 .width
7685 };
7686
7687 let viewport_bounds =
7688 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7689 right: -EditorElement::SCROLLBAR_WIDTH,
7690 ..Default::default()
7691 });
7692
7693 let x_after_longest =
7694 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7695 - scroll_pixel_position.x;
7696
7697 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7698
7699 // Fully visible if it can be displayed within the window (allow overlapping other
7700 // panes). However, this is only allowed if the popover starts within text_bounds.
7701 let can_position_to_the_right = x_after_longest < text_bounds.right()
7702 && x_after_longest + element_bounds.width < viewport_bounds.right();
7703
7704 let mut origin = if can_position_to_the_right {
7705 point(
7706 x_after_longest,
7707 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7708 - scroll_pixel_position.y,
7709 )
7710 } else {
7711 let cursor_row = newest_selection_head.map(|head| head.row());
7712 let above_edit = edit_start
7713 .row()
7714 .0
7715 .checked_sub(line_count as u32)
7716 .map(DisplayRow);
7717 let below_edit = Some(edit_end.row() + 1);
7718 let above_cursor =
7719 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7720 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7721
7722 // Place the edit popover adjacent to the edit if there is a location
7723 // available that is onscreen and does not obscure the cursor. Otherwise,
7724 // place it adjacent to the cursor.
7725 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7726 .into_iter()
7727 .flatten()
7728 .find(|&start_row| {
7729 let end_row = start_row + line_count as u32;
7730 visible_row_range.contains(&start_row)
7731 && visible_row_range.contains(&end_row)
7732 && cursor_row.map_or(true, |cursor_row| {
7733 !((start_row..end_row).contains(&cursor_row))
7734 })
7735 })?;
7736
7737 content_origin
7738 + point(
7739 -scroll_pixel_position.x,
7740 row_target.as_f32() * line_height - scroll_pixel_position.y,
7741 )
7742 };
7743
7744 origin.x -= BORDER_WIDTH;
7745
7746 window.defer_draw(element, origin, 1);
7747
7748 // Do not return an element, since it will already be drawn due to defer_draw.
7749 None
7750 }
7751
7752 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7753 px(30.)
7754 }
7755
7756 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7757 if self.read_only(cx) {
7758 cx.theme().players().read_only()
7759 } else {
7760 self.style.as_ref().unwrap().local_player
7761 }
7762 }
7763
7764 fn render_edit_prediction_accept_keybind(
7765 &self,
7766 window: &mut Window,
7767 cx: &App,
7768 ) -> Option<AnyElement> {
7769 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7770 let accept_keystroke = accept_binding.keystroke()?;
7771
7772 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7773
7774 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7775 Color::Accent
7776 } else {
7777 Color::Muted
7778 };
7779
7780 h_flex()
7781 .px_0p5()
7782 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7783 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7784 .text_size(TextSize::XSmall.rems(cx))
7785 .child(h_flex().children(ui::render_modifiers(
7786 &accept_keystroke.modifiers,
7787 PlatformStyle::platform(),
7788 Some(modifiers_color),
7789 Some(IconSize::XSmall.rems().into()),
7790 true,
7791 )))
7792 .when(is_platform_style_mac, |parent| {
7793 parent.child(accept_keystroke.key.clone())
7794 })
7795 .when(!is_platform_style_mac, |parent| {
7796 parent.child(
7797 Key::new(
7798 util::capitalize(&accept_keystroke.key),
7799 Some(Color::Default),
7800 )
7801 .size(Some(IconSize::XSmall.rems().into())),
7802 )
7803 })
7804 .into_any()
7805 .into()
7806 }
7807
7808 fn render_edit_prediction_line_popover(
7809 &self,
7810 label: impl Into<SharedString>,
7811 icon: Option<IconName>,
7812 window: &mut Window,
7813 cx: &App,
7814 ) -> Option<Stateful<Div>> {
7815 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7816
7817 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7818 let has_keybind = keybind.is_some();
7819
7820 let result = h_flex()
7821 .id("ep-line-popover")
7822 .py_0p5()
7823 .pl_1()
7824 .pr(padding_right)
7825 .gap_1()
7826 .rounded_md()
7827 .border_1()
7828 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7829 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7830 .shadow_sm()
7831 .when(!has_keybind, |el| {
7832 let status_colors = cx.theme().status();
7833
7834 el.bg(status_colors.error_background)
7835 .border_color(status_colors.error.opacity(0.6))
7836 .pl_2()
7837 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7838 .cursor_default()
7839 .hoverable_tooltip(move |_window, cx| {
7840 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7841 })
7842 })
7843 .children(keybind)
7844 .child(
7845 Label::new(label)
7846 .size(LabelSize::Small)
7847 .when(!has_keybind, |el| {
7848 el.color(cx.theme().status().error.into()).strikethrough()
7849 }),
7850 )
7851 .when(!has_keybind, |el| {
7852 el.child(
7853 h_flex().ml_1().child(
7854 Icon::new(IconName::Info)
7855 .size(IconSize::Small)
7856 .color(cx.theme().status().error.into()),
7857 ),
7858 )
7859 })
7860 .when_some(icon, |element, icon| {
7861 element.child(
7862 div()
7863 .mt(px(1.5))
7864 .child(Icon::new(icon).size(IconSize::Small)),
7865 )
7866 });
7867
7868 Some(result)
7869 }
7870
7871 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7872 let accent_color = cx.theme().colors().text_accent;
7873 let editor_bg_color = cx.theme().colors().editor_background;
7874 editor_bg_color.blend(accent_color.opacity(0.1))
7875 }
7876
7877 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7878 let accent_color = cx.theme().colors().text_accent;
7879 let editor_bg_color = cx.theme().colors().editor_background;
7880 editor_bg_color.blend(accent_color.opacity(0.6))
7881 }
7882
7883 fn render_edit_prediction_cursor_popover(
7884 &self,
7885 min_width: Pixels,
7886 max_width: Pixels,
7887 cursor_point: Point,
7888 style: &EditorStyle,
7889 accept_keystroke: Option<&gpui::Keystroke>,
7890 _window: &Window,
7891 cx: &mut Context<Editor>,
7892 ) -> Option<AnyElement> {
7893 let provider = self.edit_prediction_provider.as_ref()?;
7894
7895 if provider.provider.needs_terms_acceptance(cx) {
7896 return Some(
7897 h_flex()
7898 .min_w(min_width)
7899 .flex_1()
7900 .px_2()
7901 .py_1()
7902 .gap_3()
7903 .elevation_2(cx)
7904 .hover(|style| style.bg(cx.theme().colors().element_hover))
7905 .id("accept-terms")
7906 .cursor_pointer()
7907 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7908 .on_click(cx.listener(|this, _event, window, cx| {
7909 cx.stop_propagation();
7910 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7911 window.dispatch_action(
7912 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7913 cx,
7914 );
7915 }))
7916 .child(
7917 h_flex()
7918 .flex_1()
7919 .gap_2()
7920 .child(Icon::new(IconName::ZedPredict))
7921 .child(Label::new("Accept Terms of Service"))
7922 .child(div().w_full())
7923 .child(
7924 Icon::new(IconName::ArrowUpRight)
7925 .color(Color::Muted)
7926 .size(IconSize::Small),
7927 )
7928 .into_any_element(),
7929 )
7930 .into_any(),
7931 );
7932 }
7933
7934 let is_refreshing = provider.provider.is_refreshing(cx);
7935
7936 fn pending_completion_container() -> Div {
7937 h_flex()
7938 .h_full()
7939 .flex_1()
7940 .gap_2()
7941 .child(Icon::new(IconName::ZedPredict))
7942 }
7943
7944 let completion = match &self.active_inline_completion {
7945 Some(prediction) => {
7946 if !self.has_visible_completions_menu() {
7947 const RADIUS: Pixels = px(6.);
7948 const BORDER_WIDTH: Pixels = px(1.);
7949
7950 return Some(
7951 h_flex()
7952 .elevation_2(cx)
7953 .border(BORDER_WIDTH)
7954 .border_color(cx.theme().colors().border)
7955 .when(accept_keystroke.is_none(), |el| {
7956 el.border_color(cx.theme().status().error)
7957 })
7958 .rounded(RADIUS)
7959 .rounded_tl(px(0.))
7960 .overflow_hidden()
7961 .child(div().px_1p5().child(match &prediction.completion {
7962 InlineCompletion::Move { target, snapshot } => {
7963 use text::ToPoint as _;
7964 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7965 {
7966 Icon::new(IconName::ZedPredictDown)
7967 } else {
7968 Icon::new(IconName::ZedPredictUp)
7969 }
7970 }
7971 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7972 }))
7973 .child(
7974 h_flex()
7975 .gap_1()
7976 .py_1()
7977 .px_2()
7978 .rounded_r(RADIUS - BORDER_WIDTH)
7979 .border_l_1()
7980 .border_color(cx.theme().colors().border)
7981 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7982 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7983 el.child(
7984 Label::new("Hold")
7985 .size(LabelSize::Small)
7986 .when(accept_keystroke.is_none(), |el| {
7987 el.strikethrough()
7988 })
7989 .line_height_style(LineHeightStyle::UiLabel),
7990 )
7991 })
7992 .id("edit_prediction_cursor_popover_keybind")
7993 .when(accept_keystroke.is_none(), |el| {
7994 let status_colors = cx.theme().status();
7995
7996 el.bg(status_colors.error_background)
7997 .border_color(status_colors.error.opacity(0.6))
7998 .child(Icon::new(IconName::Info).color(Color::Error))
7999 .cursor_default()
8000 .hoverable_tooltip(move |_window, cx| {
8001 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8002 .into()
8003 })
8004 })
8005 .when_some(
8006 accept_keystroke.as_ref(),
8007 |el, accept_keystroke| {
8008 el.child(h_flex().children(ui::render_modifiers(
8009 &accept_keystroke.modifiers,
8010 PlatformStyle::platform(),
8011 Some(Color::Default),
8012 Some(IconSize::XSmall.rems().into()),
8013 false,
8014 )))
8015 },
8016 ),
8017 )
8018 .into_any(),
8019 );
8020 }
8021
8022 self.render_edit_prediction_cursor_popover_preview(
8023 prediction,
8024 cursor_point,
8025 style,
8026 cx,
8027 )?
8028 }
8029
8030 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8031 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8032 stale_completion,
8033 cursor_point,
8034 style,
8035 cx,
8036 )?,
8037
8038 None => {
8039 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8040 }
8041 },
8042
8043 None => pending_completion_container().child(Label::new("No Prediction")),
8044 };
8045
8046 let completion = if is_refreshing {
8047 completion
8048 .with_animation(
8049 "loading-completion",
8050 Animation::new(Duration::from_secs(2))
8051 .repeat()
8052 .with_easing(pulsating_between(0.4, 0.8)),
8053 |label, delta| label.opacity(delta),
8054 )
8055 .into_any_element()
8056 } else {
8057 completion.into_any_element()
8058 };
8059
8060 let has_completion = self.active_inline_completion.is_some();
8061
8062 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8063 Some(
8064 h_flex()
8065 .min_w(min_width)
8066 .max_w(max_width)
8067 .flex_1()
8068 .elevation_2(cx)
8069 .border_color(cx.theme().colors().border)
8070 .child(
8071 div()
8072 .flex_1()
8073 .py_1()
8074 .px_2()
8075 .overflow_hidden()
8076 .child(completion),
8077 )
8078 .when_some(accept_keystroke, |el, accept_keystroke| {
8079 if !accept_keystroke.modifiers.modified() {
8080 return el;
8081 }
8082
8083 el.child(
8084 h_flex()
8085 .h_full()
8086 .border_l_1()
8087 .rounded_r_lg()
8088 .border_color(cx.theme().colors().border)
8089 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8090 .gap_1()
8091 .py_1()
8092 .px_2()
8093 .child(
8094 h_flex()
8095 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8096 .when(is_platform_style_mac, |parent| parent.gap_1())
8097 .child(h_flex().children(ui::render_modifiers(
8098 &accept_keystroke.modifiers,
8099 PlatformStyle::platform(),
8100 Some(if !has_completion {
8101 Color::Muted
8102 } else {
8103 Color::Default
8104 }),
8105 None,
8106 false,
8107 ))),
8108 )
8109 .child(Label::new("Preview").into_any_element())
8110 .opacity(if has_completion { 1.0 } else { 0.4 }),
8111 )
8112 })
8113 .into_any(),
8114 )
8115 }
8116
8117 fn render_edit_prediction_cursor_popover_preview(
8118 &self,
8119 completion: &InlineCompletionState,
8120 cursor_point: Point,
8121 style: &EditorStyle,
8122 cx: &mut Context<Editor>,
8123 ) -> Option<Div> {
8124 use text::ToPoint as _;
8125
8126 fn render_relative_row_jump(
8127 prefix: impl Into<String>,
8128 current_row: u32,
8129 target_row: u32,
8130 ) -> Div {
8131 let (row_diff, arrow) = if target_row < current_row {
8132 (current_row - target_row, IconName::ArrowUp)
8133 } else {
8134 (target_row - current_row, IconName::ArrowDown)
8135 };
8136
8137 h_flex()
8138 .child(
8139 Label::new(format!("{}{}", prefix.into(), row_diff))
8140 .color(Color::Muted)
8141 .size(LabelSize::Small),
8142 )
8143 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8144 }
8145
8146 match &completion.completion {
8147 InlineCompletion::Move {
8148 target, snapshot, ..
8149 } => Some(
8150 h_flex()
8151 .px_2()
8152 .gap_2()
8153 .flex_1()
8154 .child(
8155 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8156 Icon::new(IconName::ZedPredictDown)
8157 } else {
8158 Icon::new(IconName::ZedPredictUp)
8159 },
8160 )
8161 .child(Label::new("Jump to Edit")),
8162 ),
8163
8164 InlineCompletion::Edit {
8165 edits,
8166 edit_preview,
8167 snapshot,
8168 display_mode: _,
8169 } => {
8170 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8171
8172 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8173 &snapshot,
8174 &edits,
8175 edit_preview.as_ref()?,
8176 true,
8177 cx,
8178 )
8179 .first_line_preview();
8180
8181 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8182 .with_default_highlights(&style.text, highlighted_edits.highlights);
8183
8184 let preview = h_flex()
8185 .gap_1()
8186 .min_w_16()
8187 .child(styled_text)
8188 .when(has_more_lines, |parent| parent.child("…"));
8189
8190 let left = if first_edit_row != cursor_point.row {
8191 render_relative_row_jump("", cursor_point.row, first_edit_row)
8192 .into_any_element()
8193 } else {
8194 Icon::new(IconName::ZedPredict).into_any_element()
8195 };
8196
8197 Some(
8198 h_flex()
8199 .h_full()
8200 .flex_1()
8201 .gap_2()
8202 .pr_1()
8203 .overflow_x_hidden()
8204 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8205 .child(left)
8206 .child(preview),
8207 )
8208 }
8209 }
8210 }
8211
8212 fn render_context_menu(
8213 &self,
8214 style: &EditorStyle,
8215 max_height_in_lines: u32,
8216 window: &mut Window,
8217 cx: &mut Context<Editor>,
8218 ) -> Option<AnyElement> {
8219 let menu = self.context_menu.borrow();
8220 let menu = menu.as_ref()?;
8221 if !menu.visible() {
8222 return None;
8223 };
8224 Some(menu.render(style, max_height_in_lines, window, cx))
8225 }
8226
8227 fn render_context_menu_aside(
8228 &mut self,
8229 max_size: Size<Pixels>,
8230 window: &mut Window,
8231 cx: &mut Context<Editor>,
8232 ) -> Option<AnyElement> {
8233 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8234 if menu.visible() {
8235 menu.render_aside(self, max_size, window, cx)
8236 } else {
8237 None
8238 }
8239 })
8240 }
8241
8242 fn hide_context_menu(
8243 &mut self,
8244 window: &mut Window,
8245 cx: &mut Context<Self>,
8246 ) -> Option<CodeContextMenu> {
8247 cx.notify();
8248 self.completion_tasks.clear();
8249 let context_menu = self.context_menu.borrow_mut().take();
8250 self.stale_inline_completion_in_menu.take();
8251 self.update_visible_inline_completion(window, cx);
8252 context_menu
8253 }
8254
8255 fn show_snippet_choices(
8256 &mut self,
8257 choices: &Vec<String>,
8258 selection: Range<Anchor>,
8259 cx: &mut Context<Self>,
8260 ) {
8261 if selection.start.buffer_id.is_none() {
8262 return;
8263 }
8264 let buffer_id = selection.start.buffer_id.unwrap();
8265 let buffer = self.buffer().read(cx).buffer(buffer_id);
8266 let id = post_inc(&mut self.next_completion_id);
8267 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8268
8269 if let Some(buffer) = buffer {
8270 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8271 CompletionsMenu::new_snippet_choices(
8272 id,
8273 true,
8274 choices,
8275 selection,
8276 buffer,
8277 snippet_sort_order,
8278 ),
8279 ));
8280 }
8281 }
8282
8283 pub fn insert_snippet(
8284 &mut self,
8285 insertion_ranges: &[Range<usize>],
8286 snippet: Snippet,
8287 window: &mut Window,
8288 cx: &mut Context<Self>,
8289 ) -> Result<()> {
8290 struct Tabstop<T> {
8291 is_end_tabstop: bool,
8292 ranges: Vec<Range<T>>,
8293 choices: Option<Vec<String>>,
8294 }
8295
8296 let tabstops = self.buffer.update(cx, |buffer, cx| {
8297 let snippet_text: Arc<str> = snippet.text.clone().into();
8298 let edits = insertion_ranges
8299 .iter()
8300 .cloned()
8301 .map(|range| (range, snippet_text.clone()));
8302 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8303
8304 let snapshot = &*buffer.read(cx);
8305 let snippet = &snippet;
8306 snippet
8307 .tabstops
8308 .iter()
8309 .map(|tabstop| {
8310 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8311 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8312 });
8313 let mut tabstop_ranges = tabstop
8314 .ranges
8315 .iter()
8316 .flat_map(|tabstop_range| {
8317 let mut delta = 0_isize;
8318 insertion_ranges.iter().map(move |insertion_range| {
8319 let insertion_start = insertion_range.start as isize + delta;
8320 delta +=
8321 snippet.text.len() as isize - insertion_range.len() as isize;
8322
8323 let start = ((insertion_start + tabstop_range.start) as usize)
8324 .min(snapshot.len());
8325 let end = ((insertion_start + tabstop_range.end) as usize)
8326 .min(snapshot.len());
8327 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8328 })
8329 })
8330 .collect::<Vec<_>>();
8331 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8332
8333 Tabstop {
8334 is_end_tabstop,
8335 ranges: tabstop_ranges,
8336 choices: tabstop.choices.clone(),
8337 }
8338 })
8339 .collect::<Vec<_>>()
8340 });
8341 if let Some(tabstop) = tabstops.first() {
8342 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8343 s.select_ranges(tabstop.ranges.iter().cloned());
8344 });
8345
8346 if let Some(choices) = &tabstop.choices {
8347 if let Some(selection) = tabstop.ranges.first() {
8348 self.show_snippet_choices(choices, selection.clone(), cx)
8349 }
8350 }
8351
8352 // If we're already at the last tabstop and it's at the end of the snippet,
8353 // we're done, we don't need to keep the state around.
8354 if !tabstop.is_end_tabstop {
8355 let choices = tabstops
8356 .iter()
8357 .map(|tabstop| tabstop.choices.clone())
8358 .collect();
8359
8360 let ranges = tabstops
8361 .into_iter()
8362 .map(|tabstop| tabstop.ranges)
8363 .collect::<Vec<_>>();
8364
8365 self.snippet_stack.push(SnippetState {
8366 active_index: 0,
8367 ranges,
8368 choices,
8369 });
8370 }
8371
8372 // Check whether the just-entered snippet ends with an auto-closable bracket.
8373 if self.autoclose_regions.is_empty() {
8374 let snapshot = self.buffer.read(cx).snapshot(cx);
8375 for selection in &mut self.selections.all::<Point>(cx) {
8376 let selection_head = selection.head();
8377 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8378 continue;
8379 };
8380
8381 let mut bracket_pair = None;
8382 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8383 let prev_chars = snapshot
8384 .reversed_chars_at(selection_head)
8385 .collect::<String>();
8386 for (pair, enabled) in scope.brackets() {
8387 if enabled
8388 && pair.close
8389 && prev_chars.starts_with(pair.start.as_str())
8390 && next_chars.starts_with(pair.end.as_str())
8391 {
8392 bracket_pair = Some(pair.clone());
8393 break;
8394 }
8395 }
8396 if let Some(pair) = bracket_pair {
8397 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8398 let autoclose_enabled =
8399 self.use_autoclose && snapshot_settings.use_autoclose;
8400 if autoclose_enabled {
8401 let start = snapshot.anchor_after(selection_head);
8402 let end = snapshot.anchor_after(selection_head);
8403 self.autoclose_regions.push(AutocloseRegion {
8404 selection_id: selection.id,
8405 range: start..end,
8406 pair,
8407 });
8408 }
8409 }
8410 }
8411 }
8412 }
8413 Ok(())
8414 }
8415
8416 pub fn move_to_next_snippet_tabstop(
8417 &mut self,
8418 window: &mut Window,
8419 cx: &mut Context<Self>,
8420 ) -> bool {
8421 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8422 }
8423
8424 pub fn move_to_prev_snippet_tabstop(
8425 &mut self,
8426 window: &mut Window,
8427 cx: &mut Context<Self>,
8428 ) -> bool {
8429 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8430 }
8431
8432 pub fn move_to_snippet_tabstop(
8433 &mut self,
8434 bias: Bias,
8435 window: &mut Window,
8436 cx: &mut Context<Self>,
8437 ) -> bool {
8438 if let Some(mut snippet) = self.snippet_stack.pop() {
8439 match bias {
8440 Bias::Left => {
8441 if snippet.active_index > 0 {
8442 snippet.active_index -= 1;
8443 } else {
8444 self.snippet_stack.push(snippet);
8445 return false;
8446 }
8447 }
8448 Bias::Right => {
8449 if snippet.active_index + 1 < snippet.ranges.len() {
8450 snippet.active_index += 1;
8451 } else {
8452 self.snippet_stack.push(snippet);
8453 return false;
8454 }
8455 }
8456 }
8457 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8458 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8459 s.select_anchor_ranges(current_ranges.iter().cloned())
8460 });
8461
8462 if let Some(choices) = &snippet.choices[snippet.active_index] {
8463 if let Some(selection) = current_ranges.first() {
8464 self.show_snippet_choices(&choices, selection.clone(), cx);
8465 }
8466 }
8467
8468 // If snippet state is not at the last tabstop, push it back on the stack
8469 if snippet.active_index + 1 < snippet.ranges.len() {
8470 self.snippet_stack.push(snippet);
8471 }
8472 return true;
8473 }
8474 }
8475
8476 false
8477 }
8478
8479 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8480 self.transact(window, cx, |this, window, cx| {
8481 this.select_all(&SelectAll, window, cx);
8482 this.insert("", window, cx);
8483 });
8484 }
8485
8486 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8487 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8488 self.transact(window, cx, |this, window, cx| {
8489 this.select_autoclose_pair(window, cx);
8490 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8491 if !this.linked_edit_ranges.is_empty() {
8492 let selections = this.selections.all::<MultiBufferPoint>(cx);
8493 let snapshot = this.buffer.read(cx).snapshot(cx);
8494
8495 for selection in selections.iter() {
8496 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8497 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8498 if selection_start.buffer_id != selection_end.buffer_id {
8499 continue;
8500 }
8501 if let Some(ranges) =
8502 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8503 {
8504 for (buffer, entries) in ranges {
8505 linked_ranges.entry(buffer).or_default().extend(entries);
8506 }
8507 }
8508 }
8509 }
8510
8511 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8512 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8513 for selection in &mut selections {
8514 if selection.is_empty() {
8515 let old_head = selection.head();
8516 let mut new_head =
8517 movement::left(&display_map, old_head.to_display_point(&display_map))
8518 .to_point(&display_map);
8519 if let Some((buffer, line_buffer_range)) = display_map
8520 .buffer_snapshot
8521 .buffer_line_for_row(MultiBufferRow(old_head.row))
8522 {
8523 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8524 let indent_len = match indent_size.kind {
8525 IndentKind::Space => {
8526 buffer.settings_at(line_buffer_range.start, cx).tab_size
8527 }
8528 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8529 };
8530 if old_head.column <= indent_size.len && old_head.column > 0 {
8531 let indent_len = indent_len.get();
8532 new_head = cmp::min(
8533 new_head,
8534 MultiBufferPoint::new(
8535 old_head.row,
8536 ((old_head.column - 1) / indent_len) * indent_len,
8537 ),
8538 );
8539 }
8540 }
8541
8542 selection.set_head(new_head, SelectionGoal::None);
8543 }
8544 }
8545
8546 this.signature_help_state.set_backspace_pressed(true);
8547 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8548 s.select(selections)
8549 });
8550 this.insert("", window, cx);
8551 let empty_str: Arc<str> = Arc::from("");
8552 for (buffer, edits) in linked_ranges {
8553 let snapshot = buffer.read(cx).snapshot();
8554 use text::ToPoint as TP;
8555
8556 let edits = edits
8557 .into_iter()
8558 .map(|range| {
8559 let end_point = TP::to_point(&range.end, &snapshot);
8560 let mut start_point = TP::to_point(&range.start, &snapshot);
8561
8562 if end_point == start_point {
8563 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8564 .saturating_sub(1);
8565 start_point =
8566 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8567 };
8568
8569 (start_point..end_point, empty_str.clone())
8570 })
8571 .sorted_by_key(|(range, _)| range.start)
8572 .collect::<Vec<_>>();
8573 buffer.update(cx, |this, cx| {
8574 this.edit(edits, None, cx);
8575 })
8576 }
8577 this.refresh_inline_completion(true, false, window, cx);
8578 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8579 });
8580 }
8581
8582 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8583 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8584 self.transact(window, cx, |this, window, cx| {
8585 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8586 s.move_with(|map, selection| {
8587 if selection.is_empty() {
8588 let cursor = movement::right(map, selection.head());
8589 selection.end = cursor;
8590 selection.reversed = true;
8591 selection.goal = SelectionGoal::None;
8592 }
8593 })
8594 });
8595 this.insert("", window, cx);
8596 this.refresh_inline_completion(true, false, window, cx);
8597 });
8598 }
8599
8600 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8601 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8602 if self.move_to_prev_snippet_tabstop(window, cx) {
8603 return;
8604 }
8605 self.outdent(&Outdent, window, cx);
8606 }
8607
8608 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8609 if self.move_to_next_snippet_tabstop(window, cx) {
8610 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8611 return;
8612 }
8613 if self.read_only(cx) {
8614 return;
8615 }
8616 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8617 let mut selections = self.selections.all_adjusted(cx);
8618 let buffer = self.buffer.read(cx);
8619 let snapshot = buffer.snapshot(cx);
8620 let rows_iter = selections.iter().map(|s| s.head().row);
8621 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8622
8623 let mut edits = Vec::new();
8624 let mut prev_edited_row = 0;
8625 let mut row_delta = 0;
8626 for selection in &mut selections {
8627 if selection.start.row != prev_edited_row {
8628 row_delta = 0;
8629 }
8630 prev_edited_row = selection.end.row;
8631
8632 // If the selection is non-empty, then increase the indentation of the selected lines.
8633 if !selection.is_empty() {
8634 row_delta =
8635 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8636 continue;
8637 }
8638
8639 // If the selection is empty and the cursor is in the leading whitespace before the
8640 // suggested indentation, then auto-indent the line.
8641 let cursor = selection.head();
8642 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8643 if let Some(suggested_indent) =
8644 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8645 {
8646 if cursor.column < suggested_indent.len
8647 && cursor.column <= current_indent.len
8648 && current_indent.len <= suggested_indent.len
8649 {
8650 selection.start = Point::new(cursor.row, suggested_indent.len);
8651 selection.end = selection.start;
8652 if row_delta == 0 {
8653 edits.extend(Buffer::edit_for_indent_size_adjustment(
8654 cursor.row,
8655 current_indent,
8656 suggested_indent,
8657 ));
8658 row_delta = suggested_indent.len - current_indent.len;
8659 }
8660 continue;
8661 }
8662 }
8663
8664 // Otherwise, insert a hard or soft tab.
8665 let settings = buffer.language_settings_at(cursor, cx);
8666 let tab_size = if settings.hard_tabs {
8667 IndentSize::tab()
8668 } else {
8669 let tab_size = settings.tab_size.get();
8670 let indent_remainder = snapshot
8671 .text_for_range(Point::new(cursor.row, 0)..cursor)
8672 .flat_map(str::chars)
8673 .fold(row_delta % tab_size, |counter: u32, c| {
8674 if c == '\t' {
8675 0
8676 } else {
8677 (counter + 1) % tab_size
8678 }
8679 });
8680
8681 let chars_to_next_tab_stop = tab_size - indent_remainder;
8682 IndentSize::spaces(chars_to_next_tab_stop)
8683 };
8684 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8685 selection.end = selection.start;
8686 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8687 row_delta += tab_size.len;
8688 }
8689
8690 self.transact(window, cx, |this, window, cx| {
8691 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8692 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8693 s.select(selections)
8694 });
8695 this.refresh_inline_completion(true, false, window, cx);
8696 });
8697 }
8698
8699 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8700 if self.read_only(cx) {
8701 return;
8702 }
8703 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8704 let mut selections = self.selections.all::<Point>(cx);
8705 let mut prev_edited_row = 0;
8706 let mut row_delta = 0;
8707 let mut edits = Vec::new();
8708 let buffer = self.buffer.read(cx);
8709 let snapshot = buffer.snapshot(cx);
8710 for selection in &mut selections {
8711 if selection.start.row != prev_edited_row {
8712 row_delta = 0;
8713 }
8714 prev_edited_row = selection.end.row;
8715
8716 row_delta =
8717 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8718 }
8719
8720 self.transact(window, cx, |this, window, cx| {
8721 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8722 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8723 s.select(selections)
8724 });
8725 });
8726 }
8727
8728 fn indent_selection(
8729 buffer: &MultiBuffer,
8730 snapshot: &MultiBufferSnapshot,
8731 selection: &mut Selection<Point>,
8732 edits: &mut Vec<(Range<Point>, String)>,
8733 delta_for_start_row: u32,
8734 cx: &App,
8735 ) -> u32 {
8736 let settings = buffer.language_settings_at(selection.start, cx);
8737 let tab_size = settings.tab_size.get();
8738 let indent_kind = if settings.hard_tabs {
8739 IndentKind::Tab
8740 } else {
8741 IndentKind::Space
8742 };
8743 let mut start_row = selection.start.row;
8744 let mut end_row = selection.end.row + 1;
8745
8746 // If a selection ends at the beginning of a line, don't indent
8747 // that last line.
8748 if selection.end.column == 0 && selection.end.row > selection.start.row {
8749 end_row -= 1;
8750 }
8751
8752 // Avoid re-indenting a row that has already been indented by a
8753 // previous selection, but still update this selection's column
8754 // to reflect that indentation.
8755 if delta_for_start_row > 0 {
8756 start_row += 1;
8757 selection.start.column += delta_for_start_row;
8758 if selection.end.row == selection.start.row {
8759 selection.end.column += delta_for_start_row;
8760 }
8761 }
8762
8763 let mut delta_for_end_row = 0;
8764 let has_multiple_rows = start_row + 1 != end_row;
8765 for row in start_row..end_row {
8766 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8767 let indent_delta = match (current_indent.kind, indent_kind) {
8768 (IndentKind::Space, IndentKind::Space) => {
8769 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8770 IndentSize::spaces(columns_to_next_tab_stop)
8771 }
8772 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8773 (_, IndentKind::Tab) => IndentSize::tab(),
8774 };
8775
8776 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8777 0
8778 } else {
8779 selection.start.column
8780 };
8781 let row_start = Point::new(row, start);
8782 edits.push((
8783 row_start..row_start,
8784 indent_delta.chars().collect::<String>(),
8785 ));
8786
8787 // Update this selection's endpoints to reflect the indentation.
8788 if row == selection.start.row {
8789 selection.start.column += indent_delta.len;
8790 }
8791 if row == selection.end.row {
8792 selection.end.column += indent_delta.len;
8793 delta_for_end_row = indent_delta.len;
8794 }
8795 }
8796
8797 if selection.start.row == selection.end.row {
8798 delta_for_start_row + delta_for_end_row
8799 } else {
8800 delta_for_end_row
8801 }
8802 }
8803
8804 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8805 if self.read_only(cx) {
8806 return;
8807 }
8808 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8809 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8810 let selections = self.selections.all::<Point>(cx);
8811 let mut deletion_ranges = Vec::new();
8812 let mut last_outdent = None;
8813 {
8814 let buffer = self.buffer.read(cx);
8815 let snapshot = buffer.snapshot(cx);
8816 for selection in &selections {
8817 let settings = buffer.language_settings_at(selection.start, cx);
8818 let tab_size = settings.tab_size.get();
8819 let mut rows = selection.spanned_rows(false, &display_map);
8820
8821 // Avoid re-outdenting a row that has already been outdented by a
8822 // previous selection.
8823 if let Some(last_row) = last_outdent {
8824 if last_row == rows.start {
8825 rows.start = rows.start.next_row();
8826 }
8827 }
8828 let has_multiple_rows = rows.len() > 1;
8829 for row in rows.iter_rows() {
8830 let indent_size = snapshot.indent_size_for_line(row);
8831 if indent_size.len > 0 {
8832 let deletion_len = match indent_size.kind {
8833 IndentKind::Space => {
8834 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8835 if columns_to_prev_tab_stop == 0 {
8836 tab_size
8837 } else {
8838 columns_to_prev_tab_stop
8839 }
8840 }
8841 IndentKind::Tab => 1,
8842 };
8843 let start = if has_multiple_rows
8844 || deletion_len > selection.start.column
8845 || indent_size.len < selection.start.column
8846 {
8847 0
8848 } else {
8849 selection.start.column - deletion_len
8850 };
8851 deletion_ranges.push(
8852 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8853 );
8854 last_outdent = Some(row);
8855 }
8856 }
8857 }
8858 }
8859
8860 self.transact(window, cx, |this, window, cx| {
8861 this.buffer.update(cx, |buffer, cx| {
8862 let empty_str: Arc<str> = Arc::default();
8863 buffer.edit(
8864 deletion_ranges
8865 .into_iter()
8866 .map(|range| (range, empty_str.clone())),
8867 None,
8868 cx,
8869 );
8870 });
8871 let selections = this.selections.all::<usize>(cx);
8872 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8873 s.select(selections)
8874 });
8875 });
8876 }
8877
8878 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8879 if self.read_only(cx) {
8880 return;
8881 }
8882 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8883 let selections = self
8884 .selections
8885 .all::<usize>(cx)
8886 .into_iter()
8887 .map(|s| s.range());
8888
8889 self.transact(window, cx, |this, window, cx| {
8890 this.buffer.update(cx, |buffer, cx| {
8891 buffer.autoindent_ranges(selections, cx);
8892 });
8893 let selections = this.selections.all::<usize>(cx);
8894 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8895 s.select(selections)
8896 });
8897 });
8898 }
8899
8900 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8901 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8902 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8903 let selections = self.selections.all::<Point>(cx);
8904
8905 let mut new_cursors = Vec::new();
8906 let mut edit_ranges = Vec::new();
8907 let mut selections = selections.iter().peekable();
8908 while let Some(selection) = selections.next() {
8909 let mut rows = selection.spanned_rows(false, &display_map);
8910 let goal_display_column = selection.head().to_display_point(&display_map).column();
8911
8912 // Accumulate contiguous regions of rows that we want to delete.
8913 while let Some(next_selection) = selections.peek() {
8914 let next_rows = next_selection.spanned_rows(false, &display_map);
8915 if next_rows.start <= rows.end {
8916 rows.end = next_rows.end;
8917 selections.next().unwrap();
8918 } else {
8919 break;
8920 }
8921 }
8922
8923 let buffer = &display_map.buffer_snapshot;
8924 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8925 let edit_end;
8926 let cursor_buffer_row;
8927 if buffer.max_point().row >= rows.end.0 {
8928 // If there's a line after the range, delete the \n from the end of the row range
8929 // and position the cursor on the next line.
8930 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8931 cursor_buffer_row = rows.end;
8932 } else {
8933 // If there isn't a line after the range, delete the \n from the line before the
8934 // start of the row range and position the cursor there.
8935 edit_start = edit_start.saturating_sub(1);
8936 edit_end = buffer.len();
8937 cursor_buffer_row = rows.start.previous_row();
8938 }
8939
8940 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8941 *cursor.column_mut() =
8942 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8943
8944 new_cursors.push((
8945 selection.id,
8946 buffer.anchor_after(cursor.to_point(&display_map)),
8947 ));
8948 edit_ranges.push(edit_start..edit_end);
8949 }
8950
8951 self.transact(window, cx, |this, window, cx| {
8952 let buffer = this.buffer.update(cx, |buffer, cx| {
8953 let empty_str: Arc<str> = Arc::default();
8954 buffer.edit(
8955 edit_ranges
8956 .into_iter()
8957 .map(|range| (range, empty_str.clone())),
8958 None,
8959 cx,
8960 );
8961 buffer.snapshot(cx)
8962 });
8963 let new_selections = new_cursors
8964 .into_iter()
8965 .map(|(id, cursor)| {
8966 let cursor = cursor.to_point(&buffer);
8967 Selection {
8968 id,
8969 start: cursor,
8970 end: cursor,
8971 reversed: false,
8972 goal: SelectionGoal::None,
8973 }
8974 })
8975 .collect();
8976
8977 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8978 s.select(new_selections);
8979 });
8980 });
8981 }
8982
8983 pub fn join_lines_impl(
8984 &mut self,
8985 insert_whitespace: bool,
8986 window: &mut Window,
8987 cx: &mut Context<Self>,
8988 ) {
8989 if self.read_only(cx) {
8990 return;
8991 }
8992 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8993 for selection in self.selections.all::<Point>(cx) {
8994 let start = MultiBufferRow(selection.start.row);
8995 // Treat single line selections as if they include the next line. Otherwise this action
8996 // would do nothing for single line selections individual cursors.
8997 let end = if selection.start.row == selection.end.row {
8998 MultiBufferRow(selection.start.row + 1)
8999 } else {
9000 MultiBufferRow(selection.end.row)
9001 };
9002
9003 if let Some(last_row_range) = row_ranges.last_mut() {
9004 if start <= last_row_range.end {
9005 last_row_range.end = end;
9006 continue;
9007 }
9008 }
9009 row_ranges.push(start..end);
9010 }
9011
9012 let snapshot = self.buffer.read(cx).snapshot(cx);
9013 let mut cursor_positions = Vec::new();
9014 for row_range in &row_ranges {
9015 let anchor = snapshot.anchor_before(Point::new(
9016 row_range.end.previous_row().0,
9017 snapshot.line_len(row_range.end.previous_row()),
9018 ));
9019 cursor_positions.push(anchor..anchor);
9020 }
9021
9022 self.transact(window, cx, |this, window, cx| {
9023 for row_range in row_ranges.into_iter().rev() {
9024 for row in row_range.iter_rows().rev() {
9025 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9026 let next_line_row = row.next_row();
9027 let indent = snapshot.indent_size_for_line(next_line_row);
9028 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9029
9030 let replace =
9031 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9032 " "
9033 } else {
9034 ""
9035 };
9036
9037 this.buffer.update(cx, |buffer, cx| {
9038 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9039 });
9040 }
9041 }
9042
9043 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9044 s.select_anchor_ranges(cursor_positions)
9045 });
9046 });
9047 }
9048
9049 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9050 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9051 self.join_lines_impl(true, window, cx);
9052 }
9053
9054 pub fn sort_lines_case_sensitive(
9055 &mut self,
9056 _: &SortLinesCaseSensitive,
9057 window: &mut Window,
9058 cx: &mut Context<Self>,
9059 ) {
9060 self.manipulate_lines(window, cx, |lines| lines.sort())
9061 }
9062
9063 pub fn sort_lines_case_insensitive(
9064 &mut self,
9065 _: &SortLinesCaseInsensitive,
9066 window: &mut Window,
9067 cx: &mut Context<Self>,
9068 ) {
9069 self.manipulate_lines(window, cx, |lines| {
9070 lines.sort_by_key(|line| line.to_lowercase())
9071 })
9072 }
9073
9074 pub fn unique_lines_case_insensitive(
9075 &mut self,
9076 _: &UniqueLinesCaseInsensitive,
9077 window: &mut Window,
9078 cx: &mut Context<Self>,
9079 ) {
9080 self.manipulate_lines(window, cx, |lines| {
9081 let mut seen = HashSet::default();
9082 lines.retain(|line| seen.insert(line.to_lowercase()));
9083 })
9084 }
9085
9086 pub fn unique_lines_case_sensitive(
9087 &mut self,
9088 _: &UniqueLinesCaseSensitive,
9089 window: &mut Window,
9090 cx: &mut Context<Self>,
9091 ) {
9092 self.manipulate_lines(window, cx, |lines| {
9093 let mut seen = HashSet::default();
9094 lines.retain(|line| seen.insert(*line));
9095 })
9096 }
9097
9098 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9099 let Some(project) = self.project.clone() else {
9100 return;
9101 };
9102 self.reload(project, window, cx)
9103 .detach_and_notify_err(window, cx);
9104 }
9105
9106 pub fn restore_file(
9107 &mut self,
9108 _: &::git::RestoreFile,
9109 window: &mut Window,
9110 cx: &mut Context<Self>,
9111 ) {
9112 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9113 let mut buffer_ids = HashSet::default();
9114 let snapshot = self.buffer().read(cx).snapshot(cx);
9115 for selection in self.selections.all::<usize>(cx) {
9116 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9117 }
9118
9119 let buffer = self.buffer().read(cx);
9120 let ranges = buffer_ids
9121 .into_iter()
9122 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9123 .collect::<Vec<_>>();
9124
9125 self.restore_hunks_in_ranges(ranges, window, cx);
9126 }
9127
9128 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9129 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9130 let selections = self
9131 .selections
9132 .all(cx)
9133 .into_iter()
9134 .map(|s| s.range())
9135 .collect();
9136 self.restore_hunks_in_ranges(selections, window, cx);
9137 }
9138
9139 pub fn restore_hunks_in_ranges(
9140 &mut self,
9141 ranges: Vec<Range<Point>>,
9142 window: &mut Window,
9143 cx: &mut Context<Editor>,
9144 ) {
9145 let mut revert_changes = HashMap::default();
9146 let chunk_by = self
9147 .snapshot(window, cx)
9148 .hunks_for_ranges(ranges)
9149 .into_iter()
9150 .chunk_by(|hunk| hunk.buffer_id);
9151 for (buffer_id, hunks) in &chunk_by {
9152 let hunks = hunks.collect::<Vec<_>>();
9153 for hunk in &hunks {
9154 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9155 }
9156 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9157 }
9158 drop(chunk_by);
9159 if !revert_changes.is_empty() {
9160 self.transact(window, cx, |editor, window, cx| {
9161 editor.restore(revert_changes, window, cx);
9162 });
9163 }
9164 }
9165
9166 pub fn open_active_item_in_terminal(
9167 &mut self,
9168 _: &OpenInTerminal,
9169 window: &mut Window,
9170 cx: &mut Context<Self>,
9171 ) {
9172 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9173 let project_path = buffer.read(cx).project_path(cx)?;
9174 let project = self.project.as_ref()?.read(cx);
9175 let entry = project.entry_for_path(&project_path, cx)?;
9176 let parent = match &entry.canonical_path {
9177 Some(canonical_path) => canonical_path.to_path_buf(),
9178 None => project.absolute_path(&project_path, cx)?,
9179 }
9180 .parent()?
9181 .to_path_buf();
9182 Some(parent)
9183 }) {
9184 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9185 }
9186 }
9187
9188 fn set_breakpoint_context_menu(
9189 &mut self,
9190 display_row: DisplayRow,
9191 position: Option<Anchor>,
9192 clicked_point: gpui::Point<Pixels>,
9193 window: &mut Window,
9194 cx: &mut Context<Self>,
9195 ) {
9196 if !cx.has_flag::<DebuggerFeatureFlag>() {
9197 return;
9198 }
9199 let source = self
9200 .buffer
9201 .read(cx)
9202 .snapshot(cx)
9203 .anchor_before(Point::new(display_row.0, 0u32));
9204
9205 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9206
9207 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9208 self,
9209 source,
9210 clicked_point,
9211 context_menu,
9212 window,
9213 cx,
9214 );
9215 }
9216
9217 fn add_edit_breakpoint_block(
9218 &mut self,
9219 anchor: Anchor,
9220 breakpoint: &Breakpoint,
9221 edit_action: BreakpointPromptEditAction,
9222 window: &mut Window,
9223 cx: &mut Context<Self>,
9224 ) {
9225 let weak_editor = cx.weak_entity();
9226 let bp_prompt = cx.new(|cx| {
9227 BreakpointPromptEditor::new(
9228 weak_editor,
9229 anchor,
9230 breakpoint.clone(),
9231 edit_action,
9232 window,
9233 cx,
9234 )
9235 });
9236
9237 let height = bp_prompt.update(cx, |this, cx| {
9238 this.prompt
9239 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9240 });
9241 let cloned_prompt = bp_prompt.clone();
9242 let blocks = vec![BlockProperties {
9243 style: BlockStyle::Sticky,
9244 placement: BlockPlacement::Above(anchor),
9245 height: Some(height),
9246 render: Arc::new(move |cx| {
9247 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
9248 cloned_prompt.clone().into_any_element()
9249 }),
9250 priority: 0,
9251 }];
9252
9253 let focus_handle = bp_prompt.focus_handle(cx);
9254 window.focus(&focus_handle);
9255
9256 let block_ids = self.insert_blocks(blocks, None, cx);
9257 bp_prompt.update(cx, |prompt, _| {
9258 prompt.add_block_ids(block_ids);
9259 });
9260 }
9261
9262 pub(crate) fn breakpoint_at_row(
9263 &self,
9264 row: u32,
9265 window: &mut Window,
9266 cx: &mut Context<Self>,
9267 ) -> Option<(Anchor, Breakpoint)> {
9268 let snapshot = self.snapshot(window, cx);
9269 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9270
9271 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9272 }
9273
9274 pub(crate) fn breakpoint_at_anchor(
9275 &self,
9276 breakpoint_position: Anchor,
9277 snapshot: &EditorSnapshot,
9278 cx: &mut Context<Self>,
9279 ) -> Option<(Anchor, Breakpoint)> {
9280 let project = self.project.clone()?;
9281
9282 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9283 snapshot
9284 .buffer_snapshot
9285 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9286 })?;
9287
9288 let enclosing_excerpt = breakpoint_position.excerpt_id;
9289 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9290 let buffer_snapshot = buffer.read(cx).snapshot();
9291
9292 let row = buffer_snapshot
9293 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9294 .row;
9295
9296 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9297 let anchor_end = snapshot
9298 .buffer_snapshot
9299 .anchor_after(Point::new(row, line_len));
9300
9301 let bp = self
9302 .breakpoint_store
9303 .as_ref()?
9304 .read_with(cx, |breakpoint_store, cx| {
9305 breakpoint_store
9306 .breakpoints(
9307 &buffer,
9308 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9309 &buffer_snapshot,
9310 cx,
9311 )
9312 .next()
9313 .and_then(|(anchor, bp)| {
9314 let breakpoint_row = buffer_snapshot
9315 .summary_for_anchor::<text::PointUtf16>(anchor)
9316 .row;
9317
9318 if breakpoint_row == row {
9319 snapshot
9320 .buffer_snapshot
9321 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9322 .map(|anchor| (anchor, bp.clone()))
9323 } else {
9324 None
9325 }
9326 })
9327 });
9328 bp
9329 }
9330
9331 pub fn edit_log_breakpoint(
9332 &mut self,
9333 _: &EditLogBreakpoint,
9334 window: &mut Window,
9335 cx: &mut Context<Self>,
9336 ) {
9337 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9338 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9339 message: None,
9340 state: BreakpointState::Enabled,
9341 condition: None,
9342 hit_condition: None,
9343 });
9344
9345 self.add_edit_breakpoint_block(
9346 anchor,
9347 &breakpoint,
9348 BreakpointPromptEditAction::Log,
9349 window,
9350 cx,
9351 );
9352 }
9353 }
9354
9355 fn breakpoints_at_cursors(
9356 &self,
9357 window: &mut Window,
9358 cx: &mut Context<Self>,
9359 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9360 let snapshot = self.snapshot(window, cx);
9361 let cursors = self
9362 .selections
9363 .disjoint_anchors()
9364 .into_iter()
9365 .map(|selection| {
9366 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9367
9368 let breakpoint_position = self
9369 .breakpoint_at_row(cursor_position.row, window, cx)
9370 .map(|bp| bp.0)
9371 .unwrap_or_else(|| {
9372 snapshot
9373 .display_snapshot
9374 .buffer_snapshot
9375 .anchor_after(Point::new(cursor_position.row, 0))
9376 });
9377
9378 let breakpoint = self
9379 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9380 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9381
9382 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9383 })
9384 // 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.
9385 .collect::<HashMap<Anchor, _>>();
9386
9387 cursors.into_iter().collect()
9388 }
9389
9390 pub fn enable_breakpoint(
9391 &mut self,
9392 _: &crate::actions::EnableBreakpoint,
9393 window: &mut Window,
9394 cx: &mut Context<Self>,
9395 ) {
9396 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9397 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9398 continue;
9399 };
9400 self.edit_breakpoint_at_anchor(
9401 anchor,
9402 breakpoint,
9403 BreakpointEditAction::InvertState,
9404 cx,
9405 );
9406 }
9407 }
9408
9409 pub fn disable_breakpoint(
9410 &mut self,
9411 _: &crate::actions::DisableBreakpoint,
9412 window: &mut Window,
9413 cx: &mut Context<Self>,
9414 ) {
9415 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9416 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9417 continue;
9418 };
9419 self.edit_breakpoint_at_anchor(
9420 anchor,
9421 breakpoint,
9422 BreakpointEditAction::InvertState,
9423 cx,
9424 );
9425 }
9426 }
9427
9428 pub fn toggle_breakpoint(
9429 &mut self,
9430 _: &crate::actions::ToggleBreakpoint,
9431 window: &mut Window,
9432 cx: &mut Context<Self>,
9433 ) {
9434 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9435 if let Some(breakpoint) = breakpoint {
9436 self.edit_breakpoint_at_anchor(
9437 anchor,
9438 breakpoint,
9439 BreakpointEditAction::Toggle,
9440 cx,
9441 );
9442 } else {
9443 self.edit_breakpoint_at_anchor(
9444 anchor,
9445 Breakpoint::new_standard(),
9446 BreakpointEditAction::Toggle,
9447 cx,
9448 );
9449 }
9450 }
9451 }
9452
9453 pub fn edit_breakpoint_at_anchor(
9454 &mut self,
9455 breakpoint_position: Anchor,
9456 breakpoint: Breakpoint,
9457 edit_action: BreakpointEditAction,
9458 cx: &mut Context<Self>,
9459 ) {
9460 let Some(breakpoint_store) = &self.breakpoint_store else {
9461 return;
9462 };
9463
9464 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9465 if breakpoint_position == Anchor::min() {
9466 self.buffer()
9467 .read(cx)
9468 .excerpt_buffer_ids()
9469 .into_iter()
9470 .next()
9471 } else {
9472 None
9473 }
9474 }) else {
9475 return;
9476 };
9477
9478 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9479 return;
9480 };
9481
9482 breakpoint_store.update(cx, |breakpoint_store, cx| {
9483 breakpoint_store.toggle_breakpoint(
9484 buffer,
9485 (breakpoint_position.text_anchor, breakpoint),
9486 edit_action,
9487 cx,
9488 );
9489 });
9490
9491 cx.notify();
9492 }
9493
9494 #[cfg(any(test, feature = "test-support"))]
9495 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9496 self.breakpoint_store.clone()
9497 }
9498
9499 pub fn prepare_restore_change(
9500 &self,
9501 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9502 hunk: &MultiBufferDiffHunk,
9503 cx: &mut App,
9504 ) -> Option<()> {
9505 if hunk.is_created_file() {
9506 return None;
9507 }
9508 let buffer = self.buffer.read(cx);
9509 let diff = buffer.diff_for(hunk.buffer_id)?;
9510 let buffer = buffer.buffer(hunk.buffer_id)?;
9511 let buffer = buffer.read(cx);
9512 let original_text = diff
9513 .read(cx)
9514 .base_text()
9515 .as_rope()
9516 .slice(hunk.diff_base_byte_range.clone());
9517 let buffer_snapshot = buffer.snapshot();
9518 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9519 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9520 probe
9521 .0
9522 .start
9523 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9524 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9525 }) {
9526 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9527 Some(())
9528 } else {
9529 None
9530 }
9531 }
9532
9533 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9534 self.manipulate_lines(window, cx, |lines| lines.reverse())
9535 }
9536
9537 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9538 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9539 }
9540
9541 fn manipulate_lines<Fn>(
9542 &mut self,
9543 window: &mut Window,
9544 cx: &mut Context<Self>,
9545 mut callback: Fn,
9546 ) where
9547 Fn: FnMut(&mut Vec<&str>),
9548 {
9549 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9550
9551 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9552 let buffer = self.buffer.read(cx).snapshot(cx);
9553
9554 let mut edits = Vec::new();
9555
9556 let selections = self.selections.all::<Point>(cx);
9557 let mut selections = selections.iter().peekable();
9558 let mut contiguous_row_selections = Vec::new();
9559 let mut new_selections = Vec::new();
9560 let mut added_lines = 0;
9561 let mut removed_lines = 0;
9562
9563 while let Some(selection) = selections.next() {
9564 let (start_row, end_row) = consume_contiguous_rows(
9565 &mut contiguous_row_selections,
9566 selection,
9567 &display_map,
9568 &mut selections,
9569 );
9570
9571 let start_point = Point::new(start_row.0, 0);
9572 let end_point = Point::new(
9573 end_row.previous_row().0,
9574 buffer.line_len(end_row.previous_row()),
9575 );
9576 let text = buffer
9577 .text_for_range(start_point..end_point)
9578 .collect::<String>();
9579
9580 let mut lines = text.split('\n').collect_vec();
9581
9582 let lines_before = lines.len();
9583 callback(&mut lines);
9584 let lines_after = lines.len();
9585
9586 edits.push((start_point..end_point, lines.join("\n")));
9587
9588 // Selections must change based on added and removed line count
9589 let start_row =
9590 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9591 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9592 new_selections.push(Selection {
9593 id: selection.id,
9594 start: start_row,
9595 end: end_row,
9596 goal: SelectionGoal::None,
9597 reversed: selection.reversed,
9598 });
9599
9600 if lines_after > lines_before {
9601 added_lines += lines_after - lines_before;
9602 } else if lines_before > lines_after {
9603 removed_lines += lines_before - lines_after;
9604 }
9605 }
9606
9607 self.transact(window, cx, |this, window, cx| {
9608 let buffer = this.buffer.update(cx, |buffer, cx| {
9609 buffer.edit(edits, None, cx);
9610 buffer.snapshot(cx)
9611 });
9612
9613 // Recalculate offsets on newly edited buffer
9614 let new_selections = new_selections
9615 .iter()
9616 .map(|s| {
9617 let start_point = Point::new(s.start.0, 0);
9618 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9619 Selection {
9620 id: s.id,
9621 start: buffer.point_to_offset(start_point),
9622 end: buffer.point_to_offset(end_point),
9623 goal: s.goal,
9624 reversed: s.reversed,
9625 }
9626 })
9627 .collect();
9628
9629 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9630 s.select(new_selections);
9631 });
9632
9633 this.request_autoscroll(Autoscroll::fit(), cx);
9634 });
9635 }
9636
9637 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9638 self.manipulate_text(window, cx, |text| {
9639 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9640 if has_upper_case_characters {
9641 text.to_lowercase()
9642 } else {
9643 text.to_uppercase()
9644 }
9645 })
9646 }
9647
9648 pub fn convert_to_upper_case(
9649 &mut self,
9650 _: &ConvertToUpperCase,
9651 window: &mut Window,
9652 cx: &mut Context<Self>,
9653 ) {
9654 self.manipulate_text(window, cx, |text| text.to_uppercase())
9655 }
9656
9657 pub fn convert_to_lower_case(
9658 &mut self,
9659 _: &ConvertToLowerCase,
9660 window: &mut Window,
9661 cx: &mut Context<Self>,
9662 ) {
9663 self.manipulate_text(window, cx, |text| text.to_lowercase())
9664 }
9665
9666 pub fn convert_to_title_case(
9667 &mut self,
9668 _: &ConvertToTitleCase,
9669 window: &mut Window,
9670 cx: &mut Context<Self>,
9671 ) {
9672 self.manipulate_text(window, cx, |text| {
9673 text.split('\n')
9674 .map(|line| line.to_case(Case::Title))
9675 .join("\n")
9676 })
9677 }
9678
9679 pub fn convert_to_snake_case(
9680 &mut self,
9681 _: &ConvertToSnakeCase,
9682 window: &mut Window,
9683 cx: &mut Context<Self>,
9684 ) {
9685 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9686 }
9687
9688 pub fn convert_to_kebab_case(
9689 &mut self,
9690 _: &ConvertToKebabCase,
9691 window: &mut Window,
9692 cx: &mut Context<Self>,
9693 ) {
9694 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9695 }
9696
9697 pub fn convert_to_upper_camel_case(
9698 &mut self,
9699 _: &ConvertToUpperCamelCase,
9700 window: &mut Window,
9701 cx: &mut Context<Self>,
9702 ) {
9703 self.manipulate_text(window, cx, |text| {
9704 text.split('\n')
9705 .map(|line| line.to_case(Case::UpperCamel))
9706 .join("\n")
9707 })
9708 }
9709
9710 pub fn convert_to_lower_camel_case(
9711 &mut self,
9712 _: &ConvertToLowerCamelCase,
9713 window: &mut Window,
9714 cx: &mut Context<Self>,
9715 ) {
9716 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9717 }
9718
9719 pub fn convert_to_opposite_case(
9720 &mut self,
9721 _: &ConvertToOppositeCase,
9722 window: &mut Window,
9723 cx: &mut Context<Self>,
9724 ) {
9725 self.manipulate_text(window, cx, |text| {
9726 text.chars()
9727 .fold(String::with_capacity(text.len()), |mut t, c| {
9728 if c.is_uppercase() {
9729 t.extend(c.to_lowercase());
9730 } else {
9731 t.extend(c.to_uppercase());
9732 }
9733 t
9734 })
9735 })
9736 }
9737
9738 pub fn convert_to_rot13(
9739 &mut self,
9740 _: &ConvertToRot13,
9741 window: &mut Window,
9742 cx: &mut Context<Self>,
9743 ) {
9744 self.manipulate_text(window, cx, |text| {
9745 text.chars()
9746 .map(|c| match c {
9747 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9748 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9749 _ => c,
9750 })
9751 .collect()
9752 })
9753 }
9754
9755 pub fn convert_to_rot47(
9756 &mut self,
9757 _: &ConvertToRot47,
9758 window: &mut Window,
9759 cx: &mut Context<Self>,
9760 ) {
9761 self.manipulate_text(window, cx, |text| {
9762 text.chars()
9763 .map(|c| {
9764 let code_point = c as u32;
9765 if code_point >= 33 && code_point <= 126 {
9766 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9767 }
9768 c
9769 })
9770 .collect()
9771 })
9772 }
9773
9774 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9775 where
9776 Fn: FnMut(&str) -> String,
9777 {
9778 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9779 let buffer = self.buffer.read(cx).snapshot(cx);
9780
9781 let mut new_selections = Vec::new();
9782 let mut edits = Vec::new();
9783 let mut selection_adjustment = 0i32;
9784
9785 for selection in self.selections.all::<usize>(cx) {
9786 let selection_is_empty = selection.is_empty();
9787
9788 let (start, end) = if selection_is_empty {
9789 let word_range = movement::surrounding_word(
9790 &display_map,
9791 selection.start.to_display_point(&display_map),
9792 );
9793 let start = word_range.start.to_offset(&display_map, Bias::Left);
9794 let end = word_range.end.to_offset(&display_map, Bias::Left);
9795 (start, end)
9796 } else {
9797 (selection.start, selection.end)
9798 };
9799
9800 let text = buffer.text_for_range(start..end).collect::<String>();
9801 let old_length = text.len() as i32;
9802 let text = callback(&text);
9803
9804 new_selections.push(Selection {
9805 start: (start as i32 - selection_adjustment) as usize,
9806 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9807 goal: SelectionGoal::None,
9808 ..selection
9809 });
9810
9811 selection_adjustment += old_length - text.len() as i32;
9812
9813 edits.push((start..end, text));
9814 }
9815
9816 self.transact(window, cx, |this, window, cx| {
9817 this.buffer.update(cx, |buffer, cx| {
9818 buffer.edit(edits, None, cx);
9819 });
9820
9821 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9822 s.select(new_selections);
9823 });
9824
9825 this.request_autoscroll(Autoscroll::fit(), cx);
9826 });
9827 }
9828
9829 pub fn duplicate(
9830 &mut self,
9831 upwards: bool,
9832 whole_lines: bool,
9833 window: &mut Window,
9834 cx: &mut Context<Self>,
9835 ) {
9836 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9837
9838 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9839 let buffer = &display_map.buffer_snapshot;
9840 let selections = self.selections.all::<Point>(cx);
9841
9842 let mut edits = Vec::new();
9843 let mut selections_iter = selections.iter().peekable();
9844 while let Some(selection) = selections_iter.next() {
9845 let mut rows = selection.spanned_rows(false, &display_map);
9846 // duplicate line-wise
9847 if whole_lines || selection.start == selection.end {
9848 // Avoid duplicating the same lines twice.
9849 while let Some(next_selection) = selections_iter.peek() {
9850 let next_rows = next_selection.spanned_rows(false, &display_map);
9851 if next_rows.start < rows.end {
9852 rows.end = next_rows.end;
9853 selections_iter.next().unwrap();
9854 } else {
9855 break;
9856 }
9857 }
9858
9859 // Copy the text from the selected row region and splice it either at the start
9860 // or end of the region.
9861 let start = Point::new(rows.start.0, 0);
9862 let end = Point::new(
9863 rows.end.previous_row().0,
9864 buffer.line_len(rows.end.previous_row()),
9865 );
9866 let text = buffer
9867 .text_for_range(start..end)
9868 .chain(Some("\n"))
9869 .collect::<String>();
9870 let insert_location = if upwards {
9871 Point::new(rows.end.0, 0)
9872 } else {
9873 start
9874 };
9875 edits.push((insert_location..insert_location, text));
9876 } else {
9877 // duplicate character-wise
9878 let start = selection.start;
9879 let end = selection.end;
9880 let text = buffer.text_for_range(start..end).collect::<String>();
9881 edits.push((selection.end..selection.end, text));
9882 }
9883 }
9884
9885 self.transact(window, cx, |this, _, cx| {
9886 this.buffer.update(cx, |buffer, cx| {
9887 buffer.edit(edits, None, cx);
9888 });
9889
9890 this.request_autoscroll(Autoscroll::fit(), cx);
9891 });
9892 }
9893
9894 pub fn duplicate_line_up(
9895 &mut self,
9896 _: &DuplicateLineUp,
9897 window: &mut Window,
9898 cx: &mut Context<Self>,
9899 ) {
9900 self.duplicate(true, true, window, cx);
9901 }
9902
9903 pub fn duplicate_line_down(
9904 &mut self,
9905 _: &DuplicateLineDown,
9906 window: &mut Window,
9907 cx: &mut Context<Self>,
9908 ) {
9909 self.duplicate(false, true, window, cx);
9910 }
9911
9912 pub fn duplicate_selection(
9913 &mut self,
9914 _: &DuplicateSelection,
9915 window: &mut Window,
9916 cx: &mut Context<Self>,
9917 ) {
9918 self.duplicate(false, false, window, cx);
9919 }
9920
9921 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9922 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9923
9924 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9925 let buffer = self.buffer.read(cx).snapshot(cx);
9926
9927 let mut edits = Vec::new();
9928 let mut unfold_ranges = Vec::new();
9929 let mut refold_creases = Vec::new();
9930
9931 let selections = self.selections.all::<Point>(cx);
9932 let mut selections = selections.iter().peekable();
9933 let mut contiguous_row_selections = Vec::new();
9934 let mut new_selections = Vec::new();
9935
9936 while let Some(selection) = selections.next() {
9937 // Find all the selections that span a contiguous row range
9938 let (start_row, end_row) = consume_contiguous_rows(
9939 &mut contiguous_row_selections,
9940 selection,
9941 &display_map,
9942 &mut selections,
9943 );
9944
9945 // Move the text spanned by the row range to be before the line preceding the row range
9946 if start_row.0 > 0 {
9947 let range_to_move = Point::new(
9948 start_row.previous_row().0,
9949 buffer.line_len(start_row.previous_row()),
9950 )
9951 ..Point::new(
9952 end_row.previous_row().0,
9953 buffer.line_len(end_row.previous_row()),
9954 );
9955 let insertion_point = display_map
9956 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9957 .0;
9958
9959 // Don't move lines across excerpts
9960 if buffer
9961 .excerpt_containing(insertion_point..range_to_move.end)
9962 .is_some()
9963 {
9964 let text = buffer
9965 .text_for_range(range_to_move.clone())
9966 .flat_map(|s| s.chars())
9967 .skip(1)
9968 .chain(['\n'])
9969 .collect::<String>();
9970
9971 edits.push((
9972 buffer.anchor_after(range_to_move.start)
9973 ..buffer.anchor_before(range_to_move.end),
9974 String::new(),
9975 ));
9976 let insertion_anchor = buffer.anchor_after(insertion_point);
9977 edits.push((insertion_anchor..insertion_anchor, text));
9978
9979 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9980
9981 // Move selections up
9982 new_selections.extend(contiguous_row_selections.drain(..).map(
9983 |mut selection| {
9984 selection.start.row -= row_delta;
9985 selection.end.row -= row_delta;
9986 selection
9987 },
9988 ));
9989
9990 // Move folds up
9991 unfold_ranges.push(range_to_move.clone());
9992 for fold in display_map.folds_in_range(
9993 buffer.anchor_before(range_to_move.start)
9994 ..buffer.anchor_after(range_to_move.end),
9995 ) {
9996 let mut start = fold.range.start.to_point(&buffer);
9997 let mut end = fold.range.end.to_point(&buffer);
9998 start.row -= row_delta;
9999 end.row -= row_delta;
10000 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10001 }
10002 }
10003 }
10004
10005 // If we didn't move line(s), preserve the existing selections
10006 new_selections.append(&mut contiguous_row_selections);
10007 }
10008
10009 self.transact(window, cx, |this, window, cx| {
10010 this.unfold_ranges(&unfold_ranges, true, true, cx);
10011 this.buffer.update(cx, |buffer, cx| {
10012 for (range, text) in edits {
10013 buffer.edit([(range, text)], None, cx);
10014 }
10015 });
10016 this.fold_creases(refold_creases, true, window, cx);
10017 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10018 s.select(new_selections);
10019 })
10020 });
10021 }
10022
10023 pub fn move_line_down(
10024 &mut self,
10025 _: &MoveLineDown,
10026 window: &mut Window,
10027 cx: &mut Context<Self>,
10028 ) {
10029 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10030
10031 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10032 let buffer = self.buffer.read(cx).snapshot(cx);
10033
10034 let mut edits = Vec::new();
10035 let mut unfold_ranges = Vec::new();
10036 let mut refold_creases = Vec::new();
10037
10038 let selections = self.selections.all::<Point>(cx);
10039 let mut selections = selections.iter().peekable();
10040 let mut contiguous_row_selections = Vec::new();
10041 let mut new_selections = Vec::new();
10042
10043 while let Some(selection) = selections.next() {
10044 // Find all the selections that span a contiguous row range
10045 let (start_row, end_row) = consume_contiguous_rows(
10046 &mut contiguous_row_selections,
10047 selection,
10048 &display_map,
10049 &mut selections,
10050 );
10051
10052 // Move the text spanned by the row range to be after the last line of the row range
10053 if end_row.0 <= buffer.max_point().row {
10054 let range_to_move =
10055 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10056 let insertion_point = display_map
10057 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10058 .0;
10059
10060 // Don't move lines across excerpt boundaries
10061 if buffer
10062 .excerpt_containing(range_to_move.start..insertion_point)
10063 .is_some()
10064 {
10065 let mut text = String::from("\n");
10066 text.extend(buffer.text_for_range(range_to_move.clone()));
10067 text.pop(); // Drop trailing newline
10068 edits.push((
10069 buffer.anchor_after(range_to_move.start)
10070 ..buffer.anchor_before(range_to_move.end),
10071 String::new(),
10072 ));
10073 let insertion_anchor = buffer.anchor_after(insertion_point);
10074 edits.push((insertion_anchor..insertion_anchor, text));
10075
10076 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10077
10078 // Move selections down
10079 new_selections.extend(contiguous_row_selections.drain(..).map(
10080 |mut selection| {
10081 selection.start.row += row_delta;
10082 selection.end.row += row_delta;
10083 selection
10084 },
10085 ));
10086
10087 // Move folds down
10088 unfold_ranges.push(range_to_move.clone());
10089 for fold in display_map.folds_in_range(
10090 buffer.anchor_before(range_to_move.start)
10091 ..buffer.anchor_after(range_to_move.end),
10092 ) {
10093 let mut start = fold.range.start.to_point(&buffer);
10094 let mut end = fold.range.end.to_point(&buffer);
10095 start.row += row_delta;
10096 end.row += row_delta;
10097 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10098 }
10099 }
10100 }
10101
10102 // If we didn't move line(s), preserve the existing selections
10103 new_selections.append(&mut contiguous_row_selections);
10104 }
10105
10106 self.transact(window, cx, |this, window, cx| {
10107 this.unfold_ranges(&unfold_ranges, true, true, cx);
10108 this.buffer.update(cx, |buffer, cx| {
10109 for (range, text) in edits {
10110 buffer.edit([(range, text)], None, cx);
10111 }
10112 });
10113 this.fold_creases(refold_creases, true, window, cx);
10114 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10115 s.select(new_selections)
10116 });
10117 });
10118 }
10119
10120 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10121 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10122 let text_layout_details = &self.text_layout_details(window);
10123 self.transact(window, cx, |this, window, cx| {
10124 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10125 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10126 s.move_with(|display_map, selection| {
10127 if !selection.is_empty() {
10128 return;
10129 }
10130
10131 let mut head = selection.head();
10132 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10133 if head.column() == display_map.line_len(head.row()) {
10134 transpose_offset = display_map
10135 .buffer_snapshot
10136 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10137 }
10138
10139 if transpose_offset == 0 {
10140 return;
10141 }
10142
10143 *head.column_mut() += 1;
10144 head = display_map.clip_point(head, Bias::Right);
10145 let goal = SelectionGoal::HorizontalPosition(
10146 display_map
10147 .x_for_display_point(head, text_layout_details)
10148 .into(),
10149 );
10150 selection.collapse_to(head, goal);
10151
10152 let transpose_start = display_map
10153 .buffer_snapshot
10154 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10155 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10156 let transpose_end = display_map
10157 .buffer_snapshot
10158 .clip_offset(transpose_offset + 1, Bias::Right);
10159 if let Some(ch) =
10160 display_map.buffer_snapshot.chars_at(transpose_start).next()
10161 {
10162 edits.push((transpose_start..transpose_offset, String::new()));
10163 edits.push((transpose_end..transpose_end, ch.to_string()));
10164 }
10165 }
10166 });
10167 edits
10168 });
10169 this.buffer
10170 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10171 let selections = this.selections.all::<usize>(cx);
10172 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10173 s.select(selections);
10174 });
10175 });
10176 }
10177
10178 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10179 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10180 self.rewrap_impl(RewrapOptions::default(), cx)
10181 }
10182
10183 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10184 let buffer = self.buffer.read(cx).snapshot(cx);
10185 let selections = self.selections.all::<Point>(cx);
10186 let mut selections = selections.iter().peekable();
10187
10188 let mut edits = Vec::new();
10189 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10190
10191 while let Some(selection) = selections.next() {
10192 let mut start_row = selection.start.row;
10193 let mut end_row = selection.end.row;
10194
10195 // Skip selections that overlap with a range that has already been rewrapped.
10196 let selection_range = start_row..end_row;
10197 if rewrapped_row_ranges
10198 .iter()
10199 .any(|range| range.overlaps(&selection_range))
10200 {
10201 continue;
10202 }
10203
10204 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10205
10206 // Since not all lines in the selection may be at the same indent
10207 // level, choose the indent size that is the most common between all
10208 // of the lines.
10209 //
10210 // If there is a tie, we use the deepest indent.
10211 let (indent_size, indent_end) = {
10212 let mut indent_size_occurrences = HashMap::default();
10213 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10214
10215 for row in start_row..=end_row {
10216 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10217 rows_by_indent_size.entry(indent).or_default().push(row);
10218 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10219 }
10220
10221 let indent_size = indent_size_occurrences
10222 .into_iter()
10223 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10224 .map(|(indent, _)| indent)
10225 .unwrap_or_default();
10226 let row = rows_by_indent_size[&indent_size][0];
10227 let indent_end = Point::new(row, indent_size.len);
10228
10229 (indent_size, indent_end)
10230 };
10231
10232 let mut line_prefix = indent_size.chars().collect::<String>();
10233
10234 let mut inside_comment = false;
10235 if let Some(comment_prefix) =
10236 buffer
10237 .language_scope_at(selection.head())
10238 .and_then(|language| {
10239 language
10240 .line_comment_prefixes()
10241 .iter()
10242 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10243 .cloned()
10244 })
10245 {
10246 line_prefix.push_str(&comment_prefix);
10247 inside_comment = true;
10248 }
10249
10250 let language_settings = buffer.language_settings_at(selection.head(), cx);
10251 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10252 RewrapBehavior::InComments => inside_comment,
10253 RewrapBehavior::InSelections => !selection.is_empty(),
10254 RewrapBehavior::Anywhere => true,
10255 };
10256
10257 let should_rewrap = options.override_language_settings
10258 || allow_rewrap_based_on_language
10259 || self.hard_wrap.is_some();
10260 if !should_rewrap {
10261 continue;
10262 }
10263
10264 if selection.is_empty() {
10265 'expand_upwards: while start_row > 0 {
10266 let prev_row = start_row - 1;
10267 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10268 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10269 {
10270 start_row = prev_row;
10271 } else {
10272 break 'expand_upwards;
10273 }
10274 }
10275
10276 'expand_downwards: while end_row < buffer.max_point().row {
10277 let next_row = end_row + 1;
10278 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10279 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10280 {
10281 end_row = next_row;
10282 } else {
10283 break 'expand_downwards;
10284 }
10285 }
10286 }
10287
10288 let start = Point::new(start_row, 0);
10289 let start_offset = start.to_offset(&buffer);
10290 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10291 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10292 let Some(lines_without_prefixes) = selection_text
10293 .lines()
10294 .map(|line| {
10295 line.strip_prefix(&line_prefix)
10296 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10297 .ok_or_else(|| {
10298 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10299 })
10300 })
10301 .collect::<Result<Vec<_>, _>>()
10302 .log_err()
10303 else {
10304 continue;
10305 };
10306
10307 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10308 buffer
10309 .language_settings_at(Point::new(start_row, 0), cx)
10310 .preferred_line_length as usize
10311 });
10312 let wrapped_text = wrap_with_prefix(
10313 line_prefix,
10314 lines_without_prefixes.join("\n"),
10315 wrap_column,
10316 tab_size,
10317 options.preserve_existing_whitespace,
10318 );
10319
10320 // TODO: should always use char-based diff while still supporting cursor behavior that
10321 // matches vim.
10322 let mut diff_options = DiffOptions::default();
10323 if options.override_language_settings {
10324 diff_options.max_word_diff_len = 0;
10325 diff_options.max_word_diff_line_count = 0;
10326 } else {
10327 diff_options.max_word_diff_len = usize::MAX;
10328 diff_options.max_word_diff_line_count = usize::MAX;
10329 }
10330
10331 for (old_range, new_text) in
10332 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10333 {
10334 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10335 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10336 edits.push((edit_start..edit_end, new_text));
10337 }
10338
10339 rewrapped_row_ranges.push(start_row..=end_row);
10340 }
10341
10342 self.buffer
10343 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10344 }
10345
10346 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10347 let mut text = String::new();
10348 let buffer = self.buffer.read(cx).snapshot(cx);
10349 let mut selections = self.selections.all::<Point>(cx);
10350 let mut clipboard_selections = Vec::with_capacity(selections.len());
10351 {
10352 let max_point = buffer.max_point();
10353 let mut is_first = true;
10354 for selection in &mut selections {
10355 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10356 if is_entire_line {
10357 selection.start = Point::new(selection.start.row, 0);
10358 if !selection.is_empty() && selection.end.column == 0 {
10359 selection.end = cmp::min(max_point, selection.end);
10360 } else {
10361 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10362 }
10363 selection.goal = SelectionGoal::None;
10364 }
10365 if is_first {
10366 is_first = false;
10367 } else {
10368 text += "\n";
10369 }
10370 let mut len = 0;
10371 for chunk in buffer.text_for_range(selection.start..selection.end) {
10372 text.push_str(chunk);
10373 len += chunk.len();
10374 }
10375 clipboard_selections.push(ClipboardSelection {
10376 len,
10377 is_entire_line,
10378 first_line_indent: buffer
10379 .indent_size_for_line(MultiBufferRow(selection.start.row))
10380 .len,
10381 });
10382 }
10383 }
10384
10385 self.transact(window, cx, |this, window, cx| {
10386 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10387 s.select(selections);
10388 });
10389 this.insert("", window, cx);
10390 });
10391 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10392 }
10393
10394 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10395 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10396 let item = self.cut_common(window, cx);
10397 cx.write_to_clipboard(item);
10398 }
10399
10400 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10401 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10402 self.change_selections(None, window, cx, |s| {
10403 s.move_with(|snapshot, sel| {
10404 if sel.is_empty() {
10405 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10406 }
10407 });
10408 });
10409 let item = self.cut_common(window, cx);
10410 cx.set_global(KillRing(item))
10411 }
10412
10413 pub fn kill_ring_yank(
10414 &mut self,
10415 _: &KillRingYank,
10416 window: &mut Window,
10417 cx: &mut Context<Self>,
10418 ) {
10419 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10420 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10421 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10422 (kill_ring.text().to_string(), kill_ring.metadata_json())
10423 } else {
10424 return;
10425 }
10426 } else {
10427 return;
10428 };
10429 self.do_paste(&text, metadata, false, window, cx);
10430 }
10431
10432 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10433 self.do_copy(true, cx);
10434 }
10435
10436 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10437 self.do_copy(false, cx);
10438 }
10439
10440 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10441 let selections = self.selections.all::<Point>(cx);
10442 let buffer = self.buffer.read(cx).read(cx);
10443 let mut text = String::new();
10444
10445 let mut clipboard_selections = Vec::with_capacity(selections.len());
10446 {
10447 let max_point = buffer.max_point();
10448 let mut is_first = true;
10449 for selection in &selections {
10450 let mut start = selection.start;
10451 let mut end = selection.end;
10452 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10453 if is_entire_line {
10454 start = Point::new(start.row, 0);
10455 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10456 }
10457
10458 let mut trimmed_selections = Vec::new();
10459 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10460 let row = MultiBufferRow(start.row);
10461 let first_indent = buffer.indent_size_for_line(row);
10462 if first_indent.len == 0 || start.column > first_indent.len {
10463 trimmed_selections.push(start..end);
10464 } else {
10465 trimmed_selections.push(
10466 Point::new(row.0, first_indent.len)
10467 ..Point::new(row.0, buffer.line_len(row)),
10468 );
10469 for row in start.row + 1..=end.row {
10470 let mut line_len = buffer.line_len(MultiBufferRow(row));
10471 if row == end.row {
10472 line_len = end.column;
10473 }
10474 if line_len == 0 {
10475 trimmed_selections
10476 .push(Point::new(row, 0)..Point::new(row, line_len));
10477 continue;
10478 }
10479 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10480 if row_indent_size.len >= first_indent.len {
10481 trimmed_selections.push(
10482 Point::new(row, first_indent.len)..Point::new(row, line_len),
10483 );
10484 } else {
10485 trimmed_selections.clear();
10486 trimmed_selections.push(start..end);
10487 break;
10488 }
10489 }
10490 }
10491 } else {
10492 trimmed_selections.push(start..end);
10493 }
10494
10495 for trimmed_range in trimmed_selections {
10496 if is_first {
10497 is_first = false;
10498 } else {
10499 text += "\n";
10500 }
10501 let mut len = 0;
10502 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10503 text.push_str(chunk);
10504 len += chunk.len();
10505 }
10506 clipboard_selections.push(ClipboardSelection {
10507 len,
10508 is_entire_line,
10509 first_line_indent: buffer
10510 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10511 .len,
10512 });
10513 }
10514 }
10515 }
10516
10517 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10518 text,
10519 clipboard_selections,
10520 ));
10521 }
10522
10523 pub fn do_paste(
10524 &mut self,
10525 text: &String,
10526 clipboard_selections: Option<Vec<ClipboardSelection>>,
10527 handle_entire_lines: bool,
10528 window: &mut Window,
10529 cx: &mut Context<Self>,
10530 ) {
10531 if self.read_only(cx) {
10532 return;
10533 }
10534
10535 let clipboard_text = Cow::Borrowed(text);
10536
10537 self.transact(window, cx, |this, window, cx| {
10538 if let Some(mut clipboard_selections) = clipboard_selections {
10539 let old_selections = this.selections.all::<usize>(cx);
10540 let all_selections_were_entire_line =
10541 clipboard_selections.iter().all(|s| s.is_entire_line);
10542 let first_selection_indent_column =
10543 clipboard_selections.first().map(|s| s.first_line_indent);
10544 if clipboard_selections.len() != old_selections.len() {
10545 clipboard_selections.drain(..);
10546 }
10547 let cursor_offset = this.selections.last::<usize>(cx).head();
10548 let mut auto_indent_on_paste = true;
10549
10550 this.buffer.update(cx, |buffer, cx| {
10551 let snapshot = buffer.read(cx);
10552 auto_indent_on_paste = snapshot
10553 .language_settings_at(cursor_offset, cx)
10554 .auto_indent_on_paste;
10555
10556 let mut start_offset = 0;
10557 let mut edits = Vec::new();
10558 let mut original_indent_columns = Vec::new();
10559 for (ix, selection) in old_selections.iter().enumerate() {
10560 let to_insert;
10561 let entire_line;
10562 let original_indent_column;
10563 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10564 let end_offset = start_offset + clipboard_selection.len;
10565 to_insert = &clipboard_text[start_offset..end_offset];
10566 entire_line = clipboard_selection.is_entire_line;
10567 start_offset = end_offset + 1;
10568 original_indent_column = Some(clipboard_selection.first_line_indent);
10569 } else {
10570 to_insert = clipboard_text.as_str();
10571 entire_line = all_selections_were_entire_line;
10572 original_indent_column = first_selection_indent_column
10573 }
10574
10575 // If the corresponding selection was empty when this slice of the
10576 // clipboard text was written, then the entire line containing the
10577 // selection was copied. If this selection is also currently empty,
10578 // then paste the line before the current line of the buffer.
10579 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10580 let column = selection.start.to_point(&snapshot).column as usize;
10581 let line_start = selection.start - column;
10582 line_start..line_start
10583 } else {
10584 selection.range()
10585 };
10586
10587 edits.push((range, to_insert));
10588 original_indent_columns.push(original_indent_column);
10589 }
10590 drop(snapshot);
10591
10592 buffer.edit(
10593 edits,
10594 if auto_indent_on_paste {
10595 Some(AutoindentMode::Block {
10596 original_indent_columns,
10597 })
10598 } else {
10599 None
10600 },
10601 cx,
10602 );
10603 });
10604
10605 let selections = this.selections.all::<usize>(cx);
10606 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10607 s.select(selections)
10608 });
10609 } else {
10610 this.insert(&clipboard_text, window, cx);
10611 }
10612 });
10613 }
10614
10615 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10616 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10617 if let Some(item) = cx.read_from_clipboard() {
10618 let entries = item.entries();
10619
10620 match entries.first() {
10621 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10622 // of all the pasted entries.
10623 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10624 .do_paste(
10625 clipboard_string.text(),
10626 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10627 true,
10628 window,
10629 cx,
10630 ),
10631 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10632 }
10633 }
10634 }
10635
10636 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10637 if self.read_only(cx) {
10638 return;
10639 }
10640
10641 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10642
10643 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10644 if let Some((selections, _)) =
10645 self.selection_history.transaction(transaction_id).cloned()
10646 {
10647 self.change_selections(None, window, cx, |s| {
10648 s.select_anchors(selections.to_vec());
10649 });
10650 } else {
10651 log::error!(
10652 "No entry in selection_history found for undo. \
10653 This may correspond to a bug where undo does not update the selection. \
10654 If this is occurring, please add details to \
10655 https://github.com/zed-industries/zed/issues/22692"
10656 );
10657 }
10658 self.request_autoscroll(Autoscroll::fit(), cx);
10659 self.unmark_text(window, cx);
10660 self.refresh_inline_completion(true, false, window, cx);
10661 cx.emit(EditorEvent::Edited { transaction_id });
10662 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10663 }
10664 }
10665
10666 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10667 if self.read_only(cx) {
10668 return;
10669 }
10670
10671 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10672
10673 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10674 if let Some((_, Some(selections))) =
10675 self.selection_history.transaction(transaction_id).cloned()
10676 {
10677 self.change_selections(None, window, cx, |s| {
10678 s.select_anchors(selections.to_vec());
10679 });
10680 } else {
10681 log::error!(
10682 "No entry in selection_history found for redo. \
10683 This may correspond to a bug where undo does not update the selection. \
10684 If this is occurring, please add details to \
10685 https://github.com/zed-industries/zed/issues/22692"
10686 );
10687 }
10688 self.request_autoscroll(Autoscroll::fit(), cx);
10689 self.unmark_text(window, cx);
10690 self.refresh_inline_completion(true, false, window, cx);
10691 cx.emit(EditorEvent::Edited { transaction_id });
10692 }
10693 }
10694
10695 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10696 self.buffer
10697 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10698 }
10699
10700 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10701 self.buffer
10702 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10703 }
10704
10705 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10706 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10707 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10708 s.move_with(|map, selection| {
10709 let cursor = if selection.is_empty() {
10710 movement::left(map, selection.start)
10711 } else {
10712 selection.start
10713 };
10714 selection.collapse_to(cursor, SelectionGoal::None);
10715 });
10716 })
10717 }
10718
10719 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10720 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10721 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10722 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10723 })
10724 }
10725
10726 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10727 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10728 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10729 s.move_with(|map, selection| {
10730 let cursor = if selection.is_empty() {
10731 movement::right(map, selection.end)
10732 } else {
10733 selection.end
10734 };
10735 selection.collapse_to(cursor, SelectionGoal::None)
10736 });
10737 })
10738 }
10739
10740 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10741 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10742 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10743 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10744 })
10745 }
10746
10747 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10748 if self.take_rename(true, window, cx).is_some() {
10749 return;
10750 }
10751
10752 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10753 cx.propagate();
10754 return;
10755 }
10756
10757 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10758
10759 let text_layout_details = &self.text_layout_details(window);
10760 let selection_count = self.selections.count();
10761 let first_selection = self.selections.first_anchor();
10762
10763 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10764 s.move_with(|map, selection| {
10765 if !selection.is_empty() {
10766 selection.goal = SelectionGoal::None;
10767 }
10768 let (cursor, goal) = movement::up(
10769 map,
10770 selection.start,
10771 selection.goal,
10772 false,
10773 text_layout_details,
10774 );
10775 selection.collapse_to(cursor, goal);
10776 });
10777 });
10778
10779 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10780 {
10781 cx.propagate();
10782 }
10783 }
10784
10785 pub fn move_up_by_lines(
10786 &mut self,
10787 action: &MoveUpByLines,
10788 window: &mut Window,
10789 cx: &mut Context<Self>,
10790 ) {
10791 if self.take_rename(true, window, cx).is_some() {
10792 return;
10793 }
10794
10795 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10796 cx.propagate();
10797 return;
10798 }
10799
10800 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10801
10802 let text_layout_details = &self.text_layout_details(window);
10803
10804 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10805 s.move_with(|map, selection| {
10806 if !selection.is_empty() {
10807 selection.goal = SelectionGoal::None;
10808 }
10809 let (cursor, goal) = movement::up_by_rows(
10810 map,
10811 selection.start,
10812 action.lines,
10813 selection.goal,
10814 false,
10815 text_layout_details,
10816 );
10817 selection.collapse_to(cursor, goal);
10818 });
10819 })
10820 }
10821
10822 pub fn move_down_by_lines(
10823 &mut self,
10824 action: &MoveDownByLines,
10825 window: &mut Window,
10826 cx: &mut Context<Self>,
10827 ) {
10828 if self.take_rename(true, window, cx).is_some() {
10829 return;
10830 }
10831
10832 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10833 cx.propagate();
10834 return;
10835 }
10836
10837 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10838
10839 let text_layout_details = &self.text_layout_details(window);
10840
10841 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10842 s.move_with(|map, selection| {
10843 if !selection.is_empty() {
10844 selection.goal = SelectionGoal::None;
10845 }
10846 let (cursor, goal) = movement::down_by_rows(
10847 map,
10848 selection.start,
10849 action.lines,
10850 selection.goal,
10851 false,
10852 text_layout_details,
10853 );
10854 selection.collapse_to(cursor, goal);
10855 });
10856 })
10857 }
10858
10859 pub fn select_down_by_lines(
10860 &mut self,
10861 action: &SelectDownByLines,
10862 window: &mut Window,
10863 cx: &mut Context<Self>,
10864 ) {
10865 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10866 let text_layout_details = &self.text_layout_details(window);
10867 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10868 s.move_heads_with(|map, head, goal| {
10869 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10870 })
10871 })
10872 }
10873
10874 pub fn select_up_by_lines(
10875 &mut self,
10876 action: &SelectUpByLines,
10877 window: &mut Window,
10878 cx: &mut Context<Self>,
10879 ) {
10880 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10881 let text_layout_details = &self.text_layout_details(window);
10882 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10883 s.move_heads_with(|map, head, goal| {
10884 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10885 })
10886 })
10887 }
10888
10889 pub fn select_page_up(
10890 &mut self,
10891 _: &SelectPageUp,
10892 window: &mut Window,
10893 cx: &mut Context<Self>,
10894 ) {
10895 let Some(row_count) = self.visible_row_count() else {
10896 return;
10897 };
10898
10899 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10900
10901 let text_layout_details = &self.text_layout_details(window);
10902
10903 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10904 s.move_heads_with(|map, head, goal| {
10905 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10906 })
10907 })
10908 }
10909
10910 pub fn move_page_up(
10911 &mut self,
10912 action: &MovePageUp,
10913 window: &mut Window,
10914 cx: &mut Context<Self>,
10915 ) {
10916 if self.take_rename(true, window, cx).is_some() {
10917 return;
10918 }
10919
10920 if self
10921 .context_menu
10922 .borrow_mut()
10923 .as_mut()
10924 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10925 .unwrap_or(false)
10926 {
10927 return;
10928 }
10929
10930 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10931 cx.propagate();
10932 return;
10933 }
10934
10935 let Some(row_count) = self.visible_row_count() else {
10936 return;
10937 };
10938
10939 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10940
10941 let autoscroll = if action.center_cursor {
10942 Autoscroll::center()
10943 } else {
10944 Autoscroll::fit()
10945 };
10946
10947 let text_layout_details = &self.text_layout_details(window);
10948
10949 self.change_selections(Some(autoscroll), window, cx, |s| {
10950 s.move_with(|map, selection| {
10951 if !selection.is_empty() {
10952 selection.goal = SelectionGoal::None;
10953 }
10954 let (cursor, goal) = movement::up_by_rows(
10955 map,
10956 selection.end,
10957 row_count,
10958 selection.goal,
10959 false,
10960 text_layout_details,
10961 );
10962 selection.collapse_to(cursor, goal);
10963 });
10964 });
10965 }
10966
10967 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10968 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10969 let text_layout_details = &self.text_layout_details(window);
10970 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10971 s.move_heads_with(|map, head, goal| {
10972 movement::up(map, head, goal, false, text_layout_details)
10973 })
10974 })
10975 }
10976
10977 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10978 self.take_rename(true, window, cx);
10979
10980 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10981 cx.propagate();
10982 return;
10983 }
10984
10985 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10986
10987 let text_layout_details = &self.text_layout_details(window);
10988 let selection_count = self.selections.count();
10989 let first_selection = self.selections.first_anchor();
10990
10991 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10992 s.move_with(|map, selection| {
10993 if !selection.is_empty() {
10994 selection.goal = SelectionGoal::None;
10995 }
10996 let (cursor, goal) = movement::down(
10997 map,
10998 selection.end,
10999 selection.goal,
11000 false,
11001 text_layout_details,
11002 );
11003 selection.collapse_to(cursor, goal);
11004 });
11005 });
11006
11007 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11008 {
11009 cx.propagate();
11010 }
11011 }
11012
11013 pub fn select_page_down(
11014 &mut self,
11015 _: &SelectPageDown,
11016 window: &mut Window,
11017 cx: &mut Context<Self>,
11018 ) {
11019 let Some(row_count) = self.visible_row_count() else {
11020 return;
11021 };
11022
11023 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11024
11025 let text_layout_details = &self.text_layout_details(window);
11026
11027 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11028 s.move_heads_with(|map, head, goal| {
11029 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11030 })
11031 })
11032 }
11033
11034 pub fn move_page_down(
11035 &mut self,
11036 action: &MovePageDown,
11037 window: &mut Window,
11038 cx: &mut Context<Self>,
11039 ) {
11040 if self.take_rename(true, window, cx).is_some() {
11041 return;
11042 }
11043
11044 if self
11045 .context_menu
11046 .borrow_mut()
11047 .as_mut()
11048 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11049 .unwrap_or(false)
11050 {
11051 return;
11052 }
11053
11054 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11055 cx.propagate();
11056 return;
11057 }
11058
11059 let Some(row_count) = self.visible_row_count() else {
11060 return;
11061 };
11062
11063 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11064
11065 let autoscroll = if action.center_cursor {
11066 Autoscroll::center()
11067 } else {
11068 Autoscroll::fit()
11069 };
11070
11071 let text_layout_details = &self.text_layout_details(window);
11072 self.change_selections(Some(autoscroll), window, cx, |s| {
11073 s.move_with(|map, selection| {
11074 if !selection.is_empty() {
11075 selection.goal = SelectionGoal::None;
11076 }
11077 let (cursor, goal) = movement::down_by_rows(
11078 map,
11079 selection.end,
11080 row_count,
11081 selection.goal,
11082 false,
11083 text_layout_details,
11084 );
11085 selection.collapse_to(cursor, goal);
11086 });
11087 });
11088 }
11089
11090 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11091 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11092 let text_layout_details = &self.text_layout_details(window);
11093 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11094 s.move_heads_with(|map, head, goal| {
11095 movement::down(map, head, goal, false, text_layout_details)
11096 })
11097 });
11098 }
11099
11100 pub fn context_menu_first(
11101 &mut self,
11102 _: &ContextMenuFirst,
11103 _window: &mut Window,
11104 cx: &mut Context<Self>,
11105 ) {
11106 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11107 context_menu.select_first(self.completion_provider.as_deref(), cx);
11108 }
11109 }
11110
11111 pub fn context_menu_prev(
11112 &mut self,
11113 _: &ContextMenuPrevious,
11114 _window: &mut Window,
11115 cx: &mut Context<Self>,
11116 ) {
11117 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11118 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11119 }
11120 }
11121
11122 pub fn context_menu_next(
11123 &mut self,
11124 _: &ContextMenuNext,
11125 _window: &mut Window,
11126 cx: &mut Context<Self>,
11127 ) {
11128 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11129 context_menu.select_next(self.completion_provider.as_deref(), cx);
11130 }
11131 }
11132
11133 pub fn context_menu_last(
11134 &mut self,
11135 _: &ContextMenuLast,
11136 _window: &mut Window,
11137 cx: &mut Context<Self>,
11138 ) {
11139 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11140 context_menu.select_last(self.completion_provider.as_deref(), cx);
11141 }
11142 }
11143
11144 pub fn move_to_previous_word_start(
11145 &mut self,
11146 _: &MoveToPreviousWordStart,
11147 window: &mut Window,
11148 cx: &mut Context<Self>,
11149 ) {
11150 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11151 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11152 s.move_cursors_with(|map, head, _| {
11153 (
11154 movement::previous_word_start(map, head),
11155 SelectionGoal::None,
11156 )
11157 });
11158 })
11159 }
11160
11161 pub fn move_to_previous_subword_start(
11162 &mut self,
11163 _: &MoveToPreviousSubwordStart,
11164 window: &mut Window,
11165 cx: &mut Context<Self>,
11166 ) {
11167 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11168 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11169 s.move_cursors_with(|map, head, _| {
11170 (
11171 movement::previous_subword_start(map, head),
11172 SelectionGoal::None,
11173 )
11174 });
11175 })
11176 }
11177
11178 pub fn select_to_previous_word_start(
11179 &mut self,
11180 _: &SelectToPreviousWordStart,
11181 window: &mut Window,
11182 cx: &mut Context<Self>,
11183 ) {
11184 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11185 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11186 s.move_heads_with(|map, head, _| {
11187 (
11188 movement::previous_word_start(map, head),
11189 SelectionGoal::None,
11190 )
11191 });
11192 })
11193 }
11194
11195 pub fn select_to_previous_subword_start(
11196 &mut self,
11197 _: &SelectToPreviousSubwordStart,
11198 window: &mut Window,
11199 cx: &mut Context<Self>,
11200 ) {
11201 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11202 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11203 s.move_heads_with(|map, head, _| {
11204 (
11205 movement::previous_subword_start(map, head),
11206 SelectionGoal::None,
11207 )
11208 });
11209 })
11210 }
11211
11212 pub fn delete_to_previous_word_start(
11213 &mut self,
11214 action: &DeleteToPreviousWordStart,
11215 window: &mut Window,
11216 cx: &mut Context<Self>,
11217 ) {
11218 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11219 self.transact(window, cx, |this, window, cx| {
11220 this.select_autoclose_pair(window, cx);
11221 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11222 s.move_with(|map, selection| {
11223 if selection.is_empty() {
11224 let cursor = if action.ignore_newlines {
11225 movement::previous_word_start(map, selection.head())
11226 } else {
11227 movement::previous_word_start_or_newline(map, selection.head())
11228 };
11229 selection.set_head(cursor, SelectionGoal::None);
11230 }
11231 });
11232 });
11233 this.insert("", window, cx);
11234 });
11235 }
11236
11237 pub fn delete_to_previous_subword_start(
11238 &mut self,
11239 _: &DeleteToPreviousSubwordStart,
11240 window: &mut Window,
11241 cx: &mut Context<Self>,
11242 ) {
11243 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11244 self.transact(window, cx, |this, window, cx| {
11245 this.select_autoclose_pair(window, cx);
11246 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11247 s.move_with(|map, selection| {
11248 if selection.is_empty() {
11249 let cursor = movement::previous_subword_start(map, selection.head());
11250 selection.set_head(cursor, SelectionGoal::None);
11251 }
11252 });
11253 });
11254 this.insert("", window, cx);
11255 });
11256 }
11257
11258 pub fn move_to_next_word_end(
11259 &mut self,
11260 _: &MoveToNextWordEnd,
11261 window: &mut Window,
11262 cx: &mut Context<Self>,
11263 ) {
11264 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11265 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11266 s.move_cursors_with(|map, head, _| {
11267 (movement::next_word_end(map, head), SelectionGoal::None)
11268 });
11269 })
11270 }
11271
11272 pub fn move_to_next_subword_end(
11273 &mut self,
11274 _: &MoveToNextSubwordEnd,
11275 window: &mut Window,
11276 cx: &mut Context<Self>,
11277 ) {
11278 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11279 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11280 s.move_cursors_with(|map, head, _| {
11281 (movement::next_subword_end(map, head), SelectionGoal::None)
11282 });
11283 })
11284 }
11285
11286 pub fn select_to_next_word_end(
11287 &mut self,
11288 _: &SelectToNextWordEnd,
11289 window: &mut Window,
11290 cx: &mut Context<Self>,
11291 ) {
11292 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11293 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11294 s.move_heads_with(|map, head, _| {
11295 (movement::next_word_end(map, head), SelectionGoal::None)
11296 });
11297 })
11298 }
11299
11300 pub fn select_to_next_subword_end(
11301 &mut self,
11302 _: &SelectToNextSubwordEnd,
11303 window: &mut Window,
11304 cx: &mut Context<Self>,
11305 ) {
11306 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11307 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11308 s.move_heads_with(|map, head, _| {
11309 (movement::next_subword_end(map, head), SelectionGoal::None)
11310 });
11311 })
11312 }
11313
11314 pub fn delete_to_next_word_end(
11315 &mut self,
11316 action: &DeleteToNextWordEnd,
11317 window: &mut Window,
11318 cx: &mut Context<Self>,
11319 ) {
11320 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11321 self.transact(window, cx, |this, window, cx| {
11322 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11323 s.move_with(|map, selection| {
11324 if selection.is_empty() {
11325 let cursor = if action.ignore_newlines {
11326 movement::next_word_end(map, selection.head())
11327 } else {
11328 movement::next_word_end_or_newline(map, selection.head())
11329 };
11330 selection.set_head(cursor, SelectionGoal::None);
11331 }
11332 });
11333 });
11334 this.insert("", window, cx);
11335 });
11336 }
11337
11338 pub fn delete_to_next_subword_end(
11339 &mut self,
11340 _: &DeleteToNextSubwordEnd,
11341 window: &mut Window,
11342 cx: &mut Context<Self>,
11343 ) {
11344 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11345 self.transact(window, cx, |this, window, cx| {
11346 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11347 s.move_with(|map, selection| {
11348 if selection.is_empty() {
11349 let cursor = movement::next_subword_end(map, selection.head());
11350 selection.set_head(cursor, SelectionGoal::None);
11351 }
11352 });
11353 });
11354 this.insert("", window, cx);
11355 });
11356 }
11357
11358 pub fn move_to_beginning_of_line(
11359 &mut self,
11360 action: &MoveToBeginningOfLine,
11361 window: &mut Window,
11362 cx: &mut Context<Self>,
11363 ) {
11364 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11365 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11366 s.move_cursors_with(|map, head, _| {
11367 (
11368 movement::indented_line_beginning(
11369 map,
11370 head,
11371 action.stop_at_soft_wraps,
11372 action.stop_at_indent,
11373 ),
11374 SelectionGoal::None,
11375 )
11376 });
11377 })
11378 }
11379
11380 pub fn select_to_beginning_of_line(
11381 &mut self,
11382 action: &SelectToBeginningOfLine,
11383 window: &mut Window,
11384 cx: &mut Context<Self>,
11385 ) {
11386 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11387 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11388 s.move_heads_with(|map, head, _| {
11389 (
11390 movement::indented_line_beginning(
11391 map,
11392 head,
11393 action.stop_at_soft_wraps,
11394 action.stop_at_indent,
11395 ),
11396 SelectionGoal::None,
11397 )
11398 });
11399 });
11400 }
11401
11402 pub fn delete_to_beginning_of_line(
11403 &mut self,
11404 action: &DeleteToBeginningOfLine,
11405 window: &mut Window,
11406 cx: &mut Context<Self>,
11407 ) {
11408 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11409 self.transact(window, cx, |this, window, cx| {
11410 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11411 s.move_with(|_, selection| {
11412 selection.reversed = true;
11413 });
11414 });
11415
11416 this.select_to_beginning_of_line(
11417 &SelectToBeginningOfLine {
11418 stop_at_soft_wraps: false,
11419 stop_at_indent: action.stop_at_indent,
11420 },
11421 window,
11422 cx,
11423 );
11424 this.backspace(&Backspace, window, cx);
11425 });
11426 }
11427
11428 pub fn move_to_end_of_line(
11429 &mut self,
11430 action: &MoveToEndOfLine,
11431 window: &mut Window,
11432 cx: &mut Context<Self>,
11433 ) {
11434 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11435 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11436 s.move_cursors_with(|map, head, _| {
11437 (
11438 movement::line_end(map, head, action.stop_at_soft_wraps),
11439 SelectionGoal::None,
11440 )
11441 });
11442 })
11443 }
11444
11445 pub fn select_to_end_of_line(
11446 &mut self,
11447 action: &SelectToEndOfLine,
11448 window: &mut Window,
11449 cx: &mut Context<Self>,
11450 ) {
11451 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11452 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11453 s.move_heads_with(|map, head, _| {
11454 (
11455 movement::line_end(map, head, action.stop_at_soft_wraps),
11456 SelectionGoal::None,
11457 )
11458 });
11459 })
11460 }
11461
11462 pub fn delete_to_end_of_line(
11463 &mut self,
11464 _: &DeleteToEndOfLine,
11465 window: &mut Window,
11466 cx: &mut Context<Self>,
11467 ) {
11468 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11469 self.transact(window, cx, |this, window, cx| {
11470 this.select_to_end_of_line(
11471 &SelectToEndOfLine {
11472 stop_at_soft_wraps: false,
11473 },
11474 window,
11475 cx,
11476 );
11477 this.delete(&Delete, window, cx);
11478 });
11479 }
11480
11481 pub fn cut_to_end_of_line(
11482 &mut self,
11483 _: &CutToEndOfLine,
11484 window: &mut Window,
11485 cx: &mut Context<Self>,
11486 ) {
11487 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11488 self.transact(window, cx, |this, window, cx| {
11489 this.select_to_end_of_line(
11490 &SelectToEndOfLine {
11491 stop_at_soft_wraps: false,
11492 },
11493 window,
11494 cx,
11495 );
11496 this.cut(&Cut, window, cx);
11497 });
11498 }
11499
11500 pub fn move_to_start_of_paragraph(
11501 &mut self,
11502 _: &MoveToStartOfParagraph,
11503 window: &mut Window,
11504 cx: &mut Context<Self>,
11505 ) {
11506 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11507 cx.propagate();
11508 return;
11509 }
11510 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11511 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11512 s.move_with(|map, selection| {
11513 selection.collapse_to(
11514 movement::start_of_paragraph(map, selection.head(), 1),
11515 SelectionGoal::None,
11516 )
11517 });
11518 })
11519 }
11520
11521 pub fn move_to_end_of_paragraph(
11522 &mut self,
11523 _: &MoveToEndOfParagraph,
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_paragraph(map, selection.head(), 1),
11536 SelectionGoal::None,
11537 )
11538 });
11539 })
11540 }
11541
11542 pub fn select_to_start_of_paragraph(
11543 &mut self,
11544 _: &SelectToStartOfParagraph,
11545 window: &mut Window,
11546 cx: &mut Context<Self>,
11547 ) {
11548 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11549 cx.propagate();
11550 return;
11551 }
11552 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11553 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11554 s.move_heads_with(|map, head, _| {
11555 (
11556 movement::start_of_paragraph(map, head, 1),
11557 SelectionGoal::None,
11558 )
11559 });
11560 })
11561 }
11562
11563 pub fn select_to_end_of_paragraph(
11564 &mut self,
11565 _: &SelectToEndOfParagraph,
11566 window: &mut Window,
11567 cx: &mut Context<Self>,
11568 ) {
11569 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11570 cx.propagate();
11571 return;
11572 }
11573 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11574 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11575 s.move_heads_with(|map, head, _| {
11576 (
11577 movement::end_of_paragraph(map, head, 1),
11578 SelectionGoal::None,
11579 )
11580 });
11581 })
11582 }
11583
11584 pub fn move_to_start_of_excerpt(
11585 &mut self,
11586 _: &MoveToStartOfExcerpt,
11587 window: &mut Window,
11588 cx: &mut Context<Self>,
11589 ) {
11590 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11591 cx.propagate();
11592 return;
11593 }
11594 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11595 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11596 s.move_with(|map, selection| {
11597 selection.collapse_to(
11598 movement::start_of_excerpt(
11599 map,
11600 selection.head(),
11601 workspace::searchable::Direction::Prev,
11602 ),
11603 SelectionGoal::None,
11604 )
11605 });
11606 })
11607 }
11608
11609 pub fn move_to_start_of_next_excerpt(
11610 &mut self,
11611 _: &MoveToStartOfNextExcerpt,
11612 window: &mut Window,
11613 cx: &mut Context<Self>,
11614 ) {
11615 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11616 cx.propagate();
11617 return;
11618 }
11619
11620 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11621 s.move_with(|map, selection| {
11622 selection.collapse_to(
11623 movement::start_of_excerpt(
11624 map,
11625 selection.head(),
11626 workspace::searchable::Direction::Next,
11627 ),
11628 SelectionGoal::None,
11629 )
11630 });
11631 })
11632 }
11633
11634 pub fn move_to_end_of_excerpt(
11635 &mut self,
11636 _: &MoveToEndOfExcerpt,
11637 window: &mut Window,
11638 cx: &mut Context<Self>,
11639 ) {
11640 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11641 cx.propagate();
11642 return;
11643 }
11644 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11645 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11646 s.move_with(|map, selection| {
11647 selection.collapse_to(
11648 movement::end_of_excerpt(
11649 map,
11650 selection.head(),
11651 workspace::searchable::Direction::Next,
11652 ),
11653 SelectionGoal::None,
11654 )
11655 });
11656 })
11657 }
11658
11659 pub fn move_to_end_of_previous_excerpt(
11660 &mut self,
11661 _: &MoveToEndOfPreviousExcerpt,
11662 window: &mut Window,
11663 cx: &mut Context<Self>,
11664 ) {
11665 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11666 cx.propagate();
11667 return;
11668 }
11669 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11670 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11671 s.move_with(|map, selection| {
11672 selection.collapse_to(
11673 movement::end_of_excerpt(
11674 map,
11675 selection.head(),
11676 workspace::searchable::Direction::Prev,
11677 ),
11678 SelectionGoal::None,
11679 )
11680 });
11681 })
11682 }
11683
11684 pub fn select_to_start_of_excerpt(
11685 &mut self,
11686 _: &SelectToStartOfExcerpt,
11687 window: &mut Window,
11688 cx: &mut Context<Self>,
11689 ) {
11690 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11691 cx.propagate();
11692 return;
11693 }
11694 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11695 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11696 s.move_heads_with(|map, head, _| {
11697 (
11698 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11699 SelectionGoal::None,
11700 )
11701 });
11702 })
11703 }
11704
11705 pub fn select_to_start_of_next_excerpt(
11706 &mut self,
11707 _: &SelectToStartOfNextExcerpt,
11708 window: &mut Window,
11709 cx: &mut Context<Self>,
11710 ) {
11711 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11712 cx.propagate();
11713 return;
11714 }
11715 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11716 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11717 s.move_heads_with(|map, head, _| {
11718 (
11719 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11720 SelectionGoal::None,
11721 )
11722 });
11723 })
11724 }
11725
11726 pub fn select_to_end_of_excerpt(
11727 &mut self,
11728 _: &SelectToEndOfExcerpt,
11729 window: &mut Window,
11730 cx: &mut Context<Self>,
11731 ) {
11732 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11733 cx.propagate();
11734 return;
11735 }
11736 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11737 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11738 s.move_heads_with(|map, head, _| {
11739 (
11740 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11741 SelectionGoal::None,
11742 )
11743 });
11744 })
11745 }
11746
11747 pub fn select_to_end_of_previous_excerpt(
11748 &mut self,
11749 _: &SelectToEndOfPreviousExcerpt,
11750 window: &mut Window,
11751 cx: &mut Context<Self>,
11752 ) {
11753 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11754 cx.propagate();
11755 return;
11756 }
11757 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11758 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11759 s.move_heads_with(|map, head, _| {
11760 (
11761 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11762 SelectionGoal::None,
11763 )
11764 });
11765 })
11766 }
11767
11768 pub fn move_to_beginning(
11769 &mut self,
11770 _: &MoveToBeginning,
11771 window: &mut Window,
11772 cx: &mut Context<Self>,
11773 ) {
11774 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11775 cx.propagate();
11776 return;
11777 }
11778 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11779 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11780 s.select_ranges(vec![0..0]);
11781 });
11782 }
11783
11784 pub fn select_to_beginning(
11785 &mut self,
11786 _: &SelectToBeginning,
11787 window: &mut Window,
11788 cx: &mut Context<Self>,
11789 ) {
11790 let mut selection = self.selections.last::<Point>(cx);
11791 selection.set_head(Point::zero(), SelectionGoal::None);
11792 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11793 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11794 s.select(vec![selection]);
11795 });
11796 }
11797
11798 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11799 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11800 cx.propagate();
11801 return;
11802 }
11803 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11804 let cursor = self.buffer.read(cx).read(cx).len();
11805 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11806 s.select_ranges(vec![cursor..cursor])
11807 });
11808 }
11809
11810 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11811 self.nav_history = nav_history;
11812 }
11813
11814 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11815 self.nav_history.as_ref()
11816 }
11817
11818 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11819 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11820 }
11821
11822 fn push_to_nav_history(
11823 &mut self,
11824 cursor_anchor: Anchor,
11825 new_position: Option<Point>,
11826 is_deactivate: bool,
11827 cx: &mut Context<Self>,
11828 ) {
11829 if let Some(nav_history) = self.nav_history.as_mut() {
11830 let buffer = self.buffer.read(cx).read(cx);
11831 let cursor_position = cursor_anchor.to_point(&buffer);
11832 let scroll_state = self.scroll_manager.anchor();
11833 let scroll_top_row = scroll_state.top_row(&buffer);
11834 drop(buffer);
11835
11836 if let Some(new_position) = new_position {
11837 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11838 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11839 return;
11840 }
11841 }
11842
11843 nav_history.push(
11844 Some(NavigationData {
11845 cursor_anchor,
11846 cursor_position,
11847 scroll_anchor: scroll_state,
11848 scroll_top_row,
11849 }),
11850 cx,
11851 );
11852 cx.emit(EditorEvent::PushedToNavHistory {
11853 anchor: cursor_anchor,
11854 is_deactivate,
11855 })
11856 }
11857 }
11858
11859 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11860 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11861 let buffer = self.buffer.read(cx).snapshot(cx);
11862 let mut selection = self.selections.first::<usize>(cx);
11863 selection.set_head(buffer.len(), SelectionGoal::None);
11864 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11865 s.select(vec![selection]);
11866 });
11867 }
11868
11869 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11870 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11871 let end = self.buffer.read(cx).read(cx).len();
11872 self.change_selections(None, window, cx, |s| {
11873 s.select_ranges(vec![0..end]);
11874 });
11875 }
11876
11877 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11878 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11879 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11880 let mut selections = self.selections.all::<Point>(cx);
11881 let max_point = display_map.buffer_snapshot.max_point();
11882 for selection in &mut selections {
11883 let rows = selection.spanned_rows(true, &display_map);
11884 selection.start = Point::new(rows.start.0, 0);
11885 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11886 selection.reversed = false;
11887 }
11888 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11889 s.select(selections);
11890 });
11891 }
11892
11893 pub fn split_selection_into_lines(
11894 &mut self,
11895 _: &SplitSelectionIntoLines,
11896 window: &mut Window,
11897 cx: &mut Context<Self>,
11898 ) {
11899 let selections = self
11900 .selections
11901 .all::<Point>(cx)
11902 .into_iter()
11903 .map(|selection| selection.start..selection.end)
11904 .collect::<Vec<_>>();
11905 self.unfold_ranges(&selections, true, true, cx);
11906
11907 let mut new_selection_ranges = Vec::new();
11908 {
11909 let buffer = self.buffer.read(cx).read(cx);
11910 for selection in selections {
11911 for row in selection.start.row..selection.end.row {
11912 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11913 new_selection_ranges.push(cursor..cursor);
11914 }
11915
11916 let is_multiline_selection = selection.start.row != selection.end.row;
11917 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11918 // so this action feels more ergonomic when paired with other selection operations
11919 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11920 if !should_skip_last {
11921 new_selection_ranges.push(selection.end..selection.end);
11922 }
11923 }
11924 }
11925 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11926 s.select_ranges(new_selection_ranges);
11927 });
11928 }
11929
11930 pub fn add_selection_above(
11931 &mut self,
11932 _: &AddSelectionAbove,
11933 window: &mut Window,
11934 cx: &mut Context<Self>,
11935 ) {
11936 self.add_selection(true, window, cx);
11937 }
11938
11939 pub fn add_selection_below(
11940 &mut self,
11941 _: &AddSelectionBelow,
11942 window: &mut Window,
11943 cx: &mut Context<Self>,
11944 ) {
11945 self.add_selection(false, window, cx);
11946 }
11947
11948 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11949 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11950
11951 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11952 let mut selections = self.selections.all::<Point>(cx);
11953 let text_layout_details = self.text_layout_details(window);
11954 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11955 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11956 let range = oldest_selection.display_range(&display_map).sorted();
11957
11958 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11959 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11960 let positions = start_x.min(end_x)..start_x.max(end_x);
11961
11962 selections.clear();
11963 let mut stack = Vec::new();
11964 for row in range.start.row().0..=range.end.row().0 {
11965 if let Some(selection) = self.selections.build_columnar_selection(
11966 &display_map,
11967 DisplayRow(row),
11968 &positions,
11969 oldest_selection.reversed,
11970 &text_layout_details,
11971 ) {
11972 stack.push(selection.id);
11973 selections.push(selection);
11974 }
11975 }
11976
11977 if above {
11978 stack.reverse();
11979 }
11980
11981 AddSelectionsState { above, stack }
11982 });
11983
11984 let last_added_selection = *state.stack.last().unwrap();
11985 let mut new_selections = Vec::new();
11986 if above == state.above {
11987 let end_row = if above {
11988 DisplayRow(0)
11989 } else {
11990 display_map.max_point().row()
11991 };
11992
11993 'outer: for selection in selections {
11994 if selection.id == last_added_selection {
11995 let range = selection.display_range(&display_map).sorted();
11996 debug_assert_eq!(range.start.row(), range.end.row());
11997 let mut row = range.start.row();
11998 let positions =
11999 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12000 px(start)..px(end)
12001 } else {
12002 let start_x =
12003 display_map.x_for_display_point(range.start, &text_layout_details);
12004 let end_x =
12005 display_map.x_for_display_point(range.end, &text_layout_details);
12006 start_x.min(end_x)..start_x.max(end_x)
12007 };
12008
12009 while row != end_row {
12010 if above {
12011 row.0 -= 1;
12012 } else {
12013 row.0 += 1;
12014 }
12015
12016 if let Some(new_selection) = self.selections.build_columnar_selection(
12017 &display_map,
12018 row,
12019 &positions,
12020 selection.reversed,
12021 &text_layout_details,
12022 ) {
12023 state.stack.push(new_selection.id);
12024 if above {
12025 new_selections.push(new_selection);
12026 new_selections.push(selection);
12027 } else {
12028 new_selections.push(selection);
12029 new_selections.push(new_selection);
12030 }
12031
12032 continue 'outer;
12033 }
12034 }
12035 }
12036
12037 new_selections.push(selection);
12038 }
12039 } else {
12040 new_selections = selections;
12041 new_selections.retain(|s| s.id != last_added_selection);
12042 state.stack.pop();
12043 }
12044
12045 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12046 s.select(new_selections);
12047 });
12048 if state.stack.len() > 1 {
12049 self.add_selections_state = Some(state);
12050 }
12051 }
12052
12053 pub fn select_next_match_internal(
12054 &mut self,
12055 display_map: &DisplaySnapshot,
12056 replace_newest: bool,
12057 autoscroll: Option<Autoscroll>,
12058 window: &mut Window,
12059 cx: &mut Context<Self>,
12060 ) -> Result<()> {
12061 fn select_next_match_ranges(
12062 this: &mut Editor,
12063 range: Range<usize>,
12064 reversed: bool,
12065 replace_newest: bool,
12066 auto_scroll: Option<Autoscroll>,
12067 window: &mut Window,
12068 cx: &mut Context<Editor>,
12069 ) {
12070 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12071 this.change_selections(auto_scroll, window, cx, |s| {
12072 if replace_newest {
12073 s.delete(s.newest_anchor().id);
12074 }
12075 if reversed {
12076 s.insert_range(range.end..range.start);
12077 } else {
12078 s.insert_range(range);
12079 }
12080 });
12081 }
12082
12083 let buffer = &display_map.buffer_snapshot;
12084 let mut selections = self.selections.all::<usize>(cx);
12085 if let Some(mut select_next_state) = self.select_next_state.take() {
12086 let query = &select_next_state.query;
12087 if !select_next_state.done {
12088 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12089 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12090 let mut next_selected_range = None;
12091
12092 let bytes_after_last_selection =
12093 buffer.bytes_in_range(last_selection.end..buffer.len());
12094 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12095 let query_matches = query
12096 .stream_find_iter(bytes_after_last_selection)
12097 .map(|result| (last_selection.end, result))
12098 .chain(
12099 query
12100 .stream_find_iter(bytes_before_first_selection)
12101 .map(|result| (0, result)),
12102 );
12103
12104 for (start_offset, query_match) in query_matches {
12105 let query_match = query_match.unwrap(); // can only fail due to I/O
12106 let offset_range =
12107 start_offset + query_match.start()..start_offset + query_match.end();
12108 let display_range = offset_range.start.to_display_point(display_map)
12109 ..offset_range.end.to_display_point(display_map);
12110
12111 if !select_next_state.wordwise
12112 || (!movement::is_inside_word(display_map, display_range.start)
12113 && !movement::is_inside_word(display_map, display_range.end))
12114 {
12115 // TODO: This is n^2, because we might check all the selections
12116 if !selections
12117 .iter()
12118 .any(|selection| selection.range().overlaps(&offset_range))
12119 {
12120 next_selected_range = Some(offset_range);
12121 break;
12122 }
12123 }
12124 }
12125
12126 if let Some(next_selected_range) = next_selected_range {
12127 select_next_match_ranges(
12128 self,
12129 next_selected_range,
12130 last_selection.reversed,
12131 replace_newest,
12132 autoscroll,
12133 window,
12134 cx,
12135 );
12136 } else {
12137 select_next_state.done = true;
12138 }
12139 }
12140
12141 self.select_next_state = Some(select_next_state);
12142 } else {
12143 let mut only_carets = true;
12144 let mut same_text_selected = true;
12145 let mut selected_text = None;
12146
12147 let mut selections_iter = selections.iter().peekable();
12148 while let Some(selection) = selections_iter.next() {
12149 if selection.start != selection.end {
12150 only_carets = false;
12151 }
12152
12153 if same_text_selected {
12154 if selected_text.is_none() {
12155 selected_text =
12156 Some(buffer.text_for_range(selection.range()).collect::<String>());
12157 }
12158
12159 if let Some(next_selection) = selections_iter.peek() {
12160 if next_selection.range().len() == selection.range().len() {
12161 let next_selected_text = buffer
12162 .text_for_range(next_selection.range())
12163 .collect::<String>();
12164 if Some(next_selected_text) != selected_text {
12165 same_text_selected = false;
12166 selected_text = None;
12167 }
12168 } else {
12169 same_text_selected = false;
12170 selected_text = None;
12171 }
12172 }
12173 }
12174 }
12175
12176 if only_carets {
12177 for selection in &mut selections {
12178 let word_range = movement::surrounding_word(
12179 display_map,
12180 selection.start.to_display_point(display_map),
12181 );
12182 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12183 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12184 selection.goal = SelectionGoal::None;
12185 selection.reversed = false;
12186 select_next_match_ranges(
12187 self,
12188 selection.start..selection.end,
12189 selection.reversed,
12190 replace_newest,
12191 autoscroll,
12192 window,
12193 cx,
12194 );
12195 }
12196
12197 if selections.len() == 1 {
12198 let selection = selections
12199 .last()
12200 .expect("ensured that there's only one selection");
12201 let query = buffer
12202 .text_for_range(selection.start..selection.end)
12203 .collect::<String>();
12204 let is_empty = query.is_empty();
12205 let select_state = SelectNextState {
12206 query: AhoCorasick::new(&[query])?,
12207 wordwise: true,
12208 done: is_empty,
12209 };
12210 self.select_next_state = Some(select_state);
12211 } else {
12212 self.select_next_state = None;
12213 }
12214 } else if let Some(selected_text) = selected_text {
12215 self.select_next_state = Some(SelectNextState {
12216 query: AhoCorasick::new(&[selected_text])?,
12217 wordwise: false,
12218 done: false,
12219 });
12220 self.select_next_match_internal(
12221 display_map,
12222 replace_newest,
12223 autoscroll,
12224 window,
12225 cx,
12226 )?;
12227 }
12228 }
12229 Ok(())
12230 }
12231
12232 pub fn select_all_matches(
12233 &mut self,
12234 _action: &SelectAllMatches,
12235 window: &mut Window,
12236 cx: &mut Context<Self>,
12237 ) -> Result<()> {
12238 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12239
12240 self.push_to_selection_history();
12241 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12242
12243 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12244 let Some(select_next_state) = self.select_next_state.as_mut() else {
12245 return Ok(());
12246 };
12247 if select_next_state.done {
12248 return Ok(());
12249 }
12250
12251 let mut new_selections = Vec::new();
12252
12253 let reversed = self.selections.oldest::<usize>(cx).reversed;
12254 let buffer = &display_map.buffer_snapshot;
12255 let query_matches = select_next_state
12256 .query
12257 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12258
12259 for query_match in query_matches.into_iter() {
12260 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12261 let offset_range = if reversed {
12262 query_match.end()..query_match.start()
12263 } else {
12264 query_match.start()..query_match.end()
12265 };
12266 let display_range = offset_range.start.to_display_point(&display_map)
12267 ..offset_range.end.to_display_point(&display_map);
12268
12269 if !select_next_state.wordwise
12270 || (!movement::is_inside_word(&display_map, display_range.start)
12271 && !movement::is_inside_word(&display_map, display_range.end))
12272 {
12273 new_selections.push(offset_range.start..offset_range.end);
12274 }
12275 }
12276
12277 select_next_state.done = true;
12278 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12279 self.change_selections(None, window, cx, |selections| {
12280 selections.select_ranges(new_selections)
12281 });
12282
12283 Ok(())
12284 }
12285
12286 pub fn select_next(
12287 &mut self,
12288 action: &SelectNext,
12289 window: &mut Window,
12290 cx: &mut Context<Self>,
12291 ) -> Result<()> {
12292 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12293 self.push_to_selection_history();
12294 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12295 self.select_next_match_internal(
12296 &display_map,
12297 action.replace_newest,
12298 Some(Autoscroll::newest()),
12299 window,
12300 cx,
12301 )?;
12302 Ok(())
12303 }
12304
12305 pub fn select_previous(
12306 &mut self,
12307 action: &SelectPrevious,
12308 window: &mut Window,
12309 cx: &mut Context<Self>,
12310 ) -> Result<()> {
12311 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12312 self.push_to_selection_history();
12313 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12314 let buffer = &display_map.buffer_snapshot;
12315 let mut selections = self.selections.all::<usize>(cx);
12316 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12317 let query = &select_prev_state.query;
12318 if !select_prev_state.done {
12319 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12320 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12321 let mut next_selected_range = None;
12322 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12323 let bytes_before_last_selection =
12324 buffer.reversed_bytes_in_range(0..last_selection.start);
12325 let bytes_after_first_selection =
12326 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12327 let query_matches = query
12328 .stream_find_iter(bytes_before_last_selection)
12329 .map(|result| (last_selection.start, result))
12330 .chain(
12331 query
12332 .stream_find_iter(bytes_after_first_selection)
12333 .map(|result| (buffer.len(), result)),
12334 );
12335 for (end_offset, query_match) in query_matches {
12336 let query_match = query_match.unwrap(); // can only fail due to I/O
12337 let offset_range =
12338 end_offset - query_match.end()..end_offset - query_match.start();
12339 let display_range = offset_range.start.to_display_point(&display_map)
12340 ..offset_range.end.to_display_point(&display_map);
12341
12342 if !select_prev_state.wordwise
12343 || (!movement::is_inside_word(&display_map, display_range.start)
12344 && !movement::is_inside_word(&display_map, display_range.end))
12345 {
12346 next_selected_range = Some(offset_range);
12347 break;
12348 }
12349 }
12350
12351 if let Some(next_selected_range) = next_selected_range {
12352 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
12353 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12354 if action.replace_newest {
12355 s.delete(s.newest_anchor().id);
12356 }
12357 if last_selection.reversed {
12358 s.insert_range(next_selected_range.end..next_selected_range.start);
12359 } else {
12360 s.insert_range(next_selected_range);
12361 }
12362 });
12363 } else {
12364 select_prev_state.done = true;
12365 }
12366 }
12367
12368 self.select_prev_state = Some(select_prev_state);
12369 } else {
12370 let mut only_carets = true;
12371 let mut same_text_selected = true;
12372 let mut selected_text = None;
12373
12374 let mut selections_iter = selections.iter().peekable();
12375 while let Some(selection) = selections_iter.next() {
12376 if selection.start != selection.end {
12377 only_carets = false;
12378 }
12379
12380 if same_text_selected {
12381 if selected_text.is_none() {
12382 selected_text =
12383 Some(buffer.text_for_range(selection.range()).collect::<String>());
12384 }
12385
12386 if let Some(next_selection) = selections_iter.peek() {
12387 if next_selection.range().len() == selection.range().len() {
12388 let next_selected_text = buffer
12389 .text_for_range(next_selection.range())
12390 .collect::<String>();
12391 if Some(next_selected_text) != selected_text {
12392 same_text_selected = false;
12393 selected_text = None;
12394 }
12395 } else {
12396 same_text_selected = false;
12397 selected_text = None;
12398 }
12399 }
12400 }
12401 }
12402
12403 if only_carets {
12404 for selection in &mut selections {
12405 let word_range = movement::surrounding_word(
12406 &display_map,
12407 selection.start.to_display_point(&display_map),
12408 );
12409 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12410 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12411 selection.goal = SelectionGoal::None;
12412 selection.reversed = false;
12413 }
12414 if selections.len() == 1 {
12415 let selection = selections
12416 .last()
12417 .expect("ensured that there's only one selection");
12418 let query = buffer
12419 .text_for_range(selection.start..selection.end)
12420 .collect::<String>();
12421 let is_empty = query.is_empty();
12422 let select_state = SelectNextState {
12423 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12424 wordwise: true,
12425 done: is_empty,
12426 };
12427 self.select_prev_state = Some(select_state);
12428 } else {
12429 self.select_prev_state = None;
12430 }
12431
12432 self.unfold_ranges(
12433 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
12434 false,
12435 true,
12436 cx,
12437 );
12438 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12439 s.select(selections);
12440 });
12441 } else if let Some(selected_text) = selected_text {
12442 self.select_prev_state = Some(SelectNextState {
12443 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12444 wordwise: false,
12445 done: false,
12446 });
12447 self.select_previous(action, window, cx)?;
12448 }
12449 }
12450 Ok(())
12451 }
12452
12453 pub fn find_next_match(
12454 &mut self,
12455 _: &FindNextMatch,
12456 window: &mut Window,
12457 cx: &mut Context<Self>,
12458 ) -> Result<()> {
12459 let selections = self.selections.disjoint_anchors();
12460 match selections.first() {
12461 Some(first) if selections.len() >= 2 => {
12462 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12463 s.select_ranges([first.range()]);
12464 });
12465 }
12466 _ => self.select_next(
12467 &SelectNext {
12468 replace_newest: true,
12469 },
12470 window,
12471 cx,
12472 )?,
12473 }
12474 Ok(())
12475 }
12476
12477 pub fn find_previous_match(
12478 &mut self,
12479 _: &FindPreviousMatch,
12480 window: &mut Window,
12481 cx: &mut Context<Self>,
12482 ) -> Result<()> {
12483 let selections = self.selections.disjoint_anchors();
12484 match selections.last() {
12485 Some(last) if selections.len() >= 2 => {
12486 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12487 s.select_ranges([last.range()]);
12488 });
12489 }
12490 _ => self.select_previous(
12491 &SelectPrevious {
12492 replace_newest: true,
12493 },
12494 window,
12495 cx,
12496 )?,
12497 }
12498 Ok(())
12499 }
12500
12501 pub fn toggle_comments(
12502 &mut self,
12503 action: &ToggleComments,
12504 window: &mut Window,
12505 cx: &mut Context<Self>,
12506 ) {
12507 if self.read_only(cx) {
12508 return;
12509 }
12510 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12511 let text_layout_details = &self.text_layout_details(window);
12512 self.transact(window, cx, |this, window, cx| {
12513 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12514 let mut edits = Vec::new();
12515 let mut selection_edit_ranges = Vec::new();
12516 let mut last_toggled_row = None;
12517 let snapshot = this.buffer.read(cx).read(cx);
12518 let empty_str: Arc<str> = Arc::default();
12519 let mut suffixes_inserted = Vec::new();
12520 let ignore_indent = action.ignore_indent;
12521
12522 fn comment_prefix_range(
12523 snapshot: &MultiBufferSnapshot,
12524 row: MultiBufferRow,
12525 comment_prefix: &str,
12526 comment_prefix_whitespace: &str,
12527 ignore_indent: bool,
12528 ) -> Range<Point> {
12529 let indent_size = if ignore_indent {
12530 0
12531 } else {
12532 snapshot.indent_size_for_line(row).len
12533 };
12534
12535 let start = Point::new(row.0, indent_size);
12536
12537 let mut line_bytes = snapshot
12538 .bytes_in_range(start..snapshot.max_point())
12539 .flatten()
12540 .copied();
12541
12542 // If this line currently begins with the line comment prefix, then record
12543 // the range containing the prefix.
12544 if line_bytes
12545 .by_ref()
12546 .take(comment_prefix.len())
12547 .eq(comment_prefix.bytes())
12548 {
12549 // Include any whitespace that matches the comment prefix.
12550 let matching_whitespace_len = line_bytes
12551 .zip(comment_prefix_whitespace.bytes())
12552 .take_while(|(a, b)| a == b)
12553 .count() as u32;
12554 let end = Point::new(
12555 start.row,
12556 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12557 );
12558 start..end
12559 } else {
12560 start..start
12561 }
12562 }
12563
12564 fn comment_suffix_range(
12565 snapshot: &MultiBufferSnapshot,
12566 row: MultiBufferRow,
12567 comment_suffix: &str,
12568 comment_suffix_has_leading_space: bool,
12569 ) -> Range<Point> {
12570 let end = Point::new(row.0, snapshot.line_len(row));
12571 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12572
12573 let mut line_end_bytes = snapshot
12574 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12575 .flatten()
12576 .copied();
12577
12578 let leading_space_len = if suffix_start_column > 0
12579 && line_end_bytes.next() == Some(b' ')
12580 && comment_suffix_has_leading_space
12581 {
12582 1
12583 } else {
12584 0
12585 };
12586
12587 // If this line currently begins with the line comment prefix, then record
12588 // the range containing the prefix.
12589 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12590 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12591 start..end
12592 } else {
12593 end..end
12594 }
12595 }
12596
12597 // TODO: Handle selections that cross excerpts
12598 for selection in &mut selections {
12599 let start_column = snapshot
12600 .indent_size_for_line(MultiBufferRow(selection.start.row))
12601 .len;
12602 let language = if let Some(language) =
12603 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12604 {
12605 language
12606 } else {
12607 continue;
12608 };
12609
12610 selection_edit_ranges.clear();
12611
12612 // If multiple selections contain a given row, avoid processing that
12613 // row more than once.
12614 let mut start_row = MultiBufferRow(selection.start.row);
12615 if last_toggled_row == Some(start_row) {
12616 start_row = start_row.next_row();
12617 }
12618 let end_row =
12619 if selection.end.row > selection.start.row && selection.end.column == 0 {
12620 MultiBufferRow(selection.end.row - 1)
12621 } else {
12622 MultiBufferRow(selection.end.row)
12623 };
12624 last_toggled_row = Some(end_row);
12625
12626 if start_row > end_row {
12627 continue;
12628 }
12629
12630 // If the language has line comments, toggle those.
12631 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12632
12633 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12634 if ignore_indent {
12635 full_comment_prefixes = full_comment_prefixes
12636 .into_iter()
12637 .map(|s| Arc::from(s.trim_end()))
12638 .collect();
12639 }
12640
12641 if !full_comment_prefixes.is_empty() {
12642 let first_prefix = full_comment_prefixes
12643 .first()
12644 .expect("prefixes is non-empty");
12645 let prefix_trimmed_lengths = full_comment_prefixes
12646 .iter()
12647 .map(|p| p.trim_end_matches(' ').len())
12648 .collect::<SmallVec<[usize; 4]>>();
12649
12650 let mut all_selection_lines_are_comments = true;
12651
12652 for row in start_row.0..=end_row.0 {
12653 let row = MultiBufferRow(row);
12654 if start_row < end_row && snapshot.is_line_blank(row) {
12655 continue;
12656 }
12657
12658 let prefix_range = full_comment_prefixes
12659 .iter()
12660 .zip(prefix_trimmed_lengths.iter().copied())
12661 .map(|(prefix, trimmed_prefix_len)| {
12662 comment_prefix_range(
12663 snapshot.deref(),
12664 row,
12665 &prefix[..trimmed_prefix_len],
12666 &prefix[trimmed_prefix_len..],
12667 ignore_indent,
12668 )
12669 })
12670 .max_by_key(|range| range.end.column - range.start.column)
12671 .expect("prefixes is non-empty");
12672
12673 if prefix_range.is_empty() {
12674 all_selection_lines_are_comments = false;
12675 }
12676
12677 selection_edit_ranges.push(prefix_range);
12678 }
12679
12680 if all_selection_lines_are_comments {
12681 edits.extend(
12682 selection_edit_ranges
12683 .iter()
12684 .cloned()
12685 .map(|range| (range, empty_str.clone())),
12686 );
12687 } else {
12688 let min_column = selection_edit_ranges
12689 .iter()
12690 .map(|range| range.start.column)
12691 .min()
12692 .unwrap_or(0);
12693 edits.extend(selection_edit_ranges.iter().map(|range| {
12694 let position = Point::new(range.start.row, min_column);
12695 (position..position, first_prefix.clone())
12696 }));
12697 }
12698 } else if let Some((full_comment_prefix, comment_suffix)) =
12699 language.block_comment_delimiters()
12700 {
12701 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12702 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12703 let prefix_range = comment_prefix_range(
12704 snapshot.deref(),
12705 start_row,
12706 comment_prefix,
12707 comment_prefix_whitespace,
12708 ignore_indent,
12709 );
12710 let suffix_range = comment_suffix_range(
12711 snapshot.deref(),
12712 end_row,
12713 comment_suffix.trim_start_matches(' '),
12714 comment_suffix.starts_with(' '),
12715 );
12716
12717 if prefix_range.is_empty() || suffix_range.is_empty() {
12718 edits.push((
12719 prefix_range.start..prefix_range.start,
12720 full_comment_prefix.clone(),
12721 ));
12722 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12723 suffixes_inserted.push((end_row, comment_suffix.len()));
12724 } else {
12725 edits.push((prefix_range, empty_str.clone()));
12726 edits.push((suffix_range, empty_str.clone()));
12727 }
12728 } else {
12729 continue;
12730 }
12731 }
12732
12733 drop(snapshot);
12734 this.buffer.update(cx, |buffer, cx| {
12735 buffer.edit(edits, None, cx);
12736 });
12737
12738 // Adjust selections so that they end before any comment suffixes that
12739 // were inserted.
12740 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12741 let mut selections = this.selections.all::<Point>(cx);
12742 let snapshot = this.buffer.read(cx).read(cx);
12743 for selection in &mut selections {
12744 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12745 match row.cmp(&MultiBufferRow(selection.end.row)) {
12746 Ordering::Less => {
12747 suffixes_inserted.next();
12748 continue;
12749 }
12750 Ordering::Greater => break,
12751 Ordering::Equal => {
12752 if selection.end.column == snapshot.line_len(row) {
12753 if selection.is_empty() {
12754 selection.start.column -= suffix_len as u32;
12755 }
12756 selection.end.column -= suffix_len as u32;
12757 }
12758 break;
12759 }
12760 }
12761 }
12762 }
12763
12764 drop(snapshot);
12765 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12766 s.select(selections)
12767 });
12768
12769 let selections = this.selections.all::<Point>(cx);
12770 let selections_on_single_row = selections.windows(2).all(|selections| {
12771 selections[0].start.row == selections[1].start.row
12772 && selections[0].end.row == selections[1].end.row
12773 && selections[0].start.row == selections[0].end.row
12774 });
12775 let selections_selecting = selections
12776 .iter()
12777 .any(|selection| selection.start != selection.end);
12778 let advance_downwards = action.advance_downwards
12779 && selections_on_single_row
12780 && !selections_selecting
12781 && !matches!(this.mode, EditorMode::SingleLine { .. });
12782
12783 if advance_downwards {
12784 let snapshot = this.buffer.read(cx).snapshot(cx);
12785
12786 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12787 s.move_cursors_with(|display_snapshot, display_point, _| {
12788 let mut point = display_point.to_point(display_snapshot);
12789 point.row += 1;
12790 point = snapshot.clip_point(point, Bias::Left);
12791 let display_point = point.to_display_point(display_snapshot);
12792 let goal = SelectionGoal::HorizontalPosition(
12793 display_snapshot
12794 .x_for_display_point(display_point, text_layout_details)
12795 .into(),
12796 );
12797 (display_point, goal)
12798 })
12799 });
12800 }
12801 });
12802 }
12803
12804 pub fn select_enclosing_symbol(
12805 &mut self,
12806 _: &SelectEnclosingSymbol,
12807 window: &mut Window,
12808 cx: &mut Context<Self>,
12809 ) {
12810 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12811
12812 let buffer = self.buffer.read(cx).snapshot(cx);
12813 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12814
12815 fn update_selection(
12816 selection: &Selection<usize>,
12817 buffer_snap: &MultiBufferSnapshot,
12818 ) -> Option<Selection<usize>> {
12819 let cursor = selection.head();
12820 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12821 for symbol in symbols.iter().rev() {
12822 let start = symbol.range.start.to_offset(buffer_snap);
12823 let end = symbol.range.end.to_offset(buffer_snap);
12824 let new_range = start..end;
12825 if start < selection.start || end > selection.end {
12826 return Some(Selection {
12827 id: selection.id,
12828 start: new_range.start,
12829 end: new_range.end,
12830 goal: SelectionGoal::None,
12831 reversed: selection.reversed,
12832 });
12833 }
12834 }
12835 None
12836 }
12837
12838 let mut selected_larger_symbol = false;
12839 let new_selections = old_selections
12840 .iter()
12841 .map(|selection| match update_selection(selection, &buffer) {
12842 Some(new_selection) => {
12843 if new_selection.range() != selection.range() {
12844 selected_larger_symbol = true;
12845 }
12846 new_selection
12847 }
12848 None => selection.clone(),
12849 })
12850 .collect::<Vec<_>>();
12851
12852 if selected_larger_symbol {
12853 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12854 s.select(new_selections);
12855 });
12856 }
12857 }
12858
12859 pub fn select_larger_syntax_node(
12860 &mut self,
12861 _: &SelectLargerSyntaxNode,
12862 window: &mut Window,
12863 cx: &mut Context<Self>,
12864 ) {
12865 let Some(visible_row_count) = self.visible_row_count() else {
12866 return;
12867 };
12868 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12869 if old_selections.is_empty() {
12870 return;
12871 }
12872
12873 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12874
12875 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12876 let buffer = self.buffer.read(cx).snapshot(cx);
12877
12878 let mut selected_larger_node = false;
12879 let mut new_selections = old_selections
12880 .iter()
12881 .map(|selection| {
12882 let old_range = selection.start..selection.end;
12883
12884 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
12885 // manually select word at selection
12886 if ["string_content", "inline"].contains(&node.kind()) {
12887 let word_range = {
12888 let display_point = buffer
12889 .offset_to_point(old_range.start)
12890 .to_display_point(&display_map);
12891 let Range { start, end } =
12892 movement::surrounding_word(&display_map, display_point);
12893 start.to_point(&display_map).to_offset(&buffer)
12894 ..end.to_point(&display_map).to_offset(&buffer)
12895 };
12896 // ignore if word is already selected
12897 if !word_range.is_empty() && old_range != word_range {
12898 let last_word_range = {
12899 let display_point = buffer
12900 .offset_to_point(old_range.end)
12901 .to_display_point(&display_map);
12902 let Range { start, end } =
12903 movement::surrounding_word(&display_map, display_point);
12904 start.to_point(&display_map).to_offset(&buffer)
12905 ..end.to_point(&display_map).to_offset(&buffer)
12906 };
12907 // only select word if start and end point belongs to same word
12908 if word_range == last_word_range {
12909 selected_larger_node = true;
12910 return Selection {
12911 id: selection.id,
12912 start: word_range.start,
12913 end: word_range.end,
12914 goal: SelectionGoal::None,
12915 reversed: selection.reversed,
12916 };
12917 }
12918 }
12919 }
12920 }
12921
12922 let mut new_range = old_range.clone();
12923 let mut new_node = None;
12924 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12925 {
12926 new_node = Some(node);
12927 new_range = match containing_range {
12928 MultiOrSingleBufferOffsetRange::Single(_) => break,
12929 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12930 };
12931 if !display_map.intersects_fold(new_range.start)
12932 && !display_map.intersects_fold(new_range.end)
12933 {
12934 break;
12935 }
12936 }
12937
12938 if let Some(node) = new_node {
12939 // Log the ancestor, to support using this action as a way to explore TreeSitter
12940 // nodes. Parent and grandparent are also logged because this operation will not
12941 // visit nodes that have the same range as their parent.
12942 log::info!("Node: {node:?}");
12943 let parent = node.parent();
12944 log::info!("Parent: {parent:?}");
12945 let grandparent = parent.and_then(|x| x.parent());
12946 log::info!("Grandparent: {grandparent:?}");
12947 }
12948
12949 selected_larger_node |= new_range != old_range;
12950 Selection {
12951 id: selection.id,
12952 start: new_range.start,
12953 end: new_range.end,
12954 goal: SelectionGoal::None,
12955 reversed: selection.reversed,
12956 }
12957 })
12958 .collect::<Vec<_>>();
12959
12960 if !selected_larger_node {
12961 return; // don't put this call in the history
12962 }
12963
12964 // scroll based on transformation done to the last selection created by the user
12965 let (last_old, last_new) = old_selections
12966 .last()
12967 .zip(new_selections.last().cloned())
12968 .expect("old_selections isn't empty");
12969
12970 // revert selection
12971 let is_selection_reversed = {
12972 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12973 new_selections.last_mut().expect("checked above").reversed =
12974 should_newest_selection_be_reversed;
12975 should_newest_selection_be_reversed
12976 };
12977
12978 if selected_larger_node {
12979 self.select_syntax_node_history.disable_clearing = true;
12980 self.change_selections(None, window, cx, |s| {
12981 s.select(new_selections.clone());
12982 });
12983 self.select_syntax_node_history.disable_clearing = false;
12984 }
12985
12986 let start_row = last_new.start.to_display_point(&display_map).row().0;
12987 let end_row = last_new.end.to_display_point(&display_map).row().0;
12988 let selection_height = end_row - start_row + 1;
12989 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12990
12991 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
12992 let scroll_behavior = if fits_on_the_screen {
12993 self.request_autoscroll(Autoscroll::fit(), cx);
12994 SelectSyntaxNodeScrollBehavior::FitSelection
12995 } else if is_selection_reversed {
12996 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12997 SelectSyntaxNodeScrollBehavior::CursorTop
12998 } else {
12999 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13000 SelectSyntaxNodeScrollBehavior::CursorBottom
13001 };
13002
13003 self.select_syntax_node_history.push((
13004 old_selections,
13005 scroll_behavior,
13006 is_selection_reversed,
13007 ));
13008 }
13009
13010 pub fn select_smaller_syntax_node(
13011 &mut self,
13012 _: &SelectSmallerSyntaxNode,
13013 window: &mut Window,
13014 cx: &mut Context<Self>,
13015 ) {
13016 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13017
13018 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13019 self.select_syntax_node_history.pop()
13020 {
13021 if let Some(selection) = selections.last_mut() {
13022 selection.reversed = is_selection_reversed;
13023 }
13024
13025 self.select_syntax_node_history.disable_clearing = true;
13026 self.change_selections(None, window, cx, |s| {
13027 s.select(selections.to_vec());
13028 });
13029 self.select_syntax_node_history.disable_clearing = false;
13030
13031 match scroll_behavior {
13032 SelectSyntaxNodeScrollBehavior::CursorTop => {
13033 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13034 }
13035 SelectSyntaxNodeScrollBehavior::FitSelection => {
13036 self.request_autoscroll(Autoscroll::fit(), cx);
13037 }
13038 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13039 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13040 }
13041 }
13042 }
13043 }
13044
13045 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13046 if !EditorSettings::get_global(cx).gutter.runnables {
13047 self.clear_tasks();
13048 return Task::ready(());
13049 }
13050 let project = self.project.as_ref().map(Entity::downgrade);
13051 let task_sources = self.lsp_task_sources(cx);
13052 cx.spawn_in(window, async move |editor, cx| {
13053 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13054 let Some(project) = project.and_then(|p| p.upgrade()) else {
13055 return;
13056 };
13057 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13058 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13059 }) else {
13060 return;
13061 };
13062
13063 let hide_runnables = project
13064 .update(cx, |project, cx| {
13065 // Do not display any test indicators in non-dev server remote projects.
13066 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13067 })
13068 .unwrap_or(true);
13069 if hide_runnables {
13070 return;
13071 }
13072 let new_rows =
13073 cx.background_spawn({
13074 let snapshot = display_snapshot.clone();
13075 async move {
13076 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13077 }
13078 })
13079 .await;
13080 let Ok(lsp_tasks) =
13081 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13082 else {
13083 return;
13084 };
13085 let lsp_tasks = lsp_tasks.await;
13086
13087 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13088 lsp_tasks
13089 .into_iter()
13090 .flat_map(|(kind, tasks)| {
13091 tasks.into_iter().filter_map(move |(location, task)| {
13092 Some((kind.clone(), location?, task))
13093 })
13094 })
13095 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13096 let buffer = location.target.buffer;
13097 let buffer_snapshot = buffer.read(cx).snapshot();
13098 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13099 |(excerpt_id, snapshot, _)| {
13100 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13101 display_snapshot
13102 .buffer_snapshot
13103 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13104 } else {
13105 None
13106 }
13107 },
13108 );
13109 if let Some(offset) = offset {
13110 let task_buffer_range =
13111 location.target.range.to_point(&buffer_snapshot);
13112 let context_buffer_range =
13113 task_buffer_range.to_offset(&buffer_snapshot);
13114 let context_range = BufferOffset(context_buffer_range.start)
13115 ..BufferOffset(context_buffer_range.end);
13116
13117 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13118 .or_insert_with(|| RunnableTasks {
13119 templates: Vec::new(),
13120 offset,
13121 column: task_buffer_range.start.column,
13122 extra_variables: HashMap::default(),
13123 context_range,
13124 })
13125 .templates
13126 .push((kind, task.original_task().clone()));
13127 }
13128
13129 acc
13130 })
13131 }) else {
13132 return;
13133 };
13134
13135 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13136 editor
13137 .update(cx, |editor, _| {
13138 editor.clear_tasks();
13139 for (key, mut value) in rows {
13140 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13141 value.templates.extend(lsp_tasks.templates);
13142 }
13143
13144 editor.insert_tasks(key, value);
13145 }
13146 for (key, value) in lsp_tasks_by_rows {
13147 editor.insert_tasks(key, value);
13148 }
13149 })
13150 .ok();
13151 })
13152 }
13153 fn fetch_runnable_ranges(
13154 snapshot: &DisplaySnapshot,
13155 range: Range<Anchor>,
13156 ) -> Vec<language::RunnableRange> {
13157 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13158 }
13159
13160 fn runnable_rows(
13161 project: Entity<Project>,
13162 snapshot: DisplaySnapshot,
13163 runnable_ranges: Vec<RunnableRange>,
13164 mut cx: AsyncWindowContext,
13165 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13166 runnable_ranges
13167 .into_iter()
13168 .filter_map(|mut runnable| {
13169 let tasks = cx
13170 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13171 .ok()?;
13172 if tasks.is_empty() {
13173 return None;
13174 }
13175
13176 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13177
13178 let row = snapshot
13179 .buffer_snapshot
13180 .buffer_line_for_row(MultiBufferRow(point.row))?
13181 .1
13182 .start
13183 .row;
13184
13185 let context_range =
13186 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13187 Some((
13188 (runnable.buffer_id, row),
13189 RunnableTasks {
13190 templates: tasks,
13191 offset: snapshot
13192 .buffer_snapshot
13193 .anchor_before(runnable.run_range.start),
13194 context_range,
13195 column: point.column,
13196 extra_variables: runnable.extra_captures,
13197 },
13198 ))
13199 })
13200 .collect()
13201 }
13202
13203 fn templates_with_tags(
13204 project: &Entity<Project>,
13205 runnable: &mut Runnable,
13206 cx: &mut App,
13207 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13208 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13209 let (worktree_id, file) = project
13210 .buffer_for_id(runnable.buffer, cx)
13211 .and_then(|buffer| buffer.read(cx).file())
13212 .map(|file| (file.worktree_id(cx), file.clone()))
13213 .unzip();
13214
13215 (
13216 project.task_store().read(cx).task_inventory().cloned(),
13217 worktree_id,
13218 file,
13219 )
13220 });
13221
13222 let mut templates_with_tags = mem::take(&mut runnable.tags)
13223 .into_iter()
13224 .flat_map(|RunnableTag(tag)| {
13225 inventory
13226 .as_ref()
13227 .into_iter()
13228 .flat_map(|inventory| {
13229 inventory.read(cx).list_tasks(
13230 file.clone(),
13231 Some(runnable.language.clone()),
13232 worktree_id,
13233 cx,
13234 )
13235 })
13236 .filter(move |(_, template)| {
13237 template.tags.iter().any(|source_tag| source_tag == &tag)
13238 })
13239 })
13240 .sorted_by_key(|(kind, _)| kind.to_owned())
13241 .collect::<Vec<_>>();
13242 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13243 // Strongest source wins; if we have worktree tag binding, prefer that to
13244 // global and language bindings;
13245 // if we have a global binding, prefer that to language binding.
13246 let first_mismatch = templates_with_tags
13247 .iter()
13248 .position(|(tag_source, _)| tag_source != leading_tag_source);
13249 if let Some(index) = first_mismatch {
13250 templates_with_tags.truncate(index);
13251 }
13252 }
13253
13254 templates_with_tags
13255 }
13256
13257 pub fn move_to_enclosing_bracket(
13258 &mut self,
13259 _: &MoveToEnclosingBracket,
13260 window: &mut Window,
13261 cx: &mut Context<Self>,
13262 ) {
13263 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13264 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13265 s.move_offsets_with(|snapshot, selection| {
13266 let Some(enclosing_bracket_ranges) =
13267 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13268 else {
13269 return;
13270 };
13271
13272 let mut best_length = usize::MAX;
13273 let mut best_inside = false;
13274 let mut best_in_bracket_range = false;
13275 let mut best_destination = None;
13276 for (open, close) in enclosing_bracket_ranges {
13277 let close = close.to_inclusive();
13278 let length = close.end() - open.start;
13279 let inside = selection.start >= open.end && selection.end <= *close.start();
13280 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13281 || close.contains(&selection.head());
13282
13283 // If best is next to a bracket and current isn't, skip
13284 if !in_bracket_range && best_in_bracket_range {
13285 continue;
13286 }
13287
13288 // Prefer smaller lengths unless best is inside and current isn't
13289 if length > best_length && (best_inside || !inside) {
13290 continue;
13291 }
13292
13293 best_length = length;
13294 best_inside = inside;
13295 best_in_bracket_range = in_bracket_range;
13296 best_destination = Some(
13297 if close.contains(&selection.start) && close.contains(&selection.end) {
13298 if inside { open.end } else { open.start }
13299 } else if inside {
13300 *close.start()
13301 } else {
13302 *close.end()
13303 },
13304 );
13305 }
13306
13307 if let Some(destination) = best_destination {
13308 selection.collapse_to(destination, SelectionGoal::None);
13309 }
13310 })
13311 });
13312 }
13313
13314 pub fn undo_selection(
13315 &mut self,
13316 _: &UndoSelection,
13317 window: &mut Window,
13318 cx: &mut Context<Self>,
13319 ) {
13320 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13321 self.end_selection(window, cx);
13322 self.selection_history.mode = SelectionHistoryMode::Undoing;
13323 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13324 self.change_selections(None, window, cx, |s| {
13325 s.select_anchors(entry.selections.to_vec())
13326 });
13327 self.select_next_state = entry.select_next_state;
13328 self.select_prev_state = entry.select_prev_state;
13329 self.add_selections_state = entry.add_selections_state;
13330 self.request_autoscroll(Autoscroll::newest(), cx);
13331 }
13332 self.selection_history.mode = SelectionHistoryMode::Normal;
13333 }
13334
13335 pub fn redo_selection(
13336 &mut self,
13337 _: &RedoSelection,
13338 window: &mut Window,
13339 cx: &mut Context<Self>,
13340 ) {
13341 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13342 self.end_selection(window, cx);
13343 self.selection_history.mode = SelectionHistoryMode::Redoing;
13344 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13345 self.change_selections(None, window, cx, |s| {
13346 s.select_anchors(entry.selections.to_vec())
13347 });
13348 self.select_next_state = entry.select_next_state;
13349 self.select_prev_state = entry.select_prev_state;
13350 self.add_selections_state = entry.add_selections_state;
13351 self.request_autoscroll(Autoscroll::newest(), cx);
13352 }
13353 self.selection_history.mode = SelectionHistoryMode::Normal;
13354 }
13355
13356 pub fn expand_excerpts(
13357 &mut self,
13358 action: &ExpandExcerpts,
13359 _: &mut Window,
13360 cx: &mut Context<Self>,
13361 ) {
13362 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13363 }
13364
13365 pub fn expand_excerpts_down(
13366 &mut self,
13367 action: &ExpandExcerptsDown,
13368 _: &mut Window,
13369 cx: &mut Context<Self>,
13370 ) {
13371 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13372 }
13373
13374 pub fn expand_excerpts_up(
13375 &mut self,
13376 action: &ExpandExcerptsUp,
13377 _: &mut Window,
13378 cx: &mut Context<Self>,
13379 ) {
13380 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13381 }
13382
13383 pub fn expand_excerpts_for_direction(
13384 &mut self,
13385 lines: u32,
13386 direction: ExpandExcerptDirection,
13387
13388 cx: &mut Context<Self>,
13389 ) {
13390 let selections = self.selections.disjoint_anchors();
13391
13392 let lines = if lines == 0 {
13393 EditorSettings::get_global(cx).expand_excerpt_lines
13394 } else {
13395 lines
13396 };
13397
13398 self.buffer.update(cx, |buffer, cx| {
13399 let snapshot = buffer.snapshot(cx);
13400 let mut excerpt_ids = selections
13401 .iter()
13402 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13403 .collect::<Vec<_>>();
13404 excerpt_ids.sort();
13405 excerpt_ids.dedup();
13406 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13407 })
13408 }
13409
13410 pub fn expand_excerpt(
13411 &mut self,
13412 excerpt: ExcerptId,
13413 direction: ExpandExcerptDirection,
13414 window: &mut Window,
13415 cx: &mut Context<Self>,
13416 ) {
13417 let current_scroll_position = self.scroll_position(cx);
13418 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13419 let mut should_scroll_up = false;
13420
13421 if direction == ExpandExcerptDirection::Down {
13422 let multi_buffer = self.buffer.read(cx);
13423 let snapshot = multi_buffer.snapshot(cx);
13424 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13425 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13426 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13427 let buffer_snapshot = buffer.read(cx).snapshot();
13428 let excerpt_end_row =
13429 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13430 let last_row = buffer_snapshot.max_point().row;
13431 let lines_below = last_row.saturating_sub(excerpt_end_row);
13432 should_scroll_up = lines_below >= lines_to_expand;
13433 }
13434 }
13435 }
13436 }
13437
13438 self.buffer.update(cx, |buffer, cx| {
13439 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13440 });
13441
13442 if should_scroll_up {
13443 let new_scroll_position =
13444 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13445 self.set_scroll_position(new_scroll_position, window, cx);
13446 }
13447 }
13448
13449 pub fn go_to_singleton_buffer_point(
13450 &mut self,
13451 point: Point,
13452 window: &mut Window,
13453 cx: &mut Context<Self>,
13454 ) {
13455 self.go_to_singleton_buffer_range(point..point, window, cx);
13456 }
13457
13458 pub fn go_to_singleton_buffer_range(
13459 &mut self,
13460 range: Range<Point>,
13461 window: &mut Window,
13462 cx: &mut Context<Self>,
13463 ) {
13464 let multibuffer = self.buffer().read(cx);
13465 let Some(buffer) = multibuffer.as_singleton() else {
13466 return;
13467 };
13468 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13469 return;
13470 };
13471 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13472 return;
13473 };
13474 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13475 s.select_anchor_ranges([start..end])
13476 });
13477 }
13478
13479 pub fn go_to_diagnostic(
13480 &mut self,
13481 _: &GoToDiagnostic,
13482 window: &mut Window,
13483 cx: &mut Context<Self>,
13484 ) {
13485 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13486 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13487 }
13488
13489 pub fn go_to_prev_diagnostic(
13490 &mut self,
13491 _: &GoToPreviousDiagnostic,
13492 window: &mut Window,
13493 cx: &mut Context<Self>,
13494 ) {
13495 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13496 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13497 }
13498
13499 pub fn go_to_diagnostic_impl(
13500 &mut self,
13501 direction: Direction,
13502 window: &mut Window,
13503 cx: &mut Context<Self>,
13504 ) {
13505 let buffer = self.buffer.read(cx).snapshot(cx);
13506 let selection = self.selections.newest::<usize>(cx);
13507
13508 let mut active_group_id = None;
13509 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13510 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13511 active_group_id = Some(active_group.group_id);
13512 }
13513 }
13514
13515 fn filtered(
13516 snapshot: EditorSnapshot,
13517 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13518 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13519 diagnostics
13520 .filter(|entry| entry.range.start != entry.range.end)
13521 .filter(|entry| !entry.diagnostic.is_unnecessary)
13522 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13523 }
13524
13525 let snapshot = self.snapshot(window, cx);
13526 let before = filtered(
13527 snapshot.clone(),
13528 buffer
13529 .diagnostics_in_range(0..selection.start)
13530 .filter(|entry| entry.range.start <= selection.start),
13531 );
13532 let after = filtered(
13533 snapshot,
13534 buffer
13535 .diagnostics_in_range(selection.start..buffer.len())
13536 .filter(|entry| entry.range.start >= selection.start),
13537 );
13538
13539 let mut found: Option<DiagnosticEntry<usize>> = None;
13540 if direction == Direction::Prev {
13541 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13542 {
13543 for diagnostic in prev_diagnostics.into_iter().rev() {
13544 if diagnostic.range.start != selection.start
13545 || active_group_id
13546 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13547 {
13548 found = Some(diagnostic);
13549 break 'outer;
13550 }
13551 }
13552 }
13553 } else {
13554 for diagnostic in after.chain(before) {
13555 if diagnostic.range.start != selection.start
13556 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13557 {
13558 found = Some(diagnostic);
13559 break;
13560 }
13561 }
13562 }
13563 let Some(next_diagnostic) = found else {
13564 return;
13565 };
13566
13567 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13568 return;
13569 };
13570 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13571 s.select_ranges(vec![
13572 next_diagnostic.range.start..next_diagnostic.range.start,
13573 ])
13574 });
13575 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13576 self.refresh_inline_completion(false, true, window, cx);
13577 }
13578
13579 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13580 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13581 let snapshot = self.snapshot(window, cx);
13582 let selection = self.selections.newest::<Point>(cx);
13583 self.go_to_hunk_before_or_after_position(
13584 &snapshot,
13585 selection.head(),
13586 Direction::Next,
13587 window,
13588 cx,
13589 );
13590 }
13591
13592 pub fn go_to_hunk_before_or_after_position(
13593 &mut self,
13594 snapshot: &EditorSnapshot,
13595 position: Point,
13596 direction: Direction,
13597 window: &mut Window,
13598 cx: &mut Context<Editor>,
13599 ) {
13600 let row = if direction == Direction::Next {
13601 self.hunk_after_position(snapshot, position)
13602 .map(|hunk| hunk.row_range.start)
13603 } else {
13604 self.hunk_before_position(snapshot, position)
13605 };
13606
13607 if let Some(row) = row {
13608 let destination = Point::new(row.0, 0);
13609 let autoscroll = Autoscroll::center();
13610
13611 self.unfold_ranges(&[destination..destination], false, false, cx);
13612 self.change_selections(Some(autoscroll), window, cx, |s| {
13613 s.select_ranges([destination..destination]);
13614 });
13615 }
13616 }
13617
13618 fn hunk_after_position(
13619 &mut self,
13620 snapshot: &EditorSnapshot,
13621 position: Point,
13622 ) -> Option<MultiBufferDiffHunk> {
13623 snapshot
13624 .buffer_snapshot
13625 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13626 .find(|hunk| hunk.row_range.start.0 > position.row)
13627 .or_else(|| {
13628 snapshot
13629 .buffer_snapshot
13630 .diff_hunks_in_range(Point::zero()..position)
13631 .find(|hunk| hunk.row_range.end.0 < position.row)
13632 })
13633 }
13634
13635 fn go_to_prev_hunk(
13636 &mut self,
13637 _: &GoToPreviousHunk,
13638 window: &mut Window,
13639 cx: &mut Context<Self>,
13640 ) {
13641 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13642 let snapshot = self.snapshot(window, cx);
13643 let selection = self.selections.newest::<Point>(cx);
13644 self.go_to_hunk_before_or_after_position(
13645 &snapshot,
13646 selection.head(),
13647 Direction::Prev,
13648 window,
13649 cx,
13650 );
13651 }
13652
13653 fn hunk_before_position(
13654 &mut self,
13655 snapshot: &EditorSnapshot,
13656 position: Point,
13657 ) -> Option<MultiBufferRow> {
13658 snapshot
13659 .buffer_snapshot
13660 .diff_hunk_before(position)
13661 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13662 }
13663
13664 fn go_to_next_change(
13665 &mut self,
13666 _: &GoToNextChange,
13667 window: &mut Window,
13668 cx: &mut Context<Self>,
13669 ) {
13670 if let Some(selections) = self
13671 .change_list
13672 .next_change(1, Direction::Next)
13673 .map(|s| s.to_vec())
13674 {
13675 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13676 let map = s.display_map();
13677 s.select_display_ranges(selections.iter().map(|a| {
13678 let point = a.to_display_point(&map);
13679 point..point
13680 }))
13681 })
13682 }
13683 }
13684
13685 fn go_to_previous_change(
13686 &mut self,
13687 _: &GoToPreviousChange,
13688 window: &mut Window,
13689 cx: &mut Context<Self>,
13690 ) {
13691 if let Some(selections) = self
13692 .change_list
13693 .next_change(1, Direction::Prev)
13694 .map(|s| s.to_vec())
13695 {
13696 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13697 let map = s.display_map();
13698 s.select_display_ranges(selections.iter().map(|a| {
13699 let point = a.to_display_point(&map);
13700 point..point
13701 }))
13702 })
13703 }
13704 }
13705
13706 fn go_to_line<T: 'static>(
13707 &mut self,
13708 position: Anchor,
13709 highlight_color: Option<Hsla>,
13710 window: &mut Window,
13711 cx: &mut Context<Self>,
13712 ) {
13713 let snapshot = self.snapshot(window, cx).display_snapshot;
13714 let position = position.to_point(&snapshot.buffer_snapshot);
13715 let start = snapshot
13716 .buffer_snapshot
13717 .clip_point(Point::new(position.row, 0), Bias::Left);
13718 let end = start + Point::new(1, 0);
13719 let start = snapshot.buffer_snapshot.anchor_before(start);
13720 let end = snapshot.buffer_snapshot.anchor_before(end);
13721
13722 self.highlight_rows::<T>(
13723 start..end,
13724 highlight_color
13725 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13726 Default::default(),
13727 cx,
13728 );
13729 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13730 }
13731
13732 pub fn go_to_definition(
13733 &mut self,
13734 _: &GoToDefinition,
13735 window: &mut Window,
13736 cx: &mut Context<Self>,
13737 ) -> Task<Result<Navigated>> {
13738 let definition =
13739 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13740 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13741 cx.spawn_in(window, async move |editor, cx| {
13742 if definition.await? == Navigated::Yes {
13743 return Ok(Navigated::Yes);
13744 }
13745 match fallback_strategy {
13746 GoToDefinitionFallback::None => Ok(Navigated::No),
13747 GoToDefinitionFallback::FindAllReferences => {
13748 match editor.update_in(cx, |editor, window, cx| {
13749 editor.find_all_references(&FindAllReferences, window, cx)
13750 })? {
13751 Some(references) => references.await,
13752 None => Ok(Navigated::No),
13753 }
13754 }
13755 }
13756 })
13757 }
13758
13759 pub fn go_to_declaration(
13760 &mut self,
13761 _: &GoToDeclaration,
13762 window: &mut Window,
13763 cx: &mut Context<Self>,
13764 ) -> Task<Result<Navigated>> {
13765 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13766 }
13767
13768 pub fn go_to_declaration_split(
13769 &mut self,
13770 _: &GoToDeclaration,
13771 window: &mut Window,
13772 cx: &mut Context<Self>,
13773 ) -> Task<Result<Navigated>> {
13774 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13775 }
13776
13777 pub fn go_to_implementation(
13778 &mut self,
13779 _: &GoToImplementation,
13780 window: &mut Window,
13781 cx: &mut Context<Self>,
13782 ) -> Task<Result<Navigated>> {
13783 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13784 }
13785
13786 pub fn go_to_implementation_split(
13787 &mut self,
13788 _: &GoToImplementationSplit,
13789 window: &mut Window,
13790 cx: &mut Context<Self>,
13791 ) -> Task<Result<Navigated>> {
13792 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13793 }
13794
13795 pub fn go_to_type_definition(
13796 &mut self,
13797 _: &GoToTypeDefinition,
13798 window: &mut Window,
13799 cx: &mut Context<Self>,
13800 ) -> Task<Result<Navigated>> {
13801 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13802 }
13803
13804 pub fn go_to_definition_split(
13805 &mut self,
13806 _: &GoToDefinitionSplit,
13807 window: &mut Window,
13808 cx: &mut Context<Self>,
13809 ) -> Task<Result<Navigated>> {
13810 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13811 }
13812
13813 pub fn go_to_type_definition_split(
13814 &mut self,
13815 _: &GoToTypeDefinitionSplit,
13816 window: &mut Window,
13817 cx: &mut Context<Self>,
13818 ) -> Task<Result<Navigated>> {
13819 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13820 }
13821
13822 fn go_to_definition_of_kind(
13823 &mut self,
13824 kind: GotoDefinitionKind,
13825 split: bool,
13826 window: &mut Window,
13827 cx: &mut Context<Self>,
13828 ) -> Task<Result<Navigated>> {
13829 let Some(provider) = self.semantics_provider.clone() else {
13830 return Task::ready(Ok(Navigated::No));
13831 };
13832 let head = self.selections.newest::<usize>(cx).head();
13833 let buffer = self.buffer.read(cx);
13834 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13835 text_anchor
13836 } else {
13837 return Task::ready(Ok(Navigated::No));
13838 };
13839
13840 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13841 return Task::ready(Ok(Navigated::No));
13842 };
13843
13844 cx.spawn_in(window, async move |editor, cx| {
13845 let definitions = definitions.await?;
13846 let navigated = editor
13847 .update_in(cx, |editor, window, cx| {
13848 editor.navigate_to_hover_links(
13849 Some(kind),
13850 definitions
13851 .into_iter()
13852 .filter(|location| {
13853 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13854 })
13855 .map(HoverLink::Text)
13856 .collect::<Vec<_>>(),
13857 split,
13858 window,
13859 cx,
13860 )
13861 })?
13862 .await?;
13863 anyhow::Ok(navigated)
13864 })
13865 }
13866
13867 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13868 let selection = self.selections.newest_anchor();
13869 let head = selection.head();
13870 let tail = selection.tail();
13871
13872 let Some((buffer, start_position)) =
13873 self.buffer.read(cx).text_anchor_for_position(head, cx)
13874 else {
13875 return;
13876 };
13877
13878 let end_position = if head != tail {
13879 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13880 return;
13881 };
13882 Some(pos)
13883 } else {
13884 None
13885 };
13886
13887 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13888 let url = if let Some(end_pos) = end_position {
13889 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13890 } else {
13891 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13892 };
13893
13894 if let Some(url) = url {
13895 editor.update(cx, |_, cx| {
13896 cx.open_url(&url);
13897 })
13898 } else {
13899 Ok(())
13900 }
13901 });
13902
13903 url_finder.detach();
13904 }
13905
13906 pub fn open_selected_filename(
13907 &mut self,
13908 _: &OpenSelectedFilename,
13909 window: &mut Window,
13910 cx: &mut Context<Self>,
13911 ) {
13912 let Some(workspace) = self.workspace() else {
13913 return;
13914 };
13915
13916 let position = self.selections.newest_anchor().head();
13917
13918 let Some((buffer, buffer_position)) =
13919 self.buffer.read(cx).text_anchor_for_position(position, cx)
13920 else {
13921 return;
13922 };
13923
13924 let project = self.project.clone();
13925
13926 cx.spawn_in(window, async move |_, cx| {
13927 let result = find_file(&buffer, project, buffer_position, cx).await;
13928
13929 if let Some((_, path)) = result {
13930 workspace
13931 .update_in(cx, |workspace, window, cx| {
13932 workspace.open_resolved_path(path, window, cx)
13933 })?
13934 .await?;
13935 }
13936 anyhow::Ok(())
13937 })
13938 .detach();
13939 }
13940
13941 pub(crate) fn navigate_to_hover_links(
13942 &mut self,
13943 kind: Option<GotoDefinitionKind>,
13944 mut definitions: Vec<HoverLink>,
13945 split: bool,
13946 window: &mut Window,
13947 cx: &mut Context<Editor>,
13948 ) -> Task<Result<Navigated>> {
13949 // If there is one definition, just open it directly
13950 if definitions.len() == 1 {
13951 let definition = definitions.pop().unwrap();
13952
13953 enum TargetTaskResult {
13954 Location(Option<Location>),
13955 AlreadyNavigated,
13956 }
13957
13958 let target_task = match definition {
13959 HoverLink::Text(link) => {
13960 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13961 }
13962 HoverLink::InlayHint(lsp_location, server_id) => {
13963 let computation =
13964 self.compute_target_location(lsp_location, server_id, window, cx);
13965 cx.background_spawn(async move {
13966 let location = computation.await?;
13967 Ok(TargetTaskResult::Location(location))
13968 })
13969 }
13970 HoverLink::Url(url) => {
13971 cx.open_url(&url);
13972 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13973 }
13974 HoverLink::File(path) => {
13975 if let Some(workspace) = self.workspace() {
13976 cx.spawn_in(window, async move |_, cx| {
13977 workspace
13978 .update_in(cx, |workspace, window, cx| {
13979 workspace.open_resolved_path(path, window, cx)
13980 })?
13981 .await
13982 .map(|_| TargetTaskResult::AlreadyNavigated)
13983 })
13984 } else {
13985 Task::ready(Ok(TargetTaskResult::Location(None)))
13986 }
13987 }
13988 };
13989 cx.spawn_in(window, async move |editor, cx| {
13990 let target = match target_task.await.context("target resolution task")? {
13991 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13992 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13993 TargetTaskResult::Location(Some(target)) => target,
13994 };
13995
13996 editor.update_in(cx, |editor, window, cx| {
13997 let Some(workspace) = editor.workspace() else {
13998 return Navigated::No;
13999 };
14000 let pane = workspace.read(cx).active_pane().clone();
14001
14002 let range = target.range.to_point(target.buffer.read(cx));
14003 let range = editor.range_for_match(&range);
14004 let range = collapse_multiline_range(range);
14005
14006 if !split
14007 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14008 {
14009 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14010 } else {
14011 window.defer(cx, move |window, cx| {
14012 let target_editor: Entity<Self> =
14013 workspace.update(cx, |workspace, cx| {
14014 let pane = if split {
14015 workspace.adjacent_pane(window, cx)
14016 } else {
14017 workspace.active_pane().clone()
14018 };
14019
14020 workspace.open_project_item(
14021 pane,
14022 target.buffer.clone(),
14023 true,
14024 true,
14025 window,
14026 cx,
14027 )
14028 });
14029 target_editor.update(cx, |target_editor, cx| {
14030 // When selecting a definition in a different buffer, disable the nav history
14031 // to avoid creating a history entry at the previous cursor location.
14032 pane.update(cx, |pane, _| pane.disable_history());
14033 target_editor.go_to_singleton_buffer_range(range, window, cx);
14034 pane.update(cx, |pane, _| pane.enable_history());
14035 });
14036 });
14037 }
14038 Navigated::Yes
14039 })
14040 })
14041 } else if !definitions.is_empty() {
14042 cx.spawn_in(window, async move |editor, cx| {
14043 let (title, location_tasks, workspace) = editor
14044 .update_in(cx, |editor, window, cx| {
14045 let tab_kind = match kind {
14046 Some(GotoDefinitionKind::Implementation) => "Implementations",
14047 _ => "Definitions",
14048 };
14049 let title = definitions
14050 .iter()
14051 .find_map(|definition| match definition {
14052 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14053 let buffer = origin.buffer.read(cx);
14054 format!(
14055 "{} for {}",
14056 tab_kind,
14057 buffer
14058 .text_for_range(origin.range.clone())
14059 .collect::<String>()
14060 )
14061 }),
14062 HoverLink::InlayHint(_, _) => None,
14063 HoverLink::Url(_) => None,
14064 HoverLink::File(_) => None,
14065 })
14066 .unwrap_or(tab_kind.to_string());
14067 let location_tasks = definitions
14068 .into_iter()
14069 .map(|definition| match definition {
14070 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14071 HoverLink::InlayHint(lsp_location, server_id) => editor
14072 .compute_target_location(lsp_location, server_id, window, cx),
14073 HoverLink::Url(_) => Task::ready(Ok(None)),
14074 HoverLink::File(_) => Task::ready(Ok(None)),
14075 })
14076 .collect::<Vec<_>>();
14077 (title, location_tasks, editor.workspace().clone())
14078 })
14079 .context("location tasks preparation")?;
14080
14081 let locations = future::join_all(location_tasks)
14082 .await
14083 .into_iter()
14084 .filter_map(|location| location.transpose())
14085 .collect::<Result<_>>()
14086 .context("location tasks")?;
14087
14088 let Some(workspace) = workspace else {
14089 return Ok(Navigated::No);
14090 };
14091 let opened = workspace
14092 .update_in(cx, |workspace, window, cx| {
14093 Self::open_locations_in_multibuffer(
14094 workspace,
14095 locations,
14096 title,
14097 split,
14098 MultibufferSelectionMode::First,
14099 window,
14100 cx,
14101 )
14102 })
14103 .ok();
14104
14105 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14106 })
14107 } else {
14108 Task::ready(Ok(Navigated::No))
14109 }
14110 }
14111
14112 fn compute_target_location(
14113 &self,
14114 lsp_location: lsp::Location,
14115 server_id: LanguageServerId,
14116 window: &mut Window,
14117 cx: &mut Context<Self>,
14118 ) -> Task<anyhow::Result<Option<Location>>> {
14119 let Some(project) = self.project.clone() else {
14120 return Task::ready(Ok(None));
14121 };
14122
14123 cx.spawn_in(window, async move |editor, cx| {
14124 let location_task = editor.update(cx, |_, cx| {
14125 project.update(cx, |project, cx| {
14126 let language_server_name = project
14127 .language_server_statuses(cx)
14128 .find(|(id, _)| server_id == *id)
14129 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14130 language_server_name.map(|language_server_name| {
14131 project.open_local_buffer_via_lsp(
14132 lsp_location.uri.clone(),
14133 server_id,
14134 language_server_name,
14135 cx,
14136 )
14137 })
14138 })
14139 })?;
14140 let location = match location_task {
14141 Some(task) => Some({
14142 let target_buffer_handle = task.await.context("open local buffer")?;
14143 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14144 let target_start = target_buffer
14145 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14146 let target_end = target_buffer
14147 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14148 target_buffer.anchor_after(target_start)
14149 ..target_buffer.anchor_before(target_end)
14150 })?;
14151 Location {
14152 buffer: target_buffer_handle,
14153 range,
14154 }
14155 }),
14156 None => None,
14157 };
14158 Ok(location)
14159 })
14160 }
14161
14162 pub fn find_all_references(
14163 &mut self,
14164 _: &FindAllReferences,
14165 window: &mut Window,
14166 cx: &mut Context<Self>,
14167 ) -> Option<Task<Result<Navigated>>> {
14168 let selection = self.selections.newest::<usize>(cx);
14169 let multi_buffer = self.buffer.read(cx);
14170 let head = selection.head();
14171
14172 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14173 let head_anchor = multi_buffer_snapshot.anchor_at(
14174 head,
14175 if head < selection.tail() {
14176 Bias::Right
14177 } else {
14178 Bias::Left
14179 },
14180 );
14181
14182 match self
14183 .find_all_references_task_sources
14184 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14185 {
14186 Ok(_) => {
14187 log::info!(
14188 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14189 );
14190 return None;
14191 }
14192 Err(i) => {
14193 self.find_all_references_task_sources.insert(i, head_anchor);
14194 }
14195 }
14196
14197 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14198 let workspace = self.workspace()?;
14199 let project = workspace.read(cx).project().clone();
14200 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14201 Some(cx.spawn_in(window, async move |editor, cx| {
14202 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14203 if let Ok(i) = editor
14204 .find_all_references_task_sources
14205 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14206 {
14207 editor.find_all_references_task_sources.remove(i);
14208 }
14209 });
14210
14211 let locations = references.await?;
14212 if locations.is_empty() {
14213 return anyhow::Ok(Navigated::No);
14214 }
14215
14216 workspace.update_in(cx, |workspace, window, cx| {
14217 let title = locations
14218 .first()
14219 .as_ref()
14220 .map(|location| {
14221 let buffer = location.buffer.read(cx);
14222 format!(
14223 "References to `{}`",
14224 buffer
14225 .text_for_range(location.range.clone())
14226 .collect::<String>()
14227 )
14228 })
14229 .unwrap();
14230 Self::open_locations_in_multibuffer(
14231 workspace,
14232 locations,
14233 title,
14234 false,
14235 MultibufferSelectionMode::First,
14236 window,
14237 cx,
14238 );
14239 Navigated::Yes
14240 })
14241 }))
14242 }
14243
14244 /// Opens a multibuffer with the given project locations in it
14245 pub fn open_locations_in_multibuffer(
14246 workspace: &mut Workspace,
14247 mut locations: Vec<Location>,
14248 title: String,
14249 split: bool,
14250 multibuffer_selection_mode: MultibufferSelectionMode,
14251 window: &mut Window,
14252 cx: &mut Context<Workspace>,
14253 ) {
14254 // If there are multiple definitions, open them in a multibuffer
14255 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14256 let mut locations = locations.into_iter().peekable();
14257 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14258 let capability = workspace.project().read(cx).capability();
14259
14260 let excerpt_buffer = cx.new(|cx| {
14261 let mut multibuffer = MultiBuffer::new(capability);
14262 while let Some(location) = locations.next() {
14263 let buffer = location.buffer.read(cx);
14264 let mut ranges_for_buffer = Vec::new();
14265 let range = location.range.to_point(buffer);
14266 ranges_for_buffer.push(range.clone());
14267
14268 while let Some(next_location) = locations.peek() {
14269 if next_location.buffer == location.buffer {
14270 ranges_for_buffer.push(next_location.range.to_point(buffer));
14271 locations.next();
14272 } else {
14273 break;
14274 }
14275 }
14276
14277 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14278 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14279 PathKey::for_buffer(&location.buffer, cx),
14280 location.buffer.clone(),
14281 ranges_for_buffer,
14282 DEFAULT_MULTIBUFFER_CONTEXT,
14283 cx,
14284 );
14285 ranges.extend(new_ranges)
14286 }
14287
14288 multibuffer.with_title(title)
14289 });
14290
14291 let editor = cx.new(|cx| {
14292 Editor::for_multibuffer(
14293 excerpt_buffer,
14294 Some(workspace.project().clone()),
14295 window,
14296 cx,
14297 )
14298 });
14299 editor.update(cx, |editor, cx| {
14300 match multibuffer_selection_mode {
14301 MultibufferSelectionMode::First => {
14302 if let Some(first_range) = ranges.first() {
14303 editor.change_selections(None, window, cx, |selections| {
14304 selections.clear_disjoint();
14305 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14306 });
14307 }
14308 editor.highlight_background::<Self>(
14309 &ranges,
14310 |theme| theme.editor_highlighted_line_background,
14311 cx,
14312 );
14313 }
14314 MultibufferSelectionMode::All => {
14315 editor.change_selections(None, window, cx, |selections| {
14316 selections.clear_disjoint();
14317 selections.select_anchor_ranges(ranges);
14318 });
14319 }
14320 }
14321 editor.register_buffers_with_language_servers(cx);
14322 });
14323
14324 let item = Box::new(editor);
14325 let item_id = item.item_id();
14326
14327 if split {
14328 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14329 } else {
14330 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14331 let (preview_item_id, preview_item_idx) =
14332 workspace.active_pane().update(cx, |pane, _| {
14333 (pane.preview_item_id(), pane.preview_item_idx())
14334 });
14335
14336 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14337
14338 if let Some(preview_item_id) = preview_item_id {
14339 workspace.active_pane().update(cx, |pane, cx| {
14340 pane.remove_item(preview_item_id, false, false, window, cx);
14341 });
14342 }
14343 } else {
14344 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14345 }
14346 }
14347 workspace.active_pane().update(cx, |pane, cx| {
14348 pane.set_preview_item_id(Some(item_id), cx);
14349 });
14350 }
14351
14352 pub fn rename(
14353 &mut self,
14354 _: &Rename,
14355 window: &mut Window,
14356 cx: &mut Context<Self>,
14357 ) -> Option<Task<Result<()>>> {
14358 use language::ToOffset as _;
14359
14360 let provider = self.semantics_provider.clone()?;
14361 let selection = self.selections.newest_anchor().clone();
14362 let (cursor_buffer, cursor_buffer_position) = self
14363 .buffer
14364 .read(cx)
14365 .text_anchor_for_position(selection.head(), cx)?;
14366 let (tail_buffer, cursor_buffer_position_end) = self
14367 .buffer
14368 .read(cx)
14369 .text_anchor_for_position(selection.tail(), cx)?;
14370 if tail_buffer != cursor_buffer {
14371 return None;
14372 }
14373
14374 let snapshot = cursor_buffer.read(cx).snapshot();
14375 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14376 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14377 let prepare_rename = provider
14378 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14379 .unwrap_or_else(|| Task::ready(Ok(None)));
14380 drop(snapshot);
14381
14382 Some(cx.spawn_in(window, async move |this, cx| {
14383 let rename_range = if let Some(range) = prepare_rename.await? {
14384 Some(range)
14385 } else {
14386 this.update(cx, |this, cx| {
14387 let buffer = this.buffer.read(cx).snapshot(cx);
14388 let mut buffer_highlights = this
14389 .document_highlights_for_position(selection.head(), &buffer)
14390 .filter(|highlight| {
14391 highlight.start.excerpt_id == selection.head().excerpt_id
14392 && highlight.end.excerpt_id == selection.head().excerpt_id
14393 });
14394 buffer_highlights
14395 .next()
14396 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14397 })?
14398 };
14399 if let Some(rename_range) = rename_range {
14400 this.update_in(cx, |this, window, cx| {
14401 let snapshot = cursor_buffer.read(cx).snapshot();
14402 let rename_buffer_range = rename_range.to_offset(&snapshot);
14403 let cursor_offset_in_rename_range =
14404 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14405 let cursor_offset_in_rename_range_end =
14406 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14407
14408 this.take_rename(false, window, cx);
14409 let buffer = this.buffer.read(cx).read(cx);
14410 let cursor_offset = selection.head().to_offset(&buffer);
14411 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14412 let rename_end = rename_start + rename_buffer_range.len();
14413 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14414 let mut old_highlight_id = None;
14415 let old_name: Arc<str> = buffer
14416 .chunks(rename_start..rename_end, true)
14417 .map(|chunk| {
14418 if old_highlight_id.is_none() {
14419 old_highlight_id = chunk.syntax_highlight_id;
14420 }
14421 chunk.text
14422 })
14423 .collect::<String>()
14424 .into();
14425
14426 drop(buffer);
14427
14428 // Position the selection in the rename editor so that it matches the current selection.
14429 this.show_local_selections = false;
14430 let rename_editor = cx.new(|cx| {
14431 let mut editor = Editor::single_line(window, cx);
14432 editor.buffer.update(cx, |buffer, cx| {
14433 buffer.edit([(0..0, old_name.clone())], None, cx)
14434 });
14435 let rename_selection_range = match cursor_offset_in_rename_range
14436 .cmp(&cursor_offset_in_rename_range_end)
14437 {
14438 Ordering::Equal => {
14439 editor.select_all(&SelectAll, window, cx);
14440 return editor;
14441 }
14442 Ordering::Less => {
14443 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14444 }
14445 Ordering::Greater => {
14446 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14447 }
14448 };
14449 if rename_selection_range.end > old_name.len() {
14450 editor.select_all(&SelectAll, window, cx);
14451 } else {
14452 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14453 s.select_ranges([rename_selection_range]);
14454 });
14455 }
14456 editor
14457 });
14458 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14459 if e == &EditorEvent::Focused {
14460 cx.emit(EditorEvent::FocusedIn)
14461 }
14462 })
14463 .detach();
14464
14465 let write_highlights =
14466 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14467 let read_highlights =
14468 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14469 let ranges = write_highlights
14470 .iter()
14471 .flat_map(|(_, ranges)| ranges.iter())
14472 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14473 .cloned()
14474 .collect();
14475
14476 this.highlight_text::<Rename>(
14477 ranges,
14478 HighlightStyle {
14479 fade_out: Some(0.6),
14480 ..Default::default()
14481 },
14482 cx,
14483 );
14484 let rename_focus_handle = rename_editor.focus_handle(cx);
14485 window.focus(&rename_focus_handle);
14486 let block_id = this.insert_blocks(
14487 [BlockProperties {
14488 style: BlockStyle::Flex,
14489 placement: BlockPlacement::Below(range.start),
14490 height: Some(1),
14491 render: Arc::new({
14492 let rename_editor = rename_editor.clone();
14493 move |cx: &mut BlockContext| {
14494 let mut text_style = cx.editor_style.text.clone();
14495 if let Some(highlight_style) = old_highlight_id
14496 .and_then(|h| h.style(&cx.editor_style.syntax))
14497 {
14498 text_style = text_style.highlight(highlight_style);
14499 }
14500 div()
14501 .block_mouse_down()
14502 .pl(cx.anchor_x)
14503 .child(EditorElement::new(
14504 &rename_editor,
14505 EditorStyle {
14506 background: cx.theme().system().transparent,
14507 local_player: cx.editor_style.local_player,
14508 text: text_style,
14509 scrollbar_width: cx.editor_style.scrollbar_width,
14510 syntax: cx.editor_style.syntax.clone(),
14511 status: cx.editor_style.status.clone(),
14512 inlay_hints_style: HighlightStyle {
14513 font_weight: Some(FontWeight::BOLD),
14514 ..make_inlay_hints_style(cx.app)
14515 },
14516 inline_completion_styles: make_suggestion_styles(
14517 cx.app,
14518 ),
14519 ..EditorStyle::default()
14520 },
14521 ))
14522 .into_any_element()
14523 }
14524 }),
14525 priority: 0,
14526 }],
14527 Some(Autoscroll::fit()),
14528 cx,
14529 )[0];
14530 this.pending_rename = Some(RenameState {
14531 range,
14532 old_name,
14533 editor: rename_editor,
14534 block_id,
14535 });
14536 })?;
14537 }
14538
14539 Ok(())
14540 }))
14541 }
14542
14543 pub fn confirm_rename(
14544 &mut self,
14545 _: &ConfirmRename,
14546 window: &mut Window,
14547 cx: &mut Context<Self>,
14548 ) -> Option<Task<Result<()>>> {
14549 let rename = self.take_rename(false, window, cx)?;
14550 let workspace = self.workspace()?.downgrade();
14551 let (buffer, start) = self
14552 .buffer
14553 .read(cx)
14554 .text_anchor_for_position(rename.range.start, cx)?;
14555 let (end_buffer, _) = self
14556 .buffer
14557 .read(cx)
14558 .text_anchor_for_position(rename.range.end, cx)?;
14559 if buffer != end_buffer {
14560 return None;
14561 }
14562
14563 let old_name = rename.old_name;
14564 let new_name = rename.editor.read(cx).text(cx);
14565
14566 let rename = self.semantics_provider.as_ref()?.perform_rename(
14567 &buffer,
14568 start,
14569 new_name.clone(),
14570 cx,
14571 )?;
14572
14573 Some(cx.spawn_in(window, async move |editor, cx| {
14574 let project_transaction = rename.await?;
14575 Self::open_project_transaction(
14576 &editor,
14577 workspace,
14578 project_transaction,
14579 format!("Rename: {} → {}", old_name, new_name),
14580 cx,
14581 )
14582 .await?;
14583
14584 editor.update(cx, |editor, cx| {
14585 editor.refresh_document_highlights(cx);
14586 })?;
14587 Ok(())
14588 }))
14589 }
14590
14591 fn take_rename(
14592 &mut self,
14593 moving_cursor: bool,
14594 window: &mut Window,
14595 cx: &mut Context<Self>,
14596 ) -> Option<RenameState> {
14597 let rename = self.pending_rename.take()?;
14598 if rename.editor.focus_handle(cx).is_focused(window) {
14599 window.focus(&self.focus_handle);
14600 }
14601
14602 self.remove_blocks(
14603 [rename.block_id].into_iter().collect(),
14604 Some(Autoscroll::fit()),
14605 cx,
14606 );
14607 self.clear_highlights::<Rename>(cx);
14608 self.show_local_selections = true;
14609
14610 if moving_cursor {
14611 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14612 editor.selections.newest::<usize>(cx).head()
14613 });
14614
14615 // Update the selection to match the position of the selection inside
14616 // the rename editor.
14617 let snapshot = self.buffer.read(cx).read(cx);
14618 let rename_range = rename.range.to_offset(&snapshot);
14619 let cursor_in_editor = snapshot
14620 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14621 .min(rename_range.end);
14622 drop(snapshot);
14623
14624 self.change_selections(None, window, cx, |s| {
14625 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14626 });
14627 } else {
14628 self.refresh_document_highlights(cx);
14629 }
14630
14631 Some(rename)
14632 }
14633
14634 pub fn pending_rename(&self) -> Option<&RenameState> {
14635 self.pending_rename.as_ref()
14636 }
14637
14638 fn format(
14639 &mut self,
14640 _: &Format,
14641 window: &mut Window,
14642 cx: &mut Context<Self>,
14643 ) -> Option<Task<Result<()>>> {
14644 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14645
14646 let project = match &self.project {
14647 Some(project) => project.clone(),
14648 None => return None,
14649 };
14650
14651 Some(self.perform_format(
14652 project,
14653 FormatTrigger::Manual,
14654 FormatTarget::Buffers,
14655 window,
14656 cx,
14657 ))
14658 }
14659
14660 fn format_selections(
14661 &mut self,
14662 _: &FormatSelections,
14663 window: &mut Window,
14664 cx: &mut Context<Self>,
14665 ) -> Option<Task<Result<()>>> {
14666 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14667
14668 let project = match &self.project {
14669 Some(project) => project.clone(),
14670 None => return None,
14671 };
14672
14673 let ranges = self
14674 .selections
14675 .all_adjusted(cx)
14676 .into_iter()
14677 .map(|selection| selection.range())
14678 .collect_vec();
14679
14680 Some(self.perform_format(
14681 project,
14682 FormatTrigger::Manual,
14683 FormatTarget::Ranges(ranges),
14684 window,
14685 cx,
14686 ))
14687 }
14688
14689 fn perform_format(
14690 &mut self,
14691 project: Entity<Project>,
14692 trigger: FormatTrigger,
14693 target: FormatTarget,
14694 window: &mut Window,
14695 cx: &mut Context<Self>,
14696 ) -> Task<Result<()>> {
14697 let buffer = self.buffer.clone();
14698 let (buffers, target) = match target {
14699 FormatTarget::Buffers => {
14700 let mut buffers = buffer.read(cx).all_buffers();
14701 if trigger == FormatTrigger::Save {
14702 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14703 }
14704 (buffers, LspFormatTarget::Buffers)
14705 }
14706 FormatTarget::Ranges(selection_ranges) => {
14707 let multi_buffer = buffer.read(cx);
14708 let snapshot = multi_buffer.read(cx);
14709 let mut buffers = HashSet::default();
14710 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14711 BTreeMap::new();
14712 for selection_range in selection_ranges {
14713 for (buffer, buffer_range, _) in
14714 snapshot.range_to_buffer_ranges(selection_range)
14715 {
14716 let buffer_id = buffer.remote_id();
14717 let start = buffer.anchor_before(buffer_range.start);
14718 let end = buffer.anchor_after(buffer_range.end);
14719 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14720 buffer_id_to_ranges
14721 .entry(buffer_id)
14722 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14723 .or_insert_with(|| vec![start..end]);
14724 }
14725 }
14726 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14727 }
14728 };
14729
14730 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14731 let selections_prev = transaction_id_prev
14732 .and_then(|transaction_id_prev| {
14733 // default to selections as they were after the last edit, if we have them,
14734 // instead of how they are now.
14735 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14736 // will take you back to where you made the last edit, instead of staying where you scrolled
14737 self.selection_history
14738 .transaction(transaction_id_prev)
14739 .map(|t| t.0.clone())
14740 })
14741 .unwrap_or_else(|| {
14742 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14743 self.selections.disjoint_anchors()
14744 });
14745
14746 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14747 let format = project.update(cx, |project, cx| {
14748 project.format(buffers, target, true, trigger, cx)
14749 });
14750
14751 cx.spawn_in(window, async move |editor, cx| {
14752 let transaction = futures::select_biased! {
14753 transaction = format.log_err().fuse() => transaction,
14754 () = timeout => {
14755 log::warn!("timed out waiting for formatting");
14756 None
14757 }
14758 };
14759
14760 buffer
14761 .update(cx, |buffer, cx| {
14762 if let Some(transaction) = transaction {
14763 if !buffer.is_singleton() {
14764 buffer.push_transaction(&transaction.0, cx);
14765 }
14766 }
14767 cx.notify();
14768 })
14769 .ok();
14770
14771 if let Some(transaction_id_now) =
14772 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14773 {
14774 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14775 if has_new_transaction {
14776 _ = editor.update(cx, |editor, _| {
14777 editor
14778 .selection_history
14779 .insert_transaction(transaction_id_now, selections_prev);
14780 });
14781 }
14782 }
14783
14784 Ok(())
14785 })
14786 }
14787
14788 fn organize_imports(
14789 &mut self,
14790 _: &OrganizeImports,
14791 window: &mut Window,
14792 cx: &mut Context<Self>,
14793 ) -> Option<Task<Result<()>>> {
14794 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14795 let project = match &self.project {
14796 Some(project) => project.clone(),
14797 None => return None,
14798 };
14799 Some(self.perform_code_action_kind(
14800 project,
14801 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14802 window,
14803 cx,
14804 ))
14805 }
14806
14807 fn perform_code_action_kind(
14808 &mut self,
14809 project: Entity<Project>,
14810 kind: CodeActionKind,
14811 window: &mut Window,
14812 cx: &mut Context<Self>,
14813 ) -> Task<Result<()>> {
14814 let buffer = self.buffer.clone();
14815 let buffers = buffer.read(cx).all_buffers();
14816 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14817 let apply_action = project.update(cx, |project, cx| {
14818 project.apply_code_action_kind(buffers, kind, true, cx)
14819 });
14820 cx.spawn_in(window, async move |_, cx| {
14821 let transaction = futures::select_biased! {
14822 () = timeout => {
14823 log::warn!("timed out waiting for executing code action");
14824 None
14825 }
14826 transaction = apply_action.log_err().fuse() => transaction,
14827 };
14828 buffer
14829 .update(cx, |buffer, cx| {
14830 // check if we need this
14831 if let Some(transaction) = transaction {
14832 if !buffer.is_singleton() {
14833 buffer.push_transaction(&transaction.0, cx);
14834 }
14835 }
14836 cx.notify();
14837 })
14838 .ok();
14839 Ok(())
14840 })
14841 }
14842
14843 fn restart_language_server(
14844 &mut self,
14845 _: &RestartLanguageServer,
14846 _: &mut Window,
14847 cx: &mut Context<Self>,
14848 ) {
14849 if let Some(project) = self.project.clone() {
14850 self.buffer.update(cx, |multi_buffer, cx| {
14851 project.update(cx, |project, cx| {
14852 project.restart_language_servers_for_buffers(
14853 multi_buffer.all_buffers().into_iter().collect(),
14854 cx,
14855 );
14856 });
14857 })
14858 }
14859 }
14860
14861 fn stop_language_server(
14862 &mut self,
14863 _: &StopLanguageServer,
14864 _: &mut Window,
14865 cx: &mut Context<Self>,
14866 ) {
14867 if let Some(project) = self.project.clone() {
14868 self.buffer.update(cx, |multi_buffer, cx| {
14869 project.update(cx, |project, cx| {
14870 project.stop_language_servers_for_buffers(
14871 multi_buffer.all_buffers().into_iter().collect(),
14872 cx,
14873 );
14874 cx.emit(project::Event::RefreshInlayHints);
14875 });
14876 });
14877 }
14878 }
14879
14880 fn cancel_language_server_work(
14881 workspace: &mut Workspace,
14882 _: &actions::CancelLanguageServerWork,
14883 _: &mut Window,
14884 cx: &mut Context<Workspace>,
14885 ) {
14886 let project = workspace.project();
14887 let buffers = workspace
14888 .active_item(cx)
14889 .and_then(|item| item.act_as::<Editor>(cx))
14890 .map_or(HashSet::default(), |editor| {
14891 editor.read(cx).buffer.read(cx).all_buffers()
14892 });
14893 project.update(cx, |project, cx| {
14894 project.cancel_language_server_work_for_buffers(buffers, cx);
14895 });
14896 }
14897
14898 fn show_character_palette(
14899 &mut self,
14900 _: &ShowCharacterPalette,
14901 window: &mut Window,
14902 _: &mut Context<Self>,
14903 ) {
14904 window.show_character_palette();
14905 }
14906
14907 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14908 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14909 let buffer = self.buffer.read(cx).snapshot(cx);
14910 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
14911 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
14912 let is_valid = buffer
14913 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14914 .any(|entry| {
14915 entry.diagnostic.is_primary
14916 && !entry.range.is_empty()
14917 && entry.range.start == primary_range_start
14918 && entry.diagnostic.message == active_diagnostics.active_message
14919 });
14920
14921 if !is_valid {
14922 self.dismiss_diagnostics(cx);
14923 }
14924 }
14925 }
14926
14927 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
14928 match &self.active_diagnostics {
14929 ActiveDiagnostic::Group(group) => Some(group),
14930 _ => None,
14931 }
14932 }
14933
14934 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
14935 self.dismiss_diagnostics(cx);
14936 self.active_diagnostics = ActiveDiagnostic::All;
14937 }
14938
14939 fn activate_diagnostics(
14940 &mut self,
14941 buffer_id: BufferId,
14942 diagnostic: DiagnosticEntry<usize>,
14943 window: &mut Window,
14944 cx: &mut Context<Self>,
14945 ) {
14946 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14947 return;
14948 }
14949 self.dismiss_diagnostics(cx);
14950 let snapshot = self.snapshot(window, cx);
14951 let buffer = self.buffer.read(cx).snapshot(cx);
14952 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
14953 return;
14954 };
14955
14956 let diagnostic_group = buffer
14957 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
14958 .collect::<Vec<_>>();
14959
14960 let blocks =
14961 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
14962
14963 let blocks = self.display_map.update(cx, |display_map, cx| {
14964 display_map.insert_blocks(blocks, cx).into_iter().collect()
14965 });
14966 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
14967 active_range: buffer.anchor_before(diagnostic.range.start)
14968 ..buffer.anchor_after(diagnostic.range.end),
14969 active_message: diagnostic.diagnostic.message.clone(),
14970 group_id: diagnostic.diagnostic.group_id,
14971 blocks,
14972 });
14973 cx.notify();
14974 }
14975
14976 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14977 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14978 return;
14979 };
14980
14981 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
14982 if let ActiveDiagnostic::Group(group) = prev {
14983 self.display_map.update(cx, |display_map, cx| {
14984 display_map.remove_blocks(group.blocks, cx);
14985 });
14986 cx.notify();
14987 }
14988 }
14989
14990 /// Disable inline diagnostics rendering for this editor.
14991 pub fn disable_inline_diagnostics(&mut self) {
14992 self.inline_diagnostics_enabled = false;
14993 self.inline_diagnostics_update = Task::ready(());
14994 self.inline_diagnostics.clear();
14995 }
14996
14997 pub fn inline_diagnostics_enabled(&self) -> bool {
14998 self.inline_diagnostics_enabled
14999 }
15000
15001 pub fn show_inline_diagnostics(&self) -> bool {
15002 self.show_inline_diagnostics
15003 }
15004
15005 pub fn toggle_inline_diagnostics(
15006 &mut self,
15007 _: &ToggleInlineDiagnostics,
15008 window: &mut Window,
15009 cx: &mut Context<Editor>,
15010 ) {
15011 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15012 self.refresh_inline_diagnostics(false, window, cx);
15013 }
15014
15015 fn refresh_inline_diagnostics(
15016 &mut self,
15017 debounce: bool,
15018 window: &mut Window,
15019 cx: &mut Context<Self>,
15020 ) {
15021 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
15022 self.inline_diagnostics_update = Task::ready(());
15023 self.inline_diagnostics.clear();
15024 return;
15025 }
15026
15027 let debounce_ms = ProjectSettings::get_global(cx)
15028 .diagnostics
15029 .inline
15030 .update_debounce_ms;
15031 let debounce = if debounce && debounce_ms > 0 {
15032 Some(Duration::from_millis(debounce_ms))
15033 } else {
15034 None
15035 };
15036 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15037 let editor = editor.upgrade().unwrap();
15038
15039 if let Some(debounce) = debounce {
15040 cx.background_executor().timer(debounce).await;
15041 }
15042 let Some(snapshot) = editor
15043 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15044 .ok()
15045 else {
15046 return;
15047 };
15048
15049 let new_inline_diagnostics = cx
15050 .background_spawn(async move {
15051 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15052 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15053 let message = diagnostic_entry
15054 .diagnostic
15055 .message
15056 .split_once('\n')
15057 .map(|(line, _)| line)
15058 .map(SharedString::new)
15059 .unwrap_or_else(|| {
15060 SharedString::from(diagnostic_entry.diagnostic.message)
15061 });
15062 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15063 let (Ok(i) | Err(i)) = inline_diagnostics
15064 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15065 inline_diagnostics.insert(
15066 i,
15067 (
15068 start_anchor,
15069 InlineDiagnostic {
15070 message,
15071 group_id: diagnostic_entry.diagnostic.group_id,
15072 start: diagnostic_entry.range.start.to_point(&snapshot),
15073 is_primary: diagnostic_entry.diagnostic.is_primary,
15074 severity: diagnostic_entry.diagnostic.severity,
15075 },
15076 ),
15077 );
15078 }
15079 inline_diagnostics
15080 })
15081 .await;
15082
15083 editor
15084 .update(cx, |editor, cx| {
15085 editor.inline_diagnostics = new_inline_diagnostics;
15086 cx.notify();
15087 })
15088 .ok();
15089 });
15090 }
15091
15092 pub fn set_selections_from_remote(
15093 &mut self,
15094 selections: Vec<Selection<Anchor>>,
15095 pending_selection: Option<Selection<Anchor>>,
15096 window: &mut Window,
15097 cx: &mut Context<Self>,
15098 ) {
15099 let old_cursor_position = self.selections.newest_anchor().head();
15100 self.selections.change_with(cx, |s| {
15101 s.select_anchors(selections);
15102 if let Some(pending_selection) = pending_selection {
15103 s.set_pending(pending_selection, SelectMode::Character);
15104 } else {
15105 s.clear_pending();
15106 }
15107 });
15108 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15109 }
15110
15111 fn push_to_selection_history(&mut self) {
15112 self.selection_history.push(SelectionHistoryEntry {
15113 selections: self.selections.disjoint_anchors(),
15114 select_next_state: self.select_next_state.clone(),
15115 select_prev_state: self.select_prev_state.clone(),
15116 add_selections_state: self.add_selections_state.clone(),
15117 });
15118 }
15119
15120 pub fn transact(
15121 &mut self,
15122 window: &mut Window,
15123 cx: &mut Context<Self>,
15124 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15125 ) -> Option<TransactionId> {
15126 self.start_transaction_at(Instant::now(), window, cx);
15127 update(self, window, cx);
15128 self.end_transaction_at(Instant::now(), cx)
15129 }
15130
15131 pub fn start_transaction_at(
15132 &mut self,
15133 now: Instant,
15134 window: &mut Window,
15135 cx: &mut Context<Self>,
15136 ) {
15137 self.end_selection(window, cx);
15138 if let Some(tx_id) = self
15139 .buffer
15140 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15141 {
15142 self.selection_history
15143 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15144 cx.emit(EditorEvent::TransactionBegun {
15145 transaction_id: tx_id,
15146 })
15147 }
15148 }
15149
15150 pub fn end_transaction_at(
15151 &mut self,
15152 now: Instant,
15153 cx: &mut Context<Self>,
15154 ) -> Option<TransactionId> {
15155 if let Some(transaction_id) = self
15156 .buffer
15157 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15158 {
15159 if let Some((_, end_selections)) =
15160 self.selection_history.transaction_mut(transaction_id)
15161 {
15162 *end_selections = Some(self.selections.disjoint_anchors());
15163 } else {
15164 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15165 }
15166
15167 cx.emit(EditorEvent::Edited { transaction_id });
15168 Some(transaction_id)
15169 } else {
15170 None
15171 }
15172 }
15173
15174 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15175 if self.selection_mark_mode {
15176 self.change_selections(None, window, cx, |s| {
15177 s.move_with(|_, sel| {
15178 sel.collapse_to(sel.head(), SelectionGoal::None);
15179 });
15180 })
15181 }
15182 self.selection_mark_mode = true;
15183 cx.notify();
15184 }
15185
15186 pub fn swap_selection_ends(
15187 &mut self,
15188 _: &actions::SwapSelectionEnds,
15189 window: &mut Window,
15190 cx: &mut Context<Self>,
15191 ) {
15192 self.change_selections(None, window, cx, |s| {
15193 s.move_with(|_, sel| {
15194 if sel.start != sel.end {
15195 sel.reversed = !sel.reversed
15196 }
15197 });
15198 });
15199 self.request_autoscroll(Autoscroll::newest(), cx);
15200 cx.notify();
15201 }
15202
15203 pub fn toggle_fold(
15204 &mut self,
15205 _: &actions::ToggleFold,
15206 window: &mut Window,
15207 cx: &mut Context<Self>,
15208 ) {
15209 if self.is_singleton(cx) {
15210 let selection = self.selections.newest::<Point>(cx);
15211
15212 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15213 let range = if selection.is_empty() {
15214 let point = selection.head().to_display_point(&display_map);
15215 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15216 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15217 .to_point(&display_map);
15218 start..end
15219 } else {
15220 selection.range()
15221 };
15222 if display_map.folds_in_range(range).next().is_some() {
15223 self.unfold_lines(&Default::default(), window, cx)
15224 } else {
15225 self.fold(&Default::default(), window, cx)
15226 }
15227 } else {
15228 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15229 let buffer_ids: HashSet<_> = self
15230 .selections
15231 .disjoint_anchor_ranges()
15232 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15233 .collect();
15234
15235 let should_unfold = buffer_ids
15236 .iter()
15237 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15238
15239 for buffer_id in buffer_ids {
15240 if should_unfold {
15241 self.unfold_buffer(buffer_id, cx);
15242 } else {
15243 self.fold_buffer(buffer_id, cx);
15244 }
15245 }
15246 }
15247 }
15248
15249 pub fn toggle_fold_recursive(
15250 &mut self,
15251 _: &actions::ToggleFoldRecursive,
15252 window: &mut Window,
15253 cx: &mut Context<Self>,
15254 ) {
15255 let selection = self.selections.newest::<Point>(cx);
15256
15257 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15258 let range = if selection.is_empty() {
15259 let point = selection.head().to_display_point(&display_map);
15260 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15261 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15262 .to_point(&display_map);
15263 start..end
15264 } else {
15265 selection.range()
15266 };
15267 if display_map.folds_in_range(range).next().is_some() {
15268 self.unfold_recursive(&Default::default(), window, cx)
15269 } else {
15270 self.fold_recursive(&Default::default(), window, cx)
15271 }
15272 }
15273
15274 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15275 if self.is_singleton(cx) {
15276 let mut to_fold = Vec::new();
15277 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15278 let selections = self.selections.all_adjusted(cx);
15279
15280 for selection in selections {
15281 let range = selection.range().sorted();
15282 let buffer_start_row = range.start.row;
15283
15284 if range.start.row != range.end.row {
15285 let mut found = false;
15286 let mut row = range.start.row;
15287 while row <= range.end.row {
15288 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15289 {
15290 found = true;
15291 row = crease.range().end.row + 1;
15292 to_fold.push(crease);
15293 } else {
15294 row += 1
15295 }
15296 }
15297 if found {
15298 continue;
15299 }
15300 }
15301
15302 for row in (0..=range.start.row).rev() {
15303 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15304 if crease.range().end.row >= buffer_start_row {
15305 to_fold.push(crease);
15306 if row <= range.start.row {
15307 break;
15308 }
15309 }
15310 }
15311 }
15312 }
15313
15314 self.fold_creases(to_fold, true, window, cx);
15315 } else {
15316 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15317 let buffer_ids = self
15318 .selections
15319 .disjoint_anchor_ranges()
15320 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15321 .collect::<HashSet<_>>();
15322 for buffer_id in buffer_ids {
15323 self.fold_buffer(buffer_id, cx);
15324 }
15325 }
15326 }
15327
15328 fn fold_at_level(
15329 &mut self,
15330 fold_at: &FoldAtLevel,
15331 window: &mut Window,
15332 cx: &mut Context<Self>,
15333 ) {
15334 if !self.buffer.read(cx).is_singleton() {
15335 return;
15336 }
15337
15338 let fold_at_level = fold_at.0;
15339 let snapshot = self.buffer.read(cx).snapshot(cx);
15340 let mut to_fold = Vec::new();
15341 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15342
15343 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15344 while start_row < end_row {
15345 match self
15346 .snapshot(window, cx)
15347 .crease_for_buffer_row(MultiBufferRow(start_row))
15348 {
15349 Some(crease) => {
15350 let nested_start_row = crease.range().start.row + 1;
15351 let nested_end_row = crease.range().end.row;
15352
15353 if current_level < fold_at_level {
15354 stack.push((nested_start_row, nested_end_row, current_level + 1));
15355 } else if current_level == fold_at_level {
15356 to_fold.push(crease);
15357 }
15358
15359 start_row = nested_end_row + 1;
15360 }
15361 None => start_row += 1,
15362 }
15363 }
15364 }
15365
15366 self.fold_creases(to_fold, true, window, cx);
15367 }
15368
15369 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15370 if self.buffer.read(cx).is_singleton() {
15371 let mut fold_ranges = Vec::new();
15372 let snapshot = self.buffer.read(cx).snapshot(cx);
15373
15374 for row in 0..snapshot.max_row().0 {
15375 if let Some(foldable_range) = self
15376 .snapshot(window, cx)
15377 .crease_for_buffer_row(MultiBufferRow(row))
15378 {
15379 fold_ranges.push(foldable_range);
15380 }
15381 }
15382
15383 self.fold_creases(fold_ranges, true, window, cx);
15384 } else {
15385 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15386 editor
15387 .update_in(cx, |editor, _, cx| {
15388 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15389 editor.fold_buffer(buffer_id, cx);
15390 }
15391 })
15392 .ok();
15393 });
15394 }
15395 }
15396
15397 pub fn fold_function_bodies(
15398 &mut self,
15399 _: &actions::FoldFunctionBodies,
15400 window: &mut Window,
15401 cx: &mut Context<Self>,
15402 ) {
15403 let snapshot = self.buffer.read(cx).snapshot(cx);
15404
15405 let ranges = snapshot
15406 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15407 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15408 .collect::<Vec<_>>();
15409
15410 let creases = ranges
15411 .into_iter()
15412 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15413 .collect();
15414
15415 self.fold_creases(creases, true, window, cx);
15416 }
15417
15418 pub fn fold_recursive(
15419 &mut self,
15420 _: &actions::FoldRecursive,
15421 window: &mut Window,
15422 cx: &mut Context<Self>,
15423 ) {
15424 let mut to_fold = Vec::new();
15425 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15426 let selections = self.selections.all_adjusted(cx);
15427
15428 for selection in selections {
15429 let range = selection.range().sorted();
15430 let buffer_start_row = range.start.row;
15431
15432 if range.start.row != range.end.row {
15433 let mut found = false;
15434 for row in range.start.row..=range.end.row {
15435 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15436 found = true;
15437 to_fold.push(crease);
15438 }
15439 }
15440 if found {
15441 continue;
15442 }
15443 }
15444
15445 for row in (0..=range.start.row).rev() {
15446 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15447 if crease.range().end.row >= buffer_start_row {
15448 to_fold.push(crease);
15449 } else {
15450 break;
15451 }
15452 }
15453 }
15454 }
15455
15456 self.fold_creases(to_fold, true, window, cx);
15457 }
15458
15459 pub fn fold_at(
15460 &mut self,
15461 buffer_row: MultiBufferRow,
15462 window: &mut Window,
15463 cx: &mut Context<Self>,
15464 ) {
15465 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15466
15467 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15468 let autoscroll = self
15469 .selections
15470 .all::<Point>(cx)
15471 .iter()
15472 .any(|selection| crease.range().overlaps(&selection.range()));
15473
15474 self.fold_creases(vec![crease], autoscroll, window, cx);
15475 }
15476 }
15477
15478 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15479 if self.is_singleton(cx) {
15480 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15481 let buffer = &display_map.buffer_snapshot;
15482 let selections = self.selections.all::<Point>(cx);
15483 let ranges = selections
15484 .iter()
15485 .map(|s| {
15486 let range = s.display_range(&display_map).sorted();
15487 let mut start = range.start.to_point(&display_map);
15488 let mut end = range.end.to_point(&display_map);
15489 start.column = 0;
15490 end.column = buffer.line_len(MultiBufferRow(end.row));
15491 start..end
15492 })
15493 .collect::<Vec<_>>();
15494
15495 self.unfold_ranges(&ranges, true, true, cx);
15496 } else {
15497 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15498 let buffer_ids = self
15499 .selections
15500 .disjoint_anchor_ranges()
15501 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15502 .collect::<HashSet<_>>();
15503 for buffer_id in buffer_ids {
15504 self.unfold_buffer(buffer_id, cx);
15505 }
15506 }
15507 }
15508
15509 pub fn unfold_recursive(
15510 &mut self,
15511 _: &UnfoldRecursive,
15512 _window: &mut Window,
15513 cx: &mut Context<Self>,
15514 ) {
15515 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15516 let selections = self.selections.all::<Point>(cx);
15517 let ranges = selections
15518 .iter()
15519 .map(|s| {
15520 let mut range = s.display_range(&display_map).sorted();
15521 *range.start.column_mut() = 0;
15522 *range.end.column_mut() = display_map.line_len(range.end.row());
15523 let start = range.start.to_point(&display_map);
15524 let end = range.end.to_point(&display_map);
15525 start..end
15526 })
15527 .collect::<Vec<_>>();
15528
15529 self.unfold_ranges(&ranges, true, true, cx);
15530 }
15531
15532 pub fn unfold_at(
15533 &mut self,
15534 buffer_row: MultiBufferRow,
15535 _window: &mut Window,
15536 cx: &mut Context<Self>,
15537 ) {
15538 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15539
15540 let intersection_range = Point::new(buffer_row.0, 0)
15541 ..Point::new(
15542 buffer_row.0,
15543 display_map.buffer_snapshot.line_len(buffer_row),
15544 );
15545
15546 let autoscroll = self
15547 .selections
15548 .all::<Point>(cx)
15549 .iter()
15550 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15551
15552 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15553 }
15554
15555 pub fn unfold_all(
15556 &mut self,
15557 _: &actions::UnfoldAll,
15558 _window: &mut Window,
15559 cx: &mut Context<Self>,
15560 ) {
15561 if self.buffer.read(cx).is_singleton() {
15562 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15563 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15564 } else {
15565 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15566 editor
15567 .update(cx, |editor, cx| {
15568 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15569 editor.unfold_buffer(buffer_id, cx);
15570 }
15571 })
15572 .ok();
15573 });
15574 }
15575 }
15576
15577 pub fn fold_selected_ranges(
15578 &mut self,
15579 _: &FoldSelectedRanges,
15580 window: &mut Window,
15581 cx: &mut Context<Self>,
15582 ) {
15583 let selections = self.selections.all_adjusted(cx);
15584 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15585 let ranges = selections
15586 .into_iter()
15587 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15588 .collect::<Vec<_>>();
15589 self.fold_creases(ranges, true, window, cx);
15590 }
15591
15592 pub fn fold_ranges<T: ToOffset + Clone>(
15593 &mut self,
15594 ranges: Vec<Range<T>>,
15595 auto_scroll: bool,
15596 window: &mut Window,
15597 cx: &mut Context<Self>,
15598 ) {
15599 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15600 let ranges = ranges
15601 .into_iter()
15602 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15603 .collect::<Vec<_>>();
15604 self.fold_creases(ranges, auto_scroll, window, cx);
15605 }
15606
15607 pub fn fold_creases<T: ToOffset + Clone>(
15608 &mut self,
15609 creases: Vec<Crease<T>>,
15610 auto_scroll: bool,
15611 _window: &mut Window,
15612 cx: &mut Context<Self>,
15613 ) {
15614 if creases.is_empty() {
15615 return;
15616 }
15617
15618 let mut buffers_affected = HashSet::default();
15619 let multi_buffer = self.buffer().read(cx);
15620 for crease in &creases {
15621 if let Some((_, buffer, _)) =
15622 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15623 {
15624 buffers_affected.insert(buffer.read(cx).remote_id());
15625 };
15626 }
15627
15628 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15629
15630 if auto_scroll {
15631 self.request_autoscroll(Autoscroll::fit(), cx);
15632 }
15633
15634 cx.notify();
15635
15636 self.scrollbar_marker_state.dirty = true;
15637 self.folds_did_change(cx);
15638 }
15639
15640 /// Removes any folds whose ranges intersect any of the given ranges.
15641 pub fn unfold_ranges<T: ToOffset + Clone>(
15642 &mut self,
15643 ranges: &[Range<T>],
15644 inclusive: bool,
15645 auto_scroll: bool,
15646 cx: &mut Context<Self>,
15647 ) {
15648 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15649 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15650 });
15651 self.folds_did_change(cx);
15652 }
15653
15654 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15655 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15656 return;
15657 }
15658 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15659 self.display_map.update(cx, |display_map, cx| {
15660 display_map.fold_buffers([buffer_id], cx)
15661 });
15662 cx.emit(EditorEvent::BufferFoldToggled {
15663 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15664 folded: true,
15665 });
15666 cx.notify();
15667 }
15668
15669 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15670 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15671 return;
15672 }
15673 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15674 self.display_map.update(cx, |display_map, cx| {
15675 display_map.unfold_buffers([buffer_id], cx);
15676 });
15677 cx.emit(EditorEvent::BufferFoldToggled {
15678 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15679 folded: false,
15680 });
15681 cx.notify();
15682 }
15683
15684 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15685 self.display_map.read(cx).is_buffer_folded(buffer)
15686 }
15687
15688 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15689 self.display_map.read(cx).folded_buffers()
15690 }
15691
15692 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15693 self.display_map.update(cx, |display_map, cx| {
15694 display_map.disable_header_for_buffer(buffer_id, cx);
15695 });
15696 cx.notify();
15697 }
15698
15699 /// Removes any folds with the given ranges.
15700 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15701 &mut self,
15702 ranges: &[Range<T>],
15703 type_id: TypeId,
15704 auto_scroll: bool,
15705 cx: &mut Context<Self>,
15706 ) {
15707 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15708 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15709 });
15710 self.folds_did_change(cx);
15711 }
15712
15713 fn remove_folds_with<T: ToOffset + Clone>(
15714 &mut self,
15715 ranges: &[Range<T>],
15716 auto_scroll: bool,
15717 cx: &mut Context<Self>,
15718 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15719 ) {
15720 if ranges.is_empty() {
15721 return;
15722 }
15723
15724 let mut buffers_affected = HashSet::default();
15725 let multi_buffer = self.buffer().read(cx);
15726 for range in ranges {
15727 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15728 buffers_affected.insert(buffer.read(cx).remote_id());
15729 };
15730 }
15731
15732 self.display_map.update(cx, update);
15733
15734 if auto_scroll {
15735 self.request_autoscroll(Autoscroll::fit(), cx);
15736 }
15737
15738 cx.notify();
15739 self.scrollbar_marker_state.dirty = true;
15740 self.active_indent_guides_state.dirty = true;
15741 }
15742
15743 pub fn update_fold_widths(
15744 &mut self,
15745 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15746 cx: &mut Context<Self>,
15747 ) -> bool {
15748 self.display_map
15749 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15750 }
15751
15752 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15753 self.display_map.read(cx).fold_placeholder.clone()
15754 }
15755
15756 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15757 self.buffer.update(cx, |buffer, cx| {
15758 buffer.set_all_diff_hunks_expanded(cx);
15759 });
15760 }
15761
15762 pub fn expand_all_diff_hunks(
15763 &mut self,
15764 _: &ExpandAllDiffHunks,
15765 _window: &mut Window,
15766 cx: &mut Context<Self>,
15767 ) {
15768 self.buffer.update(cx, |buffer, cx| {
15769 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15770 });
15771 }
15772
15773 pub fn toggle_selected_diff_hunks(
15774 &mut self,
15775 _: &ToggleSelectedDiffHunks,
15776 _window: &mut Window,
15777 cx: &mut Context<Self>,
15778 ) {
15779 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15780 self.toggle_diff_hunks_in_ranges(ranges, cx);
15781 }
15782
15783 pub fn diff_hunks_in_ranges<'a>(
15784 &'a self,
15785 ranges: &'a [Range<Anchor>],
15786 buffer: &'a MultiBufferSnapshot,
15787 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15788 ranges.iter().flat_map(move |range| {
15789 let end_excerpt_id = range.end.excerpt_id;
15790 let range = range.to_point(buffer);
15791 let mut peek_end = range.end;
15792 if range.end.row < buffer.max_row().0 {
15793 peek_end = Point::new(range.end.row + 1, 0);
15794 }
15795 buffer
15796 .diff_hunks_in_range(range.start..peek_end)
15797 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15798 })
15799 }
15800
15801 pub fn has_stageable_diff_hunks_in_ranges(
15802 &self,
15803 ranges: &[Range<Anchor>],
15804 snapshot: &MultiBufferSnapshot,
15805 ) -> bool {
15806 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15807 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15808 }
15809
15810 pub fn toggle_staged_selected_diff_hunks(
15811 &mut self,
15812 _: &::git::ToggleStaged,
15813 _: &mut Window,
15814 cx: &mut Context<Self>,
15815 ) {
15816 let snapshot = self.buffer.read(cx).snapshot(cx);
15817 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15818 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15819 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15820 }
15821
15822 pub fn set_render_diff_hunk_controls(
15823 &mut self,
15824 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15825 cx: &mut Context<Self>,
15826 ) {
15827 self.render_diff_hunk_controls = render_diff_hunk_controls;
15828 cx.notify();
15829 }
15830
15831 pub fn stage_and_next(
15832 &mut self,
15833 _: &::git::StageAndNext,
15834 window: &mut Window,
15835 cx: &mut Context<Self>,
15836 ) {
15837 self.do_stage_or_unstage_and_next(true, window, cx);
15838 }
15839
15840 pub fn unstage_and_next(
15841 &mut self,
15842 _: &::git::UnstageAndNext,
15843 window: &mut Window,
15844 cx: &mut Context<Self>,
15845 ) {
15846 self.do_stage_or_unstage_and_next(false, window, cx);
15847 }
15848
15849 pub fn stage_or_unstage_diff_hunks(
15850 &mut self,
15851 stage: bool,
15852 ranges: Vec<Range<Anchor>>,
15853 cx: &mut Context<Self>,
15854 ) {
15855 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15856 cx.spawn(async move |this, cx| {
15857 task.await?;
15858 this.update(cx, |this, cx| {
15859 let snapshot = this.buffer.read(cx).snapshot(cx);
15860 let chunk_by = this
15861 .diff_hunks_in_ranges(&ranges, &snapshot)
15862 .chunk_by(|hunk| hunk.buffer_id);
15863 for (buffer_id, hunks) in &chunk_by {
15864 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15865 }
15866 })
15867 })
15868 .detach_and_log_err(cx);
15869 }
15870
15871 fn save_buffers_for_ranges_if_needed(
15872 &mut self,
15873 ranges: &[Range<Anchor>],
15874 cx: &mut Context<Editor>,
15875 ) -> Task<Result<()>> {
15876 let multibuffer = self.buffer.read(cx);
15877 let snapshot = multibuffer.read(cx);
15878 let buffer_ids: HashSet<_> = ranges
15879 .iter()
15880 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15881 .collect();
15882 drop(snapshot);
15883
15884 let mut buffers = HashSet::default();
15885 for buffer_id in buffer_ids {
15886 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15887 let buffer = buffer_entity.read(cx);
15888 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15889 {
15890 buffers.insert(buffer_entity);
15891 }
15892 }
15893 }
15894
15895 if let Some(project) = &self.project {
15896 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15897 } else {
15898 Task::ready(Ok(()))
15899 }
15900 }
15901
15902 fn do_stage_or_unstage_and_next(
15903 &mut self,
15904 stage: bool,
15905 window: &mut Window,
15906 cx: &mut Context<Self>,
15907 ) {
15908 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15909
15910 if ranges.iter().any(|range| range.start != range.end) {
15911 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15912 return;
15913 }
15914
15915 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15916 let snapshot = self.snapshot(window, cx);
15917 let position = self.selections.newest::<Point>(cx).head();
15918 let mut row = snapshot
15919 .buffer_snapshot
15920 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15921 .find(|hunk| hunk.row_range.start.0 > position.row)
15922 .map(|hunk| hunk.row_range.start);
15923
15924 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15925 // Outside of the project diff editor, wrap around to the beginning.
15926 if !all_diff_hunks_expanded {
15927 row = row.or_else(|| {
15928 snapshot
15929 .buffer_snapshot
15930 .diff_hunks_in_range(Point::zero()..position)
15931 .find(|hunk| hunk.row_range.end.0 < position.row)
15932 .map(|hunk| hunk.row_range.start)
15933 });
15934 }
15935
15936 if let Some(row) = row {
15937 let destination = Point::new(row.0, 0);
15938 let autoscroll = Autoscroll::center();
15939
15940 self.unfold_ranges(&[destination..destination], false, false, cx);
15941 self.change_selections(Some(autoscroll), window, cx, |s| {
15942 s.select_ranges([destination..destination]);
15943 });
15944 }
15945 }
15946
15947 fn do_stage_or_unstage(
15948 &self,
15949 stage: bool,
15950 buffer_id: BufferId,
15951 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15952 cx: &mut App,
15953 ) -> Option<()> {
15954 let project = self.project.as_ref()?;
15955 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15956 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15957 let buffer_snapshot = buffer.read(cx).snapshot();
15958 let file_exists = buffer_snapshot
15959 .file()
15960 .is_some_and(|file| file.disk_state().exists());
15961 diff.update(cx, |diff, cx| {
15962 diff.stage_or_unstage_hunks(
15963 stage,
15964 &hunks
15965 .map(|hunk| buffer_diff::DiffHunk {
15966 buffer_range: hunk.buffer_range,
15967 diff_base_byte_range: hunk.diff_base_byte_range,
15968 secondary_status: hunk.secondary_status,
15969 range: Point::zero()..Point::zero(), // unused
15970 })
15971 .collect::<Vec<_>>(),
15972 &buffer_snapshot,
15973 file_exists,
15974 cx,
15975 )
15976 });
15977 None
15978 }
15979
15980 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15981 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15982 self.buffer
15983 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15984 }
15985
15986 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15987 self.buffer.update(cx, |buffer, cx| {
15988 let ranges = vec![Anchor::min()..Anchor::max()];
15989 if !buffer.all_diff_hunks_expanded()
15990 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15991 {
15992 buffer.collapse_diff_hunks(ranges, cx);
15993 true
15994 } else {
15995 false
15996 }
15997 })
15998 }
15999
16000 fn toggle_diff_hunks_in_ranges(
16001 &mut self,
16002 ranges: Vec<Range<Anchor>>,
16003 cx: &mut Context<Editor>,
16004 ) {
16005 self.buffer.update(cx, |buffer, cx| {
16006 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16007 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16008 })
16009 }
16010
16011 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16012 self.buffer.update(cx, |buffer, cx| {
16013 let snapshot = buffer.snapshot(cx);
16014 let excerpt_id = range.end.excerpt_id;
16015 let point_range = range.to_point(&snapshot);
16016 let expand = !buffer.single_hunk_is_expanded(range, cx);
16017 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16018 })
16019 }
16020
16021 pub(crate) fn apply_all_diff_hunks(
16022 &mut self,
16023 _: &ApplyAllDiffHunks,
16024 window: &mut Window,
16025 cx: &mut Context<Self>,
16026 ) {
16027 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16028
16029 let buffers = self.buffer.read(cx).all_buffers();
16030 for branch_buffer in buffers {
16031 branch_buffer.update(cx, |branch_buffer, cx| {
16032 branch_buffer.merge_into_base(Vec::new(), cx);
16033 });
16034 }
16035
16036 if let Some(project) = self.project.clone() {
16037 self.save(true, project, window, cx).detach_and_log_err(cx);
16038 }
16039 }
16040
16041 pub(crate) fn apply_selected_diff_hunks(
16042 &mut self,
16043 _: &ApplyDiffHunk,
16044 window: &mut Window,
16045 cx: &mut Context<Self>,
16046 ) {
16047 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16048 let snapshot = self.snapshot(window, cx);
16049 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16050 let mut ranges_by_buffer = HashMap::default();
16051 self.transact(window, cx, |editor, _window, cx| {
16052 for hunk in hunks {
16053 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16054 ranges_by_buffer
16055 .entry(buffer.clone())
16056 .or_insert_with(Vec::new)
16057 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16058 }
16059 }
16060
16061 for (buffer, ranges) in ranges_by_buffer {
16062 buffer.update(cx, |buffer, cx| {
16063 buffer.merge_into_base(ranges, cx);
16064 });
16065 }
16066 });
16067
16068 if let Some(project) = self.project.clone() {
16069 self.save(true, project, window, cx).detach_and_log_err(cx);
16070 }
16071 }
16072
16073 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16074 if hovered != self.gutter_hovered {
16075 self.gutter_hovered = hovered;
16076 cx.notify();
16077 }
16078 }
16079
16080 pub fn insert_blocks(
16081 &mut self,
16082 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16083 autoscroll: Option<Autoscroll>,
16084 cx: &mut Context<Self>,
16085 ) -> Vec<CustomBlockId> {
16086 let blocks = self
16087 .display_map
16088 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16089 if let Some(autoscroll) = autoscroll {
16090 self.request_autoscroll(autoscroll, cx);
16091 }
16092 cx.notify();
16093 blocks
16094 }
16095
16096 pub fn resize_blocks(
16097 &mut self,
16098 heights: HashMap<CustomBlockId, u32>,
16099 autoscroll: Option<Autoscroll>,
16100 cx: &mut Context<Self>,
16101 ) {
16102 self.display_map
16103 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16104 if let Some(autoscroll) = autoscroll {
16105 self.request_autoscroll(autoscroll, cx);
16106 }
16107 cx.notify();
16108 }
16109
16110 pub fn replace_blocks(
16111 &mut self,
16112 renderers: HashMap<CustomBlockId, RenderBlock>,
16113 autoscroll: Option<Autoscroll>,
16114 cx: &mut Context<Self>,
16115 ) {
16116 self.display_map
16117 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16118 if let Some(autoscroll) = autoscroll {
16119 self.request_autoscroll(autoscroll, cx);
16120 }
16121 cx.notify();
16122 }
16123
16124 pub fn remove_blocks(
16125 &mut self,
16126 block_ids: HashSet<CustomBlockId>,
16127 autoscroll: Option<Autoscroll>,
16128 cx: &mut Context<Self>,
16129 ) {
16130 self.display_map.update(cx, |display_map, cx| {
16131 display_map.remove_blocks(block_ids, cx)
16132 });
16133 if let Some(autoscroll) = autoscroll {
16134 self.request_autoscroll(autoscroll, cx);
16135 }
16136 cx.notify();
16137 }
16138
16139 pub fn row_for_block(
16140 &self,
16141 block_id: CustomBlockId,
16142 cx: &mut Context<Self>,
16143 ) -> Option<DisplayRow> {
16144 self.display_map
16145 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16146 }
16147
16148 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16149 self.focused_block = Some(focused_block);
16150 }
16151
16152 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16153 self.focused_block.take()
16154 }
16155
16156 pub fn insert_creases(
16157 &mut self,
16158 creases: impl IntoIterator<Item = Crease<Anchor>>,
16159 cx: &mut Context<Self>,
16160 ) -> Vec<CreaseId> {
16161 self.display_map
16162 .update(cx, |map, cx| map.insert_creases(creases, cx))
16163 }
16164
16165 pub fn remove_creases(
16166 &mut self,
16167 ids: impl IntoIterator<Item = CreaseId>,
16168 cx: &mut Context<Self>,
16169 ) {
16170 self.display_map
16171 .update(cx, |map, cx| map.remove_creases(ids, cx));
16172 }
16173
16174 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16175 self.display_map
16176 .update(cx, |map, cx| map.snapshot(cx))
16177 .longest_row()
16178 }
16179
16180 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16181 self.display_map
16182 .update(cx, |map, cx| map.snapshot(cx))
16183 .max_point()
16184 }
16185
16186 pub fn text(&self, cx: &App) -> String {
16187 self.buffer.read(cx).read(cx).text()
16188 }
16189
16190 pub fn is_empty(&self, cx: &App) -> bool {
16191 self.buffer.read(cx).read(cx).is_empty()
16192 }
16193
16194 pub fn text_option(&self, cx: &App) -> Option<String> {
16195 let text = self.text(cx);
16196 let text = text.trim();
16197
16198 if text.is_empty() {
16199 return None;
16200 }
16201
16202 Some(text.to_string())
16203 }
16204
16205 pub fn set_text(
16206 &mut self,
16207 text: impl Into<Arc<str>>,
16208 window: &mut Window,
16209 cx: &mut Context<Self>,
16210 ) {
16211 self.transact(window, cx, |this, _, cx| {
16212 this.buffer
16213 .read(cx)
16214 .as_singleton()
16215 .expect("you can only call set_text on editors for singleton buffers")
16216 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16217 });
16218 }
16219
16220 pub fn display_text(&self, cx: &mut App) -> String {
16221 self.display_map
16222 .update(cx, |map, cx| map.snapshot(cx))
16223 .text()
16224 }
16225
16226 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16227 let mut wrap_guides = smallvec::smallvec![];
16228
16229 if self.show_wrap_guides == Some(false) {
16230 return wrap_guides;
16231 }
16232
16233 let settings = self.buffer.read(cx).language_settings(cx);
16234 if settings.show_wrap_guides {
16235 match self.soft_wrap_mode(cx) {
16236 SoftWrap::Column(soft_wrap) => {
16237 wrap_guides.push((soft_wrap as usize, true));
16238 }
16239 SoftWrap::Bounded(soft_wrap) => {
16240 wrap_guides.push((soft_wrap as usize, true));
16241 }
16242 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16243 }
16244 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16245 }
16246
16247 wrap_guides
16248 }
16249
16250 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16251 let settings = self.buffer.read(cx).language_settings(cx);
16252 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16253 match mode {
16254 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16255 SoftWrap::None
16256 }
16257 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16258 language_settings::SoftWrap::PreferredLineLength => {
16259 SoftWrap::Column(settings.preferred_line_length)
16260 }
16261 language_settings::SoftWrap::Bounded => {
16262 SoftWrap::Bounded(settings.preferred_line_length)
16263 }
16264 }
16265 }
16266
16267 pub fn set_soft_wrap_mode(
16268 &mut self,
16269 mode: language_settings::SoftWrap,
16270
16271 cx: &mut Context<Self>,
16272 ) {
16273 self.soft_wrap_mode_override = Some(mode);
16274 cx.notify();
16275 }
16276
16277 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16278 self.hard_wrap = hard_wrap;
16279 cx.notify();
16280 }
16281
16282 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16283 self.text_style_refinement = Some(style);
16284 }
16285
16286 /// called by the Element so we know what style we were most recently rendered with.
16287 pub(crate) fn set_style(
16288 &mut self,
16289 style: EditorStyle,
16290 window: &mut Window,
16291 cx: &mut Context<Self>,
16292 ) {
16293 let rem_size = window.rem_size();
16294 self.display_map.update(cx, |map, cx| {
16295 map.set_font(
16296 style.text.font(),
16297 style.text.font_size.to_pixels(rem_size),
16298 cx,
16299 )
16300 });
16301 self.style = Some(style);
16302 }
16303
16304 pub fn style(&self) -> Option<&EditorStyle> {
16305 self.style.as_ref()
16306 }
16307
16308 // Called by the element. This method is not designed to be called outside of the editor
16309 // element's layout code because it does not notify when rewrapping is computed synchronously.
16310 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16311 self.display_map
16312 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16313 }
16314
16315 pub fn set_soft_wrap(&mut self) {
16316 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16317 }
16318
16319 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16320 if self.soft_wrap_mode_override.is_some() {
16321 self.soft_wrap_mode_override.take();
16322 } else {
16323 let soft_wrap = match self.soft_wrap_mode(cx) {
16324 SoftWrap::GitDiff => return,
16325 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16326 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16327 language_settings::SoftWrap::None
16328 }
16329 };
16330 self.soft_wrap_mode_override = Some(soft_wrap);
16331 }
16332 cx.notify();
16333 }
16334
16335 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16336 let Some(workspace) = self.workspace() else {
16337 return;
16338 };
16339 let fs = workspace.read(cx).app_state().fs.clone();
16340 let current_show = TabBarSettings::get_global(cx).show;
16341 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16342 setting.show = Some(!current_show);
16343 });
16344 }
16345
16346 pub fn toggle_indent_guides(
16347 &mut self,
16348 _: &ToggleIndentGuides,
16349 _: &mut Window,
16350 cx: &mut Context<Self>,
16351 ) {
16352 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16353 self.buffer
16354 .read(cx)
16355 .language_settings(cx)
16356 .indent_guides
16357 .enabled
16358 });
16359 self.show_indent_guides = Some(!currently_enabled);
16360 cx.notify();
16361 }
16362
16363 fn should_show_indent_guides(&self) -> Option<bool> {
16364 self.show_indent_guides
16365 }
16366
16367 pub fn toggle_line_numbers(
16368 &mut self,
16369 _: &ToggleLineNumbers,
16370 _: &mut Window,
16371 cx: &mut Context<Self>,
16372 ) {
16373 let mut editor_settings = EditorSettings::get_global(cx).clone();
16374 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16375 EditorSettings::override_global(editor_settings, cx);
16376 }
16377
16378 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16379 if let Some(show_line_numbers) = self.show_line_numbers {
16380 return show_line_numbers;
16381 }
16382 EditorSettings::get_global(cx).gutter.line_numbers
16383 }
16384
16385 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16386 self.use_relative_line_numbers
16387 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16388 }
16389
16390 pub fn toggle_relative_line_numbers(
16391 &mut self,
16392 _: &ToggleRelativeLineNumbers,
16393 _: &mut Window,
16394 cx: &mut Context<Self>,
16395 ) {
16396 let is_relative = self.should_use_relative_line_numbers(cx);
16397 self.set_relative_line_number(Some(!is_relative), cx)
16398 }
16399
16400 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16401 self.use_relative_line_numbers = is_relative;
16402 cx.notify();
16403 }
16404
16405 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16406 self.show_gutter = show_gutter;
16407 cx.notify();
16408 }
16409
16410 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16411 self.show_scrollbars = show_scrollbars;
16412 cx.notify();
16413 }
16414
16415 pub fn disable_scrolling(&mut self, cx: &mut Context<Self>) {
16416 self.disable_scrolling = true;
16417 cx.notify();
16418 }
16419
16420 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16421 self.show_line_numbers = Some(show_line_numbers);
16422 cx.notify();
16423 }
16424
16425 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16426 self.disable_expand_excerpt_buttons = true;
16427 cx.notify();
16428 }
16429
16430 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16431 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16432 cx.notify();
16433 }
16434
16435 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16436 self.show_code_actions = Some(show_code_actions);
16437 cx.notify();
16438 }
16439
16440 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16441 self.show_runnables = Some(show_runnables);
16442 cx.notify();
16443 }
16444
16445 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16446 self.show_breakpoints = Some(show_breakpoints);
16447 cx.notify();
16448 }
16449
16450 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16451 if self.display_map.read(cx).masked != masked {
16452 self.display_map.update(cx, |map, _| map.masked = masked);
16453 }
16454 cx.notify()
16455 }
16456
16457 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16458 self.show_wrap_guides = Some(show_wrap_guides);
16459 cx.notify();
16460 }
16461
16462 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16463 self.show_indent_guides = Some(show_indent_guides);
16464 cx.notify();
16465 }
16466
16467 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16468 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16469 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16470 if let Some(dir) = file.abs_path(cx).parent() {
16471 return Some(dir.to_owned());
16472 }
16473 }
16474
16475 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16476 return Some(project_path.path.to_path_buf());
16477 }
16478 }
16479
16480 None
16481 }
16482
16483 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16484 self.active_excerpt(cx)?
16485 .1
16486 .read(cx)
16487 .file()
16488 .and_then(|f| f.as_local())
16489 }
16490
16491 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16492 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16493 let buffer = buffer.read(cx);
16494 if let Some(project_path) = buffer.project_path(cx) {
16495 let project = self.project.as_ref()?.read(cx);
16496 project.absolute_path(&project_path, cx)
16497 } else {
16498 buffer
16499 .file()
16500 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16501 }
16502 })
16503 }
16504
16505 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16506 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16507 let project_path = buffer.read(cx).project_path(cx)?;
16508 let project = self.project.as_ref()?.read(cx);
16509 let entry = project.entry_for_path(&project_path, cx)?;
16510 let path = entry.path.to_path_buf();
16511 Some(path)
16512 })
16513 }
16514
16515 pub fn reveal_in_finder(
16516 &mut self,
16517 _: &RevealInFileManager,
16518 _window: &mut Window,
16519 cx: &mut Context<Self>,
16520 ) {
16521 if let Some(target) = self.target_file(cx) {
16522 cx.reveal_path(&target.abs_path(cx));
16523 }
16524 }
16525
16526 pub fn copy_path(
16527 &mut self,
16528 _: &zed_actions::workspace::CopyPath,
16529 _window: &mut Window,
16530 cx: &mut Context<Self>,
16531 ) {
16532 if let Some(path) = self.target_file_abs_path(cx) {
16533 if let Some(path) = path.to_str() {
16534 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16535 }
16536 }
16537 }
16538
16539 pub fn copy_relative_path(
16540 &mut self,
16541 _: &zed_actions::workspace::CopyRelativePath,
16542 _window: &mut Window,
16543 cx: &mut Context<Self>,
16544 ) {
16545 if let Some(path) = self.target_file_path(cx) {
16546 if let Some(path) = path.to_str() {
16547 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16548 }
16549 }
16550 }
16551
16552 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16553 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16554 buffer.read(cx).project_path(cx)
16555 } else {
16556 None
16557 }
16558 }
16559
16560 // Returns true if the editor handled a go-to-line request
16561 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16562 maybe!({
16563 let breakpoint_store = self.breakpoint_store.as_ref()?;
16564
16565 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
16566 else {
16567 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16568 return None;
16569 };
16570
16571 let position = active_stack_frame.position;
16572 let buffer_id = position.buffer_id?;
16573 let snapshot = self
16574 .project
16575 .as_ref()?
16576 .read(cx)
16577 .buffer_for_id(buffer_id, cx)?
16578 .read(cx)
16579 .snapshot();
16580
16581 let mut handled = false;
16582 for (id, ExcerptRange { context, .. }) in
16583 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
16584 {
16585 if context.start.cmp(&position, &snapshot).is_ge()
16586 || context.end.cmp(&position, &snapshot).is_lt()
16587 {
16588 continue;
16589 }
16590 let snapshot = self.buffer.read(cx).snapshot(cx);
16591 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
16592
16593 handled = true;
16594 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16595 self.go_to_line::<DebugCurrentRowHighlight>(
16596 multibuffer_anchor,
16597 Some(cx.theme().colors().editor_debugger_active_line_background),
16598 window,
16599 cx,
16600 );
16601
16602 cx.notify();
16603 }
16604
16605 handled.then_some(())
16606 })
16607 .is_some()
16608 }
16609
16610 pub fn copy_file_name_without_extension(
16611 &mut self,
16612 _: &CopyFileNameWithoutExtension,
16613 _: &mut Window,
16614 cx: &mut Context<Self>,
16615 ) {
16616 if let Some(file) = self.target_file(cx) {
16617 if let Some(file_stem) = file.path().file_stem() {
16618 if let Some(name) = file_stem.to_str() {
16619 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16620 }
16621 }
16622 }
16623 }
16624
16625 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16626 if let Some(file) = self.target_file(cx) {
16627 if let Some(file_name) = file.path().file_name() {
16628 if let Some(name) = file_name.to_str() {
16629 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16630 }
16631 }
16632 }
16633 }
16634
16635 pub fn toggle_git_blame(
16636 &mut self,
16637 _: &::git::Blame,
16638 window: &mut Window,
16639 cx: &mut Context<Self>,
16640 ) {
16641 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16642
16643 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16644 self.start_git_blame(true, window, cx);
16645 }
16646
16647 cx.notify();
16648 }
16649
16650 pub fn toggle_git_blame_inline(
16651 &mut self,
16652 _: &ToggleGitBlameInline,
16653 window: &mut Window,
16654 cx: &mut Context<Self>,
16655 ) {
16656 self.toggle_git_blame_inline_internal(true, window, cx);
16657 cx.notify();
16658 }
16659
16660 pub fn open_git_blame_commit(
16661 &mut self,
16662 _: &OpenGitBlameCommit,
16663 window: &mut Window,
16664 cx: &mut Context<Self>,
16665 ) {
16666 self.open_git_blame_commit_internal(window, cx);
16667 }
16668
16669 fn open_git_blame_commit_internal(
16670 &mut self,
16671 window: &mut Window,
16672 cx: &mut Context<Self>,
16673 ) -> Option<()> {
16674 let blame = self.blame.as_ref()?;
16675 let snapshot = self.snapshot(window, cx);
16676 let cursor = self.selections.newest::<Point>(cx).head();
16677 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16678 let blame_entry = blame
16679 .update(cx, |blame, cx| {
16680 blame
16681 .blame_for_rows(
16682 &[RowInfo {
16683 buffer_id: Some(buffer.remote_id()),
16684 buffer_row: Some(point.row),
16685 ..Default::default()
16686 }],
16687 cx,
16688 )
16689 .next()
16690 })
16691 .flatten()?;
16692 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16693 let repo = blame.read(cx).repository(cx)?;
16694 let workspace = self.workspace()?.downgrade();
16695 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16696 None
16697 }
16698
16699 pub fn git_blame_inline_enabled(&self) -> bool {
16700 self.git_blame_inline_enabled
16701 }
16702
16703 pub fn toggle_selection_menu(
16704 &mut self,
16705 _: &ToggleSelectionMenu,
16706 _: &mut Window,
16707 cx: &mut Context<Self>,
16708 ) {
16709 self.show_selection_menu = self
16710 .show_selection_menu
16711 .map(|show_selections_menu| !show_selections_menu)
16712 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16713
16714 cx.notify();
16715 }
16716
16717 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16718 self.show_selection_menu
16719 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16720 }
16721
16722 fn start_git_blame(
16723 &mut self,
16724 user_triggered: bool,
16725 window: &mut Window,
16726 cx: &mut Context<Self>,
16727 ) {
16728 if let Some(project) = self.project.as_ref() {
16729 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16730 return;
16731 };
16732
16733 if buffer.read(cx).file().is_none() {
16734 return;
16735 }
16736
16737 let focused = self.focus_handle(cx).contains_focused(window, cx);
16738
16739 let project = project.clone();
16740 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16741 self.blame_subscription =
16742 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16743 self.blame = Some(blame);
16744 }
16745 }
16746
16747 fn toggle_git_blame_inline_internal(
16748 &mut self,
16749 user_triggered: bool,
16750 window: &mut Window,
16751 cx: &mut Context<Self>,
16752 ) {
16753 if self.git_blame_inline_enabled {
16754 self.git_blame_inline_enabled = false;
16755 self.show_git_blame_inline = false;
16756 self.show_git_blame_inline_delay_task.take();
16757 } else {
16758 self.git_blame_inline_enabled = true;
16759 self.start_git_blame_inline(user_triggered, window, cx);
16760 }
16761
16762 cx.notify();
16763 }
16764
16765 fn start_git_blame_inline(
16766 &mut self,
16767 user_triggered: bool,
16768 window: &mut Window,
16769 cx: &mut Context<Self>,
16770 ) {
16771 self.start_git_blame(user_triggered, window, cx);
16772
16773 if ProjectSettings::get_global(cx)
16774 .git
16775 .inline_blame_delay()
16776 .is_some()
16777 {
16778 self.start_inline_blame_timer(window, cx);
16779 } else {
16780 self.show_git_blame_inline = true
16781 }
16782 }
16783
16784 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16785 self.blame.as_ref()
16786 }
16787
16788 pub fn show_git_blame_gutter(&self) -> bool {
16789 self.show_git_blame_gutter
16790 }
16791
16792 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16793 self.show_git_blame_gutter && self.has_blame_entries(cx)
16794 }
16795
16796 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16797 self.show_git_blame_inline
16798 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
16799 && !self.newest_selection_head_on_empty_line(cx)
16800 && self.has_blame_entries(cx)
16801 }
16802
16803 fn has_blame_entries(&self, cx: &App) -> bool {
16804 self.blame()
16805 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16806 }
16807
16808 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16809 let cursor_anchor = self.selections.newest_anchor().head();
16810
16811 let snapshot = self.buffer.read(cx).snapshot(cx);
16812 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16813
16814 snapshot.line_len(buffer_row) == 0
16815 }
16816
16817 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16818 let buffer_and_selection = maybe!({
16819 let selection = self.selections.newest::<Point>(cx);
16820 let selection_range = selection.range();
16821
16822 let multi_buffer = self.buffer().read(cx);
16823 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16824 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16825
16826 let (buffer, range, _) = if selection.reversed {
16827 buffer_ranges.first()
16828 } else {
16829 buffer_ranges.last()
16830 }?;
16831
16832 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16833 ..text::ToPoint::to_point(&range.end, &buffer).row;
16834 Some((
16835 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16836 selection,
16837 ))
16838 });
16839
16840 let Some((buffer, selection)) = buffer_and_selection else {
16841 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16842 };
16843
16844 let Some(project) = self.project.as_ref() else {
16845 return Task::ready(Err(anyhow!("editor does not have project")));
16846 };
16847
16848 project.update(cx, |project, cx| {
16849 project.get_permalink_to_line(&buffer, selection, cx)
16850 })
16851 }
16852
16853 pub fn copy_permalink_to_line(
16854 &mut self,
16855 _: &CopyPermalinkToLine,
16856 window: &mut Window,
16857 cx: &mut Context<Self>,
16858 ) {
16859 let permalink_task = self.get_permalink_to_line(cx);
16860 let workspace = self.workspace();
16861
16862 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16863 Ok(permalink) => {
16864 cx.update(|_, cx| {
16865 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16866 })
16867 .ok();
16868 }
16869 Err(err) => {
16870 let message = format!("Failed to copy permalink: {err}");
16871
16872 Err::<(), anyhow::Error>(err).log_err();
16873
16874 if let Some(workspace) = workspace {
16875 workspace
16876 .update_in(cx, |workspace, _, cx| {
16877 struct CopyPermalinkToLine;
16878
16879 workspace.show_toast(
16880 Toast::new(
16881 NotificationId::unique::<CopyPermalinkToLine>(),
16882 message,
16883 ),
16884 cx,
16885 )
16886 })
16887 .ok();
16888 }
16889 }
16890 })
16891 .detach();
16892 }
16893
16894 pub fn copy_file_location(
16895 &mut self,
16896 _: &CopyFileLocation,
16897 _: &mut Window,
16898 cx: &mut Context<Self>,
16899 ) {
16900 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16901 if let Some(file) = self.target_file(cx) {
16902 if let Some(path) = file.path().to_str() {
16903 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16904 }
16905 }
16906 }
16907
16908 pub fn open_permalink_to_line(
16909 &mut self,
16910 _: &OpenPermalinkToLine,
16911 window: &mut Window,
16912 cx: &mut Context<Self>,
16913 ) {
16914 let permalink_task = self.get_permalink_to_line(cx);
16915 let workspace = self.workspace();
16916
16917 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16918 Ok(permalink) => {
16919 cx.update(|_, cx| {
16920 cx.open_url(permalink.as_ref());
16921 })
16922 .ok();
16923 }
16924 Err(err) => {
16925 let message = format!("Failed to open permalink: {err}");
16926
16927 Err::<(), anyhow::Error>(err).log_err();
16928
16929 if let Some(workspace) = workspace {
16930 workspace
16931 .update(cx, |workspace, cx| {
16932 struct OpenPermalinkToLine;
16933
16934 workspace.show_toast(
16935 Toast::new(
16936 NotificationId::unique::<OpenPermalinkToLine>(),
16937 message,
16938 ),
16939 cx,
16940 )
16941 })
16942 .ok();
16943 }
16944 }
16945 })
16946 .detach();
16947 }
16948
16949 pub fn insert_uuid_v4(
16950 &mut self,
16951 _: &InsertUuidV4,
16952 window: &mut Window,
16953 cx: &mut Context<Self>,
16954 ) {
16955 self.insert_uuid(UuidVersion::V4, window, cx);
16956 }
16957
16958 pub fn insert_uuid_v7(
16959 &mut self,
16960 _: &InsertUuidV7,
16961 window: &mut Window,
16962 cx: &mut Context<Self>,
16963 ) {
16964 self.insert_uuid(UuidVersion::V7, window, cx);
16965 }
16966
16967 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16968 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16969 self.transact(window, cx, |this, window, cx| {
16970 let edits = this
16971 .selections
16972 .all::<Point>(cx)
16973 .into_iter()
16974 .map(|selection| {
16975 let uuid = match version {
16976 UuidVersion::V4 => uuid::Uuid::new_v4(),
16977 UuidVersion::V7 => uuid::Uuid::now_v7(),
16978 };
16979
16980 (selection.range(), uuid.to_string())
16981 });
16982 this.edit(edits, cx);
16983 this.refresh_inline_completion(true, false, window, cx);
16984 });
16985 }
16986
16987 pub fn open_selections_in_multibuffer(
16988 &mut self,
16989 _: &OpenSelectionsInMultibuffer,
16990 window: &mut Window,
16991 cx: &mut Context<Self>,
16992 ) {
16993 let multibuffer = self.buffer.read(cx);
16994
16995 let Some(buffer) = multibuffer.as_singleton() else {
16996 return;
16997 };
16998
16999 let Some(workspace) = self.workspace() else {
17000 return;
17001 };
17002
17003 let locations = self
17004 .selections
17005 .disjoint_anchors()
17006 .iter()
17007 .map(|range| Location {
17008 buffer: buffer.clone(),
17009 range: range.start.text_anchor..range.end.text_anchor,
17010 })
17011 .collect::<Vec<_>>();
17012
17013 let title = multibuffer.title(cx).to_string();
17014
17015 cx.spawn_in(window, async move |_, cx| {
17016 workspace.update_in(cx, |workspace, window, cx| {
17017 Self::open_locations_in_multibuffer(
17018 workspace,
17019 locations,
17020 format!("Selections for '{title}'"),
17021 false,
17022 MultibufferSelectionMode::All,
17023 window,
17024 cx,
17025 );
17026 })
17027 })
17028 .detach();
17029 }
17030
17031 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17032 /// last highlight added will be used.
17033 ///
17034 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17035 pub fn highlight_rows<T: 'static>(
17036 &mut self,
17037 range: Range<Anchor>,
17038 color: Hsla,
17039 options: RowHighlightOptions,
17040 cx: &mut Context<Self>,
17041 ) {
17042 let snapshot = self.buffer().read(cx).snapshot(cx);
17043 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17044 let ix = row_highlights.binary_search_by(|highlight| {
17045 Ordering::Equal
17046 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17047 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17048 });
17049
17050 if let Err(mut ix) = ix {
17051 let index = post_inc(&mut self.highlight_order);
17052
17053 // If this range intersects with the preceding highlight, then merge it with
17054 // the preceding highlight. Otherwise insert a new highlight.
17055 let mut merged = false;
17056 if ix > 0 {
17057 let prev_highlight = &mut row_highlights[ix - 1];
17058 if prev_highlight
17059 .range
17060 .end
17061 .cmp(&range.start, &snapshot)
17062 .is_ge()
17063 {
17064 ix -= 1;
17065 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17066 prev_highlight.range.end = range.end;
17067 }
17068 merged = true;
17069 prev_highlight.index = index;
17070 prev_highlight.color = color;
17071 prev_highlight.options = options;
17072 }
17073 }
17074
17075 if !merged {
17076 row_highlights.insert(
17077 ix,
17078 RowHighlight {
17079 range: range.clone(),
17080 index,
17081 color,
17082 options,
17083 type_id: TypeId::of::<T>(),
17084 },
17085 );
17086 }
17087
17088 // If any of the following highlights intersect with this one, merge them.
17089 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17090 let highlight = &row_highlights[ix];
17091 if next_highlight
17092 .range
17093 .start
17094 .cmp(&highlight.range.end, &snapshot)
17095 .is_le()
17096 {
17097 if next_highlight
17098 .range
17099 .end
17100 .cmp(&highlight.range.end, &snapshot)
17101 .is_gt()
17102 {
17103 row_highlights[ix].range.end = next_highlight.range.end;
17104 }
17105 row_highlights.remove(ix + 1);
17106 } else {
17107 break;
17108 }
17109 }
17110 }
17111 }
17112
17113 /// Remove any highlighted row ranges of the given type that intersect the
17114 /// given ranges.
17115 pub fn remove_highlighted_rows<T: 'static>(
17116 &mut self,
17117 ranges_to_remove: Vec<Range<Anchor>>,
17118 cx: &mut Context<Self>,
17119 ) {
17120 let snapshot = self.buffer().read(cx).snapshot(cx);
17121 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17122 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17123 row_highlights.retain(|highlight| {
17124 while let Some(range_to_remove) = ranges_to_remove.peek() {
17125 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17126 Ordering::Less | Ordering::Equal => {
17127 ranges_to_remove.next();
17128 }
17129 Ordering::Greater => {
17130 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17131 Ordering::Less | Ordering::Equal => {
17132 return false;
17133 }
17134 Ordering::Greater => break,
17135 }
17136 }
17137 }
17138 }
17139
17140 true
17141 })
17142 }
17143
17144 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17145 pub fn clear_row_highlights<T: 'static>(&mut self) {
17146 self.highlighted_rows.remove(&TypeId::of::<T>());
17147 }
17148
17149 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17150 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17151 self.highlighted_rows
17152 .get(&TypeId::of::<T>())
17153 .map_or(&[] as &[_], |vec| vec.as_slice())
17154 .iter()
17155 .map(|highlight| (highlight.range.clone(), highlight.color))
17156 }
17157
17158 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17159 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17160 /// Allows to ignore certain kinds of highlights.
17161 pub fn highlighted_display_rows(
17162 &self,
17163 window: &mut Window,
17164 cx: &mut App,
17165 ) -> BTreeMap<DisplayRow, LineHighlight> {
17166 let snapshot = self.snapshot(window, cx);
17167 let mut used_highlight_orders = HashMap::default();
17168 self.highlighted_rows
17169 .iter()
17170 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17171 .fold(
17172 BTreeMap::<DisplayRow, LineHighlight>::new(),
17173 |mut unique_rows, highlight| {
17174 let start = highlight.range.start.to_display_point(&snapshot);
17175 let end = highlight.range.end.to_display_point(&snapshot);
17176 let start_row = start.row().0;
17177 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17178 && end.column() == 0
17179 {
17180 end.row().0.saturating_sub(1)
17181 } else {
17182 end.row().0
17183 };
17184 for row in start_row..=end_row {
17185 let used_index =
17186 used_highlight_orders.entry(row).or_insert(highlight.index);
17187 if highlight.index >= *used_index {
17188 *used_index = highlight.index;
17189 unique_rows.insert(
17190 DisplayRow(row),
17191 LineHighlight {
17192 include_gutter: highlight.options.include_gutter,
17193 border: None,
17194 background: highlight.color.into(),
17195 type_id: Some(highlight.type_id),
17196 },
17197 );
17198 }
17199 }
17200 unique_rows
17201 },
17202 )
17203 }
17204
17205 pub fn highlighted_display_row_for_autoscroll(
17206 &self,
17207 snapshot: &DisplaySnapshot,
17208 ) -> Option<DisplayRow> {
17209 self.highlighted_rows
17210 .values()
17211 .flat_map(|highlighted_rows| highlighted_rows.iter())
17212 .filter_map(|highlight| {
17213 if highlight.options.autoscroll {
17214 Some(highlight.range.start.to_display_point(snapshot).row())
17215 } else {
17216 None
17217 }
17218 })
17219 .min()
17220 }
17221
17222 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17223 self.highlight_background::<SearchWithinRange>(
17224 ranges,
17225 |colors| colors.editor_document_highlight_read_background,
17226 cx,
17227 )
17228 }
17229
17230 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17231 self.breadcrumb_header = Some(new_header);
17232 }
17233
17234 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17235 self.clear_background_highlights::<SearchWithinRange>(cx);
17236 }
17237
17238 pub fn highlight_background<T: 'static>(
17239 &mut self,
17240 ranges: &[Range<Anchor>],
17241 color_fetcher: fn(&ThemeColors) -> Hsla,
17242 cx: &mut Context<Self>,
17243 ) {
17244 self.background_highlights
17245 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17246 self.scrollbar_marker_state.dirty = true;
17247 cx.notify();
17248 }
17249
17250 pub fn clear_background_highlights<T: 'static>(
17251 &mut self,
17252 cx: &mut Context<Self>,
17253 ) -> Option<BackgroundHighlight> {
17254 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17255 if !text_highlights.1.is_empty() {
17256 self.scrollbar_marker_state.dirty = true;
17257 cx.notify();
17258 }
17259 Some(text_highlights)
17260 }
17261
17262 pub fn highlight_gutter<T: 'static>(
17263 &mut self,
17264 ranges: &[Range<Anchor>],
17265 color_fetcher: fn(&App) -> Hsla,
17266 cx: &mut Context<Self>,
17267 ) {
17268 self.gutter_highlights
17269 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17270 cx.notify();
17271 }
17272
17273 pub fn clear_gutter_highlights<T: 'static>(
17274 &mut self,
17275 cx: &mut Context<Self>,
17276 ) -> Option<GutterHighlight> {
17277 cx.notify();
17278 self.gutter_highlights.remove(&TypeId::of::<T>())
17279 }
17280
17281 #[cfg(feature = "test-support")]
17282 pub fn all_text_background_highlights(
17283 &self,
17284 window: &mut Window,
17285 cx: &mut Context<Self>,
17286 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17287 let snapshot = self.snapshot(window, cx);
17288 let buffer = &snapshot.buffer_snapshot;
17289 let start = buffer.anchor_before(0);
17290 let end = buffer.anchor_after(buffer.len());
17291 let theme = cx.theme().colors();
17292 self.background_highlights_in_range(start..end, &snapshot, theme)
17293 }
17294
17295 #[cfg(feature = "test-support")]
17296 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17297 let snapshot = self.buffer().read(cx).snapshot(cx);
17298
17299 let highlights = self
17300 .background_highlights
17301 .get(&TypeId::of::<items::BufferSearchHighlights>());
17302
17303 if let Some((_color, ranges)) = highlights {
17304 ranges
17305 .iter()
17306 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17307 .collect_vec()
17308 } else {
17309 vec![]
17310 }
17311 }
17312
17313 fn document_highlights_for_position<'a>(
17314 &'a self,
17315 position: Anchor,
17316 buffer: &'a MultiBufferSnapshot,
17317 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17318 let read_highlights = self
17319 .background_highlights
17320 .get(&TypeId::of::<DocumentHighlightRead>())
17321 .map(|h| &h.1);
17322 let write_highlights = self
17323 .background_highlights
17324 .get(&TypeId::of::<DocumentHighlightWrite>())
17325 .map(|h| &h.1);
17326 let left_position = position.bias_left(buffer);
17327 let right_position = position.bias_right(buffer);
17328 read_highlights
17329 .into_iter()
17330 .chain(write_highlights)
17331 .flat_map(move |ranges| {
17332 let start_ix = match ranges.binary_search_by(|probe| {
17333 let cmp = probe.end.cmp(&left_position, buffer);
17334 if cmp.is_ge() {
17335 Ordering::Greater
17336 } else {
17337 Ordering::Less
17338 }
17339 }) {
17340 Ok(i) | Err(i) => i,
17341 };
17342
17343 ranges[start_ix..]
17344 .iter()
17345 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17346 })
17347 }
17348
17349 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17350 self.background_highlights
17351 .get(&TypeId::of::<T>())
17352 .map_or(false, |(_, highlights)| !highlights.is_empty())
17353 }
17354
17355 pub fn background_highlights_in_range(
17356 &self,
17357 search_range: Range<Anchor>,
17358 display_snapshot: &DisplaySnapshot,
17359 theme: &ThemeColors,
17360 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17361 let mut results = Vec::new();
17362 for (color_fetcher, ranges) in self.background_highlights.values() {
17363 let color = color_fetcher(theme);
17364 let start_ix = match ranges.binary_search_by(|probe| {
17365 let cmp = probe
17366 .end
17367 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17368 if cmp.is_gt() {
17369 Ordering::Greater
17370 } else {
17371 Ordering::Less
17372 }
17373 }) {
17374 Ok(i) | Err(i) => i,
17375 };
17376 for range in &ranges[start_ix..] {
17377 if range
17378 .start
17379 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17380 .is_ge()
17381 {
17382 break;
17383 }
17384
17385 let start = range.start.to_display_point(display_snapshot);
17386 let end = range.end.to_display_point(display_snapshot);
17387 results.push((start..end, color))
17388 }
17389 }
17390 results
17391 }
17392
17393 pub fn background_highlight_row_ranges<T: 'static>(
17394 &self,
17395 search_range: Range<Anchor>,
17396 display_snapshot: &DisplaySnapshot,
17397 count: usize,
17398 ) -> Vec<RangeInclusive<DisplayPoint>> {
17399 let mut results = Vec::new();
17400 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17401 return vec![];
17402 };
17403
17404 let start_ix = match ranges.binary_search_by(|probe| {
17405 let cmp = probe
17406 .end
17407 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17408 if cmp.is_gt() {
17409 Ordering::Greater
17410 } else {
17411 Ordering::Less
17412 }
17413 }) {
17414 Ok(i) | Err(i) => i,
17415 };
17416 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17417 if let (Some(start_display), Some(end_display)) = (start, end) {
17418 results.push(
17419 start_display.to_display_point(display_snapshot)
17420 ..=end_display.to_display_point(display_snapshot),
17421 );
17422 }
17423 };
17424 let mut start_row: Option<Point> = None;
17425 let mut end_row: Option<Point> = None;
17426 if ranges.len() > count {
17427 return Vec::new();
17428 }
17429 for range in &ranges[start_ix..] {
17430 if range
17431 .start
17432 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17433 .is_ge()
17434 {
17435 break;
17436 }
17437 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17438 if let Some(current_row) = &end_row {
17439 if end.row == current_row.row {
17440 continue;
17441 }
17442 }
17443 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17444 if start_row.is_none() {
17445 assert_eq!(end_row, None);
17446 start_row = Some(start);
17447 end_row = Some(end);
17448 continue;
17449 }
17450 if let Some(current_end) = end_row.as_mut() {
17451 if start.row > current_end.row + 1 {
17452 push_region(start_row, end_row);
17453 start_row = Some(start);
17454 end_row = Some(end);
17455 } else {
17456 // Merge two hunks.
17457 *current_end = end;
17458 }
17459 } else {
17460 unreachable!();
17461 }
17462 }
17463 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17464 push_region(start_row, end_row);
17465 results
17466 }
17467
17468 pub fn gutter_highlights_in_range(
17469 &self,
17470 search_range: Range<Anchor>,
17471 display_snapshot: &DisplaySnapshot,
17472 cx: &App,
17473 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17474 let mut results = Vec::new();
17475 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17476 let color = color_fetcher(cx);
17477 let start_ix = match ranges.binary_search_by(|probe| {
17478 let cmp = probe
17479 .end
17480 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17481 if cmp.is_gt() {
17482 Ordering::Greater
17483 } else {
17484 Ordering::Less
17485 }
17486 }) {
17487 Ok(i) | Err(i) => i,
17488 };
17489 for range in &ranges[start_ix..] {
17490 if range
17491 .start
17492 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17493 .is_ge()
17494 {
17495 break;
17496 }
17497
17498 let start = range.start.to_display_point(display_snapshot);
17499 let end = range.end.to_display_point(display_snapshot);
17500 results.push((start..end, color))
17501 }
17502 }
17503 results
17504 }
17505
17506 /// Get the text ranges corresponding to the redaction query
17507 pub fn redacted_ranges(
17508 &self,
17509 search_range: Range<Anchor>,
17510 display_snapshot: &DisplaySnapshot,
17511 cx: &App,
17512 ) -> Vec<Range<DisplayPoint>> {
17513 display_snapshot
17514 .buffer_snapshot
17515 .redacted_ranges(search_range, |file| {
17516 if let Some(file) = file {
17517 file.is_private()
17518 && EditorSettings::get(
17519 Some(SettingsLocation {
17520 worktree_id: file.worktree_id(cx),
17521 path: file.path().as_ref(),
17522 }),
17523 cx,
17524 )
17525 .redact_private_values
17526 } else {
17527 false
17528 }
17529 })
17530 .map(|range| {
17531 range.start.to_display_point(display_snapshot)
17532 ..range.end.to_display_point(display_snapshot)
17533 })
17534 .collect()
17535 }
17536
17537 pub fn highlight_text<T: 'static>(
17538 &mut self,
17539 ranges: Vec<Range<Anchor>>,
17540 style: HighlightStyle,
17541 cx: &mut Context<Self>,
17542 ) {
17543 self.display_map.update(cx, |map, _| {
17544 map.highlight_text(TypeId::of::<T>(), ranges, style)
17545 });
17546 cx.notify();
17547 }
17548
17549 pub(crate) fn highlight_inlays<T: 'static>(
17550 &mut self,
17551 highlights: Vec<InlayHighlight>,
17552 style: HighlightStyle,
17553 cx: &mut Context<Self>,
17554 ) {
17555 self.display_map.update(cx, |map, _| {
17556 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17557 });
17558 cx.notify();
17559 }
17560
17561 pub fn text_highlights<'a, T: 'static>(
17562 &'a self,
17563 cx: &'a App,
17564 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17565 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17566 }
17567
17568 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17569 let cleared = self
17570 .display_map
17571 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17572 if cleared {
17573 cx.notify();
17574 }
17575 }
17576
17577 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17578 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17579 && self.focus_handle.is_focused(window)
17580 }
17581
17582 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17583 self.show_cursor_when_unfocused = is_enabled;
17584 cx.notify();
17585 }
17586
17587 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17588 cx.notify();
17589 }
17590
17591 fn on_debug_session_event(
17592 &mut self,
17593 _session: Entity<Session>,
17594 event: &SessionEvent,
17595 cx: &mut Context<Self>,
17596 ) {
17597 match event {
17598 SessionEvent::InvalidateInlineValue => {
17599 self.refresh_inline_values(cx);
17600 }
17601 _ => {}
17602 }
17603 }
17604
17605 fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
17606 let Some(project) = self.project.clone() else {
17607 return;
17608 };
17609 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
17610 return;
17611 };
17612 if !self.inline_value_cache.enabled {
17613 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
17614 self.splice_inlays(&inlays, Vec::new(), cx);
17615 return;
17616 }
17617
17618 let current_execution_position = self
17619 .highlighted_rows
17620 .get(&TypeId::of::<DebugCurrentRowHighlight>())
17621 .and_then(|lines| lines.last().map(|line| line.range.start));
17622
17623 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
17624 let snapshot = editor
17625 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17626 .ok()?;
17627
17628 let inline_values = editor
17629 .update(cx, |_, cx| {
17630 let Some(current_execution_position) = current_execution_position else {
17631 return Some(Task::ready(Ok(Vec::new())));
17632 };
17633
17634 // todo(debugger) when introducing multi buffer inline values check execution position's buffer id to make sure the text
17635 // anchor is in the same buffer
17636 let range =
17637 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
17638 project.inline_values(buffer, range, cx)
17639 })
17640 .ok()
17641 .flatten()?
17642 .await
17643 .context("refreshing debugger inlays")
17644 .log_err()?;
17645
17646 let (excerpt_id, buffer_id) = snapshot
17647 .excerpts()
17648 .next()
17649 .map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?;
17650 editor
17651 .update(cx, |editor, cx| {
17652 let new_inlays = inline_values
17653 .into_iter()
17654 .map(|debugger_value| {
17655 Inlay::debugger_hint(
17656 post_inc(&mut editor.next_inlay_id),
17657 Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position),
17658 debugger_value.text(),
17659 )
17660 })
17661 .collect::<Vec<_>>();
17662 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
17663 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
17664
17665 editor.splice_inlays(&inlay_ids, new_inlays, cx);
17666 })
17667 .ok()?;
17668 Some(())
17669 });
17670 }
17671
17672 fn on_buffer_event(
17673 &mut self,
17674 multibuffer: &Entity<MultiBuffer>,
17675 event: &multi_buffer::Event,
17676 window: &mut Window,
17677 cx: &mut Context<Self>,
17678 ) {
17679 match event {
17680 multi_buffer::Event::Edited {
17681 singleton_buffer_edited,
17682 edited_buffer: buffer_edited,
17683 } => {
17684 self.scrollbar_marker_state.dirty = true;
17685 self.active_indent_guides_state.dirty = true;
17686 self.refresh_active_diagnostics(cx);
17687 self.refresh_code_actions(window, cx);
17688 if self.has_active_inline_completion() {
17689 self.update_visible_inline_completion(window, cx);
17690 }
17691 if let Some(buffer) = buffer_edited {
17692 let buffer_id = buffer.read(cx).remote_id();
17693 if !self.registered_buffers.contains_key(&buffer_id) {
17694 if let Some(project) = self.project.as_ref() {
17695 project.update(cx, |project, cx| {
17696 self.registered_buffers.insert(
17697 buffer_id,
17698 project.register_buffer_with_language_servers(&buffer, cx),
17699 );
17700 })
17701 }
17702 }
17703 }
17704 cx.emit(EditorEvent::BufferEdited);
17705 cx.emit(SearchEvent::MatchesInvalidated);
17706 if *singleton_buffer_edited {
17707 if let Some(project) = &self.project {
17708 #[allow(clippy::mutable_key_type)]
17709 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17710 multibuffer
17711 .all_buffers()
17712 .into_iter()
17713 .filter_map(|buffer| {
17714 buffer.update(cx, |buffer, cx| {
17715 let language = buffer.language()?;
17716 let should_discard = project.update(cx, |project, cx| {
17717 project.is_local()
17718 && !project.has_language_servers_for(buffer, cx)
17719 });
17720 should_discard.not().then_some(language.clone())
17721 })
17722 })
17723 .collect::<HashSet<_>>()
17724 });
17725 if !languages_affected.is_empty() {
17726 self.refresh_inlay_hints(
17727 InlayHintRefreshReason::BufferEdited(languages_affected),
17728 cx,
17729 );
17730 }
17731 }
17732 }
17733
17734 let Some(project) = &self.project else { return };
17735 let (telemetry, is_via_ssh) = {
17736 let project = project.read(cx);
17737 let telemetry = project.client().telemetry().clone();
17738 let is_via_ssh = project.is_via_ssh();
17739 (telemetry, is_via_ssh)
17740 };
17741 refresh_linked_ranges(self, window, cx);
17742 telemetry.log_edit_event("editor", is_via_ssh);
17743 }
17744 multi_buffer::Event::ExcerptsAdded {
17745 buffer,
17746 predecessor,
17747 excerpts,
17748 } => {
17749 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17750 let buffer_id = buffer.read(cx).remote_id();
17751 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17752 if let Some(project) = &self.project {
17753 get_uncommitted_diff_for_buffer(
17754 project,
17755 [buffer.clone()],
17756 self.buffer.clone(),
17757 cx,
17758 )
17759 .detach();
17760 }
17761 }
17762 cx.emit(EditorEvent::ExcerptsAdded {
17763 buffer: buffer.clone(),
17764 predecessor: *predecessor,
17765 excerpts: excerpts.clone(),
17766 });
17767 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17768 }
17769 multi_buffer::Event::ExcerptsRemoved {
17770 ids,
17771 removed_buffer_ids,
17772 } => {
17773 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17774 let buffer = self.buffer.read(cx);
17775 self.registered_buffers
17776 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17777 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17778 cx.emit(EditorEvent::ExcerptsRemoved {
17779 ids: ids.clone(),
17780 removed_buffer_ids: removed_buffer_ids.clone(),
17781 })
17782 }
17783 multi_buffer::Event::ExcerptsEdited {
17784 excerpt_ids,
17785 buffer_ids,
17786 } => {
17787 self.display_map.update(cx, |map, cx| {
17788 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17789 });
17790 cx.emit(EditorEvent::ExcerptsEdited {
17791 ids: excerpt_ids.clone(),
17792 })
17793 }
17794 multi_buffer::Event::ExcerptsExpanded { ids } => {
17795 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17796 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17797 }
17798 multi_buffer::Event::Reparsed(buffer_id) => {
17799 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17800 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17801
17802 cx.emit(EditorEvent::Reparsed(*buffer_id));
17803 }
17804 multi_buffer::Event::DiffHunksToggled => {
17805 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17806 }
17807 multi_buffer::Event::LanguageChanged(buffer_id) => {
17808 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17809 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17810 cx.emit(EditorEvent::Reparsed(*buffer_id));
17811 cx.notify();
17812 }
17813 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17814 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17815 multi_buffer::Event::FileHandleChanged
17816 | multi_buffer::Event::Reloaded
17817 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17818 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17819 multi_buffer::Event::DiagnosticsUpdated => {
17820 self.refresh_active_diagnostics(cx);
17821 self.refresh_inline_diagnostics(true, window, cx);
17822 self.scrollbar_marker_state.dirty = true;
17823 cx.notify();
17824 }
17825 _ => {}
17826 };
17827 }
17828
17829 fn on_display_map_changed(
17830 &mut self,
17831 _: Entity<DisplayMap>,
17832 _: &mut Window,
17833 cx: &mut Context<Self>,
17834 ) {
17835 cx.notify();
17836 }
17837
17838 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17839 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17840 self.update_edit_prediction_settings(cx);
17841 self.refresh_inline_completion(true, false, window, cx);
17842 self.refresh_inlay_hints(
17843 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17844 self.selections.newest_anchor().head(),
17845 &self.buffer.read(cx).snapshot(cx),
17846 cx,
17847 )),
17848 cx,
17849 );
17850
17851 let old_cursor_shape = self.cursor_shape;
17852
17853 {
17854 let editor_settings = EditorSettings::get_global(cx);
17855 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17856 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17857 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17858 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17859 }
17860
17861 if old_cursor_shape != self.cursor_shape {
17862 cx.emit(EditorEvent::CursorShapeChanged);
17863 }
17864
17865 let project_settings = ProjectSettings::get_global(cx);
17866 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17867
17868 if self.mode.is_full() {
17869 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17870 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17871 if self.show_inline_diagnostics != show_inline_diagnostics {
17872 self.show_inline_diagnostics = show_inline_diagnostics;
17873 self.refresh_inline_diagnostics(false, window, cx);
17874 }
17875
17876 if self.git_blame_inline_enabled != inline_blame_enabled {
17877 self.toggle_git_blame_inline_internal(false, window, cx);
17878 }
17879 }
17880
17881 cx.notify();
17882 }
17883
17884 pub fn set_searchable(&mut self, searchable: bool) {
17885 self.searchable = searchable;
17886 }
17887
17888 pub fn searchable(&self) -> bool {
17889 self.searchable
17890 }
17891
17892 fn open_proposed_changes_editor(
17893 &mut self,
17894 _: &OpenProposedChangesEditor,
17895 window: &mut Window,
17896 cx: &mut Context<Self>,
17897 ) {
17898 let Some(workspace) = self.workspace() else {
17899 cx.propagate();
17900 return;
17901 };
17902
17903 let selections = self.selections.all::<usize>(cx);
17904 let multi_buffer = self.buffer.read(cx);
17905 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17906 let mut new_selections_by_buffer = HashMap::default();
17907 for selection in selections {
17908 for (buffer, range, _) in
17909 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17910 {
17911 let mut range = range.to_point(buffer);
17912 range.start.column = 0;
17913 range.end.column = buffer.line_len(range.end.row);
17914 new_selections_by_buffer
17915 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17916 .or_insert(Vec::new())
17917 .push(range)
17918 }
17919 }
17920
17921 let proposed_changes_buffers = new_selections_by_buffer
17922 .into_iter()
17923 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17924 .collect::<Vec<_>>();
17925 let proposed_changes_editor = cx.new(|cx| {
17926 ProposedChangesEditor::new(
17927 "Proposed changes",
17928 proposed_changes_buffers,
17929 self.project.clone(),
17930 window,
17931 cx,
17932 )
17933 });
17934
17935 window.defer(cx, move |window, cx| {
17936 workspace.update(cx, |workspace, cx| {
17937 workspace.active_pane().update(cx, |pane, cx| {
17938 pane.add_item(
17939 Box::new(proposed_changes_editor),
17940 true,
17941 true,
17942 None,
17943 window,
17944 cx,
17945 );
17946 });
17947 });
17948 });
17949 }
17950
17951 pub fn open_excerpts_in_split(
17952 &mut self,
17953 _: &OpenExcerptsSplit,
17954 window: &mut Window,
17955 cx: &mut Context<Self>,
17956 ) {
17957 self.open_excerpts_common(None, true, window, cx)
17958 }
17959
17960 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17961 self.open_excerpts_common(None, false, window, cx)
17962 }
17963
17964 fn open_excerpts_common(
17965 &mut self,
17966 jump_data: Option<JumpData>,
17967 split: bool,
17968 window: &mut Window,
17969 cx: &mut Context<Self>,
17970 ) {
17971 let Some(workspace) = self.workspace() else {
17972 cx.propagate();
17973 return;
17974 };
17975
17976 if self.buffer.read(cx).is_singleton() {
17977 cx.propagate();
17978 return;
17979 }
17980
17981 let mut new_selections_by_buffer = HashMap::default();
17982 match &jump_data {
17983 Some(JumpData::MultiBufferPoint {
17984 excerpt_id,
17985 position,
17986 anchor,
17987 line_offset_from_top,
17988 }) => {
17989 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17990 if let Some(buffer) = multi_buffer_snapshot
17991 .buffer_id_for_excerpt(*excerpt_id)
17992 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17993 {
17994 let buffer_snapshot = buffer.read(cx).snapshot();
17995 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17996 language::ToPoint::to_point(anchor, &buffer_snapshot)
17997 } else {
17998 buffer_snapshot.clip_point(*position, Bias::Left)
17999 };
18000 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18001 new_selections_by_buffer.insert(
18002 buffer,
18003 (
18004 vec![jump_to_offset..jump_to_offset],
18005 Some(*line_offset_from_top),
18006 ),
18007 );
18008 }
18009 }
18010 Some(JumpData::MultiBufferRow {
18011 row,
18012 line_offset_from_top,
18013 }) => {
18014 let point = MultiBufferPoint::new(row.0, 0);
18015 if let Some((buffer, buffer_point, _)) =
18016 self.buffer.read(cx).point_to_buffer_point(point, cx)
18017 {
18018 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18019 new_selections_by_buffer
18020 .entry(buffer)
18021 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18022 .0
18023 .push(buffer_offset..buffer_offset)
18024 }
18025 }
18026 None => {
18027 let selections = self.selections.all::<usize>(cx);
18028 let multi_buffer = self.buffer.read(cx);
18029 for selection in selections {
18030 for (snapshot, range, _, anchor) in multi_buffer
18031 .snapshot(cx)
18032 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18033 {
18034 if let Some(anchor) = anchor {
18035 // selection is in a deleted hunk
18036 let Some(buffer_id) = anchor.buffer_id else {
18037 continue;
18038 };
18039 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18040 continue;
18041 };
18042 let offset = text::ToOffset::to_offset(
18043 &anchor.text_anchor,
18044 &buffer_handle.read(cx).snapshot(),
18045 );
18046 let range = offset..offset;
18047 new_selections_by_buffer
18048 .entry(buffer_handle)
18049 .or_insert((Vec::new(), None))
18050 .0
18051 .push(range)
18052 } else {
18053 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18054 else {
18055 continue;
18056 };
18057 new_selections_by_buffer
18058 .entry(buffer_handle)
18059 .or_insert((Vec::new(), None))
18060 .0
18061 .push(range)
18062 }
18063 }
18064 }
18065 }
18066 }
18067
18068 new_selections_by_buffer
18069 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18070
18071 if new_selections_by_buffer.is_empty() {
18072 return;
18073 }
18074
18075 // We defer the pane interaction because we ourselves are a workspace item
18076 // and activating a new item causes the pane to call a method on us reentrantly,
18077 // which panics if we're on the stack.
18078 window.defer(cx, move |window, cx| {
18079 workspace.update(cx, |workspace, cx| {
18080 let pane = if split {
18081 workspace.adjacent_pane(window, cx)
18082 } else {
18083 workspace.active_pane().clone()
18084 };
18085
18086 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18087 let editor = buffer
18088 .read(cx)
18089 .file()
18090 .is_none()
18091 .then(|| {
18092 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18093 // so `workspace.open_project_item` will never find them, always opening a new editor.
18094 // Instead, we try to activate the existing editor in the pane first.
18095 let (editor, pane_item_index) =
18096 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18097 let editor = item.downcast::<Editor>()?;
18098 let singleton_buffer =
18099 editor.read(cx).buffer().read(cx).as_singleton()?;
18100 if singleton_buffer == buffer {
18101 Some((editor, i))
18102 } else {
18103 None
18104 }
18105 })?;
18106 pane.update(cx, |pane, cx| {
18107 pane.activate_item(pane_item_index, true, true, window, cx)
18108 });
18109 Some(editor)
18110 })
18111 .flatten()
18112 .unwrap_or_else(|| {
18113 workspace.open_project_item::<Self>(
18114 pane.clone(),
18115 buffer,
18116 true,
18117 true,
18118 window,
18119 cx,
18120 )
18121 });
18122
18123 editor.update(cx, |editor, cx| {
18124 let autoscroll = match scroll_offset {
18125 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18126 None => Autoscroll::newest(),
18127 };
18128 let nav_history = editor.nav_history.take();
18129 editor.change_selections(Some(autoscroll), window, cx, |s| {
18130 s.select_ranges(ranges);
18131 });
18132 editor.nav_history = nav_history;
18133 });
18134 }
18135 })
18136 });
18137 }
18138
18139 // For now, don't allow opening excerpts in buffers that aren't backed by
18140 // regular project files.
18141 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18142 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18143 }
18144
18145 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18146 let snapshot = self.buffer.read(cx).read(cx);
18147 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18148 Some(
18149 ranges
18150 .iter()
18151 .map(move |range| {
18152 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18153 })
18154 .collect(),
18155 )
18156 }
18157
18158 fn selection_replacement_ranges(
18159 &self,
18160 range: Range<OffsetUtf16>,
18161 cx: &mut App,
18162 ) -> Vec<Range<OffsetUtf16>> {
18163 let selections = self.selections.all::<OffsetUtf16>(cx);
18164 let newest_selection = selections
18165 .iter()
18166 .max_by_key(|selection| selection.id)
18167 .unwrap();
18168 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18169 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18170 let snapshot = self.buffer.read(cx).read(cx);
18171 selections
18172 .into_iter()
18173 .map(|mut selection| {
18174 selection.start.0 =
18175 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18176 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18177 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18178 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18179 })
18180 .collect()
18181 }
18182
18183 fn report_editor_event(
18184 &self,
18185 event_type: &'static str,
18186 file_extension: Option<String>,
18187 cx: &App,
18188 ) {
18189 if cfg!(any(test, feature = "test-support")) {
18190 return;
18191 }
18192
18193 let Some(project) = &self.project else { return };
18194
18195 // If None, we are in a file without an extension
18196 let file = self
18197 .buffer
18198 .read(cx)
18199 .as_singleton()
18200 .and_then(|b| b.read(cx).file());
18201 let file_extension = file_extension.or(file
18202 .as_ref()
18203 .and_then(|file| Path::new(file.file_name(cx)).extension())
18204 .and_then(|e| e.to_str())
18205 .map(|a| a.to_string()));
18206
18207 let vim_mode = vim_enabled(cx);
18208
18209 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18210 let copilot_enabled = edit_predictions_provider
18211 == language::language_settings::EditPredictionProvider::Copilot;
18212 let copilot_enabled_for_language = self
18213 .buffer
18214 .read(cx)
18215 .language_settings(cx)
18216 .show_edit_predictions;
18217
18218 let project = project.read(cx);
18219 telemetry::event!(
18220 event_type,
18221 file_extension,
18222 vim_mode,
18223 copilot_enabled,
18224 copilot_enabled_for_language,
18225 edit_predictions_provider,
18226 is_via_ssh = project.is_via_ssh(),
18227 );
18228 }
18229
18230 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18231 /// with each line being an array of {text, highlight} objects.
18232 fn copy_highlight_json(
18233 &mut self,
18234 _: &CopyHighlightJson,
18235 window: &mut Window,
18236 cx: &mut Context<Self>,
18237 ) {
18238 #[derive(Serialize)]
18239 struct Chunk<'a> {
18240 text: String,
18241 highlight: Option<&'a str>,
18242 }
18243
18244 let snapshot = self.buffer.read(cx).snapshot(cx);
18245 let range = self
18246 .selected_text_range(false, window, cx)
18247 .and_then(|selection| {
18248 if selection.range.is_empty() {
18249 None
18250 } else {
18251 Some(selection.range)
18252 }
18253 })
18254 .unwrap_or_else(|| 0..snapshot.len());
18255
18256 let chunks = snapshot.chunks(range, true);
18257 let mut lines = Vec::new();
18258 let mut line: VecDeque<Chunk> = VecDeque::new();
18259
18260 let Some(style) = self.style.as_ref() else {
18261 return;
18262 };
18263
18264 for chunk in chunks {
18265 let highlight = chunk
18266 .syntax_highlight_id
18267 .and_then(|id| id.name(&style.syntax));
18268 let mut chunk_lines = chunk.text.split('\n').peekable();
18269 while let Some(text) = chunk_lines.next() {
18270 let mut merged_with_last_token = false;
18271 if let Some(last_token) = line.back_mut() {
18272 if last_token.highlight == highlight {
18273 last_token.text.push_str(text);
18274 merged_with_last_token = true;
18275 }
18276 }
18277
18278 if !merged_with_last_token {
18279 line.push_back(Chunk {
18280 text: text.into(),
18281 highlight,
18282 });
18283 }
18284
18285 if chunk_lines.peek().is_some() {
18286 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18287 line.pop_front();
18288 }
18289 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18290 line.pop_back();
18291 }
18292
18293 lines.push(mem::take(&mut line));
18294 }
18295 }
18296 }
18297
18298 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18299 return;
18300 };
18301 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18302 }
18303
18304 pub fn open_context_menu(
18305 &mut self,
18306 _: &OpenContextMenu,
18307 window: &mut Window,
18308 cx: &mut Context<Self>,
18309 ) {
18310 self.request_autoscroll(Autoscroll::newest(), cx);
18311 let position = self.selections.newest_display(cx).start;
18312 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18313 }
18314
18315 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18316 &self.inlay_hint_cache
18317 }
18318
18319 pub fn replay_insert_event(
18320 &mut self,
18321 text: &str,
18322 relative_utf16_range: Option<Range<isize>>,
18323 window: &mut Window,
18324 cx: &mut Context<Self>,
18325 ) {
18326 if !self.input_enabled {
18327 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18328 return;
18329 }
18330 if let Some(relative_utf16_range) = relative_utf16_range {
18331 let selections = self.selections.all::<OffsetUtf16>(cx);
18332 self.change_selections(None, window, cx, |s| {
18333 let new_ranges = selections.into_iter().map(|range| {
18334 let start = OffsetUtf16(
18335 range
18336 .head()
18337 .0
18338 .saturating_add_signed(relative_utf16_range.start),
18339 );
18340 let end = OffsetUtf16(
18341 range
18342 .head()
18343 .0
18344 .saturating_add_signed(relative_utf16_range.end),
18345 );
18346 start..end
18347 });
18348 s.select_ranges(new_ranges);
18349 });
18350 }
18351
18352 self.handle_input(text, window, cx);
18353 }
18354
18355 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18356 let Some(provider) = self.semantics_provider.as_ref() else {
18357 return false;
18358 };
18359
18360 let mut supports = false;
18361 self.buffer().update(cx, |this, cx| {
18362 this.for_each_buffer(|buffer| {
18363 supports |= provider.supports_inlay_hints(buffer, cx);
18364 });
18365 });
18366
18367 supports
18368 }
18369
18370 pub fn is_focused(&self, window: &Window) -> bool {
18371 self.focus_handle.is_focused(window)
18372 }
18373
18374 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18375 cx.emit(EditorEvent::Focused);
18376
18377 if let Some(descendant) = self
18378 .last_focused_descendant
18379 .take()
18380 .and_then(|descendant| descendant.upgrade())
18381 {
18382 window.focus(&descendant);
18383 } else {
18384 if let Some(blame) = self.blame.as_ref() {
18385 blame.update(cx, GitBlame::focus)
18386 }
18387
18388 self.blink_manager.update(cx, BlinkManager::enable);
18389 self.show_cursor_names(window, cx);
18390 self.buffer.update(cx, |buffer, cx| {
18391 buffer.finalize_last_transaction(cx);
18392 if self.leader_peer_id.is_none() {
18393 buffer.set_active_selections(
18394 &self.selections.disjoint_anchors(),
18395 self.selections.line_mode,
18396 self.cursor_shape,
18397 cx,
18398 );
18399 }
18400 });
18401 }
18402 }
18403
18404 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18405 cx.emit(EditorEvent::FocusedIn)
18406 }
18407
18408 fn handle_focus_out(
18409 &mut self,
18410 event: FocusOutEvent,
18411 _window: &mut Window,
18412 cx: &mut Context<Self>,
18413 ) {
18414 if event.blurred != self.focus_handle {
18415 self.last_focused_descendant = Some(event.blurred);
18416 }
18417 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18418 }
18419
18420 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18421 self.blink_manager.update(cx, BlinkManager::disable);
18422 self.buffer
18423 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18424
18425 if let Some(blame) = self.blame.as_ref() {
18426 blame.update(cx, GitBlame::blur)
18427 }
18428 if !self.hover_state.focused(window, cx) {
18429 hide_hover(self, cx);
18430 }
18431 if !self
18432 .context_menu
18433 .borrow()
18434 .as_ref()
18435 .is_some_and(|context_menu| context_menu.focused(window, cx))
18436 {
18437 self.hide_context_menu(window, cx);
18438 }
18439 self.discard_inline_completion(false, cx);
18440 cx.emit(EditorEvent::Blurred);
18441 cx.notify();
18442 }
18443
18444 pub fn register_action<A: Action>(
18445 &mut self,
18446 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18447 ) -> Subscription {
18448 let id = self.next_editor_action_id.post_inc();
18449 let listener = Arc::new(listener);
18450 self.editor_actions.borrow_mut().insert(
18451 id,
18452 Box::new(move |window, _| {
18453 let listener = listener.clone();
18454 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18455 let action = action.downcast_ref().unwrap();
18456 if phase == DispatchPhase::Bubble {
18457 listener(action, window, cx)
18458 }
18459 })
18460 }),
18461 );
18462
18463 let editor_actions = self.editor_actions.clone();
18464 Subscription::new(move || {
18465 editor_actions.borrow_mut().remove(&id);
18466 })
18467 }
18468
18469 pub fn file_header_size(&self) -> u32 {
18470 FILE_HEADER_HEIGHT
18471 }
18472
18473 pub fn restore(
18474 &mut self,
18475 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18476 window: &mut Window,
18477 cx: &mut Context<Self>,
18478 ) {
18479 let workspace = self.workspace();
18480 let project = self.project.as_ref();
18481 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18482 let mut tasks = Vec::new();
18483 for (buffer_id, changes) in revert_changes {
18484 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18485 buffer.update(cx, |buffer, cx| {
18486 buffer.edit(
18487 changes
18488 .into_iter()
18489 .map(|(range, text)| (range, text.to_string())),
18490 None,
18491 cx,
18492 );
18493 });
18494
18495 if let Some(project) =
18496 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18497 {
18498 project.update(cx, |project, cx| {
18499 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18500 })
18501 }
18502 }
18503 }
18504 tasks
18505 });
18506 cx.spawn_in(window, async move |_, cx| {
18507 for (buffer, task) in save_tasks {
18508 let result = task.await;
18509 if result.is_err() {
18510 let Some(path) = buffer
18511 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18512 .ok()
18513 else {
18514 continue;
18515 };
18516 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18517 let Some(task) = cx
18518 .update_window_entity(&workspace, |workspace, window, cx| {
18519 workspace
18520 .open_path_preview(path, None, false, false, false, window, cx)
18521 })
18522 .ok()
18523 else {
18524 continue;
18525 };
18526 task.await.log_err();
18527 }
18528 }
18529 }
18530 })
18531 .detach();
18532 self.change_selections(None, window, cx, |selections| selections.refresh());
18533 }
18534
18535 pub fn to_pixel_point(
18536 &self,
18537 source: multi_buffer::Anchor,
18538 editor_snapshot: &EditorSnapshot,
18539 window: &mut Window,
18540 ) -> Option<gpui::Point<Pixels>> {
18541 let source_point = source.to_display_point(editor_snapshot);
18542 self.display_to_pixel_point(source_point, editor_snapshot, window)
18543 }
18544
18545 pub fn display_to_pixel_point(
18546 &self,
18547 source: DisplayPoint,
18548 editor_snapshot: &EditorSnapshot,
18549 window: &mut Window,
18550 ) -> Option<gpui::Point<Pixels>> {
18551 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18552 let text_layout_details = self.text_layout_details(window);
18553 let scroll_top = text_layout_details
18554 .scroll_anchor
18555 .scroll_position(editor_snapshot)
18556 .y;
18557
18558 if source.row().as_f32() < scroll_top.floor() {
18559 return None;
18560 }
18561 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18562 let source_y = line_height * (source.row().as_f32() - scroll_top);
18563 Some(gpui::Point::new(source_x, source_y))
18564 }
18565
18566 pub fn has_visible_completions_menu(&self) -> bool {
18567 !self.edit_prediction_preview_is_active()
18568 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18569 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18570 })
18571 }
18572
18573 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18574 self.addons
18575 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18576 }
18577
18578 pub fn unregister_addon<T: Addon>(&mut self) {
18579 self.addons.remove(&std::any::TypeId::of::<T>());
18580 }
18581
18582 pub fn addon<T: Addon>(&self) -> Option<&T> {
18583 let type_id = std::any::TypeId::of::<T>();
18584 self.addons
18585 .get(&type_id)
18586 .and_then(|item| item.to_any().downcast_ref::<T>())
18587 }
18588
18589 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
18590 let type_id = std::any::TypeId::of::<T>();
18591 self.addons
18592 .get_mut(&type_id)
18593 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
18594 }
18595
18596 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18597 let text_layout_details = self.text_layout_details(window);
18598 let style = &text_layout_details.editor_style;
18599 let font_id = window.text_system().resolve_font(&style.text.font());
18600 let font_size = style.text.font_size.to_pixels(window.rem_size());
18601 let line_height = style.text.line_height_in_pixels(window.rem_size());
18602 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18603
18604 gpui::Size::new(em_width, line_height)
18605 }
18606
18607 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18608 self.load_diff_task.clone()
18609 }
18610
18611 fn read_metadata_from_db(
18612 &mut self,
18613 item_id: u64,
18614 workspace_id: WorkspaceId,
18615 window: &mut Window,
18616 cx: &mut Context<Editor>,
18617 ) {
18618 if self.is_singleton(cx)
18619 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18620 {
18621 let buffer_snapshot = OnceCell::new();
18622
18623 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18624 if !folds.is_empty() {
18625 let snapshot =
18626 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18627 self.fold_ranges(
18628 folds
18629 .into_iter()
18630 .map(|(start, end)| {
18631 snapshot.clip_offset(start, Bias::Left)
18632 ..snapshot.clip_offset(end, Bias::Right)
18633 })
18634 .collect(),
18635 false,
18636 window,
18637 cx,
18638 );
18639 }
18640 }
18641
18642 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18643 if !selections.is_empty() {
18644 let snapshot =
18645 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18646 self.change_selections(None, window, cx, |s| {
18647 s.select_ranges(selections.into_iter().map(|(start, end)| {
18648 snapshot.clip_offset(start, Bias::Left)
18649 ..snapshot.clip_offset(end, Bias::Right)
18650 }));
18651 });
18652 }
18653 };
18654 }
18655
18656 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18657 }
18658}
18659
18660fn vim_enabled(cx: &App) -> bool {
18661 cx.global::<SettingsStore>()
18662 .raw_user_settings()
18663 .get("vim_mode")
18664 == Some(&serde_json::Value::Bool(true))
18665}
18666
18667// Consider user intent and default settings
18668fn choose_completion_range(
18669 completion: &Completion,
18670 intent: CompletionIntent,
18671 buffer: &Entity<Buffer>,
18672 cx: &mut Context<Editor>,
18673) -> Range<usize> {
18674 fn should_replace(
18675 completion: &Completion,
18676 insert_range: &Range<text::Anchor>,
18677 intent: CompletionIntent,
18678 completion_mode_setting: LspInsertMode,
18679 buffer: &Buffer,
18680 ) -> bool {
18681 // specific actions take precedence over settings
18682 match intent {
18683 CompletionIntent::CompleteWithInsert => return false,
18684 CompletionIntent::CompleteWithReplace => return true,
18685 CompletionIntent::Complete | CompletionIntent::Compose => {}
18686 }
18687
18688 match completion_mode_setting {
18689 LspInsertMode::Insert => false,
18690 LspInsertMode::Replace => true,
18691 LspInsertMode::ReplaceSubsequence => {
18692 let mut text_to_replace = buffer.chars_for_range(
18693 buffer.anchor_before(completion.replace_range.start)
18694 ..buffer.anchor_after(completion.replace_range.end),
18695 );
18696 let mut completion_text = completion.new_text.chars();
18697
18698 // is `text_to_replace` a subsequence of `completion_text`
18699 text_to_replace
18700 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18701 }
18702 LspInsertMode::ReplaceSuffix => {
18703 let range_after_cursor = insert_range.end..completion.replace_range.end;
18704
18705 let text_after_cursor = buffer
18706 .text_for_range(
18707 buffer.anchor_before(range_after_cursor.start)
18708 ..buffer.anchor_after(range_after_cursor.end),
18709 )
18710 .collect::<String>();
18711 completion.new_text.ends_with(&text_after_cursor)
18712 }
18713 }
18714 }
18715
18716 let buffer = buffer.read(cx);
18717
18718 if let CompletionSource::Lsp {
18719 insert_range: Some(insert_range),
18720 ..
18721 } = &completion.source
18722 {
18723 let completion_mode_setting =
18724 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18725 .completions
18726 .lsp_insert_mode;
18727
18728 if !should_replace(
18729 completion,
18730 &insert_range,
18731 intent,
18732 completion_mode_setting,
18733 buffer,
18734 ) {
18735 return insert_range.to_offset(buffer);
18736 }
18737 }
18738
18739 completion.replace_range.to_offset(buffer)
18740}
18741
18742fn insert_extra_newline_brackets(
18743 buffer: &MultiBufferSnapshot,
18744 range: Range<usize>,
18745 language: &language::LanguageScope,
18746) -> bool {
18747 let leading_whitespace_len = buffer
18748 .reversed_chars_at(range.start)
18749 .take_while(|c| c.is_whitespace() && *c != '\n')
18750 .map(|c| c.len_utf8())
18751 .sum::<usize>();
18752 let trailing_whitespace_len = buffer
18753 .chars_at(range.end)
18754 .take_while(|c| c.is_whitespace() && *c != '\n')
18755 .map(|c| c.len_utf8())
18756 .sum::<usize>();
18757 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18758
18759 language.brackets().any(|(pair, enabled)| {
18760 let pair_start = pair.start.trim_end();
18761 let pair_end = pair.end.trim_start();
18762
18763 enabled
18764 && pair.newline
18765 && buffer.contains_str_at(range.end, pair_end)
18766 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18767 })
18768}
18769
18770fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18771 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18772 [(buffer, range, _)] => (*buffer, range.clone()),
18773 _ => return false,
18774 };
18775 let pair = {
18776 let mut result: Option<BracketMatch> = None;
18777
18778 for pair in buffer
18779 .all_bracket_ranges(range.clone())
18780 .filter(move |pair| {
18781 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18782 })
18783 {
18784 let len = pair.close_range.end - pair.open_range.start;
18785
18786 if let Some(existing) = &result {
18787 let existing_len = existing.close_range.end - existing.open_range.start;
18788 if len > existing_len {
18789 continue;
18790 }
18791 }
18792
18793 result = Some(pair);
18794 }
18795
18796 result
18797 };
18798 let Some(pair) = pair else {
18799 return false;
18800 };
18801 pair.newline_only
18802 && buffer
18803 .chars_for_range(pair.open_range.end..range.start)
18804 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18805 .all(|c| c.is_whitespace() && c != '\n')
18806}
18807
18808fn get_uncommitted_diff_for_buffer(
18809 project: &Entity<Project>,
18810 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18811 buffer: Entity<MultiBuffer>,
18812 cx: &mut App,
18813) -> Task<()> {
18814 let mut tasks = Vec::new();
18815 project.update(cx, |project, cx| {
18816 for buffer in buffers {
18817 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18818 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18819 }
18820 }
18821 });
18822 cx.spawn(async move |cx| {
18823 let diffs = future::join_all(tasks).await;
18824 buffer
18825 .update(cx, |buffer, cx| {
18826 for diff in diffs.into_iter().flatten() {
18827 buffer.add_diff(diff, cx);
18828 }
18829 })
18830 .ok();
18831 })
18832}
18833
18834fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18835 let tab_size = tab_size.get() as usize;
18836 let mut width = offset;
18837
18838 for ch in text.chars() {
18839 width += if ch == '\t' {
18840 tab_size - (width % tab_size)
18841 } else {
18842 1
18843 };
18844 }
18845
18846 width - offset
18847}
18848
18849#[cfg(test)]
18850mod tests {
18851 use super::*;
18852
18853 #[test]
18854 fn test_string_size_with_expanded_tabs() {
18855 let nz = |val| NonZeroU32::new(val).unwrap();
18856 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18857 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18858 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18859 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18860 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18861 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18862 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18863 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18864 }
18865}
18866
18867/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18868struct WordBreakingTokenizer<'a> {
18869 input: &'a str,
18870}
18871
18872impl<'a> WordBreakingTokenizer<'a> {
18873 fn new(input: &'a str) -> Self {
18874 Self { input }
18875 }
18876}
18877
18878fn is_char_ideographic(ch: char) -> bool {
18879 use unicode_script::Script::*;
18880 use unicode_script::UnicodeScript;
18881 matches!(ch.script(), Han | Tangut | Yi)
18882}
18883
18884fn is_grapheme_ideographic(text: &str) -> bool {
18885 text.chars().any(is_char_ideographic)
18886}
18887
18888fn is_grapheme_whitespace(text: &str) -> bool {
18889 text.chars().any(|x| x.is_whitespace())
18890}
18891
18892fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18893 text.chars().next().map_or(false, |ch| {
18894 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18895 })
18896}
18897
18898#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18899enum WordBreakToken<'a> {
18900 Word { token: &'a str, grapheme_len: usize },
18901 InlineWhitespace { token: &'a str, grapheme_len: usize },
18902 Newline,
18903}
18904
18905impl<'a> Iterator for WordBreakingTokenizer<'a> {
18906 /// Yields a span, the count of graphemes in the token, and whether it was
18907 /// whitespace. Note that it also breaks at word boundaries.
18908 type Item = WordBreakToken<'a>;
18909
18910 fn next(&mut self) -> Option<Self::Item> {
18911 use unicode_segmentation::UnicodeSegmentation;
18912 if self.input.is_empty() {
18913 return None;
18914 }
18915
18916 let mut iter = self.input.graphemes(true).peekable();
18917 let mut offset = 0;
18918 let mut grapheme_len = 0;
18919 if let Some(first_grapheme) = iter.next() {
18920 let is_newline = first_grapheme == "\n";
18921 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18922 offset += first_grapheme.len();
18923 grapheme_len += 1;
18924 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18925 if let Some(grapheme) = iter.peek().copied() {
18926 if should_stay_with_preceding_ideograph(grapheme) {
18927 offset += grapheme.len();
18928 grapheme_len += 1;
18929 }
18930 }
18931 } else {
18932 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18933 let mut next_word_bound = words.peek().copied();
18934 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18935 next_word_bound = words.next();
18936 }
18937 while let Some(grapheme) = iter.peek().copied() {
18938 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18939 break;
18940 };
18941 if is_grapheme_whitespace(grapheme) != is_whitespace
18942 || (grapheme == "\n") != is_newline
18943 {
18944 break;
18945 };
18946 offset += grapheme.len();
18947 grapheme_len += 1;
18948 iter.next();
18949 }
18950 }
18951 let token = &self.input[..offset];
18952 self.input = &self.input[offset..];
18953 if token == "\n" {
18954 Some(WordBreakToken::Newline)
18955 } else if is_whitespace {
18956 Some(WordBreakToken::InlineWhitespace {
18957 token,
18958 grapheme_len,
18959 })
18960 } else {
18961 Some(WordBreakToken::Word {
18962 token,
18963 grapheme_len,
18964 })
18965 }
18966 } else {
18967 None
18968 }
18969 }
18970}
18971
18972#[test]
18973fn test_word_breaking_tokenizer() {
18974 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18975 ("", &[]),
18976 (" ", &[whitespace(" ", 2)]),
18977 ("Ʒ", &[word("Ʒ", 1)]),
18978 ("Ǽ", &[word("Ǽ", 1)]),
18979 ("⋑", &[word("⋑", 1)]),
18980 ("⋑⋑", &[word("⋑⋑", 2)]),
18981 (
18982 "原理,进而",
18983 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18984 ),
18985 (
18986 "hello world",
18987 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18988 ),
18989 (
18990 "hello, world",
18991 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18992 ),
18993 (
18994 " hello world",
18995 &[
18996 whitespace(" ", 2),
18997 word("hello", 5),
18998 whitespace(" ", 1),
18999 word("world", 5),
19000 ],
19001 ),
19002 (
19003 "这是什么 \n 钢笔",
19004 &[
19005 word("这", 1),
19006 word("是", 1),
19007 word("什", 1),
19008 word("么", 1),
19009 whitespace(" ", 1),
19010 newline(),
19011 whitespace(" ", 1),
19012 word("钢", 1),
19013 word("笔", 1),
19014 ],
19015 ),
19016 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19017 ];
19018
19019 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19020 WordBreakToken::Word {
19021 token,
19022 grapheme_len,
19023 }
19024 }
19025
19026 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19027 WordBreakToken::InlineWhitespace {
19028 token,
19029 grapheme_len,
19030 }
19031 }
19032
19033 fn newline() -> WordBreakToken<'static> {
19034 WordBreakToken::Newline
19035 }
19036
19037 for (input, result) in tests {
19038 assert_eq!(
19039 WordBreakingTokenizer::new(input)
19040 .collect::<Vec<_>>()
19041 .as_slice(),
19042 *result,
19043 );
19044 }
19045}
19046
19047fn wrap_with_prefix(
19048 line_prefix: String,
19049 unwrapped_text: String,
19050 wrap_column: usize,
19051 tab_size: NonZeroU32,
19052 preserve_existing_whitespace: bool,
19053) -> String {
19054 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19055 let mut wrapped_text = String::new();
19056 let mut current_line = line_prefix.clone();
19057
19058 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19059 let mut current_line_len = line_prefix_len;
19060 let mut in_whitespace = false;
19061 for token in tokenizer {
19062 let have_preceding_whitespace = in_whitespace;
19063 match token {
19064 WordBreakToken::Word {
19065 token,
19066 grapheme_len,
19067 } => {
19068 in_whitespace = false;
19069 if current_line_len + grapheme_len > wrap_column
19070 && current_line_len != line_prefix_len
19071 {
19072 wrapped_text.push_str(current_line.trim_end());
19073 wrapped_text.push('\n');
19074 current_line.truncate(line_prefix.len());
19075 current_line_len = line_prefix_len;
19076 }
19077 current_line.push_str(token);
19078 current_line_len += grapheme_len;
19079 }
19080 WordBreakToken::InlineWhitespace {
19081 mut token,
19082 mut grapheme_len,
19083 } => {
19084 in_whitespace = true;
19085 if have_preceding_whitespace && !preserve_existing_whitespace {
19086 continue;
19087 }
19088 if !preserve_existing_whitespace {
19089 token = " ";
19090 grapheme_len = 1;
19091 }
19092 if current_line_len + grapheme_len > wrap_column {
19093 wrapped_text.push_str(current_line.trim_end());
19094 wrapped_text.push('\n');
19095 current_line.truncate(line_prefix.len());
19096 current_line_len = line_prefix_len;
19097 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19098 current_line.push_str(token);
19099 current_line_len += grapheme_len;
19100 }
19101 }
19102 WordBreakToken::Newline => {
19103 in_whitespace = true;
19104 if preserve_existing_whitespace {
19105 wrapped_text.push_str(current_line.trim_end());
19106 wrapped_text.push('\n');
19107 current_line.truncate(line_prefix.len());
19108 current_line_len = line_prefix_len;
19109 } else if have_preceding_whitespace {
19110 continue;
19111 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19112 {
19113 wrapped_text.push_str(current_line.trim_end());
19114 wrapped_text.push('\n');
19115 current_line.truncate(line_prefix.len());
19116 current_line_len = line_prefix_len;
19117 } else if current_line_len != line_prefix_len {
19118 current_line.push(' ');
19119 current_line_len += 1;
19120 }
19121 }
19122 }
19123 }
19124
19125 if !current_line.is_empty() {
19126 wrapped_text.push_str(¤t_line);
19127 }
19128 wrapped_text
19129}
19130
19131#[test]
19132fn test_wrap_with_prefix() {
19133 assert_eq!(
19134 wrap_with_prefix(
19135 "# ".to_string(),
19136 "abcdefg".to_string(),
19137 4,
19138 NonZeroU32::new(4).unwrap(),
19139 false,
19140 ),
19141 "# abcdefg"
19142 );
19143 assert_eq!(
19144 wrap_with_prefix(
19145 "".to_string(),
19146 "\thello world".to_string(),
19147 8,
19148 NonZeroU32::new(4).unwrap(),
19149 false,
19150 ),
19151 "hello\nworld"
19152 );
19153 assert_eq!(
19154 wrap_with_prefix(
19155 "// ".to_string(),
19156 "xx \nyy zz aa bb cc".to_string(),
19157 12,
19158 NonZeroU32::new(4).unwrap(),
19159 false,
19160 ),
19161 "// xx yy zz\n// aa bb cc"
19162 );
19163 assert_eq!(
19164 wrap_with_prefix(
19165 String::new(),
19166 "这是什么 \n 钢笔".to_string(),
19167 3,
19168 NonZeroU32::new(4).unwrap(),
19169 false,
19170 ),
19171 "这是什\n么 钢\n笔"
19172 );
19173}
19174
19175pub trait CollaborationHub {
19176 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19177 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19178 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19179}
19180
19181impl CollaborationHub for Entity<Project> {
19182 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19183 self.read(cx).collaborators()
19184 }
19185
19186 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19187 self.read(cx).user_store().read(cx).participant_indices()
19188 }
19189
19190 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19191 let this = self.read(cx);
19192 let user_ids = this.collaborators().values().map(|c| c.user_id);
19193 this.user_store().read_with(cx, |user_store, cx| {
19194 user_store.participant_names(user_ids, cx)
19195 })
19196 }
19197}
19198
19199pub trait SemanticsProvider {
19200 fn hover(
19201 &self,
19202 buffer: &Entity<Buffer>,
19203 position: text::Anchor,
19204 cx: &mut App,
19205 ) -> Option<Task<Vec<project::Hover>>>;
19206
19207 fn inline_values(
19208 &self,
19209 buffer_handle: Entity<Buffer>,
19210 range: Range<text::Anchor>,
19211 cx: &mut App,
19212 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19213
19214 fn inlay_hints(
19215 &self,
19216 buffer_handle: Entity<Buffer>,
19217 range: Range<text::Anchor>,
19218 cx: &mut App,
19219 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19220
19221 fn resolve_inlay_hint(
19222 &self,
19223 hint: InlayHint,
19224 buffer_handle: Entity<Buffer>,
19225 server_id: LanguageServerId,
19226 cx: &mut App,
19227 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19228
19229 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19230
19231 fn document_highlights(
19232 &self,
19233 buffer: &Entity<Buffer>,
19234 position: text::Anchor,
19235 cx: &mut App,
19236 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19237
19238 fn definitions(
19239 &self,
19240 buffer: &Entity<Buffer>,
19241 position: text::Anchor,
19242 kind: GotoDefinitionKind,
19243 cx: &mut App,
19244 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19245
19246 fn range_for_rename(
19247 &self,
19248 buffer: &Entity<Buffer>,
19249 position: text::Anchor,
19250 cx: &mut App,
19251 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19252
19253 fn perform_rename(
19254 &self,
19255 buffer: &Entity<Buffer>,
19256 position: text::Anchor,
19257 new_name: String,
19258 cx: &mut App,
19259 ) -> Option<Task<Result<ProjectTransaction>>>;
19260}
19261
19262pub trait CompletionProvider {
19263 fn completions(
19264 &self,
19265 excerpt_id: ExcerptId,
19266 buffer: &Entity<Buffer>,
19267 buffer_position: text::Anchor,
19268 trigger: CompletionContext,
19269 window: &mut Window,
19270 cx: &mut Context<Editor>,
19271 ) -> Task<Result<Option<Vec<Completion>>>>;
19272
19273 fn resolve_completions(
19274 &self,
19275 buffer: Entity<Buffer>,
19276 completion_indices: Vec<usize>,
19277 completions: Rc<RefCell<Box<[Completion]>>>,
19278 cx: &mut Context<Editor>,
19279 ) -> Task<Result<bool>>;
19280
19281 fn apply_additional_edits_for_completion(
19282 &self,
19283 _buffer: Entity<Buffer>,
19284 _completions: Rc<RefCell<Box<[Completion]>>>,
19285 _completion_index: usize,
19286 _push_to_history: bool,
19287 _cx: &mut Context<Editor>,
19288 ) -> Task<Result<Option<language::Transaction>>> {
19289 Task::ready(Ok(None))
19290 }
19291
19292 fn is_completion_trigger(
19293 &self,
19294 buffer: &Entity<Buffer>,
19295 position: language::Anchor,
19296 text: &str,
19297 trigger_in_words: bool,
19298 cx: &mut Context<Editor>,
19299 ) -> bool;
19300
19301 fn sort_completions(&self) -> bool {
19302 true
19303 }
19304
19305 fn filter_completions(&self) -> bool {
19306 true
19307 }
19308}
19309
19310pub trait CodeActionProvider {
19311 fn id(&self) -> Arc<str>;
19312
19313 fn code_actions(
19314 &self,
19315 buffer: &Entity<Buffer>,
19316 range: Range<text::Anchor>,
19317 window: &mut Window,
19318 cx: &mut App,
19319 ) -> Task<Result<Vec<CodeAction>>>;
19320
19321 fn apply_code_action(
19322 &self,
19323 buffer_handle: Entity<Buffer>,
19324 action: CodeAction,
19325 excerpt_id: ExcerptId,
19326 push_to_history: bool,
19327 window: &mut Window,
19328 cx: &mut App,
19329 ) -> Task<Result<ProjectTransaction>>;
19330}
19331
19332impl CodeActionProvider for Entity<Project> {
19333 fn id(&self) -> Arc<str> {
19334 "project".into()
19335 }
19336
19337 fn code_actions(
19338 &self,
19339 buffer: &Entity<Buffer>,
19340 range: Range<text::Anchor>,
19341 _window: &mut Window,
19342 cx: &mut App,
19343 ) -> Task<Result<Vec<CodeAction>>> {
19344 self.update(cx, |project, cx| {
19345 let code_lens = project.code_lens(buffer, range.clone(), cx);
19346 let code_actions = project.code_actions(buffer, range, None, cx);
19347 cx.background_spawn(async move {
19348 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19349 Ok(code_lens
19350 .context("code lens fetch")?
19351 .into_iter()
19352 .chain(code_actions.context("code action fetch")?)
19353 .collect())
19354 })
19355 })
19356 }
19357
19358 fn apply_code_action(
19359 &self,
19360 buffer_handle: Entity<Buffer>,
19361 action: CodeAction,
19362 _excerpt_id: ExcerptId,
19363 push_to_history: bool,
19364 _window: &mut Window,
19365 cx: &mut App,
19366 ) -> Task<Result<ProjectTransaction>> {
19367 self.update(cx, |project, cx| {
19368 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19369 })
19370 }
19371}
19372
19373fn snippet_completions(
19374 project: &Project,
19375 buffer: &Entity<Buffer>,
19376 buffer_position: text::Anchor,
19377 cx: &mut App,
19378) -> Task<Result<Vec<Completion>>> {
19379 let languages = buffer.read(cx).languages_at(buffer_position);
19380 let snippet_store = project.snippets().read(cx);
19381
19382 let scopes: Vec<_> = languages
19383 .iter()
19384 .filter_map(|language| {
19385 let language_name = language.lsp_id();
19386 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19387
19388 if snippets.is_empty() {
19389 None
19390 } else {
19391 Some((language.default_scope(), snippets))
19392 }
19393 })
19394 .collect();
19395
19396 if scopes.is_empty() {
19397 return Task::ready(Ok(vec![]));
19398 }
19399
19400 let snapshot = buffer.read(cx).text_snapshot();
19401 let chars: String = snapshot
19402 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19403 .collect();
19404 let executor = cx.background_executor().clone();
19405
19406 cx.background_spawn(async move {
19407 let mut all_results: Vec<Completion> = Vec::new();
19408 for (scope, snippets) in scopes.into_iter() {
19409 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19410 let mut last_word = chars
19411 .chars()
19412 .take_while(|c| classifier.is_word(*c))
19413 .collect::<String>();
19414 last_word = last_word.chars().rev().collect();
19415
19416 if last_word.is_empty() {
19417 return Ok(vec![]);
19418 }
19419
19420 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19421 let to_lsp = |point: &text::Anchor| {
19422 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19423 point_to_lsp(end)
19424 };
19425 let lsp_end = to_lsp(&buffer_position);
19426
19427 let candidates = snippets
19428 .iter()
19429 .enumerate()
19430 .flat_map(|(ix, snippet)| {
19431 snippet
19432 .prefix
19433 .iter()
19434 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19435 })
19436 .collect::<Vec<StringMatchCandidate>>();
19437
19438 let mut matches = fuzzy::match_strings(
19439 &candidates,
19440 &last_word,
19441 last_word.chars().any(|c| c.is_uppercase()),
19442 100,
19443 &Default::default(),
19444 executor.clone(),
19445 )
19446 .await;
19447
19448 // Remove all candidates where the query's start does not match the start of any word in the candidate
19449 if let Some(query_start) = last_word.chars().next() {
19450 matches.retain(|string_match| {
19451 split_words(&string_match.string).any(|word| {
19452 // Check that the first codepoint of the word as lowercase matches the first
19453 // codepoint of the query as lowercase
19454 word.chars()
19455 .flat_map(|codepoint| codepoint.to_lowercase())
19456 .zip(query_start.to_lowercase())
19457 .all(|(word_cp, query_cp)| word_cp == query_cp)
19458 })
19459 });
19460 }
19461
19462 let matched_strings = matches
19463 .into_iter()
19464 .map(|m| m.string)
19465 .collect::<HashSet<_>>();
19466
19467 let mut result: Vec<Completion> = snippets
19468 .iter()
19469 .filter_map(|snippet| {
19470 let matching_prefix = snippet
19471 .prefix
19472 .iter()
19473 .find(|prefix| matched_strings.contains(*prefix))?;
19474 let start = as_offset - last_word.len();
19475 let start = snapshot.anchor_before(start);
19476 let range = start..buffer_position;
19477 let lsp_start = to_lsp(&start);
19478 let lsp_range = lsp::Range {
19479 start: lsp_start,
19480 end: lsp_end,
19481 };
19482 Some(Completion {
19483 replace_range: range,
19484 new_text: snippet.body.clone(),
19485 source: CompletionSource::Lsp {
19486 insert_range: None,
19487 server_id: LanguageServerId(usize::MAX),
19488 resolved: true,
19489 lsp_completion: Box::new(lsp::CompletionItem {
19490 label: snippet.prefix.first().unwrap().clone(),
19491 kind: Some(CompletionItemKind::SNIPPET),
19492 label_details: snippet.description.as_ref().map(|description| {
19493 lsp::CompletionItemLabelDetails {
19494 detail: Some(description.clone()),
19495 description: None,
19496 }
19497 }),
19498 insert_text_format: Some(InsertTextFormat::SNIPPET),
19499 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19500 lsp::InsertReplaceEdit {
19501 new_text: snippet.body.clone(),
19502 insert: lsp_range,
19503 replace: lsp_range,
19504 },
19505 )),
19506 filter_text: Some(snippet.body.clone()),
19507 sort_text: Some(char::MAX.to_string()),
19508 ..lsp::CompletionItem::default()
19509 }),
19510 lsp_defaults: None,
19511 },
19512 label: CodeLabel {
19513 text: matching_prefix.clone(),
19514 runs: Vec::new(),
19515 filter_range: 0..matching_prefix.len(),
19516 },
19517 icon_path: None,
19518 documentation: snippet.description.clone().map(|description| {
19519 CompletionDocumentation::SingleLine(description.into())
19520 }),
19521 insert_text_mode: None,
19522 confirm: None,
19523 })
19524 })
19525 .collect();
19526
19527 all_results.append(&mut result);
19528 }
19529
19530 Ok(all_results)
19531 })
19532}
19533
19534impl CompletionProvider for Entity<Project> {
19535 fn completions(
19536 &self,
19537 _excerpt_id: ExcerptId,
19538 buffer: &Entity<Buffer>,
19539 buffer_position: text::Anchor,
19540 options: CompletionContext,
19541 _window: &mut Window,
19542 cx: &mut Context<Editor>,
19543 ) -> Task<Result<Option<Vec<Completion>>>> {
19544 self.update(cx, |project, cx| {
19545 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19546 let project_completions = project.completions(buffer, buffer_position, options, cx);
19547 cx.background_spawn(async move {
19548 let snippets_completions = snippets.await?;
19549 match project_completions.await? {
19550 Some(mut completions) => {
19551 completions.extend(snippets_completions);
19552 Ok(Some(completions))
19553 }
19554 None => {
19555 if snippets_completions.is_empty() {
19556 Ok(None)
19557 } else {
19558 Ok(Some(snippets_completions))
19559 }
19560 }
19561 }
19562 })
19563 })
19564 }
19565
19566 fn resolve_completions(
19567 &self,
19568 buffer: Entity<Buffer>,
19569 completion_indices: Vec<usize>,
19570 completions: Rc<RefCell<Box<[Completion]>>>,
19571 cx: &mut Context<Editor>,
19572 ) -> Task<Result<bool>> {
19573 self.update(cx, |project, cx| {
19574 project.lsp_store().update(cx, |lsp_store, cx| {
19575 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19576 })
19577 })
19578 }
19579
19580 fn apply_additional_edits_for_completion(
19581 &self,
19582 buffer: Entity<Buffer>,
19583 completions: Rc<RefCell<Box<[Completion]>>>,
19584 completion_index: usize,
19585 push_to_history: bool,
19586 cx: &mut Context<Editor>,
19587 ) -> Task<Result<Option<language::Transaction>>> {
19588 self.update(cx, |project, cx| {
19589 project.lsp_store().update(cx, |lsp_store, cx| {
19590 lsp_store.apply_additional_edits_for_completion(
19591 buffer,
19592 completions,
19593 completion_index,
19594 push_to_history,
19595 cx,
19596 )
19597 })
19598 })
19599 }
19600
19601 fn is_completion_trigger(
19602 &self,
19603 buffer: &Entity<Buffer>,
19604 position: language::Anchor,
19605 text: &str,
19606 trigger_in_words: bool,
19607 cx: &mut Context<Editor>,
19608 ) -> bool {
19609 let mut chars = text.chars();
19610 let char = if let Some(char) = chars.next() {
19611 char
19612 } else {
19613 return false;
19614 };
19615 if chars.next().is_some() {
19616 return false;
19617 }
19618
19619 let buffer = buffer.read(cx);
19620 let snapshot = buffer.snapshot();
19621 if !snapshot.settings_at(position, cx).show_completions_on_input {
19622 return false;
19623 }
19624 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19625 if trigger_in_words && classifier.is_word(char) {
19626 return true;
19627 }
19628
19629 buffer.completion_triggers().contains(text)
19630 }
19631}
19632
19633impl SemanticsProvider for Entity<Project> {
19634 fn hover(
19635 &self,
19636 buffer: &Entity<Buffer>,
19637 position: text::Anchor,
19638 cx: &mut App,
19639 ) -> Option<Task<Vec<project::Hover>>> {
19640 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19641 }
19642
19643 fn document_highlights(
19644 &self,
19645 buffer: &Entity<Buffer>,
19646 position: text::Anchor,
19647 cx: &mut App,
19648 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19649 Some(self.update(cx, |project, cx| {
19650 project.document_highlights(buffer, position, cx)
19651 }))
19652 }
19653
19654 fn definitions(
19655 &self,
19656 buffer: &Entity<Buffer>,
19657 position: text::Anchor,
19658 kind: GotoDefinitionKind,
19659 cx: &mut App,
19660 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19661 Some(self.update(cx, |project, cx| match kind {
19662 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19663 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19664 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19665 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19666 }))
19667 }
19668
19669 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19670 // TODO: make this work for remote projects
19671 self.update(cx, |project, cx| {
19672 if project
19673 .active_debug_session(cx)
19674 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
19675 {
19676 return true;
19677 }
19678
19679 buffer.update(cx, |buffer, cx| {
19680 project.any_language_server_supports_inlay_hints(buffer, cx)
19681 })
19682 })
19683 }
19684
19685 fn inline_values(
19686 &self,
19687 buffer_handle: Entity<Buffer>,
19688 range: Range<text::Anchor>,
19689 cx: &mut App,
19690 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19691 self.update(cx, |project, cx| {
19692 let (session, active_stack_frame) = project.active_debug_session(cx)?;
19693
19694 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
19695 })
19696 }
19697
19698 fn inlay_hints(
19699 &self,
19700 buffer_handle: Entity<Buffer>,
19701 range: Range<text::Anchor>,
19702 cx: &mut App,
19703 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19704 Some(self.update(cx, |project, cx| {
19705 project.inlay_hints(buffer_handle, range, cx)
19706 }))
19707 }
19708
19709 fn resolve_inlay_hint(
19710 &self,
19711 hint: InlayHint,
19712 buffer_handle: Entity<Buffer>,
19713 server_id: LanguageServerId,
19714 cx: &mut App,
19715 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19716 Some(self.update(cx, |project, cx| {
19717 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19718 }))
19719 }
19720
19721 fn range_for_rename(
19722 &self,
19723 buffer: &Entity<Buffer>,
19724 position: text::Anchor,
19725 cx: &mut App,
19726 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19727 Some(self.update(cx, |project, cx| {
19728 let buffer = buffer.clone();
19729 let task = project.prepare_rename(buffer.clone(), position, cx);
19730 cx.spawn(async move |_, cx| {
19731 Ok(match task.await? {
19732 PrepareRenameResponse::Success(range) => Some(range),
19733 PrepareRenameResponse::InvalidPosition => None,
19734 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19735 // Fallback on using TreeSitter info to determine identifier range
19736 buffer.update(cx, |buffer, _| {
19737 let snapshot = buffer.snapshot();
19738 let (range, kind) = snapshot.surrounding_word(position);
19739 if kind != Some(CharKind::Word) {
19740 return None;
19741 }
19742 Some(
19743 snapshot.anchor_before(range.start)
19744 ..snapshot.anchor_after(range.end),
19745 )
19746 })?
19747 }
19748 })
19749 })
19750 }))
19751 }
19752
19753 fn perform_rename(
19754 &self,
19755 buffer: &Entity<Buffer>,
19756 position: text::Anchor,
19757 new_name: String,
19758 cx: &mut App,
19759 ) -> Option<Task<Result<ProjectTransaction>>> {
19760 Some(self.update(cx, |project, cx| {
19761 project.perform_rename(buffer.clone(), position, new_name, cx)
19762 }))
19763 }
19764}
19765
19766fn inlay_hint_settings(
19767 location: Anchor,
19768 snapshot: &MultiBufferSnapshot,
19769 cx: &mut Context<Editor>,
19770) -> InlayHintSettings {
19771 let file = snapshot.file_at(location);
19772 let language = snapshot.language_at(location).map(|l| l.name());
19773 language_settings(language, file, cx).inlay_hints
19774}
19775
19776fn consume_contiguous_rows(
19777 contiguous_row_selections: &mut Vec<Selection<Point>>,
19778 selection: &Selection<Point>,
19779 display_map: &DisplaySnapshot,
19780 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19781) -> (MultiBufferRow, MultiBufferRow) {
19782 contiguous_row_selections.push(selection.clone());
19783 let start_row = MultiBufferRow(selection.start.row);
19784 let mut end_row = ending_row(selection, display_map);
19785
19786 while let Some(next_selection) = selections.peek() {
19787 if next_selection.start.row <= end_row.0 {
19788 end_row = ending_row(next_selection, display_map);
19789 contiguous_row_selections.push(selections.next().unwrap().clone());
19790 } else {
19791 break;
19792 }
19793 }
19794 (start_row, end_row)
19795}
19796
19797fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19798 if next_selection.end.column > 0 || next_selection.is_empty() {
19799 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19800 } else {
19801 MultiBufferRow(next_selection.end.row)
19802 }
19803}
19804
19805impl EditorSnapshot {
19806 pub fn remote_selections_in_range<'a>(
19807 &'a self,
19808 range: &'a Range<Anchor>,
19809 collaboration_hub: &dyn CollaborationHub,
19810 cx: &'a App,
19811 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19812 let participant_names = collaboration_hub.user_names(cx);
19813 let participant_indices = collaboration_hub.user_participant_indices(cx);
19814 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19815 let collaborators_by_replica_id = collaborators_by_peer_id
19816 .iter()
19817 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19818 .collect::<HashMap<_, _>>();
19819 self.buffer_snapshot
19820 .selections_in_range(range, false)
19821 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19822 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19823 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19824 let user_name = participant_names.get(&collaborator.user_id).cloned();
19825 Some(RemoteSelection {
19826 replica_id,
19827 selection,
19828 cursor_shape,
19829 line_mode,
19830 participant_index,
19831 peer_id: collaborator.peer_id,
19832 user_name,
19833 })
19834 })
19835 }
19836
19837 pub fn hunks_for_ranges(
19838 &self,
19839 ranges: impl IntoIterator<Item = Range<Point>>,
19840 ) -> Vec<MultiBufferDiffHunk> {
19841 let mut hunks = Vec::new();
19842 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19843 HashMap::default();
19844 for query_range in ranges {
19845 let query_rows =
19846 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19847 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19848 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19849 ) {
19850 // Include deleted hunks that are adjacent to the query range, because
19851 // otherwise they would be missed.
19852 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19853 if hunk.status().is_deleted() {
19854 intersects_range |= hunk.row_range.start == query_rows.end;
19855 intersects_range |= hunk.row_range.end == query_rows.start;
19856 }
19857 if intersects_range {
19858 if !processed_buffer_rows
19859 .entry(hunk.buffer_id)
19860 .or_default()
19861 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19862 {
19863 continue;
19864 }
19865 hunks.push(hunk);
19866 }
19867 }
19868 }
19869
19870 hunks
19871 }
19872
19873 fn display_diff_hunks_for_rows<'a>(
19874 &'a self,
19875 display_rows: Range<DisplayRow>,
19876 folded_buffers: &'a HashSet<BufferId>,
19877 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19878 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19879 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19880
19881 self.buffer_snapshot
19882 .diff_hunks_in_range(buffer_start..buffer_end)
19883 .filter_map(|hunk| {
19884 if folded_buffers.contains(&hunk.buffer_id) {
19885 return None;
19886 }
19887
19888 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19889 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19890
19891 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19892 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19893
19894 let display_hunk = if hunk_display_start.column() != 0 {
19895 DisplayDiffHunk::Folded {
19896 display_row: hunk_display_start.row(),
19897 }
19898 } else {
19899 let mut end_row = hunk_display_end.row();
19900 if hunk_display_end.column() > 0 {
19901 end_row.0 += 1;
19902 }
19903 let is_created_file = hunk.is_created_file();
19904 DisplayDiffHunk::Unfolded {
19905 status: hunk.status(),
19906 diff_base_byte_range: hunk.diff_base_byte_range,
19907 display_row_range: hunk_display_start.row()..end_row,
19908 multi_buffer_range: Anchor::range_in_buffer(
19909 hunk.excerpt_id,
19910 hunk.buffer_id,
19911 hunk.buffer_range,
19912 ),
19913 is_created_file,
19914 }
19915 };
19916
19917 Some(display_hunk)
19918 })
19919 }
19920
19921 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19922 self.display_snapshot.buffer_snapshot.language_at(position)
19923 }
19924
19925 pub fn is_focused(&self) -> bool {
19926 self.is_focused
19927 }
19928
19929 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19930 self.placeholder_text.as_ref()
19931 }
19932
19933 pub fn scroll_position(&self) -> gpui::Point<f32> {
19934 self.scroll_anchor.scroll_position(&self.display_snapshot)
19935 }
19936
19937 fn gutter_dimensions(
19938 &self,
19939 font_id: FontId,
19940 font_size: Pixels,
19941 max_line_number_width: Pixels,
19942 cx: &App,
19943 ) -> Option<GutterDimensions> {
19944 if !self.show_gutter {
19945 return None;
19946 }
19947
19948 let descent = cx.text_system().descent(font_id, font_size);
19949 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19950 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19951
19952 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19953 matches!(
19954 ProjectSettings::get_global(cx).git.git_gutter,
19955 Some(GitGutterSetting::TrackedFiles)
19956 )
19957 });
19958 let gutter_settings = EditorSettings::get_global(cx).gutter;
19959 let show_line_numbers = self
19960 .show_line_numbers
19961 .unwrap_or(gutter_settings.line_numbers);
19962 let line_gutter_width = if show_line_numbers {
19963 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19964 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19965 max_line_number_width.max(min_width_for_number_on_gutter)
19966 } else {
19967 0.0.into()
19968 };
19969
19970 let show_code_actions = self
19971 .show_code_actions
19972 .unwrap_or(gutter_settings.code_actions);
19973
19974 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19975 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19976
19977 let git_blame_entries_width =
19978 self.git_blame_gutter_max_author_length
19979 .map(|max_author_length| {
19980 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19981 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19982
19983 /// The number of characters to dedicate to gaps and margins.
19984 const SPACING_WIDTH: usize = 4;
19985
19986 let max_char_count = max_author_length.min(renderer.max_author_length())
19987 + ::git::SHORT_SHA_LENGTH
19988 + MAX_RELATIVE_TIMESTAMP.len()
19989 + SPACING_WIDTH;
19990
19991 em_advance * max_char_count
19992 });
19993
19994 let is_singleton = self.buffer_snapshot.is_singleton();
19995
19996 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19997 left_padding += if !is_singleton {
19998 em_width * 4.0
19999 } else if show_code_actions || show_runnables || show_breakpoints {
20000 em_width * 3.0
20001 } else if show_git_gutter && show_line_numbers {
20002 em_width * 2.0
20003 } else if show_git_gutter || show_line_numbers {
20004 em_width
20005 } else {
20006 px(0.)
20007 };
20008
20009 let shows_folds = is_singleton && gutter_settings.folds;
20010
20011 let right_padding = if shows_folds && show_line_numbers {
20012 em_width * 4.0
20013 } else if shows_folds || (!is_singleton && show_line_numbers) {
20014 em_width * 3.0
20015 } else if show_line_numbers {
20016 em_width
20017 } else {
20018 px(0.)
20019 };
20020
20021 Some(GutterDimensions {
20022 left_padding,
20023 right_padding,
20024 width: line_gutter_width + left_padding + right_padding,
20025 margin: -descent,
20026 git_blame_entries_width,
20027 })
20028 }
20029
20030 pub fn render_crease_toggle(
20031 &self,
20032 buffer_row: MultiBufferRow,
20033 row_contains_cursor: bool,
20034 editor: Entity<Editor>,
20035 window: &mut Window,
20036 cx: &mut App,
20037 ) -> Option<AnyElement> {
20038 let folded = self.is_line_folded(buffer_row);
20039 let mut is_foldable = false;
20040
20041 if let Some(crease) = self
20042 .crease_snapshot
20043 .query_row(buffer_row, &self.buffer_snapshot)
20044 {
20045 is_foldable = true;
20046 match crease {
20047 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20048 if let Some(render_toggle) = render_toggle {
20049 let toggle_callback =
20050 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20051 if folded {
20052 editor.update(cx, |editor, cx| {
20053 editor.fold_at(buffer_row, window, cx)
20054 });
20055 } else {
20056 editor.update(cx, |editor, cx| {
20057 editor.unfold_at(buffer_row, window, cx)
20058 });
20059 }
20060 });
20061 return Some((render_toggle)(
20062 buffer_row,
20063 folded,
20064 toggle_callback,
20065 window,
20066 cx,
20067 ));
20068 }
20069 }
20070 }
20071 }
20072
20073 is_foldable |= self.starts_indent(buffer_row);
20074
20075 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20076 Some(
20077 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20078 .toggle_state(folded)
20079 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20080 if folded {
20081 this.unfold_at(buffer_row, window, cx);
20082 } else {
20083 this.fold_at(buffer_row, window, cx);
20084 }
20085 }))
20086 .into_any_element(),
20087 )
20088 } else {
20089 None
20090 }
20091 }
20092
20093 pub fn render_crease_trailer(
20094 &self,
20095 buffer_row: MultiBufferRow,
20096 window: &mut Window,
20097 cx: &mut App,
20098 ) -> Option<AnyElement> {
20099 let folded = self.is_line_folded(buffer_row);
20100 if let Crease::Inline { render_trailer, .. } = self
20101 .crease_snapshot
20102 .query_row(buffer_row, &self.buffer_snapshot)?
20103 {
20104 let render_trailer = render_trailer.as_ref()?;
20105 Some(render_trailer(buffer_row, folded, window, cx))
20106 } else {
20107 None
20108 }
20109 }
20110}
20111
20112impl Deref for EditorSnapshot {
20113 type Target = DisplaySnapshot;
20114
20115 fn deref(&self) -> &Self::Target {
20116 &self.display_snapshot
20117 }
20118}
20119
20120#[derive(Clone, Debug, PartialEq, Eq)]
20121pub enum EditorEvent {
20122 InputIgnored {
20123 text: Arc<str>,
20124 },
20125 InputHandled {
20126 utf16_range_to_replace: Option<Range<isize>>,
20127 text: Arc<str>,
20128 },
20129 ExcerptsAdded {
20130 buffer: Entity<Buffer>,
20131 predecessor: ExcerptId,
20132 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20133 },
20134 ExcerptsRemoved {
20135 ids: Vec<ExcerptId>,
20136 removed_buffer_ids: Vec<BufferId>,
20137 },
20138 BufferFoldToggled {
20139 ids: Vec<ExcerptId>,
20140 folded: bool,
20141 },
20142 ExcerptsEdited {
20143 ids: Vec<ExcerptId>,
20144 },
20145 ExcerptsExpanded {
20146 ids: Vec<ExcerptId>,
20147 },
20148 BufferEdited,
20149 Edited {
20150 transaction_id: clock::Lamport,
20151 },
20152 Reparsed(BufferId),
20153 Focused,
20154 FocusedIn,
20155 Blurred,
20156 DirtyChanged,
20157 Saved,
20158 TitleChanged,
20159 DiffBaseChanged,
20160 SelectionsChanged {
20161 local: bool,
20162 },
20163 ScrollPositionChanged {
20164 local: bool,
20165 autoscroll: bool,
20166 },
20167 Closed,
20168 TransactionUndone {
20169 transaction_id: clock::Lamport,
20170 },
20171 TransactionBegun {
20172 transaction_id: clock::Lamport,
20173 },
20174 Reloaded,
20175 CursorShapeChanged,
20176 PushedToNavHistory {
20177 anchor: Anchor,
20178 is_deactivate: bool,
20179 },
20180}
20181
20182impl EventEmitter<EditorEvent> for Editor {}
20183
20184impl Focusable for Editor {
20185 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20186 self.focus_handle.clone()
20187 }
20188}
20189
20190impl Render for Editor {
20191 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20192 let settings = ThemeSettings::get_global(cx);
20193
20194 let mut text_style = match self.mode {
20195 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20196 color: cx.theme().colors().editor_foreground,
20197 font_family: settings.ui_font.family.clone(),
20198 font_features: settings.ui_font.features.clone(),
20199 font_fallbacks: settings.ui_font.fallbacks.clone(),
20200 font_size: rems(0.875).into(),
20201 font_weight: settings.ui_font.weight,
20202 line_height: relative(settings.buffer_line_height.value()),
20203 ..Default::default()
20204 },
20205 EditorMode::Full { .. } => TextStyle {
20206 color: cx.theme().colors().editor_foreground,
20207 font_family: settings.buffer_font.family.clone(),
20208 font_features: settings.buffer_font.features.clone(),
20209 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20210 font_size: settings.buffer_font_size(cx).into(),
20211 font_weight: settings.buffer_font.weight,
20212 line_height: relative(settings.buffer_line_height.value()),
20213 ..Default::default()
20214 },
20215 };
20216 if let Some(text_style_refinement) = &self.text_style_refinement {
20217 text_style.refine(text_style_refinement)
20218 }
20219
20220 let background = match self.mode {
20221 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20222 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20223 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20224 };
20225
20226 EditorElement::new(
20227 &cx.entity(),
20228 EditorStyle {
20229 background,
20230 local_player: cx.theme().players().local(),
20231 text: text_style,
20232 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20233 syntax: cx.theme().syntax().clone(),
20234 status: cx.theme().status().clone(),
20235 inlay_hints_style: make_inlay_hints_style(cx),
20236 inline_completion_styles: make_suggestion_styles(cx),
20237 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20238 },
20239 )
20240 }
20241}
20242
20243impl EntityInputHandler for Editor {
20244 fn text_for_range(
20245 &mut self,
20246 range_utf16: Range<usize>,
20247 adjusted_range: &mut Option<Range<usize>>,
20248 _: &mut Window,
20249 cx: &mut Context<Self>,
20250 ) -> Option<String> {
20251 let snapshot = self.buffer.read(cx).read(cx);
20252 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20253 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20254 if (start.0..end.0) != range_utf16 {
20255 adjusted_range.replace(start.0..end.0);
20256 }
20257 Some(snapshot.text_for_range(start..end).collect())
20258 }
20259
20260 fn selected_text_range(
20261 &mut self,
20262 ignore_disabled_input: bool,
20263 _: &mut Window,
20264 cx: &mut Context<Self>,
20265 ) -> Option<UTF16Selection> {
20266 // Prevent the IME menu from appearing when holding down an alphabetic key
20267 // while input is disabled.
20268 if !ignore_disabled_input && !self.input_enabled {
20269 return None;
20270 }
20271
20272 let selection = self.selections.newest::<OffsetUtf16>(cx);
20273 let range = selection.range();
20274
20275 Some(UTF16Selection {
20276 range: range.start.0..range.end.0,
20277 reversed: selection.reversed,
20278 })
20279 }
20280
20281 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20282 let snapshot = self.buffer.read(cx).read(cx);
20283 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20284 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20285 }
20286
20287 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20288 self.clear_highlights::<InputComposition>(cx);
20289 self.ime_transaction.take();
20290 }
20291
20292 fn replace_text_in_range(
20293 &mut self,
20294 range_utf16: Option<Range<usize>>,
20295 text: &str,
20296 window: &mut Window,
20297 cx: &mut Context<Self>,
20298 ) {
20299 if !self.input_enabled {
20300 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20301 return;
20302 }
20303
20304 self.transact(window, cx, |this, window, cx| {
20305 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20306 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20307 Some(this.selection_replacement_ranges(range_utf16, cx))
20308 } else {
20309 this.marked_text_ranges(cx)
20310 };
20311
20312 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20313 let newest_selection_id = this.selections.newest_anchor().id;
20314 this.selections
20315 .all::<OffsetUtf16>(cx)
20316 .iter()
20317 .zip(ranges_to_replace.iter())
20318 .find_map(|(selection, range)| {
20319 if selection.id == newest_selection_id {
20320 Some(
20321 (range.start.0 as isize - selection.head().0 as isize)
20322 ..(range.end.0 as isize - selection.head().0 as isize),
20323 )
20324 } else {
20325 None
20326 }
20327 })
20328 });
20329
20330 cx.emit(EditorEvent::InputHandled {
20331 utf16_range_to_replace: range_to_replace,
20332 text: text.into(),
20333 });
20334
20335 if let Some(new_selected_ranges) = new_selected_ranges {
20336 this.change_selections(None, window, cx, |selections| {
20337 selections.select_ranges(new_selected_ranges)
20338 });
20339 this.backspace(&Default::default(), window, cx);
20340 }
20341
20342 this.handle_input(text, window, cx);
20343 });
20344
20345 if let Some(transaction) = self.ime_transaction {
20346 self.buffer.update(cx, |buffer, cx| {
20347 buffer.group_until_transaction(transaction, cx);
20348 });
20349 }
20350
20351 self.unmark_text(window, cx);
20352 }
20353
20354 fn replace_and_mark_text_in_range(
20355 &mut self,
20356 range_utf16: Option<Range<usize>>,
20357 text: &str,
20358 new_selected_range_utf16: Option<Range<usize>>,
20359 window: &mut Window,
20360 cx: &mut Context<Self>,
20361 ) {
20362 if !self.input_enabled {
20363 return;
20364 }
20365
20366 let transaction = self.transact(window, cx, |this, window, cx| {
20367 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20368 let snapshot = this.buffer.read(cx).read(cx);
20369 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20370 for marked_range in &mut marked_ranges {
20371 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20372 marked_range.start.0 += relative_range_utf16.start;
20373 marked_range.start =
20374 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20375 marked_range.end =
20376 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20377 }
20378 }
20379 Some(marked_ranges)
20380 } else if let Some(range_utf16) = range_utf16 {
20381 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20382 Some(this.selection_replacement_ranges(range_utf16, cx))
20383 } else {
20384 None
20385 };
20386
20387 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20388 let newest_selection_id = this.selections.newest_anchor().id;
20389 this.selections
20390 .all::<OffsetUtf16>(cx)
20391 .iter()
20392 .zip(ranges_to_replace.iter())
20393 .find_map(|(selection, range)| {
20394 if selection.id == newest_selection_id {
20395 Some(
20396 (range.start.0 as isize - selection.head().0 as isize)
20397 ..(range.end.0 as isize - selection.head().0 as isize),
20398 )
20399 } else {
20400 None
20401 }
20402 })
20403 });
20404
20405 cx.emit(EditorEvent::InputHandled {
20406 utf16_range_to_replace: range_to_replace,
20407 text: text.into(),
20408 });
20409
20410 if let Some(ranges) = ranges_to_replace {
20411 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20412 }
20413
20414 let marked_ranges = {
20415 let snapshot = this.buffer.read(cx).read(cx);
20416 this.selections
20417 .disjoint_anchors()
20418 .iter()
20419 .map(|selection| {
20420 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20421 })
20422 .collect::<Vec<_>>()
20423 };
20424
20425 if text.is_empty() {
20426 this.unmark_text(window, cx);
20427 } else {
20428 this.highlight_text::<InputComposition>(
20429 marked_ranges.clone(),
20430 HighlightStyle {
20431 underline: Some(UnderlineStyle {
20432 thickness: px(1.),
20433 color: None,
20434 wavy: false,
20435 }),
20436 ..Default::default()
20437 },
20438 cx,
20439 );
20440 }
20441
20442 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20443 let use_autoclose = this.use_autoclose;
20444 let use_auto_surround = this.use_auto_surround;
20445 this.set_use_autoclose(false);
20446 this.set_use_auto_surround(false);
20447 this.handle_input(text, window, cx);
20448 this.set_use_autoclose(use_autoclose);
20449 this.set_use_auto_surround(use_auto_surround);
20450
20451 if let Some(new_selected_range) = new_selected_range_utf16 {
20452 let snapshot = this.buffer.read(cx).read(cx);
20453 let new_selected_ranges = marked_ranges
20454 .into_iter()
20455 .map(|marked_range| {
20456 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20457 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20458 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20459 snapshot.clip_offset_utf16(new_start, Bias::Left)
20460 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20461 })
20462 .collect::<Vec<_>>();
20463
20464 drop(snapshot);
20465 this.change_selections(None, window, cx, |selections| {
20466 selections.select_ranges(new_selected_ranges)
20467 });
20468 }
20469 });
20470
20471 self.ime_transaction = self.ime_transaction.or(transaction);
20472 if let Some(transaction) = self.ime_transaction {
20473 self.buffer.update(cx, |buffer, cx| {
20474 buffer.group_until_transaction(transaction, cx);
20475 });
20476 }
20477
20478 if self.text_highlights::<InputComposition>(cx).is_none() {
20479 self.ime_transaction.take();
20480 }
20481 }
20482
20483 fn bounds_for_range(
20484 &mut self,
20485 range_utf16: Range<usize>,
20486 element_bounds: gpui::Bounds<Pixels>,
20487 window: &mut Window,
20488 cx: &mut Context<Self>,
20489 ) -> Option<gpui::Bounds<Pixels>> {
20490 let text_layout_details = self.text_layout_details(window);
20491 let gpui::Size {
20492 width: em_width,
20493 height: line_height,
20494 } = self.character_size(window);
20495
20496 let snapshot = self.snapshot(window, cx);
20497 let scroll_position = snapshot.scroll_position();
20498 let scroll_left = scroll_position.x * em_width;
20499
20500 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20501 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20502 + self.gutter_dimensions.width
20503 + self.gutter_dimensions.margin;
20504 let y = line_height * (start.row().as_f32() - scroll_position.y);
20505
20506 Some(Bounds {
20507 origin: element_bounds.origin + point(x, y),
20508 size: size(em_width, line_height),
20509 })
20510 }
20511
20512 fn character_index_for_point(
20513 &mut self,
20514 point: gpui::Point<Pixels>,
20515 _window: &mut Window,
20516 _cx: &mut Context<Self>,
20517 ) -> Option<usize> {
20518 let position_map = self.last_position_map.as_ref()?;
20519 if !position_map.text_hitbox.contains(&point) {
20520 return None;
20521 }
20522 let display_point = position_map.point_for_position(point).previous_valid;
20523 let anchor = position_map
20524 .snapshot
20525 .display_point_to_anchor(display_point, Bias::Left);
20526 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20527 Some(utf16_offset.0)
20528 }
20529}
20530
20531trait SelectionExt {
20532 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20533 fn spanned_rows(
20534 &self,
20535 include_end_if_at_line_start: bool,
20536 map: &DisplaySnapshot,
20537 ) -> Range<MultiBufferRow>;
20538}
20539
20540impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20541 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20542 let start = self
20543 .start
20544 .to_point(&map.buffer_snapshot)
20545 .to_display_point(map);
20546 let end = self
20547 .end
20548 .to_point(&map.buffer_snapshot)
20549 .to_display_point(map);
20550 if self.reversed {
20551 end..start
20552 } else {
20553 start..end
20554 }
20555 }
20556
20557 fn spanned_rows(
20558 &self,
20559 include_end_if_at_line_start: bool,
20560 map: &DisplaySnapshot,
20561 ) -> Range<MultiBufferRow> {
20562 let start = self.start.to_point(&map.buffer_snapshot);
20563 let mut end = self.end.to_point(&map.buffer_snapshot);
20564 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20565 end.row -= 1;
20566 }
20567
20568 let buffer_start = map.prev_line_boundary(start).0;
20569 let buffer_end = map.next_line_boundary(end).0;
20570 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20571 }
20572}
20573
20574impl<T: InvalidationRegion> InvalidationStack<T> {
20575 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20576 where
20577 S: Clone + ToOffset,
20578 {
20579 while let Some(region) = self.last() {
20580 let all_selections_inside_invalidation_ranges =
20581 if selections.len() == region.ranges().len() {
20582 selections
20583 .iter()
20584 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20585 .all(|(selection, invalidation_range)| {
20586 let head = selection.head().to_offset(buffer);
20587 invalidation_range.start <= head && invalidation_range.end >= head
20588 })
20589 } else {
20590 false
20591 };
20592
20593 if all_selections_inside_invalidation_ranges {
20594 break;
20595 } else {
20596 self.pop();
20597 }
20598 }
20599 }
20600}
20601
20602impl<T> Default for InvalidationStack<T> {
20603 fn default() -> Self {
20604 Self(Default::default())
20605 }
20606}
20607
20608impl<T> Deref for InvalidationStack<T> {
20609 type Target = Vec<T>;
20610
20611 fn deref(&self) -> &Self::Target {
20612 &self.0
20613 }
20614}
20615
20616impl<T> DerefMut for InvalidationStack<T> {
20617 fn deref_mut(&mut self) -> &mut Self::Target {
20618 &mut self.0
20619 }
20620}
20621
20622impl InvalidationRegion for SnippetState {
20623 fn ranges(&self) -> &[Range<Anchor>] {
20624 &self.ranges[self.active_index]
20625 }
20626}
20627
20628fn inline_completion_edit_text(
20629 current_snapshot: &BufferSnapshot,
20630 edits: &[(Range<Anchor>, String)],
20631 edit_preview: &EditPreview,
20632 include_deletions: bool,
20633 cx: &App,
20634) -> HighlightedText {
20635 let edits = edits
20636 .iter()
20637 .map(|(anchor, text)| {
20638 (
20639 anchor.start.text_anchor..anchor.end.text_anchor,
20640 text.clone(),
20641 )
20642 })
20643 .collect::<Vec<_>>();
20644
20645 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20646}
20647
20648pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20649 match severity {
20650 DiagnosticSeverity::ERROR => colors.error,
20651 DiagnosticSeverity::WARNING => colors.warning,
20652 DiagnosticSeverity::INFORMATION => colors.info,
20653 DiagnosticSeverity::HINT => colors.info,
20654 _ => colors.ignored,
20655 }
20656}
20657
20658pub fn styled_runs_for_code_label<'a>(
20659 label: &'a CodeLabel,
20660 syntax_theme: &'a theme::SyntaxTheme,
20661) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20662 let fade_out = HighlightStyle {
20663 fade_out: Some(0.35),
20664 ..Default::default()
20665 };
20666
20667 let mut prev_end = label.filter_range.end;
20668 label
20669 .runs
20670 .iter()
20671 .enumerate()
20672 .flat_map(move |(ix, (range, highlight_id))| {
20673 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20674 style
20675 } else {
20676 return Default::default();
20677 };
20678 let mut muted_style = style;
20679 muted_style.highlight(fade_out);
20680
20681 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20682 if range.start >= label.filter_range.end {
20683 if range.start > prev_end {
20684 runs.push((prev_end..range.start, fade_out));
20685 }
20686 runs.push((range.clone(), muted_style));
20687 } else if range.end <= label.filter_range.end {
20688 runs.push((range.clone(), style));
20689 } else {
20690 runs.push((range.start..label.filter_range.end, style));
20691 runs.push((label.filter_range.end..range.end, muted_style));
20692 }
20693 prev_end = cmp::max(prev_end, range.end);
20694
20695 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20696 runs.push((prev_end..label.text.len(), fade_out));
20697 }
20698
20699 runs
20700 })
20701}
20702
20703pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20704 let mut prev_index = 0;
20705 let mut prev_codepoint: Option<char> = None;
20706 text.char_indices()
20707 .chain([(text.len(), '\0')])
20708 .filter_map(move |(index, codepoint)| {
20709 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20710 let is_boundary = index == text.len()
20711 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20712 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20713 if is_boundary {
20714 let chunk = &text[prev_index..index];
20715 prev_index = index;
20716 Some(chunk)
20717 } else {
20718 None
20719 }
20720 })
20721}
20722
20723pub trait RangeToAnchorExt: Sized {
20724 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20725
20726 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20727 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20728 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20729 }
20730}
20731
20732impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20733 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20734 let start_offset = self.start.to_offset(snapshot);
20735 let end_offset = self.end.to_offset(snapshot);
20736 if start_offset == end_offset {
20737 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20738 } else {
20739 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20740 }
20741 }
20742}
20743
20744pub trait RowExt {
20745 fn as_f32(&self) -> f32;
20746
20747 fn next_row(&self) -> Self;
20748
20749 fn previous_row(&self) -> Self;
20750
20751 fn minus(&self, other: Self) -> u32;
20752}
20753
20754impl RowExt for DisplayRow {
20755 fn as_f32(&self) -> f32 {
20756 self.0 as f32
20757 }
20758
20759 fn next_row(&self) -> Self {
20760 Self(self.0 + 1)
20761 }
20762
20763 fn previous_row(&self) -> Self {
20764 Self(self.0.saturating_sub(1))
20765 }
20766
20767 fn minus(&self, other: Self) -> u32 {
20768 self.0 - other.0
20769 }
20770}
20771
20772impl RowExt for MultiBufferRow {
20773 fn as_f32(&self) -> f32 {
20774 self.0 as f32
20775 }
20776
20777 fn next_row(&self) -> Self {
20778 Self(self.0 + 1)
20779 }
20780
20781 fn previous_row(&self) -> Self {
20782 Self(self.0.saturating_sub(1))
20783 }
20784
20785 fn minus(&self, other: Self) -> u32 {
20786 self.0 - other.0
20787 }
20788}
20789
20790trait RowRangeExt {
20791 type Row;
20792
20793 fn len(&self) -> usize;
20794
20795 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20796}
20797
20798impl RowRangeExt for Range<MultiBufferRow> {
20799 type Row = MultiBufferRow;
20800
20801 fn len(&self) -> usize {
20802 (self.end.0 - self.start.0) as usize
20803 }
20804
20805 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20806 (self.start.0..self.end.0).map(MultiBufferRow)
20807 }
20808}
20809
20810impl RowRangeExt for Range<DisplayRow> {
20811 type Row = DisplayRow;
20812
20813 fn len(&self) -> usize {
20814 (self.end.0 - self.start.0) as usize
20815 }
20816
20817 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20818 (self.start.0..self.end.0).map(DisplayRow)
20819 }
20820}
20821
20822/// If select range has more than one line, we
20823/// just point the cursor to range.start.
20824fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20825 if range.start.row == range.end.row {
20826 range
20827 } else {
20828 range.start..range.start
20829 }
20830}
20831pub struct KillRing(ClipboardItem);
20832impl Global for KillRing {}
20833
20834const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20835
20836enum BreakpointPromptEditAction {
20837 Log,
20838 Condition,
20839 HitCondition,
20840}
20841
20842struct BreakpointPromptEditor {
20843 pub(crate) prompt: Entity<Editor>,
20844 editor: WeakEntity<Editor>,
20845 breakpoint_anchor: Anchor,
20846 breakpoint: Breakpoint,
20847 edit_action: BreakpointPromptEditAction,
20848 block_ids: HashSet<CustomBlockId>,
20849 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20850 _subscriptions: Vec<Subscription>,
20851}
20852
20853impl BreakpointPromptEditor {
20854 const MAX_LINES: u8 = 4;
20855
20856 fn new(
20857 editor: WeakEntity<Editor>,
20858 breakpoint_anchor: Anchor,
20859 breakpoint: Breakpoint,
20860 edit_action: BreakpointPromptEditAction,
20861 window: &mut Window,
20862 cx: &mut Context<Self>,
20863 ) -> Self {
20864 let base_text = match edit_action {
20865 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20866 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20867 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20868 }
20869 .map(|msg| msg.to_string())
20870 .unwrap_or_default();
20871
20872 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20873 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20874
20875 let prompt = cx.new(|cx| {
20876 let mut prompt = Editor::new(
20877 EditorMode::AutoHeight {
20878 max_lines: Self::MAX_LINES as usize,
20879 },
20880 buffer,
20881 None,
20882 window,
20883 cx,
20884 );
20885 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20886 prompt.set_show_cursor_when_unfocused(false, cx);
20887 prompt.set_placeholder_text(
20888 match edit_action {
20889 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20890 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20891 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20892 },
20893 cx,
20894 );
20895
20896 prompt
20897 });
20898
20899 Self {
20900 prompt,
20901 editor,
20902 breakpoint_anchor,
20903 breakpoint,
20904 edit_action,
20905 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20906 block_ids: Default::default(),
20907 _subscriptions: vec![],
20908 }
20909 }
20910
20911 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20912 self.block_ids.extend(block_ids)
20913 }
20914
20915 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20916 if let Some(editor) = self.editor.upgrade() {
20917 let message = self
20918 .prompt
20919 .read(cx)
20920 .buffer
20921 .read(cx)
20922 .as_singleton()
20923 .expect("A multi buffer in breakpoint prompt isn't possible")
20924 .read(cx)
20925 .as_rope()
20926 .to_string();
20927
20928 editor.update(cx, |editor, cx| {
20929 editor.edit_breakpoint_at_anchor(
20930 self.breakpoint_anchor,
20931 self.breakpoint.clone(),
20932 match self.edit_action {
20933 BreakpointPromptEditAction::Log => {
20934 BreakpointEditAction::EditLogMessage(message.into())
20935 }
20936 BreakpointPromptEditAction::Condition => {
20937 BreakpointEditAction::EditCondition(message.into())
20938 }
20939 BreakpointPromptEditAction::HitCondition => {
20940 BreakpointEditAction::EditHitCondition(message.into())
20941 }
20942 },
20943 cx,
20944 );
20945
20946 editor.remove_blocks(self.block_ids.clone(), None, cx);
20947 cx.focus_self(window);
20948 });
20949 }
20950 }
20951
20952 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20953 self.editor
20954 .update(cx, |editor, cx| {
20955 editor.remove_blocks(self.block_ids.clone(), None, cx);
20956 window.focus(&editor.focus_handle);
20957 })
20958 .log_err();
20959 }
20960
20961 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20962 let settings = ThemeSettings::get_global(cx);
20963 let text_style = TextStyle {
20964 color: if self.prompt.read(cx).read_only(cx) {
20965 cx.theme().colors().text_disabled
20966 } else {
20967 cx.theme().colors().text
20968 },
20969 font_family: settings.buffer_font.family.clone(),
20970 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20971 font_size: settings.buffer_font_size(cx).into(),
20972 font_weight: settings.buffer_font.weight,
20973 line_height: relative(settings.buffer_line_height.value()),
20974 ..Default::default()
20975 };
20976 EditorElement::new(
20977 &self.prompt,
20978 EditorStyle {
20979 background: cx.theme().colors().editor_background,
20980 local_player: cx.theme().players().local(),
20981 text: text_style,
20982 ..Default::default()
20983 },
20984 )
20985 }
20986}
20987
20988impl Render for BreakpointPromptEditor {
20989 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20990 let gutter_dimensions = *self.gutter_dimensions.lock();
20991 h_flex()
20992 .key_context("Editor")
20993 .bg(cx.theme().colors().editor_background)
20994 .border_y_1()
20995 .border_color(cx.theme().status().info_border)
20996 .size_full()
20997 .py(window.line_height() / 2.5)
20998 .on_action(cx.listener(Self::confirm))
20999 .on_action(cx.listener(Self::cancel))
21000 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21001 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21002 }
21003}
21004
21005impl Focusable for BreakpointPromptEditor {
21006 fn focus_handle(&self, cx: &App) -> FocusHandle {
21007 self.prompt.focus_handle(cx)
21008 }
21009}
21010
21011fn all_edits_insertions_or_deletions(
21012 edits: &Vec<(Range<Anchor>, String)>,
21013 snapshot: &MultiBufferSnapshot,
21014) -> bool {
21015 let mut all_insertions = true;
21016 let mut all_deletions = true;
21017
21018 for (range, new_text) in edits.iter() {
21019 let range_is_empty = range.to_offset(&snapshot).is_empty();
21020 let text_is_empty = new_text.is_empty();
21021
21022 if range_is_empty != text_is_empty {
21023 if range_is_empty {
21024 all_deletions = false;
21025 } else {
21026 all_insertions = false;
21027 }
21028 } else {
21029 return false;
21030 }
21031
21032 if !all_insertions && !all_deletions {
21033 return false;
21034 }
21035 }
21036 all_insertions || all_deletions
21037}
21038
21039struct MissingEditPredictionKeybindingTooltip;
21040
21041impl Render for MissingEditPredictionKeybindingTooltip {
21042 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21043 ui::tooltip_container(window, cx, |container, _, cx| {
21044 container
21045 .flex_shrink_0()
21046 .max_w_80()
21047 .min_h(rems_from_px(124.))
21048 .justify_between()
21049 .child(
21050 v_flex()
21051 .flex_1()
21052 .text_ui_sm(cx)
21053 .child(Label::new("Conflict with Accept Keybinding"))
21054 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21055 )
21056 .child(
21057 h_flex()
21058 .pb_1()
21059 .gap_1()
21060 .items_end()
21061 .w_full()
21062 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21063 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21064 }))
21065 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21066 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21067 })),
21068 )
21069 })
21070 }
21071}
21072
21073#[derive(Debug, Clone, Copy, PartialEq)]
21074pub struct LineHighlight {
21075 pub background: Background,
21076 pub border: Option<gpui::Hsla>,
21077 pub include_gutter: bool,
21078 pub type_id: Option<TypeId>,
21079}
21080
21081fn render_diff_hunk_controls(
21082 row: u32,
21083 status: &DiffHunkStatus,
21084 hunk_range: Range<Anchor>,
21085 is_created_file: bool,
21086 line_height: Pixels,
21087 editor: &Entity<Editor>,
21088 _window: &mut Window,
21089 cx: &mut App,
21090) -> AnyElement {
21091 h_flex()
21092 .h(line_height)
21093 .mr_1()
21094 .gap_1()
21095 .px_0p5()
21096 .pb_1()
21097 .border_x_1()
21098 .border_b_1()
21099 .border_color(cx.theme().colors().border_variant)
21100 .rounded_b_lg()
21101 .bg(cx.theme().colors().editor_background)
21102 .gap_1()
21103 .occlude()
21104 .shadow_md()
21105 .child(if status.has_secondary_hunk() {
21106 Button::new(("stage", row as u64), "Stage")
21107 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21108 .tooltip({
21109 let focus_handle = editor.focus_handle(cx);
21110 move |window, cx| {
21111 Tooltip::for_action_in(
21112 "Stage Hunk",
21113 &::git::ToggleStaged,
21114 &focus_handle,
21115 window,
21116 cx,
21117 )
21118 }
21119 })
21120 .on_click({
21121 let editor = editor.clone();
21122 move |_event, _window, cx| {
21123 editor.update(cx, |editor, cx| {
21124 editor.stage_or_unstage_diff_hunks(
21125 true,
21126 vec![hunk_range.start..hunk_range.start],
21127 cx,
21128 );
21129 });
21130 }
21131 })
21132 } else {
21133 Button::new(("unstage", row as u64), "Unstage")
21134 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21135 .tooltip({
21136 let focus_handle = editor.focus_handle(cx);
21137 move |window, cx| {
21138 Tooltip::for_action_in(
21139 "Unstage Hunk",
21140 &::git::ToggleStaged,
21141 &focus_handle,
21142 window,
21143 cx,
21144 )
21145 }
21146 })
21147 .on_click({
21148 let editor = editor.clone();
21149 move |_event, _window, cx| {
21150 editor.update(cx, |editor, cx| {
21151 editor.stage_or_unstage_diff_hunks(
21152 false,
21153 vec![hunk_range.start..hunk_range.start],
21154 cx,
21155 );
21156 });
21157 }
21158 })
21159 })
21160 .child(
21161 Button::new(("restore", row as u64), "Restore")
21162 .tooltip({
21163 let focus_handle = editor.focus_handle(cx);
21164 move |window, cx| {
21165 Tooltip::for_action_in(
21166 "Restore Hunk",
21167 &::git::Restore,
21168 &focus_handle,
21169 window,
21170 cx,
21171 )
21172 }
21173 })
21174 .on_click({
21175 let editor = editor.clone();
21176 move |_event, window, cx| {
21177 editor.update(cx, |editor, cx| {
21178 let snapshot = editor.snapshot(window, cx);
21179 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21180 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21181 });
21182 }
21183 })
21184 .disabled(is_created_file),
21185 )
21186 .when(
21187 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21188 |el| {
21189 el.child(
21190 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21191 .shape(IconButtonShape::Square)
21192 .icon_size(IconSize::Small)
21193 // .disabled(!has_multiple_hunks)
21194 .tooltip({
21195 let focus_handle = editor.focus_handle(cx);
21196 move |window, cx| {
21197 Tooltip::for_action_in(
21198 "Next Hunk",
21199 &GoToHunk,
21200 &focus_handle,
21201 window,
21202 cx,
21203 )
21204 }
21205 })
21206 .on_click({
21207 let editor = editor.clone();
21208 move |_event, window, cx| {
21209 editor.update(cx, |editor, cx| {
21210 let snapshot = editor.snapshot(window, cx);
21211 let position =
21212 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21213 editor.go_to_hunk_before_or_after_position(
21214 &snapshot,
21215 position,
21216 Direction::Next,
21217 window,
21218 cx,
21219 );
21220 editor.expand_selected_diff_hunks(cx);
21221 });
21222 }
21223 }),
21224 )
21225 .child(
21226 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21227 .shape(IconButtonShape::Square)
21228 .icon_size(IconSize::Small)
21229 // .disabled(!has_multiple_hunks)
21230 .tooltip({
21231 let focus_handle = editor.focus_handle(cx);
21232 move |window, cx| {
21233 Tooltip::for_action_in(
21234 "Previous Hunk",
21235 &GoToPreviousHunk,
21236 &focus_handle,
21237 window,
21238 cx,
21239 )
21240 }
21241 })
21242 .on_click({
21243 let editor = editor.clone();
21244 move |_event, window, cx| {
21245 editor.update(cx, |editor, cx| {
21246 let snapshot = editor.snapshot(window, cx);
21247 let point =
21248 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21249 editor.go_to_hunk_before_or_after_position(
21250 &snapshot,
21251 point,
21252 Direction::Prev,
21253 window,
21254 cx,
21255 );
21256 editor.expand_selected_diff_hunks(cx);
21257 });
21258 }
21259 }),
21260 )
21261 },
21262 )
21263 .into_any_element()
21264}