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;
26mod 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 editor_tests;
44#[cfg(test)]
45mod inline_completion_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
52use aho_corasick::AhoCorasick;
53use anyhow::{Context as _, Result, anyhow};
54use blink_manager::BlinkManager;
55use buffer_diff::DiffHunkStatus;
56use client::{Collaborator, ParticipantIndex};
57use clock::ReplicaId;
58use collections::{BTreeMap, HashMap, HashSet, VecDeque};
59use convert_case::{Case, Casing};
60use display_map::*;
61pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
62use editor_settings::GoToDefinitionFallback;
63pub use editor_settings::{
64 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
65 ShowScrollbar,
66};
67pub use editor_settings_controls::*;
68use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
69pub use element::{
70 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
71};
72use feature_flags::{Debugger, FeatureFlagAppExt};
73use futures::{
74 FutureExt,
75 future::{self, Shared, join},
76};
77use fuzzy::StringMatchCandidate;
78
79use ::git::Restore;
80use code_context_menus::{
81 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
82 CompletionsMenu, ContextMenuOrigin,
83};
84use git::blame::{GitBlame, GlobalBlameRenderer};
85use gpui::{
86 Action, Animation, AnimationExt, AnyElement, AnyWeakEntity, App, AppContext,
87 AsyncWindowContext, AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry,
88 ClipboardItem, Context, DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter,
89 FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla,
90 KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render,
91 SharedString, Size, Stateful, Styled, StyledText, Subscription, Task, TextStyle,
92 TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity,
93 WeakFocusHandle, Window, div, impl_actions, point, prelude::*, pulsating_between, px, relative,
94 size,
95};
96use highlight_matching_bracket::refresh_matching_bracket_highlights;
97use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
98pub use hover_popover::hover_markdown_style;
99use hover_popover::{HoverState, hide_hover};
100use indent_guides::ActiveIndentGuidesState;
101use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
102pub use inline_completion::Direction;
103use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
104pub use items::MAX_TAB_TITLE_LEN;
105use itertools::Itertools;
106use language::{
107 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
108 CursorShape, Diagnostic, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
109 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
110 TransactionId, TreeSitterOptions, WordsQuery,
111 language_settings::{
112 self, InlayHintSettings, RewrapBehavior, WordsCompletionMode, all_language_settings,
113 language_settings,
114 },
115 point_from_lsp, text_diff_with_options,
116};
117use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
118use linked_editing_ranges::refresh_linked_ranges;
119use mouse_context_menu::MouseContextMenu;
120use persistence::DB;
121use project::{
122 ProjectPath,
123 debugger::breakpoint_store::{
124 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
125 },
126};
127
128pub use git::blame::BlameRenderer;
129pub use proposed_changes_editor::{
130 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
131};
132use smallvec::smallvec;
133use std::{cell::OnceCell, iter::Peekable};
134use task::{ResolvedTask, TaskTemplate, TaskVariables};
135
136pub use lsp::CompletionContext;
137use lsp::{
138 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
139 InsertTextFormat, LanguageServerId, LanguageServerName,
140};
141
142use language::BufferSnapshot;
143use movement::TextLayoutDetails;
144pub use multi_buffer::{
145 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
146 ToOffset, ToPoint,
147};
148use multi_buffer::{
149 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
150 MultiOrSingleBufferOffsetRange, PathKey, ToOffsetUtf16,
151};
152use parking_lot::Mutex;
153use project::{
154 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
155 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
156 TaskSourceKind,
157 debugger::breakpoint_store::Breakpoint,
158 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
159 project_settings::{GitGutterSetting, ProjectSettings},
160};
161use rand::prelude::*;
162use rpc::{ErrorExt, proto::*};
163use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
164use selections_collection::{
165 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
166};
167use serde::{Deserialize, Serialize};
168use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
169use smallvec::SmallVec;
170use snippet::Snippet;
171use std::sync::Arc;
172use std::{
173 any::TypeId,
174 borrow::Cow,
175 cell::RefCell,
176 cmp::{self, Ordering, Reverse},
177 mem,
178 num::NonZeroU32,
179 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
180 path::{Path, PathBuf},
181 rc::Rc,
182 time::{Duration, Instant},
183};
184pub use sum_tree::Bias;
185use sum_tree::TreeMap;
186use text::{BufferId, OffsetUtf16, Rope};
187use theme::{
188 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
189 observe_buffer_font_size_adjustment,
190};
191use ui::{
192 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
193 IconSize, Key, Tooltip, h_flex, prelude::*,
194};
195use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
196use workspace::{
197 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
198 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
199 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
200 item::{ItemHandle, PreviewTabsSettings},
201 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
202 searchable::SearchEvent,
203};
204
205use crate::hover_links::{find_url, find_url_from_range};
206use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
207
208pub const FILE_HEADER_HEIGHT: u32 = 2;
209pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
210pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
211const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
212const MAX_LINE_LEN: usize = 1024;
213const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
214const MAX_SELECTION_HISTORY_LEN: usize = 1024;
215pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
216#[doc(hidden)]
217pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
218
219pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
220pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
221pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
222
223pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
224pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
225pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
226
227pub type RenderDiffHunkControlsFn = Arc<
228 dyn Fn(
229 u32,
230 &DiffHunkStatus,
231 Range<Anchor>,
232 bool,
233 Pixels,
234 &Entity<Editor>,
235 &mut Window,
236 &mut App,
237 ) -> AnyElement,
238>;
239
240const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
241 alt: true,
242 shift: true,
243 control: false,
244 platform: false,
245 function: false,
246};
247
248#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
249pub enum InlayId {
250 InlineCompletion(usize),
251 Hint(usize),
252}
253
254impl InlayId {
255 fn id(&self) -> usize {
256 match self {
257 Self::InlineCompletion(id) => *id,
258 Self::Hint(id) => *id,
259 }
260 }
261}
262
263pub enum DebugCurrentRowHighlight {}
264enum DocumentHighlightRead {}
265enum DocumentHighlightWrite {}
266enum InputComposition {}
267enum SelectedTextHighlight {}
268
269#[derive(Debug, Copy, Clone, PartialEq, Eq)]
270pub enum Navigated {
271 Yes,
272 No,
273}
274
275impl Navigated {
276 pub fn from_bool(yes: bool) -> Navigated {
277 if yes { Navigated::Yes } else { Navigated::No }
278 }
279}
280
281#[derive(Debug, Clone, PartialEq, Eq)]
282enum DisplayDiffHunk {
283 Folded {
284 display_row: DisplayRow,
285 },
286 Unfolded {
287 is_created_file: bool,
288 diff_base_byte_range: Range<usize>,
289 display_row_range: Range<DisplayRow>,
290 multi_buffer_range: Range<Anchor>,
291 status: DiffHunkStatus,
292 },
293}
294
295pub enum HideMouseCursorOrigin {
296 TypingAction,
297 MovementAction,
298}
299
300pub fn init_settings(cx: &mut App) {
301 EditorSettings::register(cx);
302}
303
304pub fn init(cx: &mut App) {
305 init_settings(cx);
306
307 cx.set_global(GlobalBlameRenderer(Arc::new(())));
308
309 workspace::register_project_item::<Editor>(cx);
310 workspace::FollowableViewRegistry::register::<Editor>(cx);
311 workspace::register_serializable_item::<Editor>(cx);
312
313 cx.observe_new(
314 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
315 workspace.register_action(Editor::new_file);
316 workspace.register_action(Editor::new_file_vertical);
317 workspace.register_action(Editor::new_file_horizontal);
318 workspace.register_action(Editor::cancel_language_server_work);
319 },
320 )
321 .detach();
322
323 cx.on_action(move |_: &workspace::NewFile, cx| {
324 let app_state = workspace::AppState::global(cx);
325 if let Some(app_state) = app_state.upgrade() {
326 workspace::open_new(
327 Default::default(),
328 app_state,
329 cx,
330 |workspace, window, cx| {
331 Editor::new_file(workspace, &Default::default(), window, cx)
332 },
333 )
334 .detach();
335 }
336 });
337 cx.on_action(move |_: &workspace::NewWindow, cx| {
338 let app_state = workspace::AppState::global(cx);
339 if let Some(app_state) = app_state.upgrade() {
340 workspace::open_new(
341 Default::default(),
342 app_state,
343 cx,
344 |workspace, window, cx| {
345 cx.activate(true);
346 Editor::new_file(workspace, &Default::default(), window, cx)
347 },
348 )
349 .detach();
350 }
351 });
352}
353
354pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
355 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
356}
357
358pub struct SearchWithinRange;
359
360trait InvalidationRegion {
361 fn ranges(&self) -> &[Range<Anchor>];
362}
363
364#[derive(Clone, Debug, PartialEq)]
365pub enum SelectPhase {
366 Begin {
367 position: DisplayPoint,
368 add: bool,
369 click_count: usize,
370 },
371 BeginColumnar {
372 position: DisplayPoint,
373 reset: bool,
374 goal_column: u32,
375 },
376 Extend {
377 position: DisplayPoint,
378 click_count: usize,
379 },
380 Update {
381 position: DisplayPoint,
382 goal_column: u32,
383 scroll_delta: gpui::Point<f32>,
384 },
385 End,
386}
387
388#[derive(Clone, Debug)]
389pub enum SelectMode {
390 Character,
391 Word(Range<Anchor>),
392 Line(Range<Anchor>),
393 All,
394}
395
396#[derive(Copy, Clone, PartialEq, Eq, Debug)]
397pub enum EditorMode {
398 SingleLine { auto_width: bool },
399 AutoHeight { max_lines: usize },
400 Full,
401}
402
403#[derive(Copy, Clone, Debug)]
404pub enum SoftWrap {
405 /// Prefer not to wrap at all.
406 ///
407 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
408 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
409 GitDiff,
410 /// Prefer a single line generally, unless an overly long line is encountered.
411 None,
412 /// Soft wrap lines that exceed the editor width.
413 EditorWidth,
414 /// Soft wrap lines at the preferred line length.
415 Column(u32),
416 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
417 Bounded(u32),
418}
419
420#[derive(Clone)]
421pub struct EditorStyle {
422 pub background: Hsla,
423 pub local_player: PlayerColor,
424 pub text: TextStyle,
425 pub scrollbar_width: Pixels,
426 pub syntax: Arc<SyntaxTheme>,
427 pub status: StatusColors,
428 pub inlay_hints_style: HighlightStyle,
429 pub inline_completion_styles: InlineCompletionStyles,
430 pub unnecessary_code_fade: f32,
431}
432
433impl Default for EditorStyle {
434 fn default() -> Self {
435 Self {
436 background: Hsla::default(),
437 local_player: PlayerColor::default(),
438 text: TextStyle::default(),
439 scrollbar_width: Pixels::default(),
440 syntax: Default::default(),
441 // HACK: Status colors don't have a real default.
442 // We should look into removing the status colors from the editor
443 // style and retrieve them directly from the theme.
444 status: StatusColors::dark(),
445 inlay_hints_style: HighlightStyle::default(),
446 inline_completion_styles: InlineCompletionStyles {
447 insertion: HighlightStyle::default(),
448 whitespace: HighlightStyle::default(),
449 },
450 unnecessary_code_fade: Default::default(),
451 }
452 }
453}
454
455pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
456 let show_background = language_settings::language_settings(None, None, cx)
457 .inlay_hints
458 .show_background;
459
460 HighlightStyle {
461 color: Some(cx.theme().status().hint),
462 background_color: show_background.then(|| cx.theme().status().hint_background),
463 ..HighlightStyle::default()
464 }
465}
466
467pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
468 InlineCompletionStyles {
469 insertion: HighlightStyle {
470 color: Some(cx.theme().status().predictive),
471 ..HighlightStyle::default()
472 },
473 whitespace: HighlightStyle {
474 background_color: Some(cx.theme().status().created_background),
475 ..HighlightStyle::default()
476 },
477 }
478}
479
480type CompletionId = usize;
481
482pub(crate) enum EditDisplayMode {
483 TabAccept,
484 DiffPopover,
485 Inline,
486}
487
488enum InlineCompletion {
489 Edit {
490 edits: Vec<(Range<Anchor>, String)>,
491 edit_preview: Option<EditPreview>,
492 display_mode: EditDisplayMode,
493 snapshot: BufferSnapshot,
494 },
495 Move {
496 target: Anchor,
497 snapshot: BufferSnapshot,
498 },
499}
500
501struct InlineCompletionState {
502 inlay_ids: Vec<InlayId>,
503 completion: InlineCompletion,
504 completion_id: Option<SharedString>,
505 invalidation_range: Range<Anchor>,
506}
507
508enum EditPredictionSettings {
509 Disabled,
510 Enabled {
511 show_in_menu: bool,
512 preview_requires_modifier: bool,
513 },
514}
515
516enum InlineCompletionHighlight {}
517
518#[derive(Debug, Clone)]
519struct InlineDiagnostic {
520 message: SharedString,
521 group_id: usize,
522 is_primary: bool,
523 start: Point,
524 severity: DiagnosticSeverity,
525}
526
527pub enum MenuInlineCompletionsPolicy {
528 Never,
529 ByProvider,
530}
531
532pub enum EditPredictionPreview {
533 /// Modifier is not pressed
534 Inactive { released_too_fast: bool },
535 /// Modifier pressed
536 Active {
537 since: Instant,
538 previous_scroll_position: Option<ScrollAnchor>,
539 },
540}
541
542impl EditPredictionPreview {
543 pub fn released_too_fast(&self) -> bool {
544 match self {
545 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
546 EditPredictionPreview::Active { .. } => false,
547 }
548 }
549
550 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
551 if let EditPredictionPreview::Active {
552 previous_scroll_position,
553 ..
554 } = self
555 {
556 *previous_scroll_position = scroll_position;
557 }
558 }
559}
560
561pub struct ContextMenuOptions {
562 pub min_entries_visible: usize,
563 pub max_entries_visible: usize,
564 pub placement: Option<ContextMenuPlacement>,
565}
566
567#[derive(Debug, Clone, PartialEq, Eq)]
568pub enum ContextMenuPlacement {
569 Above,
570 Below,
571}
572
573#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
574struct EditorActionId(usize);
575
576impl EditorActionId {
577 pub fn post_inc(&mut self) -> Self {
578 let answer = self.0;
579
580 *self = Self(answer + 1);
581
582 Self(answer)
583 }
584}
585
586// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
587// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
588
589type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
590type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
591
592#[derive(Default)]
593struct ScrollbarMarkerState {
594 scrollbar_size: Size<Pixels>,
595 dirty: bool,
596 markers: Arc<[PaintQuad]>,
597 pending_refresh: Option<Task<Result<()>>>,
598}
599
600impl ScrollbarMarkerState {
601 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
602 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
603 }
604}
605
606#[derive(Clone, Debug)]
607struct RunnableTasks {
608 templates: Vec<(TaskSourceKind, TaskTemplate)>,
609 offset: multi_buffer::Anchor,
610 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
611 column: u32,
612 // Values of all named captures, including those starting with '_'
613 extra_variables: HashMap<String, String>,
614 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
615 context_range: Range<BufferOffset>,
616}
617
618impl RunnableTasks {
619 fn resolve<'a>(
620 &'a self,
621 cx: &'a task::TaskContext,
622 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
623 self.templates.iter().filter_map(|(kind, template)| {
624 template
625 .resolve_task(&kind.to_id_base(), cx)
626 .map(|task| (kind.clone(), task))
627 })
628 }
629}
630
631#[derive(Clone)]
632struct ResolvedTasks {
633 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
634 position: Anchor,
635}
636
637#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
638struct BufferOffset(usize);
639
640// Addons allow storing per-editor state in other crates (e.g. Vim)
641pub trait Addon: 'static {
642 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
643
644 fn render_buffer_header_controls(
645 &self,
646 _: &ExcerptInfo,
647 _: &Window,
648 _: &App,
649 ) -> Option<AnyElement> {
650 None
651 }
652
653 fn to_any(&self) -> &dyn std::any::Any;
654}
655
656/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
657///
658/// See the [module level documentation](self) for more information.
659pub struct Editor {
660 focus_handle: FocusHandle,
661 last_focused_descendant: Option<WeakFocusHandle>,
662 /// The text buffer being edited
663 buffer: Entity<MultiBuffer>,
664 /// Map of how text in the buffer should be displayed.
665 /// Handles soft wraps, folds, fake inlay text insertions, etc.
666 pub display_map: Entity<DisplayMap>,
667 pub selections: SelectionsCollection,
668 pub scroll_manager: ScrollManager,
669 /// When inline assist editors are linked, they all render cursors because
670 /// typing enters text into each of them, even the ones that aren't focused.
671 pub(crate) show_cursor_when_unfocused: bool,
672 columnar_selection_tail: Option<Anchor>,
673 add_selections_state: Option<AddSelectionsState>,
674 select_next_state: Option<SelectNextState>,
675 select_prev_state: Option<SelectNextState>,
676 selection_history: SelectionHistory,
677 autoclose_regions: Vec<AutocloseRegion>,
678 snippet_stack: InvalidationStack<SnippetState>,
679 select_syntax_node_history: SelectSyntaxNodeHistory,
680 ime_transaction: Option<TransactionId>,
681 active_diagnostics: Option<ActiveDiagnosticGroup>,
682 show_inline_diagnostics: bool,
683 inline_diagnostics_update: Task<()>,
684 inline_diagnostics_enabled: bool,
685 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
686 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
687 hard_wrap: Option<usize>,
688
689 // TODO: make this a access method
690 pub project: Option<Entity<Project>>,
691 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
692 completion_provider: Option<Box<dyn CompletionProvider>>,
693 collaboration_hub: Option<Box<dyn CollaborationHub>>,
694 blink_manager: Entity<BlinkManager>,
695 show_cursor_names: bool,
696 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
697 pub show_local_selections: bool,
698 mode: EditorMode,
699 show_breadcrumbs: bool,
700 show_gutter: bool,
701 show_scrollbars: bool,
702 show_line_numbers: Option<bool>,
703 use_relative_line_numbers: Option<bool>,
704 show_git_diff_gutter: Option<bool>,
705 show_code_actions: Option<bool>,
706 show_runnables: Option<bool>,
707 show_breakpoints: Option<bool>,
708 show_wrap_guides: Option<bool>,
709 show_indent_guides: Option<bool>,
710 placeholder_text: Option<Arc<str>>,
711 highlight_order: usize,
712 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
713 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
714 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
715 scrollbar_marker_state: ScrollbarMarkerState,
716 active_indent_guides_state: ActiveIndentGuidesState,
717 nav_history: Option<ItemNavHistory>,
718 context_menu: RefCell<Option<CodeContextMenu>>,
719 context_menu_options: Option<ContextMenuOptions>,
720 mouse_context_menu: Option<MouseContextMenu>,
721 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
722 signature_help_state: SignatureHelpState,
723 auto_signature_help: Option<bool>,
724 find_all_references_task_sources: Vec<Anchor>,
725 next_completion_id: CompletionId,
726 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
727 code_actions_task: Option<Task<Result<()>>>,
728 selection_highlight_task: Option<Task<()>>,
729 document_highlights_task: Option<Task<()>>,
730 linked_editing_range_task: Option<Task<Option<()>>>,
731 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
732 pending_rename: Option<RenameState>,
733 searchable: bool,
734 cursor_shape: CursorShape,
735 current_line_highlight: Option<CurrentLineHighlight>,
736 collapse_matches: bool,
737 autoindent_mode: Option<AutoindentMode>,
738 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
739 input_enabled: bool,
740 use_modal_editing: bool,
741 read_only: bool,
742 leader_peer_id: Option<PeerId>,
743 remote_id: Option<ViewId>,
744 hover_state: HoverState,
745 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
746 gutter_hovered: bool,
747 hovered_link_state: Option<HoveredLinkState>,
748 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
749 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
750 active_inline_completion: Option<InlineCompletionState>,
751 /// Used to prevent flickering as the user types while the menu is open
752 stale_inline_completion_in_menu: Option<InlineCompletionState>,
753 edit_prediction_settings: EditPredictionSettings,
754 inline_completions_hidden_for_vim_mode: bool,
755 show_inline_completions_override: Option<bool>,
756 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
757 edit_prediction_preview: EditPredictionPreview,
758 edit_prediction_indent_conflict: bool,
759 edit_prediction_requires_modifier_in_indent_conflict: bool,
760 inlay_hint_cache: InlayHintCache,
761 next_inlay_id: usize,
762 _subscriptions: Vec<Subscription>,
763 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
764 gutter_dimensions: GutterDimensions,
765 style: Option<EditorStyle>,
766 text_style_refinement: Option<TextStyleRefinement>,
767 next_editor_action_id: EditorActionId,
768 editor_actions:
769 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
770 use_autoclose: bool,
771 use_auto_surround: bool,
772 auto_replace_emoji_shortcode: bool,
773 jsx_tag_auto_close_enabled_in_any_buffer: bool,
774 show_git_blame_gutter: bool,
775 show_git_blame_inline: bool,
776 show_git_blame_inline_delay_task: Option<Task<()>>,
777 pub git_blame_inline_tooltip: Option<AnyWeakEntity>,
778 git_blame_inline_enabled: bool,
779 render_diff_hunk_controls: RenderDiffHunkControlsFn,
780 serialize_dirty_buffers: bool,
781 show_selection_menu: Option<bool>,
782 blame: Option<Entity<GitBlame>>,
783 blame_subscription: Option<Subscription>,
784 custom_context_menu: Option<
785 Box<
786 dyn 'static
787 + Fn(
788 &mut Self,
789 DisplayPoint,
790 &mut Window,
791 &mut Context<Self>,
792 ) -> Option<Entity<ui::ContextMenu>>,
793 >,
794 >,
795 last_bounds: Option<Bounds<Pixels>>,
796 last_position_map: Option<Rc<PositionMap>>,
797 expect_bounds_change: Option<Bounds<Pixels>>,
798 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
799 tasks_update_task: Option<Task<()>>,
800 breakpoint_store: Option<Entity<BreakpointStore>>,
801 /// Allow's a user to create a breakpoint by selecting this indicator
802 /// It should be None while a user is not hovering over the gutter
803 /// Otherwise it represents the point that the breakpoint will be shown
804 gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
805 in_project_search: bool,
806 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
807 breadcrumb_header: Option<String>,
808 focused_block: Option<FocusedBlock>,
809 next_scroll_position: NextScrollCursorCenterTopBottom,
810 addons: HashMap<TypeId, Box<dyn Addon>>,
811 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
812 load_diff_task: Option<Shared<Task<()>>>,
813 selection_mark_mode: bool,
814 toggle_fold_multiple_buffers: Task<()>,
815 _scroll_cursor_center_top_bottom_task: Task<()>,
816 serialize_selections: Task<()>,
817 serialize_folds: Task<()>,
818 mouse_cursor_hidden: bool,
819 hide_mouse_mode: HideMouseMode,
820}
821
822#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
823enum NextScrollCursorCenterTopBottom {
824 #[default]
825 Center,
826 Top,
827 Bottom,
828}
829
830impl NextScrollCursorCenterTopBottom {
831 fn next(&self) -> Self {
832 match self {
833 Self::Center => Self::Top,
834 Self::Top => Self::Bottom,
835 Self::Bottom => Self::Center,
836 }
837 }
838}
839
840#[derive(Clone)]
841pub struct EditorSnapshot {
842 pub mode: EditorMode,
843 show_gutter: bool,
844 show_line_numbers: Option<bool>,
845 show_git_diff_gutter: Option<bool>,
846 show_code_actions: Option<bool>,
847 show_runnables: Option<bool>,
848 show_breakpoints: Option<bool>,
849 git_blame_gutter_max_author_length: Option<usize>,
850 pub display_snapshot: DisplaySnapshot,
851 pub placeholder_text: Option<Arc<str>>,
852 is_focused: bool,
853 scroll_anchor: ScrollAnchor,
854 ongoing_scroll: OngoingScroll,
855 current_line_highlight: CurrentLineHighlight,
856 gutter_hovered: bool,
857}
858
859#[derive(Default, Debug, Clone, Copy)]
860pub struct GutterDimensions {
861 pub left_padding: Pixels,
862 pub right_padding: Pixels,
863 pub width: Pixels,
864 pub margin: Pixels,
865 pub git_blame_entries_width: Option<Pixels>,
866}
867
868impl GutterDimensions {
869 /// The full width of the space taken up by the gutter.
870 pub fn full_width(&self) -> Pixels {
871 self.margin + self.width
872 }
873
874 /// The width of the space reserved for the fold indicators,
875 /// use alongside 'justify_end' and `gutter_width` to
876 /// right align content with the line numbers
877 pub fn fold_area_width(&self) -> Pixels {
878 self.margin + self.right_padding
879 }
880}
881
882#[derive(Debug)]
883pub struct RemoteSelection {
884 pub replica_id: ReplicaId,
885 pub selection: Selection<Anchor>,
886 pub cursor_shape: CursorShape,
887 pub peer_id: PeerId,
888 pub line_mode: bool,
889 pub participant_index: Option<ParticipantIndex>,
890 pub user_name: Option<SharedString>,
891}
892
893#[derive(Clone, Debug)]
894struct SelectionHistoryEntry {
895 selections: Arc<[Selection<Anchor>]>,
896 select_next_state: Option<SelectNextState>,
897 select_prev_state: Option<SelectNextState>,
898 add_selections_state: Option<AddSelectionsState>,
899}
900
901enum SelectionHistoryMode {
902 Normal,
903 Undoing,
904 Redoing,
905}
906
907#[derive(Clone, PartialEq, Eq, Hash)]
908struct HoveredCursor {
909 replica_id: u16,
910 selection_id: usize,
911}
912
913impl Default for SelectionHistoryMode {
914 fn default() -> Self {
915 Self::Normal
916 }
917}
918
919#[derive(Default)]
920struct SelectionHistory {
921 #[allow(clippy::type_complexity)]
922 selections_by_transaction:
923 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
924 mode: SelectionHistoryMode,
925 undo_stack: VecDeque<SelectionHistoryEntry>,
926 redo_stack: VecDeque<SelectionHistoryEntry>,
927}
928
929impl SelectionHistory {
930 fn insert_transaction(
931 &mut self,
932 transaction_id: TransactionId,
933 selections: Arc<[Selection<Anchor>]>,
934 ) {
935 self.selections_by_transaction
936 .insert(transaction_id, (selections, None));
937 }
938
939 #[allow(clippy::type_complexity)]
940 fn transaction(
941 &self,
942 transaction_id: TransactionId,
943 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
944 self.selections_by_transaction.get(&transaction_id)
945 }
946
947 #[allow(clippy::type_complexity)]
948 fn transaction_mut(
949 &mut self,
950 transaction_id: TransactionId,
951 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
952 self.selections_by_transaction.get_mut(&transaction_id)
953 }
954
955 fn push(&mut self, entry: SelectionHistoryEntry) {
956 if !entry.selections.is_empty() {
957 match self.mode {
958 SelectionHistoryMode::Normal => {
959 self.push_undo(entry);
960 self.redo_stack.clear();
961 }
962 SelectionHistoryMode::Undoing => self.push_redo(entry),
963 SelectionHistoryMode::Redoing => self.push_undo(entry),
964 }
965 }
966 }
967
968 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
969 if self
970 .undo_stack
971 .back()
972 .map_or(true, |e| e.selections != entry.selections)
973 {
974 self.undo_stack.push_back(entry);
975 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
976 self.undo_stack.pop_front();
977 }
978 }
979 }
980
981 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
982 if self
983 .redo_stack
984 .back()
985 .map_or(true, |e| e.selections != entry.selections)
986 {
987 self.redo_stack.push_back(entry);
988 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
989 self.redo_stack.pop_front();
990 }
991 }
992 }
993}
994
995struct RowHighlight {
996 index: usize,
997 range: Range<Anchor>,
998 color: Hsla,
999 should_autoscroll: bool,
1000}
1001
1002#[derive(Clone, Debug)]
1003struct AddSelectionsState {
1004 above: bool,
1005 stack: Vec<usize>,
1006}
1007
1008#[derive(Clone)]
1009struct SelectNextState {
1010 query: AhoCorasick,
1011 wordwise: bool,
1012 done: bool,
1013}
1014
1015impl std::fmt::Debug for SelectNextState {
1016 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1017 f.debug_struct(std::any::type_name::<Self>())
1018 .field("wordwise", &self.wordwise)
1019 .field("done", &self.done)
1020 .finish()
1021 }
1022}
1023
1024#[derive(Debug)]
1025struct AutocloseRegion {
1026 selection_id: usize,
1027 range: Range<Anchor>,
1028 pair: BracketPair,
1029}
1030
1031#[derive(Debug)]
1032struct SnippetState {
1033 ranges: Vec<Vec<Range<Anchor>>>,
1034 active_index: usize,
1035 choices: Vec<Option<Vec<String>>>,
1036}
1037
1038#[doc(hidden)]
1039pub struct RenameState {
1040 pub range: Range<Anchor>,
1041 pub old_name: Arc<str>,
1042 pub editor: Entity<Editor>,
1043 block_id: CustomBlockId,
1044}
1045
1046struct InvalidationStack<T>(Vec<T>);
1047
1048struct RegisteredInlineCompletionProvider {
1049 provider: Arc<dyn InlineCompletionProviderHandle>,
1050 _subscription: Subscription,
1051}
1052
1053#[derive(Debug, PartialEq, Eq)]
1054struct ActiveDiagnosticGroup {
1055 primary_range: Range<Anchor>,
1056 primary_message: String,
1057 group_id: usize,
1058 blocks: HashMap<CustomBlockId, Diagnostic>,
1059 is_valid: bool,
1060}
1061
1062#[derive(Serialize, Deserialize, Clone, Debug)]
1063pub struct ClipboardSelection {
1064 /// The number of bytes in this selection.
1065 pub len: usize,
1066 /// Whether this was a full-line selection.
1067 pub is_entire_line: bool,
1068 /// The indentation of the first line when this content was originally copied.
1069 pub first_line_indent: u32,
1070}
1071
1072// selections, scroll behavior, was newest selection reversed
1073type SelectSyntaxNodeHistoryState = (
1074 Box<[Selection<usize>]>,
1075 SelectSyntaxNodeScrollBehavior,
1076 bool,
1077);
1078
1079#[derive(Default)]
1080struct SelectSyntaxNodeHistory {
1081 stack: Vec<SelectSyntaxNodeHistoryState>,
1082 // disable temporarily to allow changing selections without losing the stack
1083 pub disable_clearing: bool,
1084}
1085
1086impl SelectSyntaxNodeHistory {
1087 pub fn try_clear(&mut self) {
1088 if !self.disable_clearing {
1089 self.stack.clear();
1090 }
1091 }
1092
1093 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1094 self.stack.push(selection);
1095 }
1096
1097 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1098 self.stack.pop()
1099 }
1100}
1101
1102enum SelectSyntaxNodeScrollBehavior {
1103 CursorTop,
1104 CenterSelection,
1105 CursorBottom,
1106}
1107
1108#[derive(Debug)]
1109pub(crate) struct NavigationData {
1110 cursor_anchor: Anchor,
1111 cursor_position: Point,
1112 scroll_anchor: ScrollAnchor,
1113 scroll_top_row: u32,
1114}
1115
1116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1117pub enum GotoDefinitionKind {
1118 Symbol,
1119 Declaration,
1120 Type,
1121 Implementation,
1122}
1123
1124#[derive(Debug, Clone)]
1125enum InlayHintRefreshReason {
1126 ModifiersChanged(bool),
1127 Toggle(bool),
1128 SettingsChange(InlayHintSettings),
1129 NewLinesShown,
1130 BufferEdited(HashSet<Arc<Language>>),
1131 RefreshRequested,
1132 ExcerptsRemoved(Vec<ExcerptId>),
1133}
1134
1135impl InlayHintRefreshReason {
1136 fn description(&self) -> &'static str {
1137 match self {
1138 Self::ModifiersChanged(_) => "modifiers changed",
1139 Self::Toggle(_) => "toggle",
1140 Self::SettingsChange(_) => "settings change",
1141 Self::NewLinesShown => "new lines shown",
1142 Self::BufferEdited(_) => "buffer edited",
1143 Self::RefreshRequested => "refresh requested",
1144 Self::ExcerptsRemoved(_) => "excerpts removed",
1145 }
1146 }
1147}
1148
1149pub enum FormatTarget {
1150 Buffers,
1151 Ranges(Vec<Range<MultiBufferPoint>>),
1152}
1153
1154pub(crate) struct FocusedBlock {
1155 id: BlockId,
1156 focus_handle: WeakFocusHandle,
1157}
1158
1159#[derive(Clone)]
1160enum JumpData {
1161 MultiBufferRow {
1162 row: MultiBufferRow,
1163 line_offset_from_top: u32,
1164 },
1165 MultiBufferPoint {
1166 excerpt_id: ExcerptId,
1167 position: Point,
1168 anchor: text::Anchor,
1169 line_offset_from_top: u32,
1170 },
1171}
1172
1173pub enum MultibufferSelectionMode {
1174 First,
1175 All,
1176}
1177
1178#[derive(Clone, Copy, Debug, Default)]
1179pub struct RewrapOptions {
1180 pub override_language_settings: bool,
1181 pub preserve_existing_whitespace: bool,
1182}
1183
1184impl Editor {
1185 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1186 let buffer = cx.new(|cx| Buffer::local("", cx));
1187 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1188 Self::new(
1189 EditorMode::SingleLine { auto_width: false },
1190 buffer,
1191 None,
1192 window,
1193 cx,
1194 )
1195 }
1196
1197 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1198 let buffer = cx.new(|cx| Buffer::local("", cx));
1199 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1200 Self::new(EditorMode::Full, buffer, None, window, cx)
1201 }
1202
1203 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1204 let buffer = cx.new(|cx| Buffer::local("", cx));
1205 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1206 Self::new(
1207 EditorMode::SingleLine { auto_width: true },
1208 buffer,
1209 None,
1210 window,
1211 cx,
1212 )
1213 }
1214
1215 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1216 let buffer = cx.new(|cx| Buffer::local("", cx));
1217 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1218 Self::new(
1219 EditorMode::AutoHeight { max_lines },
1220 buffer,
1221 None,
1222 window,
1223 cx,
1224 )
1225 }
1226
1227 pub fn for_buffer(
1228 buffer: Entity<Buffer>,
1229 project: Option<Entity<Project>>,
1230 window: &mut Window,
1231 cx: &mut Context<Self>,
1232 ) -> Self {
1233 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1234 Self::new(EditorMode::Full, buffer, project, window, cx)
1235 }
1236
1237 pub fn for_multibuffer(
1238 buffer: Entity<MultiBuffer>,
1239 project: Option<Entity<Project>>,
1240 window: &mut Window,
1241 cx: &mut Context<Self>,
1242 ) -> Self {
1243 Self::new(EditorMode::Full, buffer, project, window, cx)
1244 }
1245
1246 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1247 let mut clone = Self::new(
1248 self.mode,
1249 self.buffer.clone(),
1250 self.project.clone(),
1251 window,
1252 cx,
1253 );
1254 self.display_map.update(cx, |display_map, cx| {
1255 let snapshot = display_map.snapshot(cx);
1256 clone.display_map.update(cx, |display_map, cx| {
1257 display_map.set_state(&snapshot, cx);
1258 });
1259 });
1260 clone.folds_did_change(cx);
1261 clone.selections.clone_state(&self.selections);
1262 clone.scroll_manager.clone_state(&self.scroll_manager);
1263 clone.searchable = self.searchable;
1264 clone
1265 }
1266
1267 pub fn new(
1268 mode: EditorMode,
1269 buffer: Entity<MultiBuffer>,
1270 project: Option<Entity<Project>>,
1271 window: &mut Window,
1272 cx: &mut Context<Self>,
1273 ) -> Self {
1274 let style = window.text_style();
1275 let font_size = style.font_size.to_pixels(window.rem_size());
1276 let editor = cx.entity().downgrade();
1277 let fold_placeholder = FoldPlaceholder {
1278 constrain_width: true,
1279 render: Arc::new(move |fold_id, fold_range, cx| {
1280 let editor = editor.clone();
1281 div()
1282 .id(fold_id)
1283 .bg(cx.theme().colors().ghost_element_background)
1284 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1285 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1286 .rounded_xs()
1287 .size_full()
1288 .cursor_pointer()
1289 .child("⋯")
1290 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1291 .on_click(move |_, _window, cx| {
1292 editor
1293 .update(cx, |editor, cx| {
1294 editor.unfold_ranges(
1295 &[fold_range.start..fold_range.end],
1296 true,
1297 false,
1298 cx,
1299 );
1300 cx.stop_propagation();
1301 })
1302 .ok();
1303 })
1304 .into_any()
1305 }),
1306 merge_adjacent: true,
1307 ..Default::default()
1308 };
1309 let display_map = cx.new(|cx| {
1310 DisplayMap::new(
1311 buffer.clone(),
1312 style.font(),
1313 font_size,
1314 None,
1315 FILE_HEADER_HEIGHT,
1316 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1317 fold_placeholder,
1318 cx,
1319 )
1320 });
1321
1322 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1323
1324 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1325
1326 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1327 .then(|| language_settings::SoftWrap::None);
1328
1329 let mut project_subscriptions = Vec::new();
1330 if mode == EditorMode::Full {
1331 if let Some(project) = project.as_ref() {
1332 project_subscriptions.push(cx.subscribe_in(
1333 project,
1334 window,
1335 |editor, _, event, window, cx| match event {
1336 project::Event::RefreshCodeLens => {
1337 // we always query lens with actions, without storing them, always refreshing them
1338 }
1339 project::Event::RefreshInlayHints => {
1340 editor
1341 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1342 }
1343 project::Event::SnippetEdit(id, snippet_edits) => {
1344 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1345 let focus_handle = editor.focus_handle(cx);
1346 if focus_handle.is_focused(window) {
1347 let snapshot = buffer.read(cx).snapshot();
1348 for (range, snippet) in snippet_edits {
1349 let editor_range =
1350 language::range_from_lsp(*range).to_offset(&snapshot);
1351 editor
1352 .insert_snippet(
1353 &[editor_range],
1354 snippet.clone(),
1355 window,
1356 cx,
1357 )
1358 .ok();
1359 }
1360 }
1361 }
1362 }
1363 _ => {}
1364 },
1365 ));
1366 if let Some(task_inventory) = project
1367 .read(cx)
1368 .task_store()
1369 .read(cx)
1370 .task_inventory()
1371 .cloned()
1372 {
1373 project_subscriptions.push(cx.observe_in(
1374 &task_inventory,
1375 window,
1376 |editor, _, window, cx| {
1377 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1378 },
1379 ));
1380 };
1381
1382 project_subscriptions.push(cx.subscribe_in(
1383 &project.read(cx).breakpoint_store(),
1384 window,
1385 |editor, _, event, window, cx| match event {
1386 BreakpointStoreEvent::ActiveDebugLineChanged => {
1387 editor.go_to_active_debug_line(window, cx);
1388 }
1389 _ => {}
1390 },
1391 ));
1392 }
1393 }
1394
1395 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1396
1397 let inlay_hint_settings =
1398 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1399 let focus_handle = cx.focus_handle();
1400 cx.on_focus(&focus_handle, window, Self::handle_focus)
1401 .detach();
1402 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1403 .detach();
1404 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1405 .detach();
1406 cx.on_blur(&focus_handle, window, Self::handle_blur)
1407 .detach();
1408
1409 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1410 Some(false)
1411 } else {
1412 None
1413 };
1414
1415 let breakpoint_store = match (mode, project.as_ref()) {
1416 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1417 _ => None,
1418 };
1419
1420 let mut code_action_providers = Vec::new();
1421 let mut load_uncommitted_diff = None;
1422 if let Some(project) = project.clone() {
1423 load_uncommitted_diff = Some(
1424 get_uncommitted_diff_for_buffer(
1425 &project,
1426 buffer.read(cx).all_buffers(),
1427 buffer.clone(),
1428 cx,
1429 )
1430 .shared(),
1431 );
1432 code_action_providers.push(Rc::new(project) as Rc<_>);
1433 }
1434
1435 let mut this = Self {
1436 focus_handle,
1437 show_cursor_when_unfocused: false,
1438 last_focused_descendant: None,
1439 buffer: buffer.clone(),
1440 display_map: display_map.clone(),
1441 selections,
1442 scroll_manager: ScrollManager::new(cx),
1443 columnar_selection_tail: None,
1444 add_selections_state: None,
1445 select_next_state: None,
1446 select_prev_state: None,
1447 selection_history: Default::default(),
1448 autoclose_regions: Default::default(),
1449 snippet_stack: Default::default(),
1450 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1451 ime_transaction: Default::default(),
1452 active_diagnostics: None,
1453 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1454 inline_diagnostics_update: Task::ready(()),
1455 inline_diagnostics: Vec::new(),
1456 soft_wrap_mode_override,
1457 hard_wrap: None,
1458 completion_provider: project.clone().map(|project| Box::new(project) as _),
1459 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1460 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1461 project,
1462 blink_manager: blink_manager.clone(),
1463 show_local_selections: true,
1464 show_scrollbars: true,
1465 mode,
1466 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1467 show_gutter: mode == EditorMode::Full,
1468 show_line_numbers: None,
1469 use_relative_line_numbers: None,
1470 show_git_diff_gutter: None,
1471 show_code_actions: None,
1472 show_runnables: None,
1473 show_breakpoints: None,
1474 show_wrap_guides: None,
1475 show_indent_guides,
1476 placeholder_text: None,
1477 highlight_order: 0,
1478 highlighted_rows: HashMap::default(),
1479 background_highlights: Default::default(),
1480 gutter_highlights: TreeMap::default(),
1481 scrollbar_marker_state: ScrollbarMarkerState::default(),
1482 active_indent_guides_state: ActiveIndentGuidesState::default(),
1483 nav_history: None,
1484 context_menu: RefCell::new(None),
1485 context_menu_options: None,
1486 mouse_context_menu: None,
1487 completion_tasks: Default::default(),
1488 signature_help_state: SignatureHelpState::default(),
1489 auto_signature_help: None,
1490 find_all_references_task_sources: Vec::new(),
1491 next_completion_id: 0,
1492 next_inlay_id: 0,
1493 code_action_providers,
1494 available_code_actions: Default::default(),
1495 code_actions_task: Default::default(),
1496 selection_highlight_task: Default::default(),
1497 document_highlights_task: Default::default(),
1498 linked_editing_range_task: Default::default(),
1499 pending_rename: Default::default(),
1500 searchable: true,
1501 cursor_shape: EditorSettings::get_global(cx)
1502 .cursor_shape
1503 .unwrap_or_default(),
1504 current_line_highlight: None,
1505 autoindent_mode: Some(AutoindentMode::EachLine),
1506 collapse_matches: false,
1507 workspace: None,
1508 input_enabled: true,
1509 use_modal_editing: mode == EditorMode::Full,
1510 read_only: false,
1511 use_autoclose: true,
1512 use_auto_surround: true,
1513 auto_replace_emoji_shortcode: false,
1514 jsx_tag_auto_close_enabled_in_any_buffer: false,
1515 leader_peer_id: None,
1516 remote_id: None,
1517 hover_state: Default::default(),
1518 pending_mouse_down: None,
1519 hovered_link_state: Default::default(),
1520 edit_prediction_provider: None,
1521 active_inline_completion: None,
1522 stale_inline_completion_in_menu: None,
1523 edit_prediction_preview: EditPredictionPreview::Inactive {
1524 released_too_fast: false,
1525 },
1526 inline_diagnostics_enabled: mode == EditorMode::Full,
1527 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1528
1529 gutter_hovered: false,
1530 pixel_position_of_newest_cursor: None,
1531 last_bounds: None,
1532 last_position_map: None,
1533 expect_bounds_change: None,
1534 gutter_dimensions: GutterDimensions::default(),
1535 style: None,
1536 show_cursor_names: false,
1537 hovered_cursors: Default::default(),
1538 next_editor_action_id: EditorActionId::default(),
1539 editor_actions: Rc::default(),
1540 inline_completions_hidden_for_vim_mode: false,
1541 show_inline_completions_override: None,
1542 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1543 edit_prediction_settings: EditPredictionSettings::Disabled,
1544 edit_prediction_indent_conflict: false,
1545 edit_prediction_requires_modifier_in_indent_conflict: true,
1546 custom_context_menu: None,
1547 show_git_blame_gutter: false,
1548 show_git_blame_inline: false,
1549 show_selection_menu: None,
1550 show_git_blame_inline_delay_task: None,
1551 git_blame_inline_tooltip: None,
1552 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1553 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1554 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1555 .session
1556 .restore_unsaved_buffers,
1557 blame: None,
1558 blame_subscription: None,
1559 tasks: Default::default(),
1560
1561 breakpoint_store,
1562 gutter_breakpoint_indicator: (None, None),
1563 _subscriptions: vec![
1564 cx.observe(&buffer, Self::on_buffer_changed),
1565 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1566 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1567 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1568 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1569 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1570 cx.observe_window_activation(window, |editor, window, cx| {
1571 let active = window.is_window_active();
1572 editor.blink_manager.update(cx, |blink_manager, cx| {
1573 if active {
1574 blink_manager.enable(cx);
1575 } else {
1576 blink_manager.disable(cx);
1577 }
1578 });
1579 }),
1580 ],
1581 tasks_update_task: None,
1582 linked_edit_ranges: Default::default(),
1583 in_project_search: false,
1584 previous_search_ranges: None,
1585 breadcrumb_header: None,
1586 focused_block: None,
1587 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1588 addons: HashMap::default(),
1589 registered_buffers: HashMap::default(),
1590 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1591 selection_mark_mode: false,
1592 toggle_fold_multiple_buffers: Task::ready(()),
1593 serialize_selections: Task::ready(()),
1594 serialize_folds: Task::ready(()),
1595 text_style_refinement: None,
1596 load_diff_task: load_uncommitted_diff,
1597 mouse_cursor_hidden: false,
1598 hide_mouse_mode: EditorSettings::get_global(cx)
1599 .hide_mouse
1600 .unwrap_or_default(),
1601 };
1602 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1603 this._subscriptions
1604 .push(cx.observe(breakpoints, |_, _, cx| {
1605 cx.notify();
1606 }));
1607 }
1608 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1609 this._subscriptions.extend(project_subscriptions);
1610
1611 this._subscriptions.push(cx.subscribe_in(
1612 &cx.entity(),
1613 window,
1614 |editor, _, e: &EditorEvent, window, cx| {
1615 if let EditorEvent::SelectionsChanged { local } = e {
1616 if *local {
1617 let new_anchor = editor.scroll_manager.anchor();
1618 let snapshot = editor.snapshot(window, cx);
1619 editor.update_restoration_data(cx, move |data| {
1620 data.scroll_position = (
1621 new_anchor.top_row(&snapshot.buffer_snapshot),
1622 new_anchor.offset,
1623 );
1624 });
1625 }
1626 }
1627 },
1628 ));
1629
1630 this.end_selection(window, cx);
1631 this.scroll_manager.show_scrollbars(window, cx);
1632 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1633
1634 if mode == EditorMode::Full {
1635 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1636 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1637
1638 if this.git_blame_inline_enabled {
1639 this.git_blame_inline_enabled = true;
1640 this.start_git_blame_inline(false, window, cx);
1641 }
1642
1643 this.go_to_active_debug_line(window, cx);
1644
1645 if let Some(buffer) = buffer.read(cx).as_singleton() {
1646 if let Some(project) = this.project.as_ref() {
1647 let handle = project.update(cx, |project, cx| {
1648 project.register_buffer_with_language_servers(&buffer, cx)
1649 });
1650 this.registered_buffers
1651 .insert(buffer.read(cx).remote_id(), handle);
1652 }
1653 }
1654 }
1655
1656 this.report_editor_event("Editor Opened", None, cx);
1657 this
1658 }
1659
1660 pub fn deploy_mouse_context_menu(
1661 &mut self,
1662 position: gpui::Point<Pixels>,
1663 context_menu: Entity<ContextMenu>,
1664 window: &mut Window,
1665 cx: &mut Context<Self>,
1666 ) {
1667 self.mouse_context_menu = Some(MouseContextMenu::new(
1668 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1669 context_menu,
1670 window,
1671 cx,
1672 ));
1673 }
1674
1675 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1676 self.mouse_context_menu
1677 .as_ref()
1678 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1679 }
1680
1681 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1682 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1683 }
1684
1685 fn key_context_internal(
1686 &self,
1687 has_active_edit_prediction: bool,
1688 window: &Window,
1689 cx: &App,
1690 ) -> KeyContext {
1691 let mut key_context = KeyContext::new_with_defaults();
1692 key_context.add("Editor");
1693 let mode = match self.mode {
1694 EditorMode::SingleLine { .. } => "single_line",
1695 EditorMode::AutoHeight { .. } => "auto_height",
1696 EditorMode::Full => "full",
1697 };
1698
1699 if EditorSettings::jupyter_enabled(cx) {
1700 key_context.add("jupyter");
1701 }
1702
1703 key_context.set("mode", mode);
1704 if self.pending_rename.is_some() {
1705 key_context.add("renaming");
1706 }
1707
1708 match self.context_menu.borrow().as_ref() {
1709 Some(CodeContextMenu::Completions(_)) => {
1710 key_context.add("menu");
1711 key_context.add("showing_completions");
1712 }
1713 Some(CodeContextMenu::CodeActions(_)) => {
1714 key_context.add("menu");
1715 key_context.add("showing_code_actions")
1716 }
1717 None => {}
1718 }
1719
1720 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1721 if !self.focus_handle(cx).contains_focused(window, cx)
1722 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1723 {
1724 for addon in self.addons.values() {
1725 addon.extend_key_context(&mut key_context, cx)
1726 }
1727 }
1728
1729 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1730 if let Some(extension) = singleton_buffer
1731 .read(cx)
1732 .file()
1733 .and_then(|file| file.path().extension()?.to_str())
1734 {
1735 key_context.set("extension", extension.to_string());
1736 }
1737 } else {
1738 key_context.add("multibuffer");
1739 }
1740
1741 if has_active_edit_prediction {
1742 if self.edit_prediction_in_conflict() {
1743 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1744 } else {
1745 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1746 key_context.add("copilot_suggestion");
1747 }
1748 }
1749
1750 if self.selection_mark_mode {
1751 key_context.add("selection_mode");
1752 }
1753
1754 key_context
1755 }
1756
1757 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1758 self.mouse_cursor_hidden = match origin {
1759 HideMouseCursorOrigin::TypingAction => {
1760 matches!(
1761 self.hide_mouse_mode,
1762 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1763 )
1764 }
1765 HideMouseCursorOrigin::MovementAction => {
1766 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
1767 }
1768 };
1769 }
1770
1771 pub fn edit_prediction_in_conflict(&self) -> bool {
1772 if !self.show_edit_predictions_in_menu() {
1773 return false;
1774 }
1775
1776 let showing_completions = self
1777 .context_menu
1778 .borrow()
1779 .as_ref()
1780 .map_or(false, |context| {
1781 matches!(context, CodeContextMenu::Completions(_))
1782 });
1783
1784 showing_completions
1785 || self.edit_prediction_requires_modifier()
1786 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1787 // bindings to insert tab characters.
1788 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1789 }
1790
1791 pub fn accept_edit_prediction_keybind(
1792 &self,
1793 window: &Window,
1794 cx: &App,
1795 ) -> AcceptEditPredictionBinding {
1796 let key_context = self.key_context_internal(true, window, cx);
1797 let in_conflict = self.edit_prediction_in_conflict();
1798
1799 AcceptEditPredictionBinding(
1800 window
1801 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1802 .into_iter()
1803 .filter(|binding| {
1804 !in_conflict
1805 || binding
1806 .keystrokes()
1807 .first()
1808 .map_or(false, |keystroke| keystroke.modifiers.modified())
1809 })
1810 .rev()
1811 .min_by_key(|binding| {
1812 binding
1813 .keystrokes()
1814 .first()
1815 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1816 }),
1817 )
1818 }
1819
1820 pub fn new_file(
1821 workspace: &mut Workspace,
1822 _: &workspace::NewFile,
1823 window: &mut Window,
1824 cx: &mut Context<Workspace>,
1825 ) {
1826 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1827 "Failed to create buffer",
1828 window,
1829 cx,
1830 |e, _, _| match e.error_code() {
1831 ErrorCode::RemoteUpgradeRequired => Some(format!(
1832 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1833 e.error_tag("required").unwrap_or("the latest version")
1834 )),
1835 _ => None,
1836 },
1837 );
1838 }
1839
1840 pub fn new_in_workspace(
1841 workspace: &mut Workspace,
1842 window: &mut Window,
1843 cx: &mut Context<Workspace>,
1844 ) -> Task<Result<Entity<Editor>>> {
1845 let project = workspace.project().clone();
1846 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1847
1848 cx.spawn_in(window, async move |workspace, cx| {
1849 let buffer = create.await?;
1850 workspace.update_in(cx, |workspace, window, cx| {
1851 let editor =
1852 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1853 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1854 editor
1855 })
1856 })
1857 }
1858
1859 fn new_file_vertical(
1860 workspace: &mut Workspace,
1861 _: &workspace::NewFileSplitVertical,
1862 window: &mut Window,
1863 cx: &mut Context<Workspace>,
1864 ) {
1865 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1866 }
1867
1868 fn new_file_horizontal(
1869 workspace: &mut Workspace,
1870 _: &workspace::NewFileSplitHorizontal,
1871 window: &mut Window,
1872 cx: &mut Context<Workspace>,
1873 ) {
1874 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1875 }
1876
1877 fn new_file_in_direction(
1878 workspace: &mut Workspace,
1879 direction: SplitDirection,
1880 window: &mut Window,
1881 cx: &mut Context<Workspace>,
1882 ) {
1883 let project = workspace.project().clone();
1884 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1885
1886 cx.spawn_in(window, async move |workspace, cx| {
1887 let buffer = create.await?;
1888 workspace.update_in(cx, move |workspace, window, cx| {
1889 workspace.split_item(
1890 direction,
1891 Box::new(
1892 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1893 ),
1894 window,
1895 cx,
1896 )
1897 })?;
1898 anyhow::Ok(())
1899 })
1900 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1901 match e.error_code() {
1902 ErrorCode::RemoteUpgradeRequired => Some(format!(
1903 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1904 e.error_tag("required").unwrap_or("the latest version")
1905 )),
1906 _ => None,
1907 }
1908 });
1909 }
1910
1911 pub fn leader_peer_id(&self) -> Option<PeerId> {
1912 self.leader_peer_id
1913 }
1914
1915 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1916 &self.buffer
1917 }
1918
1919 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1920 self.workspace.as_ref()?.0.upgrade()
1921 }
1922
1923 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1924 self.buffer().read(cx).title(cx)
1925 }
1926
1927 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1928 let git_blame_gutter_max_author_length = self
1929 .render_git_blame_gutter(cx)
1930 .then(|| {
1931 if let Some(blame) = self.blame.as_ref() {
1932 let max_author_length =
1933 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1934 Some(max_author_length)
1935 } else {
1936 None
1937 }
1938 })
1939 .flatten();
1940
1941 EditorSnapshot {
1942 mode: self.mode,
1943 show_gutter: self.show_gutter,
1944 show_line_numbers: self.show_line_numbers,
1945 show_git_diff_gutter: self.show_git_diff_gutter,
1946 show_code_actions: self.show_code_actions,
1947 show_runnables: self.show_runnables,
1948 show_breakpoints: self.show_breakpoints,
1949 git_blame_gutter_max_author_length,
1950 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1951 scroll_anchor: self.scroll_manager.anchor(),
1952 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1953 placeholder_text: self.placeholder_text.clone(),
1954 is_focused: self.focus_handle.is_focused(window),
1955 current_line_highlight: self
1956 .current_line_highlight
1957 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1958 gutter_hovered: self.gutter_hovered,
1959 }
1960 }
1961
1962 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1963 self.buffer.read(cx).language_at(point, cx)
1964 }
1965
1966 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1967 self.buffer.read(cx).read(cx).file_at(point).cloned()
1968 }
1969
1970 pub fn active_excerpt(
1971 &self,
1972 cx: &App,
1973 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1974 self.buffer
1975 .read(cx)
1976 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1977 }
1978
1979 pub fn mode(&self) -> EditorMode {
1980 self.mode
1981 }
1982
1983 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1984 self.collaboration_hub.as_deref()
1985 }
1986
1987 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1988 self.collaboration_hub = Some(hub);
1989 }
1990
1991 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1992 self.in_project_search = in_project_search;
1993 }
1994
1995 pub fn set_custom_context_menu(
1996 &mut self,
1997 f: impl 'static
1998 + Fn(
1999 &mut Self,
2000 DisplayPoint,
2001 &mut Window,
2002 &mut Context<Self>,
2003 ) -> Option<Entity<ui::ContextMenu>>,
2004 ) {
2005 self.custom_context_menu = Some(Box::new(f))
2006 }
2007
2008 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2009 self.completion_provider = provider;
2010 }
2011
2012 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2013 self.semantics_provider.clone()
2014 }
2015
2016 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2017 self.semantics_provider = provider;
2018 }
2019
2020 pub fn set_edit_prediction_provider<T>(
2021 &mut self,
2022 provider: Option<Entity<T>>,
2023 window: &mut Window,
2024 cx: &mut Context<Self>,
2025 ) where
2026 T: EditPredictionProvider,
2027 {
2028 self.edit_prediction_provider =
2029 provider.map(|provider| RegisteredInlineCompletionProvider {
2030 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2031 if this.focus_handle.is_focused(window) {
2032 this.update_visible_inline_completion(window, cx);
2033 }
2034 }),
2035 provider: Arc::new(provider),
2036 });
2037 self.update_edit_prediction_settings(cx);
2038 self.refresh_inline_completion(false, false, window, cx);
2039 }
2040
2041 pub fn placeholder_text(&self) -> Option<&str> {
2042 self.placeholder_text.as_deref()
2043 }
2044
2045 pub fn set_placeholder_text(
2046 &mut self,
2047 placeholder_text: impl Into<Arc<str>>,
2048 cx: &mut Context<Self>,
2049 ) {
2050 let placeholder_text = Some(placeholder_text.into());
2051 if self.placeholder_text != placeholder_text {
2052 self.placeholder_text = placeholder_text;
2053 cx.notify();
2054 }
2055 }
2056
2057 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2058 self.cursor_shape = cursor_shape;
2059
2060 // Disrupt blink for immediate user feedback that the cursor shape has changed
2061 self.blink_manager.update(cx, BlinkManager::show_cursor);
2062
2063 cx.notify();
2064 }
2065
2066 pub fn set_current_line_highlight(
2067 &mut self,
2068 current_line_highlight: Option<CurrentLineHighlight>,
2069 ) {
2070 self.current_line_highlight = current_line_highlight;
2071 }
2072
2073 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2074 self.collapse_matches = collapse_matches;
2075 }
2076
2077 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2078 let buffers = self.buffer.read(cx).all_buffers();
2079 let Some(project) = self.project.as_ref() else {
2080 return;
2081 };
2082 project.update(cx, |project, cx| {
2083 for buffer in buffers {
2084 self.registered_buffers
2085 .entry(buffer.read(cx).remote_id())
2086 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2087 }
2088 })
2089 }
2090
2091 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2092 if self.collapse_matches {
2093 return range.start..range.start;
2094 }
2095 range.clone()
2096 }
2097
2098 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2099 if self.display_map.read(cx).clip_at_line_ends != clip {
2100 self.display_map
2101 .update(cx, |map, _| map.clip_at_line_ends = clip);
2102 }
2103 }
2104
2105 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2106 self.input_enabled = input_enabled;
2107 }
2108
2109 pub fn set_inline_completions_hidden_for_vim_mode(
2110 &mut self,
2111 hidden: bool,
2112 window: &mut Window,
2113 cx: &mut Context<Self>,
2114 ) {
2115 if hidden != self.inline_completions_hidden_for_vim_mode {
2116 self.inline_completions_hidden_for_vim_mode = hidden;
2117 if hidden {
2118 self.update_visible_inline_completion(window, cx);
2119 } else {
2120 self.refresh_inline_completion(true, false, window, cx);
2121 }
2122 }
2123 }
2124
2125 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2126 self.menu_inline_completions_policy = value;
2127 }
2128
2129 pub fn set_autoindent(&mut self, autoindent: bool) {
2130 if autoindent {
2131 self.autoindent_mode = Some(AutoindentMode::EachLine);
2132 } else {
2133 self.autoindent_mode = None;
2134 }
2135 }
2136
2137 pub fn read_only(&self, cx: &App) -> bool {
2138 self.read_only || self.buffer.read(cx).read_only()
2139 }
2140
2141 pub fn set_read_only(&mut self, read_only: bool) {
2142 self.read_only = read_only;
2143 }
2144
2145 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2146 self.use_autoclose = autoclose;
2147 }
2148
2149 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2150 self.use_auto_surround = auto_surround;
2151 }
2152
2153 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2154 self.auto_replace_emoji_shortcode = auto_replace;
2155 }
2156
2157 pub fn toggle_edit_predictions(
2158 &mut self,
2159 _: &ToggleEditPrediction,
2160 window: &mut Window,
2161 cx: &mut Context<Self>,
2162 ) {
2163 if self.show_inline_completions_override.is_some() {
2164 self.set_show_edit_predictions(None, window, cx);
2165 } else {
2166 let show_edit_predictions = !self.edit_predictions_enabled();
2167 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2168 }
2169 }
2170
2171 pub fn set_show_edit_predictions(
2172 &mut self,
2173 show_edit_predictions: Option<bool>,
2174 window: &mut Window,
2175 cx: &mut Context<Self>,
2176 ) {
2177 self.show_inline_completions_override = show_edit_predictions;
2178 self.update_edit_prediction_settings(cx);
2179
2180 if let Some(false) = show_edit_predictions {
2181 self.discard_inline_completion(false, cx);
2182 } else {
2183 self.refresh_inline_completion(false, true, window, cx);
2184 }
2185 }
2186
2187 fn inline_completions_disabled_in_scope(
2188 &self,
2189 buffer: &Entity<Buffer>,
2190 buffer_position: language::Anchor,
2191 cx: &App,
2192 ) -> bool {
2193 let snapshot = buffer.read(cx).snapshot();
2194 let settings = snapshot.settings_at(buffer_position, cx);
2195
2196 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2197 return false;
2198 };
2199
2200 scope.override_name().map_or(false, |scope_name| {
2201 settings
2202 .edit_predictions_disabled_in
2203 .iter()
2204 .any(|s| s == scope_name)
2205 })
2206 }
2207
2208 pub fn set_use_modal_editing(&mut self, to: bool) {
2209 self.use_modal_editing = to;
2210 }
2211
2212 pub fn use_modal_editing(&self) -> bool {
2213 self.use_modal_editing
2214 }
2215
2216 fn selections_did_change(
2217 &mut self,
2218 local: bool,
2219 old_cursor_position: &Anchor,
2220 show_completions: bool,
2221 window: &mut Window,
2222 cx: &mut Context<Self>,
2223 ) {
2224 window.invalidate_character_coordinates();
2225
2226 // Copy selections to primary selection buffer
2227 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2228 if local {
2229 let selections = self.selections.all::<usize>(cx);
2230 let buffer_handle = self.buffer.read(cx).read(cx);
2231
2232 let mut text = String::new();
2233 for (index, selection) in selections.iter().enumerate() {
2234 let text_for_selection = buffer_handle
2235 .text_for_range(selection.start..selection.end)
2236 .collect::<String>();
2237
2238 text.push_str(&text_for_selection);
2239 if index != selections.len() - 1 {
2240 text.push('\n');
2241 }
2242 }
2243
2244 if !text.is_empty() {
2245 cx.write_to_primary(ClipboardItem::new_string(text));
2246 }
2247 }
2248
2249 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2250 self.buffer.update(cx, |buffer, cx| {
2251 buffer.set_active_selections(
2252 &self.selections.disjoint_anchors(),
2253 self.selections.line_mode,
2254 self.cursor_shape,
2255 cx,
2256 )
2257 });
2258 }
2259 let display_map = self
2260 .display_map
2261 .update(cx, |display_map, cx| display_map.snapshot(cx));
2262 let buffer = &display_map.buffer_snapshot;
2263 self.add_selections_state = None;
2264 self.select_next_state = None;
2265 self.select_prev_state = None;
2266 self.select_syntax_node_history.try_clear();
2267 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2268 self.snippet_stack
2269 .invalidate(&self.selections.disjoint_anchors(), buffer);
2270 self.take_rename(false, window, cx);
2271
2272 let new_cursor_position = self.selections.newest_anchor().head();
2273
2274 self.push_to_nav_history(
2275 *old_cursor_position,
2276 Some(new_cursor_position.to_point(buffer)),
2277 false,
2278 cx,
2279 );
2280
2281 if local {
2282 let new_cursor_position = self.selections.newest_anchor().head();
2283 let mut context_menu = self.context_menu.borrow_mut();
2284 let completion_menu = match context_menu.as_ref() {
2285 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2286 _ => {
2287 *context_menu = None;
2288 None
2289 }
2290 };
2291 if let Some(buffer_id) = new_cursor_position.buffer_id {
2292 if !self.registered_buffers.contains_key(&buffer_id) {
2293 if let Some(project) = self.project.as_ref() {
2294 project.update(cx, |project, cx| {
2295 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2296 return;
2297 };
2298 self.registered_buffers.insert(
2299 buffer_id,
2300 project.register_buffer_with_language_servers(&buffer, cx),
2301 );
2302 })
2303 }
2304 }
2305 }
2306
2307 if let Some(completion_menu) = completion_menu {
2308 let cursor_position = new_cursor_position.to_offset(buffer);
2309 let (word_range, kind) =
2310 buffer.surrounding_word(completion_menu.initial_position, true);
2311 if kind == Some(CharKind::Word)
2312 && word_range.to_inclusive().contains(&cursor_position)
2313 {
2314 let mut completion_menu = completion_menu.clone();
2315 drop(context_menu);
2316
2317 let query = Self::completion_query(buffer, cursor_position);
2318 cx.spawn(async move |this, cx| {
2319 completion_menu
2320 .filter(query.as_deref(), cx.background_executor().clone())
2321 .await;
2322
2323 this.update(cx, |this, cx| {
2324 let mut context_menu = this.context_menu.borrow_mut();
2325 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2326 else {
2327 return;
2328 };
2329
2330 if menu.id > completion_menu.id {
2331 return;
2332 }
2333
2334 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2335 drop(context_menu);
2336 cx.notify();
2337 })
2338 })
2339 .detach();
2340
2341 if show_completions {
2342 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2343 }
2344 } else {
2345 drop(context_menu);
2346 self.hide_context_menu(window, cx);
2347 }
2348 } else {
2349 drop(context_menu);
2350 }
2351
2352 hide_hover(self, cx);
2353
2354 if old_cursor_position.to_display_point(&display_map).row()
2355 != new_cursor_position.to_display_point(&display_map).row()
2356 {
2357 self.available_code_actions.take();
2358 }
2359 self.refresh_code_actions(window, cx);
2360 self.refresh_document_highlights(cx);
2361 self.refresh_selected_text_highlights(window, cx);
2362 refresh_matching_bracket_highlights(self, window, cx);
2363 self.update_visible_inline_completion(window, cx);
2364 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2365 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2366 if self.git_blame_inline_enabled {
2367 self.start_inline_blame_timer(window, cx);
2368 }
2369 }
2370
2371 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2372 cx.emit(EditorEvent::SelectionsChanged { local });
2373
2374 let selections = &self.selections.disjoint;
2375 if selections.len() == 1 {
2376 cx.emit(SearchEvent::ActiveMatchChanged)
2377 }
2378 if local {
2379 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2380 let inmemory_selections = selections
2381 .iter()
2382 .map(|s| {
2383 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2384 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2385 })
2386 .collect();
2387 self.update_restoration_data(cx, |data| {
2388 data.selections = inmemory_selections;
2389 });
2390
2391 if WorkspaceSettings::get(None, cx).restore_on_startup
2392 != RestoreOnStartupBehavior::None
2393 {
2394 if let Some(workspace_id) =
2395 self.workspace.as_ref().and_then(|workspace| workspace.1)
2396 {
2397 let snapshot = self.buffer().read(cx).snapshot(cx);
2398 let selections = selections.clone();
2399 let background_executor = cx.background_executor().clone();
2400 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2401 self.serialize_selections = cx.background_spawn(async move {
2402 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2403 let db_selections = selections
2404 .iter()
2405 .map(|selection| {
2406 (
2407 selection.start.to_offset(&snapshot),
2408 selection.end.to_offset(&snapshot),
2409 )
2410 })
2411 .collect();
2412
2413 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2414 .await
2415 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2416 .log_err();
2417 });
2418 }
2419 }
2420 }
2421 }
2422
2423 cx.notify();
2424 }
2425
2426 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2427 use text::ToOffset as _;
2428 use text::ToPoint as _;
2429
2430 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2431 return;
2432 }
2433
2434 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2435 return;
2436 };
2437
2438 let snapshot = singleton.read(cx).snapshot();
2439 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2440 let display_snapshot = display_map.snapshot(cx);
2441
2442 display_snapshot
2443 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2444 .map(|fold| {
2445 fold.range.start.text_anchor.to_point(&snapshot)
2446 ..fold.range.end.text_anchor.to_point(&snapshot)
2447 })
2448 .collect()
2449 });
2450 self.update_restoration_data(cx, |data| {
2451 data.folds = inmemory_folds;
2452 });
2453
2454 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2455 return;
2456 };
2457 let background_executor = cx.background_executor().clone();
2458 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2459 let db_folds = self.display_map.update(cx, |display_map, cx| {
2460 display_map
2461 .snapshot(cx)
2462 .folds_in_range(0..snapshot.len())
2463 .map(|fold| {
2464 (
2465 fold.range.start.text_anchor.to_offset(&snapshot),
2466 fold.range.end.text_anchor.to_offset(&snapshot),
2467 )
2468 })
2469 .collect()
2470 });
2471 self.serialize_folds = cx.background_spawn(async move {
2472 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2473 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2474 .await
2475 .with_context(|| {
2476 format!(
2477 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2478 )
2479 })
2480 .log_err();
2481 });
2482 }
2483
2484 pub fn sync_selections(
2485 &mut self,
2486 other: Entity<Editor>,
2487 cx: &mut Context<Self>,
2488 ) -> gpui::Subscription {
2489 let other_selections = other.read(cx).selections.disjoint.to_vec();
2490 self.selections.change_with(cx, |selections| {
2491 selections.select_anchors(other_selections);
2492 });
2493
2494 let other_subscription =
2495 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2496 EditorEvent::SelectionsChanged { local: true } => {
2497 let other_selections = other.read(cx).selections.disjoint.to_vec();
2498 if other_selections.is_empty() {
2499 return;
2500 }
2501 this.selections.change_with(cx, |selections| {
2502 selections.select_anchors(other_selections);
2503 });
2504 }
2505 _ => {}
2506 });
2507
2508 let this_subscription =
2509 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2510 EditorEvent::SelectionsChanged { local: true } => {
2511 let these_selections = this.selections.disjoint.to_vec();
2512 if these_selections.is_empty() {
2513 return;
2514 }
2515 other.update(cx, |other_editor, cx| {
2516 other_editor.selections.change_with(cx, |selections| {
2517 selections.select_anchors(these_selections);
2518 })
2519 });
2520 }
2521 _ => {}
2522 });
2523
2524 Subscription::join(other_subscription, this_subscription)
2525 }
2526
2527 pub fn change_selections<R>(
2528 &mut self,
2529 autoscroll: Option<Autoscroll>,
2530 window: &mut Window,
2531 cx: &mut Context<Self>,
2532 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2533 ) -> R {
2534 self.change_selections_inner(autoscroll, true, window, cx, change)
2535 }
2536
2537 fn change_selections_inner<R>(
2538 &mut self,
2539 autoscroll: Option<Autoscroll>,
2540 request_completions: bool,
2541 window: &mut Window,
2542 cx: &mut Context<Self>,
2543 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2544 ) -> R {
2545 let old_cursor_position = self.selections.newest_anchor().head();
2546 self.push_to_selection_history();
2547
2548 let (changed, result) = self.selections.change_with(cx, change);
2549
2550 if changed {
2551 if let Some(autoscroll) = autoscroll {
2552 self.request_autoscroll(autoscroll, cx);
2553 }
2554 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2555
2556 if self.should_open_signature_help_automatically(
2557 &old_cursor_position,
2558 self.signature_help_state.backspace_pressed(),
2559 cx,
2560 ) {
2561 self.show_signature_help(&ShowSignatureHelp, window, cx);
2562 }
2563 self.signature_help_state.set_backspace_pressed(false);
2564 }
2565
2566 result
2567 }
2568
2569 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2570 where
2571 I: IntoIterator<Item = (Range<S>, T)>,
2572 S: ToOffset,
2573 T: Into<Arc<str>>,
2574 {
2575 if self.read_only(cx) {
2576 return;
2577 }
2578
2579 self.buffer
2580 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2581 }
2582
2583 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2584 where
2585 I: IntoIterator<Item = (Range<S>, T)>,
2586 S: ToOffset,
2587 T: Into<Arc<str>>,
2588 {
2589 if self.read_only(cx) {
2590 return;
2591 }
2592
2593 self.buffer.update(cx, |buffer, cx| {
2594 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2595 });
2596 }
2597
2598 pub fn edit_with_block_indent<I, S, T>(
2599 &mut self,
2600 edits: I,
2601 original_indent_columns: Vec<Option<u32>>,
2602 cx: &mut Context<Self>,
2603 ) where
2604 I: IntoIterator<Item = (Range<S>, T)>,
2605 S: ToOffset,
2606 T: Into<Arc<str>>,
2607 {
2608 if self.read_only(cx) {
2609 return;
2610 }
2611
2612 self.buffer.update(cx, |buffer, cx| {
2613 buffer.edit(
2614 edits,
2615 Some(AutoindentMode::Block {
2616 original_indent_columns,
2617 }),
2618 cx,
2619 )
2620 });
2621 }
2622
2623 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2624 self.hide_context_menu(window, cx);
2625
2626 match phase {
2627 SelectPhase::Begin {
2628 position,
2629 add,
2630 click_count,
2631 } => self.begin_selection(position, add, click_count, window, cx),
2632 SelectPhase::BeginColumnar {
2633 position,
2634 goal_column,
2635 reset,
2636 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2637 SelectPhase::Extend {
2638 position,
2639 click_count,
2640 } => self.extend_selection(position, click_count, window, cx),
2641 SelectPhase::Update {
2642 position,
2643 goal_column,
2644 scroll_delta,
2645 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2646 SelectPhase::End => self.end_selection(window, cx),
2647 }
2648 }
2649
2650 fn extend_selection(
2651 &mut self,
2652 position: DisplayPoint,
2653 click_count: usize,
2654 window: &mut Window,
2655 cx: &mut Context<Self>,
2656 ) {
2657 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2658 let tail = self.selections.newest::<usize>(cx).tail();
2659 self.begin_selection(position, false, click_count, window, cx);
2660
2661 let position = position.to_offset(&display_map, Bias::Left);
2662 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2663
2664 let mut pending_selection = self
2665 .selections
2666 .pending_anchor()
2667 .expect("extend_selection not called with pending selection");
2668 if position >= tail {
2669 pending_selection.start = tail_anchor;
2670 } else {
2671 pending_selection.end = tail_anchor;
2672 pending_selection.reversed = true;
2673 }
2674
2675 let mut pending_mode = self.selections.pending_mode().unwrap();
2676 match &mut pending_mode {
2677 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2678 _ => {}
2679 }
2680
2681 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2682 s.set_pending(pending_selection, pending_mode)
2683 });
2684 }
2685
2686 fn begin_selection(
2687 &mut self,
2688 position: DisplayPoint,
2689 add: bool,
2690 click_count: usize,
2691 window: &mut Window,
2692 cx: &mut Context<Self>,
2693 ) {
2694 if !self.focus_handle.is_focused(window) {
2695 self.last_focused_descendant = None;
2696 window.focus(&self.focus_handle);
2697 }
2698
2699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2700 let buffer = &display_map.buffer_snapshot;
2701 let newest_selection = self.selections.newest_anchor().clone();
2702 let position = display_map.clip_point(position, Bias::Left);
2703
2704 let start;
2705 let end;
2706 let mode;
2707 let mut auto_scroll;
2708 match click_count {
2709 1 => {
2710 start = buffer.anchor_before(position.to_point(&display_map));
2711 end = start;
2712 mode = SelectMode::Character;
2713 auto_scroll = true;
2714 }
2715 2 => {
2716 let range = movement::surrounding_word(&display_map, position);
2717 start = buffer.anchor_before(range.start.to_point(&display_map));
2718 end = buffer.anchor_before(range.end.to_point(&display_map));
2719 mode = SelectMode::Word(start..end);
2720 auto_scroll = true;
2721 }
2722 3 => {
2723 let position = display_map
2724 .clip_point(position, Bias::Left)
2725 .to_point(&display_map);
2726 let line_start = display_map.prev_line_boundary(position).0;
2727 let next_line_start = buffer.clip_point(
2728 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2729 Bias::Left,
2730 );
2731 start = buffer.anchor_before(line_start);
2732 end = buffer.anchor_before(next_line_start);
2733 mode = SelectMode::Line(start..end);
2734 auto_scroll = true;
2735 }
2736 _ => {
2737 start = buffer.anchor_before(0);
2738 end = buffer.anchor_before(buffer.len());
2739 mode = SelectMode::All;
2740 auto_scroll = false;
2741 }
2742 }
2743 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2744
2745 let point_to_delete: Option<usize> = {
2746 let selected_points: Vec<Selection<Point>> =
2747 self.selections.disjoint_in_range(start..end, cx);
2748
2749 if !add || click_count > 1 {
2750 None
2751 } else if !selected_points.is_empty() {
2752 Some(selected_points[0].id)
2753 } else {
2754 let clicked_point_already_selected =
2755 self.selections.disjoint.iter().find(|selection| {
2756 selection.start.to_point(buffer) == start.to_point(buffer)
2757 || selection.end.to_point(buffer) == end.to_point(buffer)
2758 });
2759
2760 clicked_point_already_selected.map(|selection| selection.id)
2761 }
2762 };
2763
2764 let selections_count = self.selections.count();
2765
2766 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2767 if let Some(point_to_delete) = point_to_delete {
2768 s.delete(point_to_delete);
2769
2770 if selections_count == 1 {
2771 s.set_pending_anchor_range(start..end, mode);
2772 }
2773 } else {
2774 if !add {
2775 s.clear_disjoint();
2776 } else if click_count > 1 {
2777 s.delete(newest_selection.id)
2778 }
2779
2780 s.set_pending_anchor_range(start..end, mode);
2781 }
2782 });
2783 }
2784
2785 fn begin_columnar_selection(
2786 &mut self,
2787 position: DisplayPoint,
2788 goal_column: u32,
2789 reset: bool,
2790 window: &mut Window,
2791 cx: &mut Context<Self>,
2792 ) {
2793 if !self.focus_handle.is_focused(window) {
2794 self.last_focused_descendant = None;
2795 window.focus(&self.focus_handle);
2796 }
2797
2798 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2799
2800 if reset {
2801 let pointer_position = display_map
2802 .buffer_snapshot
2803 .anchor_before(position.to_point(&display_map));
2804
2805 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2806 s.clear_disjoint();
2807 s.set_pending_anchor_range(
2808 pointer_position..pointer_position,
2809 SelectMode::Character,
2810 );
2811 });
2812 }
2813
2814 let tail = self.selections.newest::<Point>(cx).tail();
2815 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2816
2817 if !reset {
2818 self.select_columns(
2819 tail.to_display_point(&display_map),
2820 position,
2821 goal_column,
2822 &display_map,
2823 window,
2824 cx,
2825 );
2826 }
2827 }
2828
2829 fn update_selection(
2830 &mut self,
2831 position: DisplayPoint,
2832 goal_column: u32,
2833 scroll_delta: gpui::Point<f32>,
2834 window: &mut Window,
2835 cx: &mut Context<Self>,
2836 ) {
2837 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2838
2839 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2840 let tail = tail.to_display_point(&display_map);
2841 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2842 } else if let Some(mut pending) = self.selections.pending_anchor() {
2843 let buffer = self.buffer.read(cx).snapshot(cx);
2844 let head;
2845 let tail;
2846 let mode = self.selections.pending_mode().unwrap();
2847 match &mode {
2848 SelectMode::Character => {
2849 head = position.to_point(&display_map);
2850 tail = pending.tail().to_point(&buffer);
2851 }
2852 SelectMode::Word(original_range) => {
2853 let original_display_range = original_range.start.to_display_point(&display_map)
2854 ..original_range.end.to_display_point(&display_map);
2855 let original_buffer_range = original_display_range.start.to_point(&display_map)
2856 ..original_display_range.end.to_point(&display_map);
2857 if movement::is_inside_word(&display_map, position)
2858 || original_display_range.contains(&position)
2859 {
2860 let word_range = movement::surrounding_word(&display_map, position);
2861 if word_range.start < original_display_range.start {
2862 head = word_range.start.to_point(&display_map);
2863 } else {
2864 head = word_range.end.to_point(&display_map);
2865 }
2866 } else {
2867 head = position.to_point(&display_map);
2868 }
2869
2870 if head <= original_buffer_range.start {
2871 tail = original_buffer_range.end;
2872 } else {
2873 tail = original_buffer_range.start;
2874 }
2875 }
2876 SelectMode::Line(original_range) => {
2877 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2878
2879 let position = display_map
2880 .clip_point(position, Bias::Left)
2881 .to_point(&display_map);
2882 let line_start = display_map.prev_line_boundary(position).0;
2883 let next_line_start = buffer.clip_point(
2884 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2885 Bias::Left,
2886 );
2887
2888 if line_start < original_range.start {
2889 head = line_start
2890 } else {
2891 head = next_line_start
2892 }
2893
2894 if head <= original_range.start {
2895 tail = original_range.end;
2896 } else {
2897 tail = original_range.start;
2898 }
2899 }
2900 SelectMode::All => {
2901 return;
2902 }
2903 };
2904
2905 if head < tail {
2906 pending.start = buffer.anchor_before(head);
2907 pending.end = buffer.anchor_before(tail);
2908 pending.reversed = true;
2909 } else {
2910 pending.start = buffer.anchor_before(tail);
2911 pending.end = buffer.anchor_before(head);
2912 pending.reversed = false;
2913 }
2914
2915 self.change_selections(None, window, cx, |s| {
2916 s.set_pending(pending, mode);
2917 });
2918 } else {
2919 log::error!("update_selection dispatched with no pending selection");
2920 return;
2921 }
2922
2923 self.apply_scroll_delta(scroll_delta, window, cx);
2924 cx.notify();
2925 }
2926
2927 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2928 self.columnar_selection_tail.take();
2929 if self.selections.pending_anchor().is_some() {
2930 let selections = self.selections.all::<usize>(cx);
2931 self.change_selections(None, window, cx, |s| {
2932 s.select(selections);
2933 s.clear_pending();
2934 });
2935 }
2936 }
2937
2938 fn select_columns(
2939 &mut self,
2940 tail: DisplayPoint,
2941 head: DisplayPoint,
2942 goal_column: u32,
2943 display_map: &DisplaySnapshot,
2944 window: &mut Window,
2945 cx: &mut Context<Self>,
2946 ) {
2947 let start_row = cmp::min(tail.row(), head.row());
2948 let end_row = cmp::max(tail.row(), head.row());
2949 let start_column = cmp::min(tail.column(), goal_column);
2950 let end_column = cmp::max(tail.column(), goal_column);
2951 let reversed = start_column < tail.column();
2952
2953 let selection_ranges = (start_row.0..=end_row.0)
2954 .map(DisplayRow)
2955 .filter_map(|row| {
2956 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2957 let start = display_map
2958 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2959 .to_point(display_map);
2960 let end = display_map
2961 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2962 .to_point(display_map);
2963 if reversed {
2964 Some(end..start)
2965 } else {
2966 Some(start..end)
2967 }
2968 } else {
2969 None
2970 }
2971 })
2972 .collect::<Vec<_>>();
2973
2974 self.change_selections(None, window, cx, |s| {
2975 s.select_ranges(selection_ranges);
2976 });
2977 cx.notify();
2978 }
2979
2980 pub fn has_pending_nonempty_selection(&self) -> bool {
2981 let pending_nonempty_selection = match self.selections.pending_anchor() {
2982 Some(Selection { start, end, .. }) => start != end,
2983 None => false,
2984 };
2985
2986 pending_nonempty_selection
2987 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2988 }
2989
2990 pub fn has_pending_selection(&self) -> bool {
2991 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2992 }
2993
2994 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2995 self.selection_mark_mode = false;
2996
2997 if self.clear_expanded_diff_hunks(cx) {
2998 cx.notify();
2999 return;
3000 }
3001 if self.dismiss_menus_and_popups(true, window, cx) {
3002 return;
3003 }
3004
3005 if self.mode == EditorMode::Full
3006 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3007 {
3008 return;
3009 }
3010
3011 cx.propagate();
3012 }
3013
3014 pub fn dismiss_menus_and_popups(
3015 &mut self,
3016 is_user_requested: bool,
3017 window: &mut Window,
3018 cx: &mut Context<Self>,
3019 ) -> bool {
3020 if self.take_rename(false, window, cx).is_some() {
3021 return true;
3022 }
3023
3024 if hide_hover(self, cx) {
3025 return true;
3026 }
3027
3028 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3029 return true;
3030 }
3031
3032 if self.hide_context_menu(window, cx).is_some() {
3033 return true;
3034 }
3035
3036 if self.mouse_context_menu.take().is_some() {
3037 return true;
3038 }
3039
3040 if is_user_requested && self.discard_inline_completion(true, cx) {
3041 return true;
3042 }
3043
3044 if self.snippet_stack.pop().is_some() {
3045 return true;
3046 }
3047
3048 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
3049 self.dismiss_diagnostics(cx);
3050 return true;
3051 }
3052
3053 false
3054 }
3055
3056 fn linked_editing_ranges_for(
3057 &self,
3058 selection: Range<text::Anchor>,
3059 cx: &App,
3060 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3061 if self.linked_edit_ranges.is_empty() {
3062 return None;
3063 }
3064 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3065 selection.end.buffer_id.and_then(|end_buffer_id| {
3066 if selection.start.buffer_id != Some(end_buffer_id) {
3067 return None;
3068 }
3069 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3070 let snapshot = buffer.read(cx).snapshot();
3071 self.linked_edit_ranges
3072 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3073 .map(|ranges| (ranges, snapshot, buffer))
3074 })?;
3075 use text::ToOffset as TO;
3076 // find offset from the start of current range to current cursor position
3077 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3078
3079 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3080 let start_difference = start_offset - start_byte_offset;
3081 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3082 let end_difference = end_offset - start_byte_offset;
3083 // Current range has associated linked ranges.
3084 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3085 for range in linked_ranges.iter() {
3086 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3087 let end_offset = start_offset + end_difference;
3088 let start_offset = start_offset + start_difference;
3089 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3090 continue;
3091 }
3092 if self.selections.disjoint_anchor_ranges().any(|s| {
3093 if s.start.buffer_id != selection.start.buffer_id
3094 || s.end.buffer_id != selection.end.buffer_id
3095 {
3096 return false;
3097 }
3098 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3099 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3100 }) {
3101 continue;
3102 }
3103 let start = buffer_snapshot.anchor_after(start_offset);
3104 let end = buffer_snapshot.anchor_after(end_offset);
3105 linked_edits
3106 .entry(buffer.clone())
3107 .or_default()
3108 .push(start..end);
3109 }
3110 Some(linked_edits)
3111 }
3112
3113 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3114 let text: Arc<str> = text.into();
3115
3116 if self.read_only(cx) {
3117 return;
3118 }
3119
3120 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3121
3122 let selections = self.selections.all_adjusted(cx);
3123 let mut bracket_inserted = false;
3124 let mut edits = Vec::new();
3125 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3126 let mut new_selections = Vec::with_capacity(selections.len());
3127 let mut new_autoclose_regions = Vec::new();
3128 let snapshot = self.buffer.read(cx).read(cx);
3129
3130 for (selection, autoclose_region) in
3131 self.selections_with_autoclose_regions(selections, &snapshot)
3132 {
3133 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3134 // Determine if the inserted text matches the opening or closing
3135 // bracket of any of this language's bracket pairs.
3136 let mut bracket_pair = None;
3137 let mut is_bracket_pair_start = false;
3138 let mut is_bracket_pair_end = false;
3139 if !text.is_empty() {
3140 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3141 // and they are removing the character that triggered IME popup.
3142 for (pair, enabled) in scope.brackets() {
3143 if !pair.close && !pair.surround {
3144 continue;
3145 }
3146
3147 if enabled && pair.start.ends_with(text.as_ref()) {
3148 let prefix_len = pair.start.len() - text.len();
3149 let preceding_text_matches_prefix = prefix_len == 0
3150 || (selection.start.column >= (prefix_len as u32)
3151 && snapshot.contains_str_at(
3152 Point::new(
3153 selection.start.row,
3154 selection.start.column - (prefix_len as u32),
3155 ),
3156 &pair.start[..prefix_len],
3157 ));
3158 if preceding_text_matches_prefix {
3159 bracket_pair = Some(pair.clone());
3160 is_bracket_pair_start = true;
3161 break;
3162 }
3163 }
3164 if pair.end.as_str() == text.as_ref() {
3165 bracket_pair = Some(pair.clone());
3166 is_bracket_pair_end = true;
3167 break;
3168 }
3169 }
3170 }
3171
3172 if let Some(bracket_pair) = bracket_pair {
3173 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3174 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3175 let auto_surround =
3176 self.use_auto_surround && snapshot_settings.use_auto_surround;
3177 if selection.is_empty() {
3178 if is_bracket_pair_start {
3179 // If the inserted text is a suffix of an opening bracket and the
3180 // selection is preceded by the rest of the opening bracket, then
3181 // insert the closing bracket.
3182 let following_text_allows_autoclose = snapshot
3183 .chars_at(selection.start)
3184 .next()
3185 .map_or(true, |c| scope.should_autoclose_before(c));
3186
3187 let preceding_text_allows_autoclose = selection.start.column == 0
3188 || snapshot.reversed_chars_at(selection.start).next().map_or(
3189 true,
3190 |c| {
3191 bracket_pair.start != bracket_pair.end
3192 || !snapshot
3193 .char_classifier_at(selection.start)
3194 .is_word(c)
3195 },
3196 );
3197
3198 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3199 && bracket_pair.start.len() == 1
3200 {
3201 let target = bracket_pair.start.chars().next().unwrap();
3202 let current_line_count = snapshot
3203 .reversed_chars_at(selection.start)
3204 .take_while(|&c| c != '\n')
3205 .filter(|&c| c == target)
3206 .count();
3207 current_line_count % 2 == 1
3208 } else {
3209 false
3210 };
3211
3212 if autoclose
3213 && bracket_pair.close
3214 && following_text_allows_autoclose
3215 && preceding_text_allows_autoclose
3216 && !is_closing_quote
3217 {
3218 let anchor = snapshot.anchor_before(selection.end);
3219 new_selections.push((selection.map(|_| anchor), text.len()));
3220 new_autoclose_regions.push((
3221 anchor,
3222 text.len(),
3223 selection.id,
3224 bracket_pair.clone(),
3225 ));
3226 edits.push((
3227 selection.range(),
3228 format!("{}{}", text, bracket_pair.end).into(),
3229 ));
3230 bracket_inserted = true;
3231 continue;
3232 }
3233 }
3234
3235 if let Some(region) = autoclose_region {
3236 // If the selection is followed by an auto-inserted closing bracket,
3237 // then don't insert that closing bracket again; just move the selection
3238 // past the closing bracket.
3239 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3240 && text.as_ref() == region.pair.end.as_str();
3241 if should_skip {
3242 let anchor = snapshot.anchor_after(selection.end);
3243 new_selections
3244 .push((selection.map(|_| anchor), region.pair.end.len()));
3245 continue;
3246 }
3247 }
3248
3249 let always_treat_brackets_as_autoclosed = snapshot
3250 .language_settings_at(selection.start, cx)
3251 .always_treat_brackets_as_autoclosed;
3252 if always_treat_brackets_as_autoclosed
3253 && is_bracket_pair_end
3254 && snapshot.contains_str_at(selection.end, text.as_ref())
3255 {
3256 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3257 // and the inserted text is a closing bracket and the selection is followed
3258 // by the closing bracket then move the selection past the closing bracket.
3259 let anchor = snapshot.anchor_after(selection.end);
3260 new_selections.push((selection.map(|_| anchor), text.len()));
3261 continue;
3262 }
3263 }
3264 // If an opening bracket is 1 character long and is typed while
3265 // text is selected, then surround that text with the bracket pair.
3266 else if auto_surround
3267 && bracket_pair.surround
3268 && is_bracket_pair_start
3269 && bracket_pair.start.chars().count() == 1
3270 {
3271 edits.push((selection.start..selection.start, text.clone()));
3272 edits.push((
3273 selection.end..selection.end,
3274 bracket_pair.end.as_str().into(),
3275 ));
3276 bracket_inserted = true;
3277 new_selections.push((
3278 Selection {
3279 id: selection.id,
3280 start: snapshot.anchor_after(selection.start),
3281 end: snapshot.anchor_before(selection.end),
3282 reversed: selection.reversed,
3283 goal: selection.goal,
3284 },
3285 0,
3286 ));
3287 continue;
3288 }
3289 }
3290 }
3291
3292 if self.auto_replace_emoji_shortcode
3293 && selection.is_empty()
3294 && text.as_ref().ends_with(':')
3295 {
3296 if let Some(possible_emoji_short_code) =
3297 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3298 {
3299 if !possible_emoji_short_code.is_empty() {
3300 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3301 let emoji_shortcode_start = Point::new(
3302 selection.start.row,
3303 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3304 );
3305
3306 // Remove shortcode from buffer
3307 edits.push((
3308 emoji_shortcode_start..selection.start,
3309 "".to_string().into(),
3310 ));
3311 new_selections.push((
3312 Selection {
3313 id: selection.id,
3314 start: snapshot.anchor_after(emoji_shortcode_start),
3315 end: snapshot.anchor_before(selection.start),
3316 reversed: selection.reversed,
3317 goal: selection.goal,
3318 },
3319 0,
3320 ));
3321
3322 // Insert emoji
3323 let selection_start_anchor = snapshot.anchor_after(selection.start);
3324 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3325 edits.push((selection.start..selection.end, emoji.to_string().into()));
3326
3327 continue;
3328 }
3329 }
3330 }
3331 }
3332
3333 // If not handling any auto-close operation, then just replace the selected
3334 // text with the given input and move the selection to the end of the
3335 // newly inserted text.
3336 let anchor = snapshot.anchor_after(selection.end);
3337 if !self.linked_edit_ranges.is_empty() {
3338 let start_anchor = snapshot.anchor_before(selection.start);
3339
3340 let is_word_char = text.chars().next().map_or(true, |char| {
3341 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3342 classifier.is_word(char)
3343 });
3344
3345 if is_word_char {
3346 if let Some(ranges) = self
3347 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3348 {
3349 for (buffer, edits) in ranges {
3350 linked_edits
3351 .entry(buffer.clone())
3352 .or_default()
3353 .extend(edits.into_iter().map(|range| (range, text.clone())));
3354 }
3355 }
3356 }
3357 }
3358
3359 new_selections.push((selection.map(|_| anchor), 0));
3360 edits.push((selection.start..selection.end, text.clone()));
3361 }
3362
3363 drop(snapshot);
3364
3365 self.transact(window, cx, |this, window, cx| {
3366 let initial_buffer_versions =
3367 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3368
3369 this.buffer.update(cx, |buffer, cx| {
3370 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3371 });
3372 for (buffer, edits) in linked_edits {
3373 buffer.update(cx, |buffer, cx| {
3374 let snapshot = buffer.snapshot();
3375 let edits = edits
3376 .into_iter()
3377 .map(|(range, text)| {
3378 use text::ToPoint as TP;
3379 let end_point = TP::to_point(&range.end, &snapshot);
3380 let start_point = TP::to_point(&range.start, &snapshot);
3381 (start_point..end_point, text)
3382 })
3383 .sorted_by_key(|(range, _)| range.start);
3384 buffer.edit(edits, None, cx);
3385 })
3386 }
3387 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3388 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3389 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3390 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3391 .zip(new_selection_deltas)
3392 .map(|(selection, delta)| Selection {
3393 id: selection.id,
3394 start: selection.start + delta,
3395 end: selection.end + delta,
3396 reversed: selection.reversed,
3397 goal: SelectionGoal::None,
3398 })
3399 .collect::<Vec<_>>();
3400
3401 let mut i = 0;
3402 for (position, delta, selection_id, pair) in new_autoclose_regions {
3403 let position = position.to_offset(&map.buffer_snapshot) + delta;
3404 let start = map.buffer_snapshot.anchor_before(position);
3405 let end = map.buffer_snapshot.anchor_after(position);
3406 while let Some(existing_state) = this.autoclose_regions.get(i) {
3407 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3408 Ordering::Less => i += 1,
3409 Ordering::Greater => break,
3410 Ordering::Equal => {
3411 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3412 Ordering::Less => i += 1,
3413 Ordering::Equal => break,
3414 Ordering::Greater => break,
3415 }
3416 }
3417 }
3418 }
3419 this.autoclose_regions.insert(
3420 i,
3421 AutocloseRegion {
3422 selection_id,
3423 range: start..end,
3424 pair,
3425 },
3426 );
3427 }
3428
3429 let had_active_inline_completion = this.has_active_inline_completion();
3430 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3431 s.select(new_selections)
3432 });
3433
3434 if !bracket_inserted {
3435 if let Some(on_type_format_task) =
3436 this.trigger_on_type_formatting(text.to_string(), window, cx)
3437 {
3438 on_type_format_task.detach_and_log_err(cx);
3439 }
3440 }
3441
3442 let editor_settings = EditorSettings::get_global(cx);
3443 if bracket_inserted
3444 && (editor_settings.auto_signature_help
3445 || editor_settings.show_signature_help_after_edits)
3446 {
3447 this.show_signature_help(&ShowSignatureHelp, window, cx);
3448 }
3449
3450 let trigger_in_words =
3451 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3452 if this.hard_wrap.is_some() {
3453 let latest: Range<Point> = this.selections.newest(cx).range();
3454 if latest.is_empty()
3455 && this
3456 .buffer()
3457 .read(cx)
3458 .snapshot(cx)
3459 .line_len(MultiBufferRow(latest.start.row))
3460 == latest.start.column
3461 {
3462 this.rewrap_impl(
3463 RewrapOptions {
3464 override_language_settings: true,
3465 preserve_existing_whitespace: true,
3466 },
3467 cx,
3468 )
3469 }
3470 }
3471 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3472 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3473 this.refresh_inline_completion(true, false, window, cx);
3474 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3475 });
3476 }
3477
3478 fn find_possible_emoji_shortcode_at_position(
3479 snapshot: &MultiBufferSnapshot,
3480 position: Point,
3481 ) -> Option<String> {
3482 let mut chars = Vec::new();
3483 let mut found_colon = false;
3484 for char in snapshot.reversed_chars_at(position).take(100) {
3485 // Found a possible emoji shortcode in the middle of the buffer
3486 if found_colon {
3487 if char.is_whitespace() {
3488 chars.reverse();
3489 return Some(chars.iter().collect());
3490 }
3491 // If the previous character is not a whitespace, we are in the middle of a word
3492 // and we only want to complete the shortcode if the word is made up of other emojis
3493 let mut containing_word = String::new();
3494 for ch in snapshot
3495 .reversed_chars_at(position)
3496 .skip(chars.len() + 1)
3497 .take(100)
3498 {
3499 if ch.is_whitespace() {
3500 break;
3501 }
3502 containing_word.push(ch);
3503 }
3504 let containing_word = containing_word.chars().rev().collect::<String>();
3505 if util::word_consists_of_emojis(containing_word.as_str()) {
3506 chars.reverse();
3507 return Some(chars.iter().collect());
3508 }
3509 }
3510
3511 if char.is_whitespace() || !char.is_ascii() {
3512 return None;
3513 }
3514 if char == ':' {
3515 found_colon = true;
3516 } else {
3517 chars.push(char);
3518 }
3519 }
3520 // Found a possible emoji shortcode at the beginning of the buffer
3521 chars.reverse();
3522 Some(chars.iter().collect())
3523 }
3524
3525 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3526 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3527 self.transact(window, cx, |this, window, cx| {
3528 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3529 let selections = this.selections.all::<usize>(cx);
3530 let multi_buffer = this.buffer.read(cx);
3531 let buffer = multi_buffer.snapshot(cx);
3532 selections
3533 .iter()
3534 .map(|selection| {
3535 let start_point = selection.start.to_point(&buffer);
3536 let mut indent =
3537 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3538 indent.len = cmp::min(indent.len, start_point.column);
3539 let start = selection.start;
3540 let end = selection.end;
3541 let selection_is_empty = start == end;
3542 let language_scope = buffer.language_scope_at(start);
3543 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3544 &language_scope
3545 {
3546 let insert_extra_newline =
3547 insert_extra_newline_brackets(&buffer, start..end, language)
3548 || insert_extra_newline_tree_sitter(&buffer, start..end);
3549
3550 // Comment extension on newline is allowed only for cursor selections
3551 let comment_delimiter = maybe!({
3552 if !selection_is_empty {
3553 return None;
3554 }
3555
3556 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3557 return None;
3558 }
3559
3560 let delimiters = language.line_comment_prefixes();
3561 let max_len_of_delimiter =
3562 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3563 let (snapshot, range) =
3564 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3565
3566 let mut index_of_first_non_whitespace = 0;
3567 let comment_candidate = snapshot
3568 .chars_for_range(range)
3569 .skip_while(|c| {
3570 let should_skip = c.is_whitespace();
3571 if should_skip {
3572 index_of_first_non_whitespace += 1;
3573 }
3574 should_skip
3575 })
3576 .take(max_len_of_delimiter)
3577 .collect::<String>();
3578 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3579 comment_candidate.starts_with(comment_prefix.as_ref())
3580 })?;
3581 let cursor_is_placed_after_comment_marker =
3582 index_of_first_non_whitespace + comment_prefix.len()
3583 <= start_point.column as usize;
3584 if cursor_is_placed_after_comment_marker {
3585 Some(comment_prefix.clone())
3586 } else {
3587 None
3588 }
3589 });
3590 (comment_delimiter, insert_extra_newline)
3591 } else {
3592 (None, false)
3593 };
3594
3595 let capacity_for_delimiter = comment_delimiter
3596 .as_deref()
3597 .map(str::len)
3598 .unwrap_or_default();
3599 let mut new_text =
3600 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3601 new_text.push('\n');
3602 new_text.extend(indent.chars());
3603 if let Some(delimiter) = &comment_delimiter {
3604 new_text.push_str(delimiter);
3605 }
3606 if insert_extra_newline {
3607 new_text = new_text.repeat(2);
3608 }
3609
3610 let anchor = buffer.anchor_after(end);
3611 let new_selection = selection.map(|_| anchor);
3612 (
3613 (start..end, new_text),
3614 (insert_extra_newline, new_selection),
3615 )
3616 })
3617 .unzip()
3618 };
3619
3620 this.edit_with_autoindent(edits, cx);
3621 let buffer = this.buffer.read(cx).snapshot(cx);
3622 let new_selections = selection_fixup_info
3623 .into_iter()
3624 .map(|(extra_newline_inserted, new_selection)| {
3625 let mut cursor = new_selection.end.to_point(&buffer);
3626 if extra_newline_inserted {
3627 cursor.row -= 1;
3628 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3629 }
3630 new_selection.map(|_| cursor)
3631 })
3632 .collect();
3633
3634 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3635 s.select(new_selections)
3636 });
3637 this.refresh_inline_completion(true, false, window, cx);
3638 });
3639 }
3640
3641 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3642 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3643
3644 let buffer = self.buffer.read(cx);
3645 let snapshot = buffer.snapshot(cx);
3646
3647 let mut edits = Vec::new();
3648 let mut rows = Vec::new();
3649
3650 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3651 let cursor = selection.head();
3652 let row = cursor.row;
3653
3654 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3655
3656 let newline = "\n".to_string();
3657 edits.push((start_of_line..start_of_line, newline));
3658
3659 rows.push(row + rows_inserted as u32);
3660 }
3661
3662 self.transact(window, cx, |editor, window, cx| {
3663 editor.edit(edits, cx);
3664
3665 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3666 let mut index = 0;
3667 s.move_cursors_with(|map, _, _| {
3668 let row = rows[index];
3669 index += 1;
3670
3671 let point = Point::new(row, 0);
3672 let boundary = map.next_line_boundary(point).1;
3673 let clipped = map.clip_point(boundary, Bias::Left);
3674
3675 (clipped, SelectionGoal::None)
3676 });
3677 });
3678
3679 let mut indent_edits = Vec::new();
3680 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3681 for row in rows {
3682 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3683 for (row, indent) in indents {
3684 if indent.len == 0 {
3685 continue;
3686 }
3687
3688 let text = match indent.kind {
3689 IndentKind::Space => " ".repeat(indent.len as usize),
3690 IndentKind::Tab => "\t".repeat(indent.len as usize),
3691 };
3692 let point = Point::new(row.0, 0);
3693 indent_edits.push((point..point, text));
3694 }
3695 }
3696 editor.edit(indent_edits, cx);
3697 });
3698 }
3699
3700 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3701 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3702
3703 let buffer = self.buffer.read(cx);
3704 let snapshot = buffer.snapshot(cx);
3705
3706 let mut edits = Vec::new();
3707 let mut rows = Vec::new();
3708 let mut rows_inserted = 0;
3709
3710 for selection in self.selections.all_adjusted(cx) {
3711 let cursor = selection.head();
3712 let row = cursor.row;
3713
3714 let point = Point::new(row + 1, 0);
3715 let start_of_line = snapshot.clip_point(point, Bias::Left);
3716
3717 let newline = "\n".to_string();
3718 edits.push((start_of_line..start_of_line, newline));
3719
3720 rows_inserted += 1;
3721 rows.push(row + rows_inserted);
3722 }
3723
3724 self.transact(window, cx, |editor, window, cx| {
3725 editor.edit(edits, cx);
3726
3727 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3728 let mut index = 0;
3729 s.move_cursors_with(|map, _, _| {
3730 let row = rows[index];
3731 index += 1;
3732
3733 let point = Point::new(row, 0);
3734 let boundary = map.next_line_boundary(point).1;
3735 let clipped = map.clip_point(boundary, Bias::Left);
3736
3737 (clipped, SelectionGoal::None)
3738 });
3739 });
3740
3741 let mut indent_edits = Vec::new();
3742 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3743 for row in rows {
3744 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3745 for (row, indent) in indents {
3746 if indent.len == 0 {
3747 continue;
3748 }
3749
3750 let text = match indent.kind {
3751 IndentKind::Space => " ".repeat(indent.len as usize),
3752 IndentKind::Tab => "\t".repeat(indent.len as usize),
3753 };
3754 let point = Point::new(row.0, 0);
3755 indent_edits.push((point..point, text));
3756 }
3757 }
3758 editor.edit(indent_edits, cx);
3759 });
3760 }
3761
3762 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3763 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3764 original_indent_columns: Vec::new(),
3765 });
3766 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3767 }
3768
3769 fn insert_with_autoindent_mode(
3770 &mut self,
3771 text: &str,
3772 autoindent_mode: Option<AutoindentMode>,
3773 window: &mut Window,
3774 cx: &mut Context<Self>,
3775 ) {
3776 if self.read_only(cx) {
3777 return;
3778 }
3779
3780 let text: Arc<str> = text.into();
3781 self.transact(window, cx, |this, window, cx| {
3782 let old_selections = this.selections.all_adjusted(cx);
3783 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3784 let anchors = {
3785 let snapshot = buffer.read(cx);
3786 old_selections
3787 .iter()
3788 .map(|s| {
3789 let anchor = snapshot.anchor_after(s.head());
3790 s.map(|_| anchor)
3791 })
3792 .collect::<Vec<_>>()
3793 };
3794 buffer.edit(
3795 old_selections
3796 .iter()
3797 .map(|s| (s.start..s.end, text.clone())),
3798 autoindent_mode,
3799 cx,
3800 );
3801 anchors
3802 });
3803
3804 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3805 s.select_anchors(selection_anchors);
3806 });
3807
3808 cx.notify();
3809 });
3810 }
3811
3812 fn trigger_completion_on_input(
3813 &mut self,
3814 text: &str,
3815 trigger_in_words: bool,
3816 window: &mut Window,
3817 cx: &mut Context<Self>,
3818 ) {
3819 let ignore_completion_provider = self
3820 .context_menu
3821 .borrow()
3822 .as_ref()
3823 .map(|menu| match menu {
3824 CodeContextMenu::Completions(completions_menu) => {
3825 completions_menu.ignore_completion_provider
3826 }
3827 CodeContextMenu::CodeActions(_) => false,
3828 })
3829 .unwrap_or(false);
3830
3831 if ignore_completion_provider {
3832 self.show_word_completions(&ShowWordCompletions, window, cx);
3833 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3834 self.show_completions(
3835 &ShowCompletions {
3836 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3837 },
3838 window,
3839 cx,
3840 );
3841 } else {
3842 self.hide_context_menu(window, cx);
3843 }
3844 }
3845
3846 fn is_completion_trigger(
3847 &self,
3848 text: &str,
3849 trigger_in_words: bool,
3850 cx: &mut Context<Self>,
3851 ) -> bool {
3852 let position = self.selections.newest_anchor().head();
3853 let multibuffer = self.buffer.read(cx);
3854 let Some(buffer) = position
3855 .buffer_id
3856 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3857 else {
3858 return false;
3859 };
3860
3861 if let Some(completion_provider) = &self.completion_provider {
3862 completion_provider.is_completion_trigger(
3863 &buffer,
3864 position.text_anchor,
3865 text,
3866 trigger_in_words,
3867 cx,
3868 )
3869 } else {
3870 false
3871 }
3872 }
3873
3874 /// If any empty selections is touching the start of its innermost containing autoclose
3875 /// region, expand it to select the brackets.
3876 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3877 let selections = self.selections.all::<usize>(cx);
3878 let buffer = self.buffer.read(cx).read(cx);
3879 let new_selections = self
3880 .selections_with_autoclose_regions(selections, &buffer)
3881 .map(|(mut selection, region)| {
3882 if !selection.is_empty() {
3883 return selection;
3884 }
3885
3886 if let Some(region) = region {
3887 let mut range = region.range.to_offset(&buffer);
3888 if selection.start == range.start && range.start >= region.pair.start.len() {
3889 range.start -= region.pair.start.len();
3890 if buffer.contains_str_at(range.start, ®ion.pair.start)
3891 && buffer.contains_str_at(range.end, ®ion.pair.end)
3892 {
3893 range.end += region.pair.end.len();
3894 selection.start = range.start;
3895 selection.end = range.end;
3896
3897 return selection;
3898 }
3899 }
3900 }
3901
3902 let always_treat_brackets_as_autoclosed = buffer
3903 .language_settings_at(selection.start, cx)
3904 .always_treat_brackets_as_autoclosed;
3905
3906 if !always_treat_brackets_as_autoclosed {
3907 return selection;
3908 }
3909
3910 if let Some(scope) = buffer.language_scope_at(selection.start) {
3911 for (pair, enabled) in scope.brackets() {
3912 if !enabled || !pair.close {
3913 continue;
3914 }
3915
3916 if buffer.contains_str_at(selection.start, &pair.end) {
3917 let pair_start_len = pair.start.len();
3918 if buffer.contains_str_at(
3919 selection.start.saturating_sub(pair_start_len),
3920 &pair.start,
3921 ) {
3922 selection.start -= pair_start_len;
3923 selection.end += pair.end.len();
3924
3925 return selection;
3926 }
3927 }
3928 }
3929 }
3930
3931 selection
3932 })
3933 .collect();
3934
3935 drop(buffer);
3936 self.change_selections(None, window, cx, |selections| {
3937 selections.select(new_selections)
3938 });
3939 }
3940
3941 /// Iterate the given selections, and for each one, find the smallest surrounding
3942 /// autoclose region. This uses the ordering of the selections and the autoclose
3943 /// regions to avoid repeated comparisons.
3944 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3945 &'a self,
3946 selections: impl IntoIterator<Item = Selection<D>>,
3947 buffer: &'a MultiBufferSnapshot,
3948 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3949 let mut i = 0;
3950 let mut regions = self.autoclose_regions.as_slice();
3951 selections.into_iter().map(move |selection| {
3952 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3953
3954 let mut enclosing = None;
3955 while let Some(pair_state) = regions.get(i) {
3956 if pair_state.range.end.to_offset(buffer) < range.start {
3957 regions = ®ions[i + 1..];
3958 i = 0;
3959 } else if pair_state.range.start.to_offset(buffer) > range.end {
3960 break;
3961 } else {
3962 if pair_state.selection_id == selection.id {
3963 enclosing = Some(pair_state);
3964 }
3965 i += 1;
3966 }
3967 }
3968
3969 (selection, enclosing)
3970 })
3971 }
3972
3973 /// Remove any autoclose regions that no longer contain their selection.
3974 fn invalidate_autoclose_regions(
3975 &mut self,
3976 mut selections: &[Selection<Anchor>],
3977 buffer: &MultiBufferSnapshot,
3978 ) {
3979 self.autoclose_regions.retain(|state| {
3980 let mut i = 0;
3981 while let Some(selection) = selections.get(i) {
3982 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3983 selections = &selections[1..];
3984 continue;
3985 }
3986 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3987 break;
3988 }
3989 if selection.id == state.selection_id {
3990 return true;
3991 } else {
3992 i += 1;
3993 }
3994 }
3995 false
3996 });
3997 }
3998
3999 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4000 let offset = position.to_offset(buffer);
4001 let (word_range, kind) = buffer.surrounding_word(offset, true);
4002 if offset > word_range.start && kind == Some(CharKind::Word) {
4003 Some(
4004 buffer
4005 .text_for_range(word_range.start..offset)
4006 .collect::<String>(),
4007 )
4008 } else {
4009 None
4010 }
4011 }
4012
4013 pub fn toggle_inlay_hints(
4014 &mut self,
4015 _: &ToggleInlayHints,
4016 _: &mut Window,
4017 cx: &mut Context<Self>,
4018 ) {
4019 self.refresh_inlay_hints(
4020 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4021 cx,
4022 );
4023 }
4024
4025 pub fn inlay_hints_enabled(&self) -> bool {
4026 self.inlay_hint_cache.enabled
4027 }
4028
4029 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4030 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
4031 return;
4032 }
4033
4034 let reason_description = reason.description();
4035 let ignore_debounce = matches!(
4036 reason,
4037 InlayHintRefreshReason::SettingsChange(_)
4038 | InlayHintRefreshReason::Toggle(_)
4039 | InlayHintRefreshReason::ExcerptsRemoved(_)
4040 | InlayHintRefreshReason::ModifiersChanged(_)
4041 );
4042 let (invalidate_cache, required_languages) = match reason {
4043 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4044 match self.inlay_hint_cache.modifiers_override(enabled) {
4045 Some(enabled) => {
4046 if enabled {
4047 (InvalidationStrategy::RefreshRequested, None)
4048 } else {
4049 self.splice_inlays(
4050 &self
4051 .visible_inlay_hints(cx)
4052 .iter()
4053 .map(|inlay| inlay.id)
4054 .collect::<Vec<InlayId>>(),
4055 Vec::new(),
4056 cx,
4057 );
4058 return;
4059 }
4060 }
4061 None => return,
4062 }
4063 }
4064 InlayHintRefreshReason::Toggle(enabled) => {
4065 if self.inlay_hint_cache.toggle(enabled) {
4066 if enabled {
4067 (InvalidationStrategy::RefreshRequested, None)
4068 } else {
4069 self.splice_inlays(
4070 &self
4071 .visible_inlay_hints(cx)
4072 .iter()
4073 .map(|inlay| inlay.id)
4074 .collect::<Vec<InlayId>>(),
4075 Vec::new(),
4076 cx,
4077 );
4078 return;
4079 }
4080 } else {
4081 return;
4082 }
4083 }
4084 InlayHintRefreshReason::SettingsChange(new_settings) => {
4085 match self.inlay_hint_cache.update_settings(
4086 &self.buffer,
4087 new_settings,
4088 self.visible_inlay_hints(cx),
4089 cx,
4090 ) {
4091 ControlFlow::Break(Some(InlaySplice {
4092 to_remove,
4093 to_insert,
4094 })) => {
4095 self.splice_inlays(&to_remove, to_insert, cx);
4096 return;
4097 }
4098 ControlFlow::Break(None) => return,
4099 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4100 }
4101 }
4102 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4103 if let Some(InlaySplice {
4104 to_remove,
4105 to_insert,
4106 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
4107 {
4108 self.splice_inlays(&to_remove, to_insert, cx);
4109 }
4110 return;
4111 }
4112 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4113 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4114 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4115 }
4116 InlayHintRefreshReason::RefreshRequested => {
4117 (InvalidationStrategy::RefreshRequested, None)
4118 }
4119 };
4120
4121 if let Some(InlaySplice {
4122 to_remove,
4123 to_insert,
4124 }) = self.inlay_hint_cache.spawn_hint_refresh(
4125 reason_description,
4126 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4127 invalidate_cache,
4128 ignore_debounce,
4129 cx,
4130 ) {
4131 self.splice_inlays(&to_remove, to_insert, cx);
4132 }
4133 }
4134
4135 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4136 self.display_map
4137 .read(cx)
4138 .current_inlays()
4139 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4140 .cloned()
4141 .collect()
4142 }
4143
4144 pub fn excerpts_for_inlay_hints_query(
4145 &self,
4146 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4147 cx: &mut Context<Editor>,
4148 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4149 let Some(project) = self.project.as_ref() else {
4150 return HashMap::default();
4151 };
4152 let project = project.read(cx);
4153 let multi_buffer = self.buffer().read(cx);
4154 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4155 let multi_buffer_visible_start = self
4156 .scroll_manager
4157 .anchor()
4158 .anchor
4159 .to_point(&multi_buffer_snapshot);
4160 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4161 multi_buffer_visible_start
4162 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4163 Bias::Left,
4164 );
4165 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4166 multi_buffer_snapshot
4167 .range_to_buffer_ranges(multi_buffer_visible_range)
4168 .into_iter()
4169 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4170 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4171 let buffer_file = project::File::from_dyn(buffer.file())?;
4172 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4173 let worktree_entry = buffer_worktree
4174 .read(cx)
4175 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4176 if worktree_entry.is_ignored {
4177 return None;
4178 }
4179
4180 let language = buffer.language()?;
4181 if let Some(restrict_to_languages) = restrict_to_languages {
4182 if !restrict_to_languages.contains(language) {
4183 return None;
4184 }
4185 }
4186 Some((
4187 excerpt_id,
4188 (
4189 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4190 buffer.version().clone(),
4191 excerpt_visible_range,
4192 ),
4193 ))
4194 })
4195 .collect()
4196 }
4197
4198 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4199 TextLayoutDetails {
4200 text_system: window.text_system().clone(),
4201 editor_style: self.style.clone().unwrap(),
4202 rem_size: window.rem_size(),
4203 scroll_anchor: self.scroll_manager.anchor(),
4204 visible_rows: self.visible_line_count(),
4205 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4206 }
4207 }
4208
4209 pub fn splice_inlays(
4210 &self,
4211 to_remove: &[InlayId],
4212 to_insert: Vec<Inlay>,
4213 cx: &mut Context<Self>,
4214 ) {
4215 self.display_map.update(cx, |display_map, cx| {
4216 display_map.splice_inlays(to_remove, to_insert, cx)
4217 });
4218 cx.notify();
4219 }
4220
4221 fn trigger_on_type_formatting(
4222 &self,
4223 input: String,
4224 window: &mut Window,
4225 cx: &mut Context<Self>,
4226 ) -> Option<Task<Result<()>>> {
4227 if input.len() != 1 {
4228 return None;
4229 }
4230
4231 let project = self.project.as_ref()?;
4232 let position = self.selections.newest_anchor().head();
4233 let (buffer, buffer_position) = self
4234 .buffer
4235 .read(cx)
4236 .text_anchor_for_position(position, cx)?;
4237
4238 let settings = language_settings::language_settings(
4239 buffer
4240 .read(cx)
4241 .language_at(buffer_position)
4242 .map(|l| l.name()),
4243 buffer.read(cx).file(),
4244 cx,
4245 );
4246 if !settings.use_on_type_format {
4247 return None;
4248 }
4249
4250 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4251 // hence we do LSP request & edit on host side only — add formats to host's history.
4252 let push_to_lsp_host_history = true;
4253 // If this is not the host, append its history with new edits.
4254 let push_to_client_history = project.read(cx).is_via_collab();
4255
4256 let on_type_formatting = project.update(cx, |project, cx| {
4257 project.on_type_format(
4258 buffer.clone(),
4259 buffer_position,
4260 input,
4261 push_to_lsp_host_history,
4262 cx,
4263 )
4264 });
4265 Some(cx.spawn_in(window, async move |editor, cx| {
4266 if let Some(transaction) = on_type_formatting.await? {
4267 if push_to_client_history {
4268 buffer
4269 .update(cx, |buffer, _| {
4270 buffer.push_transaction(transaction, Instant::now());
4271 buffer.finalize_last_transaction();
4272 })
4273 .ok();
4274 }
4275 editor.update(cx, |editor, cx| {
4276 editor.refresh_document_highlights(cx);
4277 })?;
4278 }
4279 Ok(())
4280 }))
4281 }
4282
4283 pub fn show_word_completions(
4284 &mut self,
4285 _: &ShowWordCompletions,
4286 window: &mut Window,
4287 cx: &mut Context<Self>,
4288 ) {
4289 self.open_completions_menu(true, None, window, cx);
4290 }
4291
4292 pub fn show_completions(
4293 &mut self,
4294 options: &ShowCompletions,
4295 window: &mut Window,
4296 cx: &mut Context<Self>,
4297 ) {
4298 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4299 }
4300
4301 fn open_completions_menu(
4302 &mut self,
4303 ignore_completion_provider: bool,
4304 trigger: Option<&str>,
4305 window: &mut Window,
4306 cx: &mut Context<Self>,
4307 ) {
4308 if self.pending_rename.is_some() {
4309 return;
4310 }
4311 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4312 return;
4313 }
4314
4315 let position = self.selections.newest_anchor().head();
4316 if position.diff_base_anchor.is_some() {
4317 return;
4318 }
4319 let (buffer, buffer_position) =
4320 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4321 output
4322 } else {
4323 return;
4324 };
4325 let buffer_snapshot = buffer.read(cx).snapshot();
4326 let show_completion_documentation = buffer_snapshot
4327 .settings_at(buffer_position, cx)
4328 .show_completion_documentation;
4329
4330 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4331
4332 let trigger_kind = match trigger {
4333 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4334 CompletionTriggerKind::TRIGGER_CHARACTER
4335 }
4336 _ => CompletionTriggerKind::INVOKED,
4337 };
4338 let completion_context = CompletionContext {
4339 trigger_character: trigger.and_then(|trigger| {
4340 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4341 Some(String::from(trigger))
4342 } else {
4343 None
4344 }
4345 }),
4346 trigger_kind,
4347 };
4348
4349 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4350 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4351 let word_to_exclude = buffer_snapshot
4352 .text_for_range(old_range.clone())
4353 .collect::<String>();
4354 (
4355 buffer_snapshot.anchor_before(old_range.start)
4356 ..buffer_snapshot.anchor_after(old_range.end),
4357 Some(word_to_exclude),
4358 )
4359 } else {
4360 (buffer_position..buffer_position, None)
4361 };
4362
4363 let completion_settings = language_settings(
4364 buffer_snapshot
4365 .language_at(buffer_position)
4366 .map(|language| language.name()),
4367 buffer_snapshot.file(),
4368 cx,
4369 )
4370 .completions;
4371
4372 // The document can be large, so stay in reasonable bounds when searching for words,
4373 // otherwise completion pop-up might be slow to appear.
4374 const WORD_LOOKUP_ROWS: u32 = 5_000;
4375 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4376 let min_word_search = buffer_snapshot.clip_point(
4377 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4378 Bias::Left,
4379 );
4380 let max_word_search = buffer_snapshot.clip_point(
4381 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4382 Bias::Right,
4383 );
4384 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4385 ..buffer_snapshot.point_to_offset(max_word_search);
4386
4387 let provider = self
4388 .completion_provider
4389 .as_ref()
4390 .filter(|_| !ignore_completion_provider);
4391 let skip_digits = query
4392 .as_ref()
4393 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4394
4395 let (mut words, provided_completions) = match provider {
4396 Some(provider) => {
4397 let completions = provider.completions(
4398 position.excerpt_id,
4399 &buffer,
4400 buffer_position,
4401 completion_context,
4402 window,
4403 cx,
4404 );
4405
4406 let words = match completion_settings.words {
4407 WordsCompletionMode::Disabled => Task::ready(HashMap::default()),
4408 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4409 .background_spawn(async move {
4410 buffer_snapshot.words_in_range(WordsQuery {
4411 fuzzy_contents: None,
4412 range: word_search_range,
4413 skip_digits,
4414 })
4415 }),
4416 };
4417
4418 (words, completions)
4419 }
4420 None => (
4421 cx.background_spawn(async move {
4422 buffer_snapshot.words_in_range(WordsQuery {
4423 fuzzy_contents: None,
4424 range: word_search_range,
4425 skip_digits,
4426 })
4427 }),
4428 Task::ready(Ok(None)),
4429 ),
4430 };
4431
4432 let sort_completions = provider
4433 .as_ref()
4434 .map_or(true, |provider| provider.sort_completions());
4435
4436 let filter_completions = provider
4437 .as_ref()
4438 .map_or(true, |provider| provider.filter_completions());
4439
4440 let id = post_inc(&mut self.next_completion_id);
4441 let task = cx.spawn_in(window, async move |editor, cx| {
4442 async move {
4443 editor.update(cx, |this, _| {
4444 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4445 })?;
4446
4447 let mut completions = Vec::new();
4448 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4449 completions.extend(provided_completions);
4450 if completion_settings.words == WordsCompletionMode::Fallback {
4451 words = Task::ready(HashMap::default());
4452 }
4453 }
4454
4455 let mut words = words.await;
4456 if let Some(word_to_exclude) = &word_to_exclude {
4457 words.remove(word_to_exclude);
4458 }
4459 for lsp_completion in &completions {
4460 words.remove(&lsp_completion.new_text);
4461 }
4462 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4463 old_range: old_range.clone(),
4464 new_text: word.clone(),
4465 label: CodeLabel::plain(word, None),
4466 icon_path: None,
4467 documentation: None,
4468 source: CompletionSource::BufferWord {
4469 word_range,
4470 resolved: false,
4471 },
4472 confirm: None,
4473 }));
4474
4475 let menu = if completions.is_empty() {
4476 None
4477 } else {
4478 let mut menu = CompletionsMenu::new(
4479 id,
4480 sort_completions,
4481 show_completion_documentation,
4482 ignore_completion_provider,
4483 position,
4484 buffer.clone(),
4485 completions.into(),
4486 );
4487
4488 menu.filter(
4489 if filter_completions {
4490 query.as_deref()
4491 } else {
4492 None
4493 },
4494 cx.background_executor().clone(),
4495 )
4496 .await;
4497
4498 menu.visible().then_some(menu)
4499 };
4500
4501 editor.update_in(cx, |editor, window, cx| {
4502 match editor.context_menu.borrow().as_ref() {
4503 None => {}
4504 Some(CodeContextMenu::Completions(prev_menu)) => {
4505 if prev_menu.id > id {
4506 return;
4507 }
4508 }
4509 _ => return,
4510 }
4511
4512 if editor.focus_handle.is_focused(window) && menu.is_some() {
4513 let mut menu = menu.unwrap();
4514 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4515
4516 *editor.context_menu.borrow_mut() =
4517 Some(CodeContextMenu::Completions(menu));
4518
4519 if editor.show_edit_predictions_in_menu() {
4520 editor.update_visible_inline_completion(window, cx);
4521 } else {
4522 editor.discard_inline_completion(false, cx);
4523 }
4524
4525 cx.notify();
4526 } else if editor.completion_tasks.len() <= 1 {
4527 // If there are no more completion tasks and the last menu was
4528 // empty, we should hide it.
4529 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4530 // If it was already hidden and we don't show inline
4531 // completions in the menu, we should also show the
4532 // inline-completion when available.
4533 if was_hidden && editor.show_edit_predictions_in_menu() {
4534 editor.update_visible_inline_completion(window, cx);
4535 }
4536 }
4537 })?;
4538
4539 anyhow::Ok(())
4540 }
4541 .log_err()
4542 .await
4543 });
4544
4545 self.completion_tasks.push((id, task));
4546 }
4547
4548 #[cfg(feature = "test-support")]
4549 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4550 let menu = self.context_menu.borrow();
4551 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4552 let completions = menu.completions.borrow();
4553 Some(completions.to_vec())
4554 } else {
4555 None
4556 }
4557 }
4558
4559 pub fn confirm_completion(
4560 &mut self,
4561 action: &ConfirmCompletion,
4562 window: &mut Window,
4563 cx: &mut Context<Self>,
4564 ) -> Option<Task<Result<()>>> {
4565 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4566 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4567 }
4568
4569 pub fn compose_completion(
4570 &mut self,
4571 action: &ComposeCompletion,
4572 window: &mut Window,
4573 cx: &mut Context<Self>,
4574 ) -> Option<Task<Result<()>>> {
4575 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4576 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4577 }
4578
4579 fn do_completion(
4580 &mut self,
4581 item_ix: Option<usize>,
4582 intent: CompletionIntent,
4583 window: &mut Window,
4584 cx: &mut Context<Editor>,
4585 ) -> Option<Task<Result<()>>> {
4586 use language::ToOffset as _;
4587
4588 let completions_menu =
4589 if let CodeContextMenu::Completions(menu) = self.hide_context_menu(window, cx)? {
4590 menu
4591 } else {
4592 return None;
4593 };
4594
4595 let candidate_id = {
4596 let entries = completions_menu.entries.borrow();
4597 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4598 if self.show_edit_predictions_in_menu() {
4599 self.discard_inline_completion(true, cx);
4600 }
4601 mat.candidate_id
4602 };
4603
4604 let buffer_handle = completions_menu.buffer;
4605 let completion = completions_menu
4606 .completions
4607 .borrow()
4608 .get(candidate_id)?
4609 .clone();
4610 cx.stop_propagation();
4611
4612 let snippet;
4613 let new_text;
4614 if completion.is_snippet() {
4615 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4616 new_text = snippet.as_ref().unwrap().text.clone();
4617 } else {
4618 snippet = None;
4619 new_text = completion.new_text.clone();
4620 };
4621 let selections = self.selections.all::<usize>(cx);
4622 let buffer = buffer_handle.read(cx);
4623 let old_range = completion.old_range.to_offset(buffer);
4624 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
4625
4626 let newest_selection = self.selections.newest_anchor();
4627 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4628 return None;
4629 }
4630
4631 let lookbehind = newest_selection
4632 .start
4633 .text_anchor
4634 .to_offset(buffer)
4635 .saturating_sub(old_range.start);
4636 let lookahead = old_range
4637 .end
4638 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4639 let mut common_prefix_len = 0;
4640 for (a, b) in old_text.chars().zip(new_text.chars()) {
4641 if a == b {
4642 common_prefix_len += a.len_utf8();
4643 } else {
4644 break;
4645 }
4646 }
4647
4648 let snapshot = self.buffer.read(cx).snapshot(cx);
4649 let mut range_to_replace: Option<Range<usize>> = None;
4650 let mut ranges = Vec::new();
4651 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4652 for selection in &selections {
4653 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4654 let start = selection.start.saturating_sub(lookbehind);
4655 let end = selection.end + lookahead;
4656 if selection.id == newest_selection.id {
4657 range_to_replace = Some(start + common_prefix_len..end);
4658 }
4659 ranges.push(start + common_prefix_len..end);
4660 } else {
4661 common_prefix_len = 0;
4662 ranges.clear();
4663 ranges.extend(selections.iter().map(|s| {
4664 if s.id == newest_selection.id {
4665 range_to_replace = Some(old_range.clone());
4666 old_range.clone()
4667 } else {
4668 s.start..s.end
4669 }
4670 }));
4671 break;
4672 }
4673 if !self.linked_edit_ranges.is_empty() {
4674 let start_anchor = snapshot.anchor_before(selection.head());
4675 let end_anchor = snapshot.anchor_after(selection.tail());
4676 if let Some(ranges) = self
4677 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4678 {
4679 for (buffer, edits) in ranges {
4680 linked_edits.entry(buffer.clone()).or_default().extend(
4681 edits
4682 .into_iter()
4683 .map(|range| (range, new_text[common_prefix_len..].to_owned())),
4684 );
4685 }
4686 }
4687 }
4688 }
4689 let text = &new_text[common_prefix_len..];
4690
4691 let utf16_range_to_replace = range_to_replace.map(|range| {
4692 let newest_selection = self.selections.newest::<OffsetUtf16>(cx).range();
4693 let selection_start_utf16 = newest_selection.start.0 as isize;
4694
4695 range.start.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16
4696 ..range.end.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16
4697 });
4698 cx.emit(EditorEvent::InputHandled {
4699 utf16_range_to_replace,
4700 text: text.into(),
4701 });
4702
4703 self.transact(window, cx, |this, window, cx| {
4704 if let Some(mut snippet) = snippet {
4705 snippet.text = text.to_string();
4706 for tabstop in snippet
4707 .tabstops
4708 .iter_mut()
4709 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4710 {
4711 tabstop.start -= common_prefix_len as isize;
4712 tabstop.end -= common_prefix_len as isize;
4713 }
4714
4715 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4716 } else {
4717 this.buffer.update(cx, |buffer, cx| {
4718 let edits = ranges.iter().map(|range| (range.clone(), text));
4719 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4720 });
4721 }
4722 for (buffer, edits) in linked_edits {
4723 buffer.update(cx, |buffer, cx| {
4724 let snapshot = buffer.snapshot();
4725 let edits = edits
4726 .into_iter()
4727 .map(|(range, text)| {
4728 use text::ToPoint as TP;
4729 let end_point = TP::to_point(&range.end, &snapshot);
4730 let start_point = TP::to_point(&range.start, &snapshot);
4731 (start_point..end_point, text)
4732 })
4733 .sorted_by_key(|(range, _)| range.start);
4734 buffer.edit(edits, None, cx);
4735 })
4736 }
4737
4738 this.refresh_inline_completion(true, false, window, cx);
4739 });
4740
4741 let show_new_completions_on_confirm = completion
4742 .confirm
4743 .as_ref()
4744 .map_or(false, |confirm| confirm(intent, window, cx));
4745 if show_new_completions_on_confirm {
4746 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4747 }
4748
4749 let provider = self.completion_provider.as_ref()?;
4750 drop(completion);
4751 let apply_edits = provider.apply_additional_edits_for_completion(
4752 buffer_handle,
4753 completions_menu.completions.clone(),
4754 candidate_id,
4755 true,
4756 cx,
4757 );
4758
4759 let editor_settings = EditorSettings::get_global(cx);
4760 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4761 // After the code completion is finished, users often want to know what signatures are needed.
4762 // so we should automatically call signature_help
4763 self.show_signature_help(&ShowSignatureHelp, window, cx);
4764 }
4765
4766 Some(cx.foreground_executor().spawn(async move {
4767 apply_edits.await?;
4768 Ok(())
4769 }))
4770 }
4771
4772 pub fn toggle_code_actions(
4773 &mut self,
4774 action: &ToggleCodeActions,
4775 window: &mut Window,
4776 cx: &mut Context<Self>,
4777 ) {
4778 let mut context_menu = self.context_menu.borrow_mut();
4779 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4780 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4781 // Toggle if we're selecting the same one
4782 *context_menu = None;
4783 cx.notify();
4784 return;
4785 } else {
4786 // Otherwise, clear it and start a new one
4787 *context_menu = None;
4788 cx.notify();
4789 }
4790 }
4791 drop(context_menu);
4792 let snapshot = self.snapshot(window, cx);
4793 let deployed_from_indicator = action.deployed_from_indicator;
4794 let mut task = self.code_actions_task.take();
4795 let action = action.clone();
4796 cx.spawn_in(window, async move |editor, cx| {
4797 while let Some(prev_task) = task {
4798 prev_task.await.log_err();
4799 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4800 }
4801
4802 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4803 if editor.focus_handle.is_focused(window) {
4804 let multibuffer_point = action
4805 .deployed_from_indicator
4806 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4807 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4808 let (buffer, buffer_row) = snapshot
4809 .buffer_snapshot
4810 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4811 .and_then(|(buffer_snapshot, range)| {
4812 editor
4813 .buffer
4814 .read(cx)
4815 .buffer(buffer_snapshot.remote_id())
4816 .map(|buffer| (buffer, range.start.row))
4817 })?;
4818 let (_, code_actions) = editor
4819 .available_code_actions
4820 .clone()
4821 .and_then(|(location, code_actions)| {
4822 let snapshot = location.buffer.read(cx).snapshot();
4823 let point_range = location.range.to_point(&snapshot);
4824 let point_range = point_range.start.row..=point_range.end.row;
4825 if point_range.contains(&buffer_row) {
4826 Some((location, code_actions))
4827 } else {
4828 None
4829 }
4830 })
4831 .unzip();
4832 let buffer_id = buffer.read(cx).remote_id();
4833 let tasks = editor
4834 .tasks
4835 .get(&(buffer_id, buffer_row))
4836 .map(|t| Arc::new(t.to_owned()));
4837 if tasks.is_none() && code_actions.is_none() {
4838 return None;
4839 }
4840
4841 editor.completion_tasks.clear();
4842 editor.discard_inline_completion(false, cx);
4843 let task_context =
4844 tasks
4845 .as_ref()
4846 .zip(editor.project.clone())
4847 .map(|(tasks, project)| {
4848 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4849 });
4850
4851 let debugger_flag = cx.has_flag::<Debugger>();
4852
4853 Some(cx.spawn_in(window, async move |editor, cx| {
4854 let task_context = match task_context {
4855 Some(task_context) => task_context.await,
4856 None => None,
4857 };
4858 let resolved_tasks =
4859 tasks.zip(task_context).map(|(tasks, task_context)| {
4860 Rc::new(ResolvedTasks {
4861 templates: tasks.resolve(&task_context).collect(),
4862 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4863 multibuffer_point.row,
4864 tasks.column,
4865 )),
4866 })
4867 });
4868 let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
4869 tasks
4870 .templates
4871 .iter()
4872 .filter(|task| {
4873 if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
4874 debugger_flag
4875 } else {
4876 true
4877 }
4878 })
4879 .count()
4880 == 1
4881 }) && code_actions
4882 .as_ref()
4883 .map_or(true, |actions| actions.is_empty());
4884 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4885 *editor.context_menu.borrow_mut() =
4886 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4887 buffer,
4888 actions: CodeActionContents {
4889 tasks: resolved_tasks,
4890 actions: code_actions,
4891 },
4892 selected_item: Default::default(),
4893 scroll_handle: UniformListScrollHandle::default(),
4894 deployed_from_indicator,
4895 }));
4896 if spawn_straight_away {
4897 if let Some(task) = editor.confirm_code_action(
4898 &ConfirmCodeAction { item_ix: Some(0) },
4899 window,
4900 cx,
4901 ) {
4902 cx.notify();
4903 return task;
4904 }
4905 }
4906 cx.notify();
4907 Task::ready(Ok(()))
4908 }) {
4909 task.await
4910 } else {
4911 Ok(())
4912 }
4913 }))
4914 } else {
4915 Some(Task::ready(Ok(())))
4916 }
4917 })?;
4918 if let Some(task) = spawned_test_task {
4919 task.await?;
4920 }
4921
4922 Ok::<_, anyhow::Error>(())
4923 })
4924 .detach_and_log_err(cx);
4925 }
4926
4927 pub fn confirm_code_action(
4928 &mut self,
4929 action: &ConfirmCodeAction,
4930 window: &mut Window,
4931 cx: &mut Context<Self>,
4932 ) -> Option<Task<Result<()>>> {
4933 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4934
4935 let actions_menu =
4936 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4937 menu
4938 } else {
4939 return None;
4940 };
4941
4942 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4943 let action = actions_menu.actions.get(action_ix)?;
4944 let title = action.label();
4945 let buffer = actions_menu.buffer;
4946 let workspace = self.workspace()?;
4947
4948 match action {
4949 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4950 match resolved_task.task_type() {
4951 task::TaskType::Script => workspace.update(cx, |workspace, cx| {
4952 workspace::tasks::schedule_resolved_task(
4953 workspace,
4954 task_source_kind,
4955 resolved_task,
4956 false,
4957 cx,
4958 );
4959
4960 Some(Task::ready(Ok(())))
4961 }),
4962 task::TaskType::Debug(debug_args) => {
4963 if debug_args.locator.is_some() {
4964 workspace.update(cx, |workspace, cx| {
4965 workspace::tasks::schedule_resolved_task(
4966 workspace,
4967 task_source_kind,
4968 resolved_task,
4969 false,
4970 cx,
4971 );
4972 });
4973
4974 return Some(Task::ready(Ok(())));
4975 }
4976
4977 if let Some(project) = self.project.as_ref() {
4978 project
4979 .update(cx, |project, cx| {
4980 project.start_debug_session(
4981 resolved_task.resolved_debug_adapter_config().unwrap(),
4982 cx,
4983 )
4984 })
4985 .detach_and_log_err(cx);
4986 Some(Task::ready(Ok(())))
4987 } else {
4988 Some(Task::ready(Ok(())))
4989 }
4990 }
4991 }
4992 }
4993 CodeActionsItem::CodeAction {
4994 excerpt_id,
4995 action,
4996 provider,
4997 } => {
4998 let apply_code_action =
4999 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5000 let workspace = workspace.downgrade();
5001 Some(cx.spawn_in(window, async move |editor, cx| {
5002 let project_transaction = apply_code_action.await?;
5003 Self::open_project_transaction(
5004 &editor,
5005 workspace,
5006 project_transaction,
5007 title,
5008 cx,
5009 )
5010 .await
5011 }))
5012 }
5013 }
5014 }
5015
5016 pub async fn open_project_transaction(
5017 this: &WeakEntity<Editor>,
5018 workspace: WeakEntity<Workspace>,
5019 transaction: ProjectTransaction,
5020 title: String,
5021 cx: &mut AsyncWindowContext,
5022 ) -> Result<()> {
5023 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5024 cx.update(|_, cx| {
5025 entries.sort_unstable_by_key(|(buffer, _)| {
5026 buffer.read(cx).file().map(|f| f.path().clone())
5027 });
5028 })?;
5029
5030 // If the project transaction's edits are all contained within this editor, then
5031 // avoid opening a new editor to display them.
5032
5033 if let Some((buffer, transaction)) = entries.first() {
5034 if entries.len() == 1 {
5035 let excerpt = this.update(cx, |editor, cx| {
5036 editor
5037 .buffer()
5038 .read(cx)
5039 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5040 })?;
5041 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5042 if excerpted_buffer == *buffer {
5043 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5044 let excerpt_range = excerpt_range.to_offset(buffer);
5045 buffer
5046 .edited_ranges_for_transaction::<usize>(transaction)
5047 .all(|range| {
5048 excerpt_range.start <= range.start
5049 && excerpt_range.end >= range.end
5050 })
5051 })?;
5052
5053 if all_edits_within_excerpt {
5054 return Ok(());
5055 }
5056 }
5057 }
5058 }
5059 } else {
5060 return Ok(());
5061 }
5062
5063 let mut ranges_to_highlight = Vec::new();
5064 let excerpt_buffer = cx.new(|cx| {
5065 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5066 for (buffer_handle, transaction) in &entries {
5067 let edited_ranges = buffer_handle
5068 .read(cx)
5069 .edited_ranges_for_transaction::<Point>(transaction)
5070 .collect::<Vec<_>>();
5071 let (ranges, _) = multibuffer.set_excerpts_for_path(
5072 PathKey::for_buffer(buffer_handle, cx),
5073 buffer_handle.clone(),
5074 edited_ranges,
5075 DEFAULT_MULTIBUFFER_CONTEXT,
5076 cx,
5077 );
5078
5079 ranges_to_highlight.extend(ranges);
5080 }
5081 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5082 multibuffer
5083 })?;
5084
5085 workspace.update_in(cx, |workspace, window, cx| {
5086 let project = workspace.project().clone();
5087 let editor =
5088 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5089 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5090 editor.update(cx, |editor, cx| {
5091 editor.highlight_background::<Self>(
5092 &ranges_to_highlight,
5093 |theme| theme.editor_highlighted_line_background,
5094 cx,
5095 );
5096 });
5097 })?;
5098
5099 Ok(())
5100 }
5101
5102 pub fn clear_code_action_providers(&mut self) {
5103 self.code_action_providers.clear();
5104 self.available_code_actions.take();
5105 }
5106
5107 pub fn add_code_action_provider(
5108 &mut self,
5109 provider: Rc<dyn CodeActionProvider>,
5110 window: &mut Window,
5111 cx: &mut Context<Self>,
5112 ) {
5113 if self
5114 .code_action_providers
5115 .iter()
5116 .any(|existing_provider| existing_provider.id() == provider.id())
5117 {
5118 return;
5119 }
5120
5121 self.code_action_providers.push(provider);
5122 self.refresh_code_actions(window, cx);
5123 }
5124
5125 pub fn remove_code_action_provider(
5126 &mut self,
5127 id: Arc<str>,
5128 window: &mut Window,
5129 cx: &mut Context<Self>,
5130 ) {
5131 self.code_action_providers
5132 .retain(|provider| provider.id() != id);
5133 self.refresh_code_actions(window, cx);
5134 }
5135
5136 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5137 let buffer = self.buffer.read(cx);
5138 let newest_selection = self.selections.newest_anchor().clone();
5139 if newest_selection.head().diff_base_anchor.is_some() {
5140 return None;
5141 }
5142 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
5143 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
5144 if start_buffer != end_buffer {
5145 return None;
5146 }
5147
5148 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5149 cx.background_executor()
5150 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5151 .await;
5152
5153 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5154 let providers = this.code_action_providers.clone();
5155 let tasks = this
5156 .code_action_providers
5157 .iter()
5158 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5159 .collect::<Vec<_>>();
5160 (providers, tasks)
5161 })?;
5162
5163 let mut actions = Vec::new();
5164 for (provider, provider_actions) in
5165 providers.into_iter().zip(future::join_all(tasks).await)
5166 {
5167 if let Some(provider_actions) = provider_actions.log_err() {
5168 actions.extend(provider_actions.into_iter().map(|action| {
5169 AvailableCodeAction {
5170 excerpt_id: newest_selection.start.excerpt_id,
5171 action,
5172 provider: provider.clone(),
5173 }
5174 }));
5175 }
5176 }
5177
5178 this.update(cx, |this, cx| {
5179 this.available_code_actions = if actions.is_empty() {
5180 None
5181 } else {
5182 Some((
5183 Location {
5184 buffer: start_buffer,
5185 range: start..end,
5186 },
5187 actions.into(),
5188 ))
5189 };
5190 cx.notify();
5191 })
5192 }));
5193 None
5194 }
5195
5196 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5197 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5198 self.show_git_blame_inline = false;
5199
5200 self.show_git_blame_inline_delay_task =
5201 Some(cx.spawn_in(window, async move |this, cx| {
5202 cx.background_executor().timer(delay).await;
5203
5204 this.update(cx, |this, cx| {
5205 this.show_git_blame_inline = true;
5206 cx.notify();
5207 })
5208 .log_err();
5209 }));
5210 }
5211 }
5212
5213 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5214 if self.pending_rename.is_some() {
5215 return None;
5216 }
5217
5218 let provider = self.semantics_provider.clone()?;
5219 let buffer = self.buffer.read(cx);
5220 let newest_selection = self.selections.newest_anchor().clone();
5221 let cursor_position = newest_selection.head();
5222 let (cursor_buffer, cursor_buffer_position) =
5223 buffer.text_anchor_for_position(cursor_position, cx)?;
5224 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5225 if cursor_buffer != tail_buffer {
5226 return None;
5227 }
5228 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5229 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5230 cx.background_executor()
5231 .timer(Duration::from_millis(debounce))
5232 .await;
5233
5234 let highlights = if let Some(highlights) = cx
5235 .update(|cx| {
5236 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5237 })
5238 .ok()
5239 .flatten()
5240 {
5241 highlights.await.log_err()
5242 } else {
5243 None
5244 };
5245
5246 if let Some(highlights) = highlights {
5247 this.update(cx, |this, cx| {
5248 if this.pending_rename.is_some() {
5249 return;
5250 }
5251
5252 let buffer_id = cursor_position.buffer_id;
5253 let buffer = this.buffer.read(cx);
5254 if !buffer
5255 .text_anchor_for_position(cursor_position, cx)
5256 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5257 {
5258 return;
5259 }
5260
5261 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5262 let mut write_ranges = Vec::new();
5263 let mut read_ranges = Vec::new();
5264 for highlight in highlights {
5265 for (excerpt_id, excerpt_range) in
5266 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5267 {
5268 let start = highlight
5269 .range
5270 .start
5271 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5272 let end = highlight
5273 .range
5274 .end
5275 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5276 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5277 continue;
5278 }
5279
5280 let range = Anchor {
5281 buffer_id,
5282 excerpt_id,
5283 text_anchor: start,
5284 diff_base_anchor: None,
5285 }..Anchor {
5286 buffer_id,
5287 excerpt_id,
5288 text_anchor: end,
5289 diff_base_anchor: None,
5290 };
5291 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5292 write_ranges.push(range);
5293 } else {
5294 read_ranges.push(range);
5295 }
5296 }
5297 }
5298
5299 this.highlight_background::<DocumentHighlightRead>(
5300 &read_ranges,
5301 |theme| theme.editor_document_highlight_read_background,
5302 cx,
5303 );
5304 this.highlight_background::<DocumentHighlightWrite>(
5305 &write_ranges,
5306 |theme| theme.editor_document_highlight_write_background,
5307 cx,
5308 );
5309 cx.notify();
5310 })
5311 .log_err();
5312 }
5313 }));
5314 None
5315 }
5316
5317 pub fn refresh_selected_text_highlights(
5318 &mut self,
5319 window: &mut Window,
5320 cx: &mut Context<Editor>,
5321 ) {
5322 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5323 return;
5324 }
5325 self.selection_highlight_task.take();
5326 if !EditorSettings::get_global(cx).selection_highlight {
5327 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5328 return;
5329 }
5330 if self.selections.count() != 1 || self.selections.line_mode {
5331 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5332 return;
5333 }
5334 let selection = self.selections.newest::<Point>(cx);
5335 if selection.is_empty() || selection.start.row != selection.end.row {
5336 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5337 return;
5338 }
5339 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5340 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5341 cx.background_executor()
5342 .timer(Duration::from_millis(debounce))
5343 .await;
5344 let Some(Some(matches_task)) = editor
5345 .update_in(cx, |editor, _, cx| {
5346 if editor.selections.count() != 1 || editor.selections.line_mode {
5347 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5348 return None;
5349 }
5350 let selection = editor.selections.newest::<Point>(cx);
5351 if selection.is_empty() || selection.start.row != selection.end.row {
5352 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5353 return None;
5354 }
5355 let buffer = editor.buffer().read(cx).snapshot(cx);
5356 let query = buffer.text_for_range(selection.range()).collect::<String>();
5357 if query.trim().is_empty() {
5358 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5359 return None;
5360 }
5361 Some(cx.background_spawn(async move {
5362 let mut ranges = Vec::new();
5363 let selection_anchors = selection.range().to_anchors(&buffer);
5364 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5365 for (search_buffer, search_range, excerpt_id) in
5366 buffer.range_to_buffer_ranges(range)
5367 {
5368 ranges.extend(
5369 project::search::SearchQuery::text(
5370 query.clone(),
5371 false,
5372 false,
5373 false,
5374 Default::default(),
5375 Default::default(),
5376 None,
5377 )
5378 .unwrap()
5379 .search(search_buffer, Some(search_range.clone()))
5380 .await
5381 .into_iter()
5382 .filter_map(
5383 |match_range| {
5384 let start = search_buffer.anchor_after(
5385 search_range.start + match_range.start,
5386 );
5387 let end = search_buffer.anchor_before(
5388 search_range.start + match_range.end,
5389 );
5390 let range = Anchor::range_in_buffer(
5391 excerpt_id,
5392 search_buffer.remote_id(),
5393 start..end,
5394 );
5395 (range != selection_anchors).then_some(range)
5396 },
5397 ),
5398 );
5399 }
5400 }
5401 ranges
5402 }))
5403 })
5404 .log_err()
5405 else {
5406 return;
5407 };
5408 let matches = matches_task.await;
5409 editor
5410 .update_in(cx, |editor, _, cx| {
5411 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5412 if !matches.is_empty() {
5413 editor.highlight_background::<SelectedTextHighlight>(
5414 &matches,
5415 |theme| theme.editor_document_highlight_bracket_background,
5416 cx,
5417 )
5418 }
5419 })
5420 .log_err();
5421 }));
5422 }
5423
5424 pub fn refresh_inline_completion(
5425 &mut self,
5426 debounce: bool,
5427 user_requested: bool,
5428 window: &mut Window,
5429 cx: &mut Context<Self>,
5430 ) -> Option<()> {
5431 let provider = self.edit_prediction_provider()?;
5432 let cursor = self.selections.newest_anchor().head();
5433 let (buffer, cursor_buffer_position) =
5434 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5435
5436 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5437 self.discard_inline_completion(false, cx);
5438 return None;
5439 }
5440
5441 if !user_requested
5442 && (!self.should_show_edit_predictions()
5443 || !self.is_focused(window)
5444 || buffer.read(cx).is_empty())
5445 {
5446 self.discard_inline_completion(false, cx);
5447 return None;
5448 }
5449
5450 self.update_visible_inline_completion(window, cx);
5451 provider.refresh(
5452 self.project.clone(),
5453 buffer,
5454 cursor_buffer_position,
5455 debounce,
5456 cx,
5457 );
5458 Some(())
5459 }
5460
5461 fn show_edit_predictions_in_menu(&self) -> bool {
5462 match self.edit_prediction_settings {
5463 EditPredictionSettings::Disabled => false,
5464 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5465 }
5466 }
5467
5468 pub fn edit_predictions_enabled(&self) -> bool {
5469 match self.edit_prediction_settings {
5470 EditPredictionSettings::Disabled => false,
5471 EditPredictionSettings::Enabled { .. } => true,
5472 }
5473 }
5474
5475 fn edit_prediction_requires_modifier(&self) -> bool {
5476 match self.edit_prediction_settings {
5477 EditPredictionSettings::Disabled => false,
5478 EditPredictionSettings::Enabled {
5479 preview_requires_modifier,
5480 ..
5481 } => preview_requires_modifier,
5482 }
5483 }
5484
5485 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5486 if self.edit_prediction_provider.is_none() {
5487 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5488 } else {
5489 let selection = self.selections.newest_anchor();
5490 let cursor = selection.head();
5491
5492 if let Some((buffer, cursor_buffer_position)) =
5493 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5494 {
5495 self.edit_prediction_settings =
5496 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5497 }
5498 }
5499 }
5500
5501 fn edit_prediction_settings_at_position(
5502 &self,
5503 buffer: &Entity<Buffer>,
5504 buffer_position: language::Anchor,
5505 cx: &App,
5506 ) -> EditPredictionSettings {
5507 if self.mode != EditorMode::Full
5508 || !self.show_inline_completions_override.unwrap_or(true)
5509 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5510 {
5511 return EditPredictionSettings::Disabled;
5512 }
5513
5514 let buffer = buffer.read(cx);
5515
5516 let file = buffer.file();
5517
5518 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5519 return EditPredictionSettings::Disabled;
5520 };
5521
5522 let by_provider = matches!(
5523 self.menu_inline_completions_policy,
5524 MenuInlineCompletionsPolicy::ByProvider
5525 );
5526
5527 let show_in_menu = by_provider
5528 && self
5529 .edit_prediction_provider
5530 .as_ref()
5531 .map_or(false, |provider| {
5532 provider.provider.show_completions_in_menu()
5533 });
5534
5535 let preview_requires_modifier =
5536 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5537
5538 EditPredictionSettings::Enabled {
5539 show_in_menu,
5540 preview_requires_modifier,
5541 }
5542 }
5543
5544 fn should_show_edit_predictions(&self) -> bool {
5545 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5546 }
5547
5548 pub fn edit_prediction_preview_is_active(&self) -> bool {
5549 matches!(
5550 self.edit_prediction_preview,
5551 EditPredictionPreview::Active { .. }
5552 )
5553 }
5554
5555 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5556 let cursor = self.selections.newest_anchor().head();
5557 if let Some((buffer, cursor_position)) =
5558 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5559 {
5560 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5561 } else {
5562 false
5563 }
5564 }
5565
5566 fn edit_predictions_enabled_in_buffer(
5567 &self,
5568 buffer: &Entity<Buffer>,
5569 buffer_position: language::Anchor,
5570 cx: &App,
5571 ) -> bool {
5572 maybe!({
5573 if self.read_only(cx) {
5574 return Some(false);
5575 }
5576 let provider = self.edit_prediction_provider()?;
5577 if !provider.is_enabled(&buffer, buffer_position, cx) {
5578 return Some(false);
5579 }
5580 let buffer = buffer.read(cx);
5581 let Some(file) = buffer.file() else {
5582 return Some(true);
5583 };
5584 let settings = all_language_settings(Some(file), cx);
5585 Some(settings.edit_predictions_enabled_for_file(file, cx))
5586 })
5587 .unwrap_or(false)
5588 }
5589
5590 fn cycle_inline_completion(
5591 &mut self,
5592 direction: Direction,
5593 window: &mut Window,
5594 cx: &mut Context<Self>,
5595 ) -> Option<()> {
5596 let provider = self.edit_prediction_provider()?;
5597 let cursor = self.selections.newest_anchor().head();
5598 let (buffer, cursor_buffer_position) =
5599 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5600 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5601 return None;
5602 }
5603
5604 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5605 self.update_visible_inline_completion(window, cx);
5606
5607 Some(())
5608 }
5609
5610 pub fn show_inline_completion(
5611 &mut self,
5612 _: &ShowEditPrediction,
5613 window: &mut Window,
5614 cx: &mut Context<Self>,
5615 ) {
5616 if !self.has_active_inline_completion() {
5617 self.refresh_inline_completion(false, true, window, cx);
5618 return;
5619 }
5620
5621 self.update_visible_inline_completion(window, cx);
5622 }
5623
5624 pub fn display_cursor_names(
5625 &mut self,
5626 _: &DisplayCursorNames,
5627 window: &mut Window,
5628 cx: &mut Context<Self>,
5629 ) {
5630 self.show_cursor_names(window, cx);
5631 }
5632
5633 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5634 self.show_cursor_names = true;
5635 cx.notify();
5636 cx.spawn_in(window, async move |this, cx| {
5637 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5638 this.update(cx, |this, cx| {
5639 this.show_cursor_names = false;
5640 cx.notify()
5641 })
5642 .ok()
5643 })
5644 .detach();
5645 }
5646
5647 pub fn next_edit_prediction(
5648 &mut self,
5649 _: &NextEditPrediction,
5650 window: &mut Window,
5651 cx: &mut Context<Self>,
5652 ) {
5653 if self.has_active_inline_completion() {
5654 self.cycle_inline_completion(Direction::Next, window, cx);
5655 } else {
5656 let is_copilot_disabled = self
5657 .refresh_inline_completion(false, true, window, cx)
5658 .is_none();
5659 if is_copilot_disabled {
5660 cx.propagate();
5661 }
5662 }
5663 }
5664
5665 pub fn previous_edit_prediction(
5666 &mut self,
5667 _: &PreviousEditPrediction,
5668 window: &mut Window,
5669 cx: &mut Context<Self>,
5670 ) {
5671 if self.has_active_inline_completion() {
5672 self.cycle_inline_completion(Direction::Prev, window, cx);
5673 } else {
5674 let is_copilot_disabled = self
5675 .refresh_inline_completion(false, true, window, cx)
5676 .is_none();
5677 if is_copilot_disabled {
5678 cx.propagate();
5679 }
5680 }
5681 }
5682
5683 pub fn accept_edit_prediction(
5684 &mut self,
5685 _: &AcceptEditPrediction,
5686 window: &mut Window,
5687 cx: &mut Context<Self>,
5688 ) {
5689 if self.show_edit_predictions_in_menu() {
5690 self.hide_context_menu(window, cx);
5691 }
5692
5693 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5694 return;
5695 };
5696
5697 self.report_inline_completion_event(
5698 active_inline_completion.completion_id.clone(),
5699 true,
5700 cx,
5701 );
5702
5703 match &active_inline_completion.completion {
5704 InlineCompletion::Move { target, .. } => {
5705 let target = *target;
5706
5707 if let Some(position_map) = &self.last_position_map {
5708 if position_map
5709 .visible_row_range
5710 .contains(&target.to_display_point(&position_map.snapshot).row())
5711 || !self.edit_prediction_requires_modifier()
5712 {
5713 self.unfold_ranges(&[target..target], true, false, cx);
5714 // Note that this is also done in vim's handler of the Tab action.
5715 self.change_selections(
5716 Some(Autoscroll::newest()),
5717 window,
5718 cx,
5719 |selections| {
5720 selections.select_anchor_ranges([target..target]);
5721 },
5722 );
5723 self.clear_row_highlights::<EditPredictionPreview>();
5724
5725 self.edit_prediction_preview
5726 .set_previous_scroll_position(None);
5727 } else {
5728 self.edit_prediction_preview
5729 .set_previous_scroll_position(Some(
5730 position_map.snapshot.scroll_anchor,
5731 ));
5732
5733 self.highlight_rows::<EditPredictionPreview>(
5734 target..target,
5735 cx.theme().colors().editor_highlighted_line_background,
5736 true,
5737 cx,
5738 );
5739 self.request_autoscroll(Autoscroll::fit(), cx);
5740 }
5741 }
5742 }
5743 InlineCompletion::Edit { edits, .. } => {
5744 if let Some(provider) = self.edit_prediction_provider() {
5745 provider.accept(cx);
5746 }
5747
5748 let snapshot = self.buffer.read(cx).snapshot(cx);
5749 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5750
5751 self.buffer.update(cx, |buffer, cx| {
5752 buffer.edit(edits.iter().cloned(), None, cx)
5753 });
5754
5755 self.change_selections(None, window, cx, |s| {
5756 s.select_anchor_ranges([last_edit_end..last_edit_end])
5757 });
5758
5759 self.update_visible_inline_completion(window, cx);
5760 if self.active_inline_completion.is_none() {
5761 self.refresh_inline_completion(true, true, window, cx);
5762 }
5763
5764 cx.notify();
5765 }
5766 }
5767
5768 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5769 }
5770
5771 pub fn accept_partial_inline_completion(
5772 &mut self,
5773 _: &AcceptPartialEditPrediction,
5774 window: &mut Window,
5775 cx: &mut Context<Self>,
5776 ) {
5777 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5778 return;
5779 };
5780 if self.selections.count() != 1 {
5781 return;
5782 }
5783
5784 self.report_inline_completion_event(
5785 active_inline_completion.completion_id.clone(),
5786 true,
5787 cx,
5788 );
5789
5790 match &active_inline_completion.completion {
5791 InlineCompletion::Move { target, .. } => {
5792 let target = *target;
5793 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5794 selections.select_anchor_ranges([target..target]);
5795 });
5796 }
5797 InlineCompletion::Edit { edits, .. } => {
5798 // Find an insertion that starts at the cursor position.
5799 let snapshot = self.buffer.read(cx).snapshot(cx);
5800 let cursor_offset = self.selections.newest::<usize>(cx).head();
5801 let insertion = edits.iter().find_map(|(range, text)| {
5802 let range = range.to_offset(&snapshot);
5803 if range.is_empty() && range.start == cursor_offset {
5804 Some(text)
5805 } else {
5806 None
5807 }
5808 });
5809
5810 if let Some(text) = insertion {
5811 let mut partial_completion = text
5812 .chars()
5813 .by_ref()
5814 .take_while(|c| c.is_alphabetic())
5815 .collect::<String>();
5816 if partial_completion.is_empty() {
5817 partial_completion = text
5818 .chars()
5819 .by_ref()
5820 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5821 .collect::<String>();
5822 }
5823
5824 cx.emit(EditorEvent::InputHandled {
5825 utf16_range_to_replace: None,
5826 text: partial_completion.clone().into(),
5827 });
5828
5829 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5830
5831 self.refresh_inline_completion(true, true, window, cx);
5832 cx.notify();
5833 } else {
5834 self.accept_edit_prediction(&Default::default(), window, cx);
5835 }
5836 }
5837 }
5838 }
5839
5840 fn discard_inline_completion(
5841 &mut self,
5842 should_report_inline_completion_event: bool,
5843 cx: &mut Context<Self>,
5844 ) -> bool {
5845 if should_report_inline_completion_event {
5846 let completion_id = self
5847 .active_inline_completion
5848 .as_ref()
5849 .and_then(|active_completion| active_completion.completion_id.clone());
5850
5851 self.report_inline_completion_event(completion_id, false, cx);
5852 }
5853
5854 if let Some(provider) = self.edit_prediction_provider() {
5855 provider.discard(cx);
5856 }
5857
5858 self.take_active_inline_completion(cx)
5859 }
5860
5861 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5862 let Some(provider) = self.edit_prediction_provider() else {
5863 return;
5864 };
5865
5866 let Some((_, buffer, _)) = self
5867 .buffer
5868 .read(cx)
5869 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5870 else {
5871 return;
5872 };
5873
5874 let extension = buffer
5875 .read(cx)
5876 .file()
5877 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5878
5879 let event_type = match accepted {
5880 true => "Edit Prediction Accepted",
5881 false => "Edit Prediction Discarded",
5882 };
5883 telemetry::event!(
5884 event_type,
5885 provider = provider.name(),
5886 prediction_id = id,
5887 suggestion_accepted = accepted,
5888 file_extension = extension,
5889 );
5890 }
5891
5892 pub fn has_active_inline_completion(&self) -> bool {
5893 self.active_inline_completion.is_some()
5894 }
5895
5896 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5897 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5898 return false;
5899 };
5900
5901 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5902 self.clear_highlights::<InlineCompletionHighlight>(cx);
5903 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5904 true
5905 }
5906
5907 /// Returns true when we're displaying the edit prediction popover below the cursor
5908 /// like we are not previewing and the LSP autocomplete menu is visible
5909 /// or we are in `when_holding_modifier` mode.
5910 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5911 if self.edit_prediction_preview_is_active()
5912 || !self.show_edit_predictions_in_menu()
5913 || !self.edit_predictions_enabled()
5914 {
5915 return false;
5916 }
5917
5918 if self.has_visible_completions_menu() {
5919 return true;
5920 }
5921
5922 has_completion && self.edit_prediction_requires_modifier()
5923 }
5924
5925 fn handle_modifiers_changed(
5926 &mut self,
5927 modifiers: Modifiers,
5928 position_map: &PositionMap,
5929 window: &mut Window,
5930 cx: &mut Context<Self>,
5931 ) {
5932 if self.show_edit_predictions_in_menu() {
5933 self.update_edit_prediction_preview(&modifiers, window, cx);
5934 }
5935
5936 self.update_selection_mode(&modifiers, position_map, window, cx);
5937
5938 let mouse_position = window.mouse_position();
5939 if !position_map.text_hitbox.is_hovered(window) {
5940 return;
5941 }
5942
5943 self.update_hovered_link(
5944 position_map.point_for_position(mouse_position),
5945 &position_map.snapshot,
5946 modifiers,
5947 window,
5948 cx,
5949 )
5950 }
5951
5952 fn update_selection_mode(
5953 &mut self,
5954 modifiers: &Modifiers,
5955 position_map: &PositionMap,
5956 window: &mut Window,
5957 cx: &mut Context<Self>,
5958 ) {
5959 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
5960 return;
5961 }
5962
5963 let mouse_position = window.mouse_position();
5964 let point_for_position = position_map.point_for_position(mouse_position);
5965 let position = point_for_position.previous_valid;
5966
5967 self.select(
5968 SelectPhase::BeginColumnar {
5969 position,
5970 reset: false,
5971 goal_column: point_for_position.exact_unclipped.column(),
5972 },
5973 window,
5974 cx,
5975 );
5976 }
5977
5978 fn update_edit_prediction_preview(
5979 &mut self,
5980 modifiers: &Modifiers,
5981 window: &mut Window,
5982 cx: &mut Context<Self>,
5983 ) {
5984 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
5985 let Some(accept_keystroke) = accept_keybind.keystroke() else {
5986 return;
5987 };
5988
5989 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
5990 if matches!(
5991 self.edit_prediction_preview,
5992 EditPredictionPreview::Inactive { .. }
5993 ) {
5994 self.edit_prediction_preview = EditPredictionPreview::Active {
5995 previous_scroll_position: None,
5996 since: Instant::now(),
5997 };
5998
5999 self.update_visible_inline_completion(window, cx);
6000 cx.notify();
6001 }
6002 } else if let EditPredictionPreview::Active {
6003 previous_scroll_position,
6004 since,
6005 } = self.edit_prediction_preview
6006 {
6007 if let (Some(previous_scroll_position), Some(position_map)) =
6008 (previous_scroll_position, self.last_position_map.as_ref())
6009 {
6010 self.set_scroll_position(
6011 previous_scroll_position
6012 .scroll_position(&position_map.snapshot.display_snapshot),
6013 window,
6014 cx,
6015 );
6016 }
6017
6018 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6019 released_too_fast: since.elapsed() < Duration::from_millis(200),
6020 };
6021 self.clear_row_highlights::<EditPredictionPreview>();
6022 self.update_visible_inline_completion(window, cx);
6023 cx.notify();
6024 }
6025 }
6026
6027 fn update_visible_inline_completion(
6028 &mut self,
6029 _window: &mut Window,
6030 cx: &mut Context<Self>,
6031 ) -> Option<()> {
6032 let selection = self.selections.newest_anchor();
6033 let cursor = selection.head();
6034 let multibuffer = self.buffer.read(cx).snapshot(cx);
6035 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6036 let excerpt_id = cursor.excerpt_id;
6037
6038 let show_in_menu = self.show_edit_predictions_in_menu();
6039 let completions_menu_has_precedence = !show_in_menu
6040 && (self.context_menu.borrow().is_some()
6041 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6042
6043 if completions_menu_has_precedence
6044 || !offset_selection.is_empty()
6045 || self
6046 .active_inline_completion
6047 .as_ref()
6048 .map_or(false, |completion| {
6049 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6050 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6051 !invalidation_range.contains(&offset_selection.head())
6052 })
6053 {
6054 self.discard_inline_completion(false, cx);
6055 return None;
6056 }
6057
6058 self.take_active_inline_completion(cx);
6059 let Some(provider) = self.edit_prediction_provider() else {
6060 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6061 return None;
6062 };
6063
6064 let (buffer, cursor_buffer_position) =
6065 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6066
6067 self.edit_prediction_settings =
6068 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6069
6070 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6071
6072 if self.edit_prediction_indent_conflict {
6073 let cursor_point = cursor.to_point(&multibuffer);
6074
6075 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6076
6077 if let Some((_, indent)) = indents.iter().next() {
6078 if indent.len == cursor_point.column {
6079 self.edit_prediction_indent_conflict = false;
6080 }
6081 }
6082 }
6083
6084 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6085 let edits = inline_completion
6086 .edits
6087 .into_iter()
6088 .flat_map(|(range, new_text)| {
6089 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6090 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6091 Some((start..end, new_text))
6092 })
6093 .collect::<Vec<_>>();
6094 if edits.is_empty() {
6095 return None;
6096 }
6097
6098 let first_edit_start = edits.first().unwrap().0.start;
6099 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6100 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6101
6102 let last_edit_end = edits.last().unwrap().0.end;
6103 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6104 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6105
6106 let cursor_row = cursor.to_point(&multibuffer).row;
6107
6108 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6109
6110 let mut inlay_ids = Vec::new();
6111 let invalidation_row_range;
6112 let move_invalidation_row_range = if cursor_row < edit_start_row {
6113 Some(cursor_row..edit_end_row)
6114 } else if cursor_row > edit_end_row {
6115 Some(edit_start_row..cursor_row)
6116 } else {
6117 None
6118 };
6119 let is_move =
6120 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6121 let completion = if is_move {
6122 invalidation_row_range =
6123 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6124 let target = first_edit_start;
6125 InlineCompletion::Move { target, snapshot }
6126 } else {
6127 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6128 && !self.inline_completions_hidden_for_vim_mode;
6129
6130 if show_completions_in_buffer {
6131 if edits
6132 .iter()
6133 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6134 {
6135 let mut inlays = Vec::new();
6136 for (range, new_text) in &edits {
6137 let inlay = Inlay::inline_completion(
6138 post_inc(&mut self.next_inlay_id),
6139 range.start,
6140 new_text.as_str(),
6141 );
6142 inlay_ids.push(inlay.id);
6143 inlays.push(inlay);
6144 }
6145
6146 self.splice_inlays(&[], inlays, cx);
6147 } else {
6148 let background_color = cx.theme().status().deleted_background;
6149 self.highlight_text::<InlineCompletionHighlight>(
6150 edits.iter().map(|(range, _)| range.clone()).collect(),
6151 HighlightStyle {
6152 background_color: Some(background_color),
6153 ..Default::default()
6154 },
6155 cx,
6156 );
6157 }
6158 }
6159
6160 invalidation_row_range = edit_start_row..edit_end_row;
6161
6162 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6163 if provider.show_tab_accept_marker() {
6164 EditDisplayMode::TabAccept
6165 } else {
6166 EditDisplayMode::Inline
6167 }
6168 } else {
6169 EditDisplayMode::DiffPopover
6170 };
6171
6172 InlineCompletion::Edit {
6173 edits,
6174 edit_preview: inline_completion.edit_preview,
6175 display_mode,
6176 snapshot,
6177 }
6178 };
6179
6180 let invalidation_range = multibuffer
6181 .anchor_before(Point::new(invalidation_row_range.start, 0))
6182 ..multibuffer.anchor_after(Point::new(
6183 invalidation_row_range.end,
6184 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6185 ));
6186
6187 self.stale_inline_completion_in_menu = None;
6188 self.active_inline_completion = Some(InlineCompletionState {
6189 inlay_ids,
6190 completion,
6191 completion_id: inline_completion.id,
6192 invalidation_range,
6193 });
6194
6195 cx.notify();
6196
6197 Some(())
6198 }
6199
6200 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6201 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6202 }
6203
6204 fn render_code_actions_indicator(
6205 &self,
6206 _style: &EditorStyle,
6207 row: DisplayRow,
6208 is_active: bool,
6209 breakpoint: Option<&(Anchor, Breakpoint)>,
6210 cx: &mut Context<Self>,
6211 ) -> Option<IconButton> {
6212 let color = Color::Muted;
6213 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6214 let show_tooltip = !self.context_menu_visible();
6215
6216 if self.available_code_actions.is_some() {
6217 Some(
6218 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6219 .shape(ui::IconButtonShape::Square)
6220 .icon_size(IconSize::XSmall)
6221 .icon_color(color)
6222 .toggle_state(is_active)
6223 .when(show_tooltip, |this| {
6224 this.tooltip({
6225 let focus_handle = self.focus_handle.clone();
6226 move |window, cx| {
6227 Tooltip::for_action_in(
6228 "Toggle Code Actions",
6229 &ToggleCodeActions {
6230 deployed_from_indicator: None,
6231 },
6232 &focus_handle,
6233 window,
6234 cx,
6235 )
6236 }
6237 })
6238 })
6239 .on_click(cx.listener(move |editor, _e, window, cx| {
6240 window.focus(&editor.focus_handle(cx));
6241 editor.toggle_code_actions(
6242 &ToggleCodeActions {
6243 deployed_from_indicator: Some(row),
6244 },
6245 window,
6246 cx,
6247 );
6248 }))
6249 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6250 editor.set_breakpoint_context_menu(
6251 row,
6252 position,
6253 event.down.position,
6254 window,
6255 cx,
6256 );
6257 })),
6258 )
6259 } else {
6260 None
6261 }
6262 }
6263
6264 fn clear_tasks(&mut self) {
6265 self.tasks.clear()
6266 }
6267
6268 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6269 if self.tasks.insert(key, value).is_some() {
6270 // This case should hopefully be rare, but just in case...
6271 log::error!(
6272 "multiple different run targets found on a single line, only the last target will be rendered"
6273 )
6274 }
6275 }
6276
6277 /// Get all display points of breakpoints that will be rendered within editor
6278 ///
6279 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6280 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6281 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6282 fn active_breakpoints(
6283 &self,
6284 range: Range<DisplayRow>,
6285 window: &mut Window,
6286 cx: &mut Context<Self>,
6287 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6288 let mut breakpoint_display_points = HashMap::default();
6289
6290 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6291 return breakpoint_display_points;
6292 };
6293
6294 let snapshot = self.snapshot(window, cx);
6295
6296 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6297 let Some(project) = self.project.as_ref() else {
6298 return breakpoint_display_points;
6299 };
6300
6301 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6302 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6303
6304 for (buffer_snapshot, range, excerpt_id) in
6305 multi_buffer_snapshot.range_to_buffer_ranges(range)
6306 {
6307 let Some(buffer) = project.read_with(cx, |this, cx| {
6308 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6309 }) else {
6310 continue;
6311 };
6312 let breakpoints = breakpoint_store.read(cx).breakpoints(
6313 &buffer,
6314 Some(
6315 buffer_snapshot.anchor_before(range.start)
6316 ..buffer_snapshot.anchor_after(range.end),
6317 ),
6318 buffer_snapshot,
6319 cx,
6320 );
6321 for (anchor, breakpoint) in breakpoints {
6322 let multi_buffer_anchor =
6323 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6324 let position = multi_buffer_anchor
6325 .to_point(&multi_buffer_snapshot)
6326 .to_display_point(&snapshot);
6327
6328 breakpoint_display_points
6329 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6330 }
6331 }
6332
6333 breakpoint_display_points
6334 }
6335
6336 fn breakpoint_context_menu(
6337 &self,
6338 anchor: Anchor,
6339 window: &mut Window,
6340 cx: &mut Context<Self>,
6341 ) -> Entity<ui::ContextMenu> {
6342 let weak_editor = cx.weak_entity();
6343 let focus_handle = self.focus_handle(cx);
6344
6345 let row = self
6346 .buffer
6347 .read(cx)
6348 .snapshot(cx)
6349 .summary_for_anchor::<Point>(&anchor)
6350 .row;
6351
6352 let breakpoint = self
6353 .breakpoint_at_row(row, window, cx)
6354 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6355
6356 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6357 "Edit Log Breakpoint"
6358 } else {
6359 "Set Log Breakpoint"
6360 };
6361
6362 let condition_breakpoint_msg = if breakpoint
6363 .as_ref()
6364 .is_some_and(|bp| bp.1.condition.is_some())
6365 {
6366 "Edit Condition Breakpoint"
6367 } else {
6368 "Set Condition Breakpoint"
6369 };
6370
6371 let hit_condition_breakpoint_msg = if breakpoint
6372 .as_ref()
6373 .is_some_and(|bp| bp.1.hit_condition.is_some())
6374 {
6375 "Edit Hit Condition Breakpoint"
6376 } else {
6377 "Set Hit Condition Breakpoint"
6378 };
6379
6380 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6381 "Unset Breakpoint"
6382 } else {
6383 "Set Breakpoint"
6384 };
6385
6386 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6387 BreakpointState::Enabled => Some("Disable"),
6388 BreakpointState::Disabled => Some("Enable"),
6389 });
6390
6391 let (anchor, breakpoint) =
6392 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6393
6394 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6395 menu.on_blur_subscription(Subscription::new(|| {}))
6396 .context(focus_handle)
6397 .when_some(toggle_state_msg, |this, msg| {
6398 this.entry(msg, None, {
6399 let weak_editor = weak_editor.clone();
6400 let breakpoint = breakpoint.clone();
6401 move |_window, cx| {
6402 weak_editor
6403 .update(cx, |this, cx| {
6404 this.edit_breakpoint_at_anchor(
6405 anchor,
6406 breakpoint.as_ref().clone(),
6407 BreakpointEditAction::InvertState,
6408 cx,
6409 );
6410 })
6411 .log_err();
6412 }
6413 })
6414 })
6415 .entry(set_breakpoint_msg, None, {
6416 let weak_editor = weak_editor.clone();
6417 let breakpoint = breakpoint.clone();
6418 move |_window, cx| {
6419 weak_editor
6420 .update(cx, |this, cx| {
6421 this.edit_breakpoint_at_anchor(
6422 anchor,
6423 breakpoint.as_ref().clone(),
6424 BreakpointEditAction::Toggle,
6425 cx,
6426 );
6427 })
6428 .log_err();
6429 }
6430 })
6431 .entry(log_breakpoint_msg, None, {
6432 let breakpoint = breakpoint.clone();
6433 let weak_editor = weak_editor.clone();
6434 move |window, cx| {
6435 weak_editor
6436 .update(cx, |this, cx| {
6437 this.add_edit_breakpoint_block(
6438 anchor,
6439 breakpoint.as_ref(),
6440 BreakpointPromptEditAction::Log,
6441 window,
6442 cx,
6443 );
6444 })
6445 .log_err();
6446 }
6447 })
6448 .entry(condition_breakpoint_msg, None, {
6449 let breakpoint = breakpoint.clone();
6450 let weak_editor = weak_editor.clone();
6451 move |window, cx| {
6452 weak_editor
6453 .update(cx, |this, cx| {
6454 this.add_edit_breakpoint_block(
6455 anchor,
6456 breakpoint.as_ref(),
6457 BreakpointPromptEditAction::Condition,
6458 window,
6459 cx,
6460 );
6461 })
6462 .log_err();
6463 }
6464 })
6465 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6466 weak_editor
6467 .update(cx, |this, cx| {
6468 this.add_edit_breakpoint_block(
6469 anchor,
6470 breakpoint.as_ref(),
6471 BreakpointPromptEditAction::HitCondition,
6472 window,
6473 cx,
6474 );
6475 })
6476 .log_err();
6477 })
6478 })
6479 }
6480
6481 fn render_breakpoint(
6482 &self,
6483 position: Anchor,
6484 row: DisplayRow,
6485 breakpoint: &Breakpoint,
6486 cx: &mut Context<Self>,
6487 ) -> IconButton {
6488 let (color, icon) = {
6489 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6490 (false, false) => ui::IconName::DebugBreakpoint,
6491 (true, false) => ui::IconName::DebugLogBreakpoint,
6492 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6493 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6494 };
6495
6496 let color = if self
6497 .gutter_breakpoint_indicator
6498 .0
6499 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6500 {
6501 Color::Hint
6502 } else {
6503 Color::Debugger
6504 };
6505
6506 (color, icon)
6507 };
6508
6509 let breakpoint = Arc::from(breakpoint.clone());
6510
6511 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6512 .icon_size(IconSize::XSmall)
6513 .size(ui::ButtonSize::None)
6514 .icon_color(color)
6515 .style(ButtonStyle::Transparent)
6516 .on_click(cx.listener({
6517 let breakpoint = breakpoint.clone();
6518
6519 move |editor, event: &ClickEvent, window, cx| {
6520 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6521 BreakpointEditAction::InvertState
6522 } else {
6523 BreakpointEditAction::Toggle
6524 };
6525
6526 window.focus(&editor.focus_handle(cx));
6527 editor.edit_breakpoint_at_anchor(
6528 position,
6529 breakpoint.as_ref().clone(),
6530 edit_action,
6531 cx,
6532 );
6533 }
6534 }))
6535 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6536 editor.set_breakpoint_context_menu(
6537 row,
6538 Some(position),
6539 event.down.position,
6540 window,
6541 cx,
6542 );
6543 }))
6544 }
6545
6546 fn build_tasks_context(
6547 project: &Entity<Project>,
6548 buffer: &Entity<Buffer>,
6549 buffer_row: u32,
6550 tasks: &Arc<RunnableTasks>,
6551 cx: &mut Context<Self>,
6552 ) -> Task<Option<task::TaskContext>> {
6553 let position = Point::new(buffer_row, tasks.column);
6554 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6555 let location = Location {
6556 buffer: buffer.clone(),
6557 range: range_start..range_start,
6558 };
6559 // Fill in the environmental variables from the tree-sitter captures
6560 let mut captured_task_variables = TaskVariables::default();
6561 for (capture_name, value) in tasks.extra_variables.clone() {
6562 captured_task_variables.insert(
6563 task::VariableName::Custom(capture_name.into()),
6564 value.clone(),
6565 );
6566 }
6567 project.update(cx, |project, cx| {
6568 project.task_store().update(cx, |task_store, cx| {
6569 task_store.task_context_for_location(captured_task_variables, location, cx)
6570 })
6571 })
6572 }
6573
6574 pub fn spawn_nearest_task(
6575 &mut self,
6576 action: &SpawnNearestTask,
6577 window: &mut Window,
6578 cx: &mut Context<Self>,
6579 ) {
6580 let Some((workspace, _)) = self.workspace.clone() else {
6581 return;
6582 };
6583 let Some(project) = self.project.clone() else {
6584 return;
6585 };
6586
6587 // Try to find a closest, enclosing node using tree-sitter that has a
6588 // task
6589 let Some((buffer, buffer_row, tasks)) = self
6590 .find_enclosing_node_task(cx)
6591 // Or find the task that's closest in row-distance.
6592 .or_else(|| self.find_closest_task(cx))
6593 else {
6594 return;
6595 };
6596
6597 let reveal_strategy = action.reveal;
6598 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6599 cx.spawn_in(window, async move |_, cx| {
6600 let context = task_context.await?;
6601 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6602
6603 let resolved = resolved_task.resolved.as_mut()?;
6604 resolved.reveal = reveal_strategy;
6605
6606 workspace
6607 .update(cx, |workspace, cx| {
6608 workspace::tasks::schedule_resolved_task(
6609 workspace,
6610 task_source_kind,
6611 resolved_task,
6612 false,
6613 cx,
6614 );
6615 })
6616 .ok()
6617 })
6618 .detach();
6619 }
6620
6621 fn find_closest_task(
6622 &mut self,
6623 cx: &mut Context<Self>,
6624 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6625 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6626
6627 let ((buffer_id, row), tasks) = self
6628 .tasks
6629 .iter()
6630 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6631
6632 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6633 let tasks = Arc::new(tasks.to_owned());
6634 Some((buffer, *row, tasks))
6635 }
6636
6637 fn find_enclosing_node_task(
6638 &mut self,
6639 cx: &mut Context<Self>,
6640 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6641 let snapshot = self.buffer.read(cx).snapshot(cx);
6642 let offset = self.selections.newest::<usize>(cx).head();
6643 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6644 let buffer_id = excerpt.buffer().remote_id();
6645
6646 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6647 let mut cursor = layer.node().walk();
6648
6649 while cursor.goto_first_child_for_byte(offset).is_some() {
6650 if cursor.node().end_byte() == offset {
6651 cursor.goto_next_sibling();
6652 }
6653 }
6654
6655 // Ascend to the smallest ancestor that contains the range and has a task.
6656 loop {
6657 let node = cursor.node();
6658 let node_range = node.byte_range();
6659 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6660
6661 // Check if this node contains our offset
6662 if node_range.start <= offset && node_range.end >= offset {
6663 // If it contains offset, check for task
6664 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6665 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6666 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6667 }
6668 }
6669
6670 if !cursor.goto_parent() {
6671 break;
6672 }
6673 }
6674 None
6675 }
6676
6677 fn render_run_indicator(
6678 &self,
6679 _style: &EditorStyle,
6680 is_active: bool,
6681 row: DisplayRow,
6682 breakpoint: Option<(Anchor, Breakpoint)>,
6683 cx: &mut Context<Self>,
6684 ) -> IconButton {
6685 let color = Color::Muted;
6686 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6687
6688 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6689 .shape(ui::IconButtonShape::Square)
6690 .icon_size(IconSize::XSmall)
6691 .icon_color(color)
6692 .toggle_state(is_active)
6693 .on_click(cx.listener(move |editor, _e, window, cx| {
6694 window.focus(&editor.focus_handle(cx));
6695 editor.toggle_code_actions(
6696 &ToggleCodeActions {
6697 deployed_from_indicator: Some(row),
6698 },
6699 window,
6700 cx,
6701 );
6702 }))
6703 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6704 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6705 }))
6706 }
6707
6708 pub fn context_menu_visible(&self) -> bool {
6709 !self.edit_prediction_preview_is_active()
6710 && self
6711 .context_menu
6712 .borrow()
6713 .as_ref()
6714 .map_or(false, |menu| menu.visible())
6715 }
6716
6717 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6718 self.context_menu
6719 .borrow()
6720 .as_ref()
6721 .map(|menu| menu.origin())
6722 }
6723
6724 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6725 self.context_menu_options = Some(options);
6726 }
6727
6728 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6729 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6730
6731 fn render_edit_prediction_popover(
6732 &mut self,
6733 text_bounds: &Bounds<Pixels>,
6734 content_origin: gpui::Point<Pixels>,
6735 editor_snapshot: &EditorSnapshot,
6736 visible_row_range: Range<DisplayRow>,
6737 scroll_top: f32,
6738 scroll_bottom: f32,
6739 line_layouts: &[LineWithInvisibles],
6740 line_height: Pixels,
6741 scroll_pixel_position: gpui::Point<Pixels>,
6742 newest_selection_head: Option<DisplayPoint>,
6743 editor_width: Pixels,
6744 style: &EditorStyle,
6745 window: &mut Window,
6746 cx: &mut App,
6747 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6748 let active_inline_completion = self.active_inline_completion.as_ref()?;
6749
6750 if self.edit_prediction_visible_in_cursor_popover(true) {
6751 return None;
6752 }
6753
6754 match &active_inline_completion.completion {
6755 InlineCompletion::Move { target, .. } => {
6756 let target_display_point = target.to_display_point(editor_snapshot);
6757
6758 if self.edit_prediction_requires_modifier() {
6759 if !self.edit_prediction_preview_is_active() {
6760 return None;
6761 }
6762
6763 self.render_edit_prediction_modifier_jump_popover(
6764 text_bounds,
6765 content_origin,
6766 visible_row_range,
6767 line_layouts,
6768 line_height,
6769 scroll_pixel_position,
6770 newest_selection_head,
6771 target_display_point,
6772 window,
6773 cx,
6774 )
6775 } else {
6776 self.render_edit_prediction_eager_jump_popover(
6777 text_bounds,
6778 content_origin,
6779 editor_snapshot,
6780 visible_row_range,
6781 scroll_top,
6782 scroll_bottom,
6783 line_height,
6784 scroll_pixel_position,
6785 target_display_point,
6786 editor_width,
6787 window,
6788 cx,
6789 )
6790 }
6791 }
6792 InlineCompletion::Edit {
6793 display_mode: EditDisplayMode::Inline,
6794 ..
6795 } => None,
6796 InlineCompletion::Edit {
6797 display_mode: EditDisplayMode::TabAccept,
6798 edits,
6799 ..
6800 } => {
6801 let range = &edits.first()?.0;
6802 let target_display_point = range.end.to_display_point(editor_snapshot);
6803
6804 self.render_edit_prediction_end_of_line_popover(
6805 "Accept",
6806 editor_snapshot,
6807 visible_row_range,
6808 target_display_point,
6809 line_height,
6810 scroll_pixel_position,
6811 content_origin,
6812 editor_width,
6813 window,
6814 cx,
6815 )
6816 }
6817 InlineCompletion::Edit {
6818 edits,
6819 edit_preview,
6820 display_mode: EditDisplayMode::DiffPopover,
6821 snapshot,
6822 } => self.render_edit_prediction_diff_popover(
6823 text_bounds,
6824 content_origin,
6825 editor_snapshot,
6826 visible_row_range,
6827 line_layouts,
6828 line_height,
6829 scroll_pixel_position,
6830 newest_selection_head,
6831 editor_width,
6832 style,
6833 edits,
6834 edit_preview,
6835 snapshot,
6836 window,
6837 cx,
6838 ),
6839 }
6840 }
6841
6842 fn render_edit_prediction_modifier_jump_popover(
6843 &mut self,
6844 text_bounds: &Bounds<Pixels>,
6845 content_origin: gpui::Point<Pixels>,
6846 visible_row_range: Range<DisplayRow>,
6847 line_layouts: &[LineWithInvisibles],
6848 line_height: Pixels,
6849 scroll_pixel_position: gpui::Point<Pixels>,
6850 newest_selection_head: Option<DisplayPoint>,
6851 target_display_point: DisplayPoint,
6852 window: &mut Window,
6853 cx: &mut App,
6854 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6855 let scrolled_content_origin =
6856 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6857
6858 const SCROLL_PADDING_Y: Pixels = px(12.);
6859
6860 if target_display_point.row() < visible_row_range.start {
6861 return self.render_edit_prediction_scroll_popover(
6862 |_| SCROLL_PADDING_Y,
6863 IconName::ArrowUp,
6864 visible_row_range,
6865 line_layouts,
6866 newest_selection_head,
6867 scrolled_content_origin,
6868 window,
6869 cx,
6870 );
6871 } else if target_display_point.row() >= visible_row_range.end {
6872 return self.render_edit_prediction_scroll_popover(
6873 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6874 IconName::ArrowDown,
6875 visible_row_range,
6876 line_layouts,
6877 newest_selection_head,
6878 scrolled_content_origin,
6879 window,
6880 cx,
6881 );
6882 }
6883
6884 const POLE_WIDTH: Pixels = px(2.);
6885
6886 let line_layout =
6887 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6888 let target_column = target_display_point.column() as usize;
6889
6890 let target_x = line_layout.x_for_index(target_column);
6891 let target_y =
6892 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6893
6894 let flag_on_right = target_x < text_bounds.size.width / 2.;
6895
6896 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6897 border_color.l += 0.001;
6898
6899 let mut element = v_flex()
6900 .items_end()
6901 .when(flag_on_right, |el| el.items_start())
6902 .child(if flag_on_right {
6903 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6904 .rounded_bl(px(0.))
6905 .rounded_tl(px(0.))
6906 .border_l_2()
6907 .border_color(border_color)
6908 } else {
6909 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6910 .rounded_br(px(0.))
6911 .rounded_tr(px(0.))
6912 .border_r_2()
6913 .border_color(border_color)
6914 })
6915 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6916 .into_any();
6917
6918 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6919
6920 let mut origin = scrolled_content_origin + point(target_x, target_y)
6921 - point(
6922 if flag_on_right {
6923 POLE_WIDTH
6924 } else {
6925 size.width - POLE_WIDTH
6926 },
6927 size.height - line_height,
6928 );
6929
6930 origin.x = origin.x.max(content_origin.x);
6931
6932 element.prepaint_at(origin, window, cx);
6933
6934 Some((element, origin))
6935 }
6936
6937 fn render_edit_prediction_scroll_popover(
6938 &mut self,
6939 to_y: impl Fn(Size<Pixels>) -> Pixels,
6940 scroll_icon: IconName,
6941 visible_row_range: Range<DisplayRow>,
6942 line_layouts: &[LineWithInvisibles],
6943 newest_selection_head: Option<DisplayPoint>,
6944 scrolled_content_origin: gpui::Point<Pixels>,
6945 window: &mut Window,
6946 cx: &mut App,
6947 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6948 let mut element = self
6949 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
6950 .into_any();
6951
6952 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6953
6954 let cursor = newest_selection_head?;
6955 let cursor_row_layout =
6956 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
6957 let cursor_column = cursor.column() as usize;
6958
6959 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
6960
6961 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
6962
6963 element.prepaint_at(origin, window, cx);
6964 Some((element, origin))
6965 }
6966
6967 fn render_edit_prediction_eager_jump_popover(
6968 &mut self,
6969 text_bounds: &Bounds<Pixels>,
6970 content_origin: gpui::Point<Pixels>,
6971 editor_snapshot: &EditorSnapshot,
6972 visible_row_range: Range<DisplayRow>,
6973 scroll_top: f32,
6974 scroll_bottom: f32,
6975 line_height: Pixels,
6976 scroll_pixel_position: gpui::Point<Pixels>,
6977 target_display_point: DisplayPoint,
6978 editor_width: Pixels,
6979 window: &mut Window,
6980 cx: &mut App,
6981 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6982 if target_display_point.row().as_f32() < scroll_top {
6983 let mut element = self
6984 .render_edit_prediction_line_popover(
6985 "Jump to Edit",
6986 Some(IconName::ArrowUp),
6987 window,
6988 cx,
6989 )?
6990 .into_any();
6991
6992 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6993 let offset = point(
6994 (text_bounds.size.width - size.width) / 2.,
6995 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6996 );
6997
6998 let origin = text_bounds.origin + offset;
6999 element.prepaint_at(origin, window, cx);
7000 Some((element, origin))
7001 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7002 let mut element = self
7003 .render_edit_prediction_line_popover(
7004 "Jump to Edit",
7005 Some(IconName::ArrowDown),
7006 window,
7007 cx,
7008 )?
7009 .into_any();
7010
7011 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7012 let offset = point(
7013 (text_bounds.size.width - size.width) / 2.,
7014 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7015 );
7016
7017 let origin = text_bounds.origin + offset;
7018 element.prepaint_at(origin, window, cx);
7019 Some((element, origin))
7020 } else {
7021 self.render_edit_prediction_end_of_line_popover(
7022 "Jump to Edit",
7023 editor_snapshot,
7024 visible_row_range,
7025 target_display_point,
7026 line_height,
7027 scroll_pixel_position,
7028 content_origin,
7029 editor_width,
7030 window,
7031 cx,
7032 )
7033 }
7034 }
7035
7036 fn render_edit_prediction_end_of_line_popover(
7037 self: &mut Editor,
7038 label: &'static str,
7039 editor_snapshot: &EditorSnapshot,
7040 visible_row_range: Range<DisplayRow>,
7041 target_display_point: DisplayPoint,
7042 line_height: Pixels,
7043 scroll_pixel_position: gpui::Point<Pixels>,
7044 content_origin: gpui::Point<Pixels>,
7045 editor_width: Pixels,
7046 window: &mut Window,
7047 cx: &mut App,
7048 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7049 let target_line_end = DisplayPoint::new(
7050 target_display_point.row(),
7051 editor_snapshot.line_len(target_display_point.row()),
7052 );
7053
7054 let mut element = self
7055 .render_edit_prediction_line_popover(label, None, window, cx)?
7056 .into_any();
7057
7058 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7059
7060 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7061
7062 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7063 let mut origin = start_point
7064 + line_origin
7065 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7066 origin.x = origin.x.max(content_origin.x);
7067
7068 let max_x = content_origin.x + editor_width - size.width;
7069
7070 if origin.x > max_x {
7071 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7072
7073 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7074 origin.y += offset;
7075 IconName::ArrowUp
7076 } else {
7077 origin.y -= offset;
7078 IconName::ArrowDown
7079 };
7080
7081 element = self
7082 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7083 .into_any();
7084
7085 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7086
7087 origin.x = content_origin.x + editor_width - size.width - px(2.);
7088 }
7089
7090 element.prepaint_at(origin, window, cx);
7091 Some((element, origin))
7092 }
7093
7094 fn render_edit_prediction_diff_popover(
7095 self: &Editor,
7096 text_bounds: &Bounds<Pixels>,
7097 content_origin: gpui::Point<Pixels>,
7098 editor_snapshot: &EditorSnapshot,
7099 visible_row_range: Range<DisplayRow>,
7100 line_layouts: &[LineWithInvisibles],
7101 line_height: Pixels,
7102 scroll_pixel_position: gpui::Point<Pixels>,
7103 newest_selection_head: Option<DisplayPoint>,
7104 editor_width: Pixels,
7105 style: &EditorStyle,
7106 edits: &Vec<(Range<Anchor>, String)>,
7107 edit_preview: &Option<language::EditPreview>,
7108 snapshot: &language::BufferSnapshot,
7109 window: &mut Window,
7110 cx: &mut App,
7111 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7112 let edit_start = edits
7113 .first()
7114 .unwrap()
7115 .0
7116 .start
7117 .to_display_point(editor_snapshot);
7118 let edit_end = edits
7119 .last()
7120 .unwrap()
7121 .0
7122 .end
7123 .to_display_point(editor_snapshot);
7124
7125 let is_visible = visible_row_range.contains(&edit_start.row())
7126 || visible_row_range.contains(&edit_end.row());
7127 if !is_visible {
7128 return None;
7129 }
7130
7131 let highlighted_edits =
7132 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7133
7134 let styled_text = highlighted_edits.to_styled_text(&style.text);
7135 let line_count = highlighted_edits.text.lines().count();
7136
7137 const BORDER_WIDTH: Pixels = px(1.);
7138
7139 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7140 let has_keybind = keybind.is_some();
7141
7142 let mut element = h_flex()
7143 .items_start()
7144 .child(
7145 h_flex()
7146 .bg(cx.theme().colors().editor_background)
7147 .border(BORDER_WIDTH)
7148 .shadow_sm()
7149 .border_color(cx.theme().colors().border)
7150 .rounded_l_lg()
7151 .when(line_count > 1, |el| el.rounded_br_lg())
7152 .pr_1()
7153 .child(styled_text),
7154 )
7155 .child(
7156 h_flex()
7157 .h(line_height + BORDER_WIDTH * 2.)
7158 .px_1p5()
7159 .gap_1()
7160 // Workaround: For some reason, there's a gap if we don't do this
7161 .ml(-BORDER_WIDTH)
7162 .shadow(smallvec![gpui::BoxShadow {
7163 color: gpui::black().opacity(0.05),
7164 offset: point(px(1.), px(1.)),
7165 blur_radius: px(2.),
7166 spread_radius: px(0.),
7167 }])
7168 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7169 .border(BORDER_WIDTH)
7170 .border_color(cx.theme().colors().border)
7171 .rounded_r_lg()
7172 .id("edit_prediction_diff_popover_keybind")
7173 .when(!has_keybind, |el| {
7174 let status_colors = cx.theme().status();
7175
7176 el.bg(status_colors.error_background)
7177 .border_color(status_colors.error.opacity(0.6))
7178 .child(Icon::new(IconName::Info).color(Color::Error))
7179 .cursor_default()
7180 .hoverable_tooltip(move |_window, cx| {
7181 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7182 })
7183 })
7184 .children(keybind),
7185 )
7186 .into_any();
7187
7188 let longest_row =
7189 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7190 let longest_line_width = if visible_row_range.contains(&longest_row) {
7191 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7192 } else {
7193 layout_line(
7194 longest_row,
7195 editor_snapshot,
7196 style,
7197 editor_width,
7198 |_| false,
7199 window,
7200 cx,
7201 )
7202 .width
7203 };
7204
7205 let viewport_bounds =
7206 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7207 right: -EditorElement::SCROLLBAR_WIDTH,
7208 ..Default::default()
7209 });
7210
7211 let x_after_longest =
7212 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7213 - scroll_pixel_position.x;
7214
7215 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7216
7217 // Fully visible if it can be displayed within the window (allow overlapping other
7218 // panes). However, this is only allowed if the popover starts within text_bounds.
7219 let can_position_to_the_right = x_after_longest < text_bounds.right()
7220 && x_after_longest + element_bounds.width < viewport_bounds.right();
7221
7222 let mut origin = if can_position_to_the_right {
7223 point(
7224 x_after_longest,
7225 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7226 - scroll_pixel_position.y,
7227 )
7228 } else {
7229 let cursor_row = newest_selection_head.map(|head| head.row());
7230 let above_edit = edit_start
7231 .row()
7232 .0
7233 .checked_sub(line_count as u32)
7234 .map(DisplayRow);
7235 let below_edit = Some(edit_end.row() + 1);
7236 let above_cursor =
7237 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7238 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7239
7240 // Place the edit popover adjacent to the edit if there is a location
7241 // available that is onscreen and does not obscure the cursor. Otherwise,
7242 // place it adjacent to the cursor.
7243 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7244 .into_iter()
7245 .flatten()
7246 .find(|&start_row| {
7247 let end_row = start_row + line_count as u32;
7248 visible_row_range.contains(&start_row)
7249 && visible_row_range.contains(&end_row)
7250 && cursor_row.map_or(true, |cursor_row| {
7251 !((start_row..end_row).contains(&cursor_row))
7252 })
7253 })?;
7254
7255 content_origin
7256 + point(
7257 -scroll_pixel_position.x,
7258 row_target.as_f32() * line_height - scroll_pixel_position.y,
7259 )
7260 };
7261
7262 origin.x -= BORDER_WIDTH;
7263
7264 window.defer_draw(element, origin, 1);
7265
7266 // Do not return an element, since it will already be drawn due to defer_draw.
7267 None
7268 }
7269
7270 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7271 px(30.)
7272 }
7273
7274 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7275 if self.read_only(cx) {
7276 cx.theme().players().read_only()
7277 } else {
7278 self.style.as_ref().unwrap().local_player
7279 }
7280 }
7281
7282 fn render_edit_prediction_accept_keybind(
7283 &self,
7284 window: &mut Window,
7285 cx: &App,
7286 ) -> Option<AnyElement> {
7287 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7288 let accept_keystroke = accept_binding.keystroke()?;
7289
7290 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7291
7292 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7293 Color::Accent
7294 } else {
7295 Color::Muted
7296 };
7297
7298 h_flex()
7299 .px_0p5()
7300 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7301 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7302 .text_size(TextSize::XSmall.rems(cx))
7303 .child(h_flex().children(ui::render_modifiers(
7304 &accept_keystroke.modifiers,
7305 PlatformStyle::platform(),
7306 Some(modifiers_color),
7307 Some(IconSize::XSmall.rems().into()),
7308 true,
7309 )))
7310 .when(is_platform_style_mac, |parent| {
7311 parent.child(accept_keystroke.key.clone())
7312 })
7313 .when(!is_platform_style_mac, |parent| {
7314 parent.child(
7315 Key::new(
7316 util::capitalize(&accept_keystroke.key),
7317 Some(Color::Default),
7318 )
7319 .size(Some(IconSize::XSmall.rems().into())),
7320 )
7321 })
7322 .into_any()
7323 .into()
7324 }
7325
7326 fn render_edit_prediction_line_popover(
7327 &self,
7328 label: impl Into<SharedString>,
7329 icon: Option<IconName>,
7330 window: &mut Window,
7331 cx: &App,
7332 ) -> Option<Stateful<Div>> {
7333 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7334
7335 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7336 let has_keybind = keybind.is_some();
7337
7338 let result = h_flex()
7339 .id("ep-line-popover")
7340 .py_0p5()
7341 .pl_1()
7342 .pr(padding_right)
7343 .gap_1()
7344 .rounded_md()
7345 .border_1()
7346 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7347 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7348 .shadow_sm()
7349 .when(!has_keybind, |el| {
7350 let status_colors = cx.theme().status();
7351
7352 el.bg(status_colors.error_background)
7353 .border_color(status_colors.error.opacity(0.6))
7354 .pl_2()
7355 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7356 .cursor_default()
7357 .hoverable_tooltip(move |_window, cx| {
7358 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7359 })
7360 })
7361 .children(keybind)
7362 .child(
7363 Label::new(label)
7364 .size(LabelSize::Small)
7365 .when(!has_keybind, |el| {
7366 el.color(cx.theme().status().error.into()).strikethrough()
7367 }),
7368 )
7369 .when(!has_keybind, |el| {
7370 el.child(
7371 h_flex().ml_1().child(
7372 Icon::new(IconName::Info)
7373 .size(IconSize::Small)
7374 .color(cx.theme().status().error.into()),
7375 ),
7376 )
7377 })
7378 .when_some(icon, |element, icon| {
7379 element.child(
7380 div()
7381 .mt(px(1.5))
7382 .child(Icon::new(icon).size(IconSize::Small)),
7383 )
7384 });
7385
7386 Some(result)
7387 }
7388
7389 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7390 let accent_color = cx.theme().colors().text_accent;
7391 let editor_bg_color = cx.theme().colors().editor_background;
7392 editor_bg_color.blend(accent_color.opacity(0.1))
7393 }
7394
7395 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7396 let accent_color = cx.theme().colors().text_accent;
7397 let editor_bg_color = cx.theme().colors().editor_background;
7398 editor_bg_color.blend(accent_color.opacity(0.6))
7399 }
7400
7401 fn render_edit_prediction_cursor_popover(
7402 &self,
7403 min_width: Pixels,
7404 max_width: Pixels,
7405 cursor_point: Point,
7406 style: &EditorStyle,
7407 accept_keystroke: Option<&gpui::Keystroke>,
7408 _window: &Window,
7409 cx: &mut Context<Editor>,
7410 ) -> Option<AnyElement> {
7411 let provider = self.edit_prediction_provider.as_ref()?;
7412
7413 if provider.provider.needs_terms_acceptance(cx) {
7414 return Some(
7415 h_flex()
7416 .min_w(min_width)
7417 .flex_1()
7418 .px_2()
7419 .py_1()
7420 .gap_3()
7421 .elevation_2(cx)
7422 .hover(|style| style.bg(cx.theme().colors().element_hover))
7423 .id("accept-terms")
7424 .cursor_pointer()
7425 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7426 .on_click(cx.listener(|this, _event, window, cx| {
7427 cx.stop_propagation();
7428 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7429 window.dispatch_action(
7430 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7431 cx,
7432 );
7433 }))
7434 .child(
7435 h_flex()
7436 .flex_1()
7437 .gap_2()
7438 .child(Icon::new(IconName::ZedPredict))
7439 .child(Label::new("Accept Terms of Service"))
7440 .child(div().w_full())
7441 .child(
7442 Icon::new(IconName::ArrowUpRight)
7443 .color(Color::Muted)
7444 .size(IconSize::Small),
7445 )
7446 .into_any_element(),
7447 )
7448 .into_any(),
7449 );
7450 }
7451
7452 let is_refreshing = provider.provider.is_refreshing(cx);
7453
7454 fn pending_completion_container() -> Div {
7455 h_flex()
7456 .h_full()
7457 .flex_1()
7458 .gap_2()
7459 .child(Icon::new(IconName::ZedPredict))
7460 }
7461
7462 let completion = match &self.active_inline_completion {
7463 Some(prediction) => {
7464 if !self.has_visible_completions_menu() {
7465 const RADIUS: Pixels = px(6.);
7466 const BORDER_WIDTH: Pixels = px(1.);
7467
7468 return Some(
7469 h_flex()
7470 .elevation_2(cx)
7471 .border(BORDER_WIDTH)
7472 .border_color(cx.theme().colors().border)
7473 .when(accept_keystroke.is_none(), |el| {
7474 el.border_color(cx.theme().status().error)
7475 })
7476 .rounded(RADIUS)
7477 .rounded_tl(px(0.))
7478 .overflow_hidden()
7479 .child(div().px_1p5().child(match &prediction.completion {
7480 InlineCompletion::Move { target, snapshot } => {
7481 use text::ToPoint as _;
7482 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7483 {
7484 Icon::new(IconName::ZedPredictDown)
7485 } else {
7486 Icon::new(IconName::ZedPredictUp)
7487 }
7488 }
7489 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7490 }))
7491 .child(
7492 h_flex()
7493 .gap_1()
7494 .py_1()
7495 .px_2()
7496 .rounded_r(RADIUS - BORDER_WIDTH)
7497 .border_l_1()
7498 .border_color(cx.theme().colors().border)
7499 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7500 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7501 el.child(
7502 Label::new("Hold")
7503 .size(LabelSize::Small)
7504 .when(accept_keystroke.is_none(), |el| {
7505 el.strikethrough()
7506 })
7507 .line_height_style(LineHeightStyle::UiLabel),
7508 )
7509 })
7510 .id("edit_prediction_cursor_popover_keybind")
7511 .when(accept_keystroke.is_none(), |el| {
7512 let status_colors = cx.theme().status();
7513
7514 el.bg(status_colors.error_background)
7515 .border_color(status_colors.error.opacity(0.6))
7516 .child(Icon::new(IconName::Info).color(Color::Error))
7517 .cursor_default()
7518 .hoverable_tooltip(move |_window, cx| {
7519 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7520 .into()
7521 })
7522 })
7523 .when_some(
7524 accept_keystroke.as_ref(),
7525 |el, accept_keystroke| {
7526 el.child(h_flex().children(ui::render_modifiers(
7527 &accept_keystroke.modifiers,
7528 PlatformStyle::platform(),
7529 Some(Color::Default),
7530 Some(IconSize::XSmall.rems().into()),
7531 false,
7532 )))
7533 },
7534 ),
7535 )
7536 .into_any(),
7537 );
7538 }
7539
7540 self.render_edit_prediction_cursor_popover_preview(
7541 prediction,
7542 cursor_point,
7543 style,
7544 cx,
7545 )?
7546 }
7547
7548 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7549 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7550 stale_completion,
7551 cursor_point,
7552 style,
7553 cx,
7554 )?,
7555
7556 None => {
7557 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7558 }
7559 },
7560
7561 None => pending_completion_container().child(Label::new("No Prediction")),
7562 };
7563
7564 let completion = if is_refreshing {
7565 completion
7566 .with_animation(
7567 "loading-completion",
7568 Animation::new(Duration::from_secs(2))
7569 .repeat()
7570 .with_easing(pulsating_between(0.4, 0.8)),
7571 |label, delta| label.opacity(delta),
7572 )
7573 .into_any_element()
7574 } else {
7575 completion.into_any_element()
7576 };
7577
7578 let has_completion = self.active_inline_completion.is_some();
7579
7580 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7581 Some(
7582 h_flex()
7583 .min_w(min_width)
7584 .max_w(max_width)
7585 .flex_1()
7586 .elevation_2(cx)
7587 .border_color(cx.theme().colors().border)
7588 .child(
7589 div()
7590 .flex_1()
7591 .py_1()
7592 .px_2()
7593 .overflow_hidden()
7594 .child(completion),
7595 )
7596 .when_some(accept_keystroke, |el, accept_keystroke| {
7597 if !accept_keystroke.modifiers.modified() {
7598 return el;
7599 }
7600
7601 el.child(
7602 h_flex()
7603 .h_full()
7604 .border_l_1()
7605 .rounded_r_lg()
7606 .border_color(cx.theme().colors().border)
7607 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7608 .gap_1()
7609 .py_1()
7610 .px_2()
7611 .child(
7612 h_flex()
7613 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7614 .when(is_platform_style_mac, |parent| parent.gap_1())
7615 .child(h_flex().children(ui::render_modifiers(
7616 &accept_keystroke.modifiers,
7617 PlatformStyle::platform(),
7618 Some(if !has_completion {
7619 Color::Muted
7620 } else {
7621 Color::Default
7622 }),
7623 None,
7624 false,
7625 ))),
7626 )
7627 .child(Label::new("Preview").into_any_element())
7628 .opacity(if has_completion { 1.0 } else { 0.4 }),
7629 )
7630 })
7631 .into_any(),
7632 )
7633 }
7634
7635 fn render_edit_prediction_cursor_popover_preview(
7636 &self,
7637 completion: &InlineCompletionState,
7638 cursor_point: Point,
7639 style: &EditorStyle,
7640 cx: &mut Context<Editor>,
7641 ) -> Option<Div> {
7642 use text::ToPoint as _;
7643
7644 fn render_relative_row_jump(
7645 prefix: impl Into<String>,
7646 current_row: u32,
7647 target_row: u32,
7648 ) -> Div {
7649 let (row_diff, arrow) = if target_row < current_row {
7650 (current_row - target_row, IconName::ArrowUp)
7651 } else {
7652 (target_row - current_row, IconName::ArrowDown)
7653 };
7654
7655 h_flex()
7656 .child(
7657 Label::new(format!("{}{}", prefix.into(), row_diff))
7658 .color(Color::Muted)
7659 .size(LabelSize::Small),
7660 )
7661 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7662 }
7663
7664 match &completion.completion {
7665 InlineCompletion::Move {
7666 target, snapshot, ..
7667 } => Some(
7668 h_flex()
7669 .px_2()
7670 .gap_2()
7671 .flex_1()
7672 .child(
7673 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7674 Icon::new(IconName::ZedPredictDown)
7675 } else {
7676 Icon::new(IconName::ZedPredictUp)
7677 },
7678 )
7679 .child(Label::new("Jump to Edit")),
7680 ),
7681
7682 InlineCompletion::Edit {
7683 edits,
7684 edit_preview,
7685 snapshot,
7686 display_mode: _,
7687 } => {
7688 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7689
7690 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7691 &snapshot,
7692 &edits,
7693 edit_preview.as_ref()?,
7694 true,
7695 cx,
7696 )
7697 .first_line_preview();
7698
7699 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7700 .with_default_highlights(&style.text, highlighted_edits.highlights);
7701
7702 let preview = h_flex()
7703 .gap_1()
7704 .min_w_16()
7705 .child(styled_text)
7706 .when(has_more_lines, |parent| parent.child("…"));
7707
7708 let left = if first_edit_row != cursor_point.row {
7709 render_relative_row_jump("", cursor_point.row, first_edit_row)
7710 .into_any_element()
7711 } else {
7712 Icon::new(IconName::ZedPredict).into_any_element()
7713 };
7714
7715 Some(
7716 h_flex()
7717 .h_full()
7718 .flex_1()
7719 .gap_2()
7720 .pr_1()
7721 .overflow_x_hidden()
7722 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7723 .child(left)
7724 .child(preview),
7725 )
7726 }
7727 }
7728 }
7729
7730 fn render_context_menu(
7731 &self,
7732 style: &EditorStyle,
7733 max_height_in_lines: u32,
7734 y_flipped: bool,
7735 window: &mut Window,
7736 cx: &mut Context<Editor>,
7737 ) -> Option<AnyElement> {
7738 let menu = self.context_menu.borrow();
7739 let menu = menu.as_ref()?;
7740 if !menu.visible() {
7741 return None;
7742 };
7743 Some(menu.render(style, max_height_in_lines, y_flipped, window, cx))
7744 }
7745
7746 fn render_context_menu_aside(
7747 &mut self,
7748 max_size: Size<Pixels>,
7749 window: &mut Window,
7750 cx: &mut Context<Editor>,
7751 ) -> Option<AnyElement> {
7752 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7753 if menu.visible() {
7754 menu.render_aside(self, max_size, window, cx)
7755 } else {
7756 None
7757 }
7758 })
7759 }
7760
7761 fn hide_context_menu(
7762 &mut self,
7763 window: &mut Window,
7764 cx: &mut Context<Self>,
7765 ) -> Option<CodeContextMenu> {
7766 cx.notify();
7767 self.completion_tasks.clear();
7768 let context_menu = self.context_menu.borrow_mut().take();
7769 self.stale_inline_completion_in_menu.take();
7770 self.update_visible_inline_completion(window, cx);
7771 context_menu
7772 }
7773
7774 fn show_snippet_choices(
7775 &mut self,
7776 choices: &Vec<String>,
7777 selection: Range<Anchor>,
7778 cx: &mut Context<Self>,
7779 ) {
7780 if selection.start.buffer_id.is_none() {
7781 return;
7782 }
7783 let buffer_id = selection.start.buffer_id.unwrap();
7784 let buffer = self.buffer().read(cx).buffer(buffer_id);
7785 let id = post_inc(&mut self.next_completion_id);
7786
7787 if let Some(buffer) = buffer {
7788 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7789 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7790 ));
7791 }
7792 }
7793
7794 pub fn insert_snippet(
7795 &mut self,
7796 insertion_ranges: &[Range<usize>],
7797 snippet: Snippet,
7798 window: &mut Window,
7799 cx: &mut Context<Self>,
7800 ) -> Result<()> {
7801 struct Tabstop<T> {
7802 is_end_tabstop: bool,
7803 ranges: Vec<Range<T>>,
7804 choices: Option<Vec<String>>,
7805 }
7806
7807 let tabstops = self.buffer.update(cx, |buffer, cx| {
7808 let snippet_text: Arc<str> = snippet.text.clone().into();
7809 let edits = insertion_ranges
7810 .iter()
7811 .cloned()
7812 .map(|range| (range, snippet_text.clone()));
7813 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
7814
7815 let snapshot = &*buffer.read(cx);
7816 let snippet = &snippet;
7817 snippet
7818 .tabstops
7819 .iter()
7820 .map(|tabstop| {
7821 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7822 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7823 });
7824 let mut tabstop_ranges = tabstop
7825 .ranges
7826 .iter()
7827 .flat_map(|tabstop_range| {
7828 let mut delta = 0_isize;
7829 insertion_ranges.iter().map(move |insertion_range| {
7830 let insertion_start = insertion_range.start as isize + delta;
7831 delta +=
7832 snippet.text.len() as isize - insertion_range.len() as isize;
7833
7834 let start = ((insertion_start + tabstop_range.start) as usize)
7835 .min(snapshot.len());
7836 let end = ((insertion_start + tabstop_range.end) as usize)
7837 .min(snapshot.len());
7838 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7839 })
7840 })
7841 .collect::<Vec<_>>();
7842 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7843
7844 Tabstop {
7845 is_end_tabstop,
7846 ranges: tabstop_ranges,
7847 choices: tabstop.choices.clone(),
7848 }
7849 })
7850 .collect::<Vec<_>>()
7851 });
7852 if let Some(tabstop) = tabstops.first() {
7853 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7854 s.select_ranges(tabstop.ranges.iter().cloned());
7855 });
7856
7857 if let Some(choices) = &tabstop.choices {
7858 if let Some(selection) = tabstop.ranges.first() {
7859 self.show_snippet_choices(choices, selection.clone(), cx)
7860 }
7861 }
7862
7863 // If we're already at the last tabstop and it's at the end of the snippet,
7864 // we're done, we don't need to keep the state around.
7865 if !tabstop.is_end_tabstop {
7866 let choices = tabstops
7867 .iter()
7868 .map(|tabstop| tabstop.choices.clone())
7869 .collect();
7870
7871 let ranges = tabstops
7872 .into_iter()
7873 .map(|tabstop| tabstop.ranges)
7874 .collect::<Vec<_>>();
7875
7876 self.snippet_stack.push(SnippetState {
7877 active_index: 0,
7878 ranges,
7879 choices,
7880 });
7881 }
7882
7883 // Check whether the just-entered snippet ends with an auto-closable bracket.
7884 if self.autoclose_regions.is_empty() {
7885 let snapshot = self.buffer.read(cx).snapshot(cx);
7886 for selection in &mut self.selections.all::<Point>(cx) {
7887 let selection_head = selection.head();
7888 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7889 continue;
7890 };
7891
7892 let mut bracket_pair = None;
7893 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7894 let prev_chars = snapshot
7895 .reversed_chars_at(selection_head)
7896 .collect::<String>();
7897 for (pair, enabled) in scope.brackets() {
7898 if enabled
7899 && pair.close
7900 && prev_chars.starts_with(pair.start.as_str())
7901 && next_chars.starts_with(pair.end.as_str())
7902 {
7903 bracket_pair = Some(pair.clone());
7904 break;
7905 }
7906 }
7907 if let Some(pair) = bracket_pair {
7908 let start = snapshot.anchor_after(selection_head);
7909 let end = snapshot.anchor_after(selection_head);
7910 self.autoclose_regions.push(AutocloseRegion {
7911 selection_id: selection.id,
7912 range: start..end,
7913 pair,
7914 });
7915 }
7916 }
7917 }
7918 }
7919 Ok(())
7920 }
7921
7922 pub fn move_to_next_snippet_tabstop(
7923 &mut self,
7924 window: &mut Window,
7925 cx: &mut Context<Self>,
7926 ) -> bool {
7927 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7928 }
7929
7930 pub fn move_to_prev_snippet_tabstop(
7931 &mut self,
7932 window: &mut Window,
7933 cx: &mut Context<Self>,
7934 ) -> bool {
7935 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7936 }
7937
7938 pub fn move_to_snippet_tabstop(
7939 &mut self,
7940 bias: Bias,
7941 window: &mut Window,
7942 cx: &mut Context<Self>,
7943 ) -> bool {
7944 if let Some(mut snippet) = self.snippet_stack.pop() {
7945 match bias {
7946 Bias::Left => {
7947 if snippet.active_index > 0 {
7948 snippet.active_index -= 1;
7949 } else {
7950 self.snippet_stack.push(snippet);
7951 return false;
7952 }
7953 }
7954 Bias::Right => {
7955 if snippet.active_index + 1 < snippet.ranges.len() {
7956 snippet.active_index += 1;
7957 } else {
7958 self.snippet_stack.push(snippet);
7959 return false;
7960 }
7961 }
7962 }
7963 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
7964 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7965 s.select_anchor_ranges(current_ranges.iter().cloned())
7966 });
7967
7968 if let Some(choices) = &snippet.choices[snippet.active_index] {
7969 if let Some(selection) = current_ranges.first() {
7970 self.show_snippet_choices(&choices, selection.clone(), cx);
7971 }
7972 }
7973
7974 // If snippet state is not at the last tabstop, push it back on the stack
7975 if snippet.active_index + 1 < snippet.ranges.len() {
7976 self.snippet_stack.push(snippet);
7977 }
7978 return true;
7979 }
7980 }
7981
7982 false
7983 }
7984
7985 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7986 self.transact(window, cx, |this, window, cx| {
7987 this.select_all(&SelectAll, window, cx);
7988 this.insert("", window, cx);
7989 });
7990 }
7991
7992 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
7993 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
7994 self.transact(window, cx, |this, window, cx| {
7995 this.select_autoclose_pair(window, cx);
7996 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
7997 if !this.linked_edit_ranges.is_empty() {
7998 let selections = this.selections.all::<MultiBufferPoint>(cx);
7999 let snapshot = this.buffer.read(cx).snapshot(cx);
8000
8001 for selection in selections.iter() {
8002 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8003 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8004 if selection_start.buffer_id != selection_end.buffer_id {
8005 continue;
8006 }
8007 if let Some(ranges) =
8008 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8009 {
8010 for (buffer, entries) in ranges {
8011 linked_ranges.entry(buffer).or_default().extend(entries);
8012 }
8013 }
8014 }
8015 }
8016
8017 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8018 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8019 for selection in &mut selections {
8020 if selection.is_empty() {
8021 let old_head = selection.head();
8022 let mut new_head =
8023 movement::left(&display_map, old_head.to_display_point(&display_map))
8024 .to_point(&display_map);
8025 if let Some((buffer, line_buffer_range)) = display_map
8026 .buffer_snapshot
8027 .buffer_line_for_row(MultiBufferRow(old_head.row))
8028 {
8029 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8030 let indent_len = match indent_size.kind {
8031 IndentKind::Space => {
8032 buffer.settings_at(line_buffer_range.start, cx).tab_size
8033 }
8034 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8035 };
8036 if old_head.column <= indent_size.len && old_head.column > 0 {
8037 let indent_len = indent_len.get();
8038 new_head = cmp::min(
8039 new_head,
8040 MultiBufferPoint::new(
8041 old_head.row,
8042 ((old_head.column - 1) / indent_len) * indent_len,
8043 ),
8044 );
8045 }
8046 }
8047
8048 selection.set_head(new_head, SelectionGoal::None);
8049 }
8050 }
8051
8052 this.signature_help_state.set_backspace_pressed(true);
8053 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8054 s.select(selections)
8055 });
8056 this.insert("", window, cx);
8057 let empty_str: Arc<str> = Arc::from("");
8058 for (buffer, edits) in linked_ranges {
8059 let snapshot = buffer.read(cx).snapshot();
8060 use text::ToPoint as TP;
8061
8062 let edits = edits
8063 .into_iter()
8064 .map(|range| {
8065 let end_point = TP::to_point(&range.end, &snapshot);
8066 let mut start_point = TP::to_point(&range.start, &snapshot);
8067
8068 if end_point == start_point {
8069 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8070 .saturating_sub(1);
8071 start_point =
8072 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8073 };
8074
8075 (start_point..end_point, empty_str.clone())
8076 })
8077 .sorted_by_key(|(range, _)| range.start)
8078 .collect::<Vec<_>>();
8079 buffer.update(cx, |this, cx| {
8080 this.edit(edits, None, cx);
8081 })
8082 }
8083 this.refresh_inline_completion(true, false, window, cx);
8084 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8085 });
8086 }
8087
8088 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8089 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8090 self.transact(window, cx, |this, window, cx| {
8091 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8092 s.move_with(|map, selection| {
8093 if selection.is_empty() {
8094 let cursor = movement::right(map, selection.head());
8095 selection.end = cursor;
8096 selection.reversed = true;
8097 selection.goal = SelectionGoal::None;
8098 }
8099 })
8100 });
8101 this.insert("", window, cx);
8102 this.refresh_inline_completion(true, false, window, cx);
8103 });
8104 }
8105
8106 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8107 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8108 if self.move_to_prev_snippet_tabstop(window, cx) {
8109 return;
8110 }
8111 self.outdent(&Outdent, window, cx);
8112 }
8113
8114 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8115 if self.move_to_next_snippet_tabstop(window, cx) {
8116 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8117 return;
8118 }
8119 if self.read_only(cx) {
8120 return;
8121 }
8122 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8123 let mut selections = self.selections.all_adjusted(cx);
8124 let buffer = self.buffer.read(cx);
8125 let snapshot = buffer.snapshot(cx);
8126 let rows_iter = selections.iter().map(|s| s.head().row);
8127 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8128
8129 let mut edits = Vec::new();
8130 let mut prev_edited_row = 0;
8131 let mut row_delta = 0;
8132 for selection in &mut selections {
8133 if selection.start.row != prev_edited_row {
8134 row_delta = 0;
8135 }
8136 prev_edited_row = selection.end.row;
8137
8138 // If the selection is non-empty, then increase the indentation of the selected lines.
8139 if !selection.is_empty() {
8140 row_delta =
8141 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8142 continue;
8143 }
8144
8145 // If the selection is empty and the cursor is in the leading whitespace before the
8146 // suggested indentation, then auto-indent the line.
8147 let cursor = selection.head();
8148 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8149 if let Some(suggested_indent) =
8150 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8151 {
8152 if cursor.column < suggested_indent.len
8153 && cursor.column <= current_indent.len
8154 && current_indent.len <= suggested_indent.len
8155 {
8156 selection.start = Point::new(cursor.row, suggested_indent.len);
8157 selection.end = selection.start;
8158 if row_delta == 0 {
8159 edits.extend(Buffer::edit_for_indent_size_adjustment(
8160 cursor.row,
8161 current_indent,
8162 suggested_indent,
8163 ));
8164 row_delta = suggested_indent.len - current_indent.len;
8165 }
8166 continue;
8167 }
8168 }
8169
8170 // Otherwise, insert a hard or soft tab.
8171 let settings = buffer.language_settings_at(cursor, cx);
8172 let tab_size = if settings.hard_tabs {
8173 IndentSize::tab()
8174 } else {
8175 let tab_size = settings.tab_size.get();
8176 let char_column = snapshot
8177 .text_for_range(Point::new(cursor.row, 0)..cursor)
8178 .flat_map(str::chars)
8179 .count()
8180 + row_delta as usize;
8181 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
8182 IndentSize::spaces(chars_to_next_tab_stop)
8183 };
8184 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8185 selection.end = selection.start;
8186 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8187 row_delta += tab_size.len;
8188 }
8189
8190 self.transact(window, cx, |this, window, cx| {
8191 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8192 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8193 s.select(selections)
8194 });
8195 this.refresh_inline_completion(true, false, window, cx);
8196 });
8197 }
8198
8199 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8200 if self.read_only(cx) {
8201 return;
8202 }
8203 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8204 let mut selections = self.selections.all::<Point>(cx);
8205 let mut prev_edited_row = 0;
8206 let mut row_delta = 0;
8207 let mut edits = Vec::new();
8208 let buffer = self.buffer.read(cx);
8209 let snapshot = buffer.snapshot(cx);
8210 for selection in &mut selections {
8211 if selection.start.row != prev_edited_row {
8212 row_delta = 0;
8213 }
8214 prev_edited_row = selection.end.row;
8215
8216 row_delta =
8217 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8218 }
8219
8220 self.transact(window, cx, |this, window, cx| {
8221 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8222 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8223 s.select(selections)
8224 });
8225 });
8226 }
8227
8228 fn indent_selection(
8229 buffer: &MultiBuffer,
8230 snapshot: &MultiBufferSnapshot,
8231 selection: &mut Selection<Point>,
8232 edits: &mut Vec<(Range<Point>, String)>,
8233 delta_for_start_row: u32,
8234 cx: &App,
8235 ) -> u32 {
8236 let settings = buffer.language_settings_at(selection.start, cx);
8237 let tab_size = settings.tab_size.get();
8238 let indent_kind = if settings.hard_tabs {
8239 IndentKind::Tab
8240 } else {
8241 IndentKind::Space
8242 };
8243 let mut start_row = selection.start.row;
8244 let mut end_row = selection.end.row + 1;
8245
8246 // If a selection ends at the beginning of a line, don't indent
8247 // that last line.
8248 if selection.end.column == 0 && selection.end.row > selection.start.row {
8249 end_row -= 1;
8250 }
8251
8252 // Avoid re-indenting a row that has already been indented by a
8253 // previous selection, but still update this selection's column
8254 // to reflect that indentation.
8255 if delta_for_start_row > 0 {
8256 start_row += 1;
8257 selection.start.column += delta_for_start_row;
8258 if selection.end.row == selection.start.row {
8259 selection.end.column += delta_for_start_row;
8260 }
8261 }
8262
8263 let mut delta_for_end_row = 0;
8264 let has_multiple_rows = start_row + 1 != end_row;
8265 for row in start_row..end_row {
8266 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8267 let indent_delta = match (current_indent.kind, indent_kind) {
8268 (IndentKind::Space, IndentKind::Space) => {
8269 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8270 IndentSize::spaces(columns_to_next_tab_stop)
8271 }
8272 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8273 (_, IndentKind::Tab) => IndentSize::tab(),
8274 };
8275
8276 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8277 0
8278 } else {
8279 selection.start.column
8280 };
8281 let row_start = Point::new(row, start);
8282 edits.push((
8283 row_start..row_start,
8284 indent_delta.chars().collect::<String>(),
8285 ));
8286
8287 // Update this selection's endpoints to reflect the indentation.
8288 if row == selection.start.row {
8289 selection.start.column += indent_delta.len;
8290 }
8291 if row == selection.end.row {
8292 selection.end.column += indent_delta.len;
8293 delta_for_end_row = indent_delta.len;
8294 }
8295 }
8296
8297 if selection.start.row == selection.end.row {
8298 delta_for_start_row + delta_for_end_row
8299 } else {
8300 delta_for_end_row
8301 }
8302 }
8303
8304 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8305 if self.read_only(cx) {
8306 return;
8307 }
8308 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8309 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8310 let selections = self.selections.all::<Point>(cx);
8311 let mut deletion_ranges = Vec::new();
8312 let mut last_outdent = None;
8313 {
8314 let buffer = self.buffer.read(cx);
8315 let snapshot = buffer.snapshot(cx);
8316 for selection in &selections {
8317 let settings = buffer.language_settings_at(selection.start, cx);
8318 let tab_size = settings.tab_size.get();
8319 let mut rows = selection.spanned_rows(false, &display_map);
8320
8321 // Avoid re-outdenting a row that has already been outdented by a
8322 // previous selection.
8323 if let Some(last_row) = last_outdent {
8324 if last_row == rows.start {
8325 rows.start = rows.start.next_row();
8326 }
8327 }
8328 let has_multiple_rows = rows.len() > 1;
8329 for row in rows.iter_rows() {
8330 let indent_size = snapshot.indent_size_for_line(row);
8331 if indent_size.len > 0 {
8332 let deletion_len = match indent_size.kind {
8333 IndentKind::Space => {
8334 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8335 if columns_to_prev_tab_stop == 0 {
8336 tab_size
8337 } else {
8338 columns_to_prev_tab_stop
8339 }
8340 }
8341 IndentKind::Tab => 1,
8342 };
8343 let start = if has_multiple_rows
8344 || deletion_len > selection.start.column
8345 || indent_size.len < selection.start.column
8346 {
8347 0
8348 } else {
8349 selection.start.column - deletion_len
8350 };
8351 deletion_ranges.push(
8352 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8353 );
8354 last_outdent = Some(row);
8355 }
8356 }
8357 }
8358 }
8359
8360 self.transact(window, cx, |this, window, cx| {
8361 this.buffer.update(cx, |buffer, cx| {
8362 let empty_str: Arc<str> = Arc::default();
8363 buffer.edit(
8364 deletion_ranges
8365 .into_iter()
8366 .map(|range| (range, empty_str.clone())),
8367 None,
8368 cx,
8369 );
8370 });
8371 let selections = this.selections.all::<usize>(cx);
8372 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8373 s.select(selections)
8374 });
8375 });
8376 }
8377
8378 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8379 if self.read_only(cx) {
8380 return;
8381 }
8382 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8383 let selections = self
8384 .selections
8385 .all::<usize>(cx)
8386 .into_iter()
8387 .map(|s| s.range());
8388
8389 self.transact(window, cx, |this, window, cx| {
8390 this.buffer.update(cx, |buffer, cx| {
8391 buffer.autoindent_ranges(selections, cx);
8392 });
8393 let selections = this.selections.all::<usize>(cx);
8394 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8395 s.select(selections)
8396 });
8397 });
8398 }
8399
8400 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8401 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8402 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8403 let selections = self.selections.all::<Point>(cx);
8404
8405 let mut new_cursors = Vec::new();
8406 let mut edit_ranges = Vec::new();
8407 let mut selections = selections.iter().peekable();
8408 while let Some(selection) = selections.next() {
8409 let mut rows = selection.spanned_rows(false, &display_map);
8410 let goal_display_column = selection.head().to_display_point(&display_map).column();
8411
8412 // Accumulate contiguous regions of rows that we want to delete.
8413 while let Some(next_selection) = selections.peek() {
8414 let next_rows = next_selection.spanned_rows(false, &display_map);
8415 if next_rows.start <= rows.end {
8416 rows.end = next_rows.end;
8417 selections.next().unwrap();
8418 } else {
8419 break;
8420 }
8421 }
8422
8423 let buffer = &display_map.buffer_snapshot;
8424 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8425 let edit_end;
8426 let cursor_buffer_row;
8427 if buffer.max_point().row >= rows.end.0 {
8428 // If there's a line after the range, delete the \n from the end of the row range
8429 // and position the cursor on the next line.
8430 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8431 cursor_buffer_row = rows.end;
8432 } else {
8433 // If there isn't a line after the range, delete the \n from the line before the
8434 // start of the row range and position the cursor there.
8435 edit_start = edit_start.saturating_sub(1);
8436 edit_end = buffer.len();
8437 cursor_buffer_row = rows.start.previous_row();
8438 }
8439
8440 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8441 *cursor.column_mut() =
8442 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8443
8444 new_cursors.push((
8445 selection.id,
8446 buffer.anchor_after(cursor.to_point(&display_map)),
8447 ));
8448 edit_ranges.push(edit_start..edit_end);
8449 }
8450
8451 self.transact(window, cx, |this, window, cx| {
8452 let buffer = this.buffer.update(cx, |buffer, cx| {
8453 let empty_str: Arc<str> = Arc::default();
8454 buffer.edit(
8455 edit_ranges
8456 .into_iter()
8457 .map(|range| (range, empty_str.clone())),
8458 None,
8459 cx,
8460 );
8461 buffer.snapshot(cx)
8462 });
8463 let new_selections = new_cursors
8464 .into_iter()
8465 .map(|(id, cursor)| {
8466 let cursor = cursor.to_point(&buffer);
8467 Selection {
8468 id,
8469 start: cursor,
8470 end: cursor,
8471 reversed: false,
8472 goal: SelectionGoal::None,
8473 }
8474 })
8475 .collect();
8476
8477 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8478 s.select(new_selections);
8479 });
8480 });
8481 }
8482
8483 pub fn join_lines_impl(
8484 &mut self,
8485 insert_whitespace: bool,
8486 window: &mut Window,
8487 cx: &mut Context<Self>,
8488 ) {
8489 if self.read_only(cx) {
8490 return;
8491 }
8492 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8493 for selection in self.selections.all::<Point>(cx) {
8494 let start = MultiBufferRow(selection.start.row);
8495 // Treat single line selections as if they include the next line. Otherwise this action
8496 // would do nothing for single line selections individual cursors.
8497 let end = if selection.start.row == selection.end.row {
8498 MultiBufferRow(selection.start.row + 1)
8499 } else {
8500 MultiBufferRow(selection.end.row)
8501 };
8502
8503 if let Some(last_row_range) = row_ranges.last_mut() {
8504 if start <= last_row_range.end {
8505 last_row_range.end = end;
8506 continue;
8507 }
8508 }
8509 row_ranges.push(start..end);
8510 }
8511
8512 let snapshot = self.buffer.read(cx).snapshot(cx);
8513 let mut cursor_positions = Vec::new();
8514 for row_range in &row_ranges {
8515 let anchor = snapshot.anchor_before(Point::new(
8516 row_range.end.previous_row().0,
8517 snapshot.line_len(row_range.end.previous_row()),
8518 ));
8519 cursor_positions.push(anchor..anchor);
8520 }
8521
8522 self.transact(window, cx, |this, window, cx| {
8523 for row_range in row_ranges.into_iter().rev() {
8524 for row in row_range.iter_rows().rev() {
8525 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8526 let next_line_row = row.next_row();
8527 let indent = snapshot.indent_size_for_line(next_line_row);
8528 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8529
8530 let replace =
8531 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8532 " "
8533 } else {
8534 ""
8535 };
8536
8537 this.buffer.update(cx, |buffer, cx| {
8538 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8539 });
8540 }
8541 }
8542
8543 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8544 s.select_anchor_ranges(cursor_positions)
8545 });
8546 });
8547 }
8548
8549 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8550 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8551 self.join_lines_impl(true, window, cx);
8552 }
8553
8554 pub fn sort_lines_case_sensitive(
8555 &mut self,
8556 _: &SortLinesCaseSensitive,
8557 window: &mut Window,
8558 cx: &mut Context<Self>,
8559 ) {
8560 self.manipulate_lines(window, cx, |lines| lines.sort())
8561 }
8562
8563 pub fn sort_lines_case_insensitive(
8564 &mut self,
8565 _: &SortLinesCaseInsensitive,
8566 window: &mut Window,
8567 cx: &mut Context<Self>,
8568 ) {
8569 self.manipulate_lines(window, cx, |lines| {
8570 lines.sort_by_key(|line| line.to_lowercase())
8571 })
8572 }
8573
8574 pub fn unique_lines_case_insensitive(
8575 &mut self,
8576 _: &UniqueLinesCaseInsensitive,
8577 window: &mut Window,
8578 cx: &mut Context<Self>,
8579 ) {
8580 self.manipulate_lines(window, cx, |lines| {
8581 let mut seen = HashSet::default();
8582 lines.retain(|line| seen.insert(line.to_lowercase()));
8583 })
8584 }
8585
8586 pub fn unique_lines_case_sensitive(
8587 &mut self,
8588 _: &UniqueLinesCaseSensitive,
8589 window: &mut Window,
8590 cx: &mut Context<Self>,
8591 ) {
8592 self.manipulate_lines(window, cx, |lines| {
8593 let mut seen = HashSet::default();
8594 lines.retain(|line| seen.insert(*line));
8595 })
8596 }
8597
8598 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8599 let Some(project) = self.project.clone() else {
8600 return;
8601 };
8602 self.reload(project, window, cx)
8603 .detach_and_notify_err(window, cx);
8604 }
8605
8606 pub fn restore_file(
8607 &mut self,
8608 _: &::git::RestoreFile,
8609 window: &mut Window,
8610 cx: &mut Context<Self>,
8611 ) {
8612 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8613 let mut buffer_ids = HashSet::default();
8614 let snapshot = self.buffer().read(cx).snapshot(cx);
8615 for selection in self.selections.all::<usize>(cx) {
8616 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8617 }
8618
8619 let buffer = self.buffer().read(cx);
8620 let ranges = buffer_ids
8621 .into_iter()
8622 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8623 .collect::<Vec<_>>();
8624
8625 self.restore_hunks_in_ranges(ranges, window, cx);
8626 }
8627
8628 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8629 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8630 let selections = self
8631 .selections
8632 .all(cx)
8633 .into_iter()
8634 .map(|s| s.range())
8635 .collect();
8636 self.restore_hunks_in_ranges(selections, window, cx);
8637 }
8638
8639 pub fn restore_hunks_in_ranges(
8640 &mut self,
8641 ranges: Vec<Range<Point>>,
8642 window: &mut Window,
8643 cx: &mut Context<Editor>,
8644 ) {
8645 let mut revert_changes = HashMap::default();
8646 let chunk_by = self
8647 .snapshot(window, cx)
8648 .hunks_for_ranges(ranges)
8649 .into_iter()
8650 .chunk_by(|hunk| hunk.buffer_id);
8651 for (buffer_id, hunks) in &chunk_by {
8652 let hunks = hunks.collect::<Vec<_>>();
8653 for hunk in &hunks {
8654 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8655 }
8656 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8657 }
8658 drop(chunk_by);
8659 if !revert_changes.is_empty() {
8660 self.transact(window, cx, |editor, window, cx| {
8661 editor.restore(revert_changes, window, cx);
8662 });
8663 }
8664 }
8665
8666 pub fn open_active_item_in_terminal(
8667 &mut self,
8668 _: &OpenInTerminal,
8669 window: &mut Window,
8670 cx: &mut Context<Self>,
8671 ) {
8672 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8673 let project_path = buffer.read(cx).project_path(cx)?;
8674 let project = self.project.as_ref()?.read(cx);
8675 let entry = project.entry_for_path(&project_path, cx)?;
8676 let parent = match &entry.canonical_path {
8677 Some(canonical_path) => canonical_path.to_path_buf(),
8678 None => project.absolute_path(&project_path, cx)?,
8679 }
8680 .parent()?
8681 .to_path_buf();
8682 Some(parent)
8683 }) {
8684 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8685 }
8686 }
8687
8688 fn set_breakpoint_context_menu(
8689 &mut self,
8690 display_row: DisplayRow,
8691 position: Option<Anchor>,
8692 clicked_point: gpui::Point<Pixels>,
8693 window: &mut Window,
8694 cx: &mut Context<Self>,
8695 ) {
8696 if !cx.has_flag::<Debugger>() {
8697 return;
8698 }
8699 let source = self
8700 .buffer
8701 .read(cx)
8702 .snapshot(cx)
8703 .anchor_before(Point::new(display_row.0, 0u32));
8704
8705 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8706
8707 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8708 self,
8709 source,
8710 clicked_point,
8711 context_menu,
8712 window,
8713 cx,
8714 );
8715 }
8716
8717 fn add_edit_breakpoint_block(
8718 &mut self,
8719 anchor: Anchor,
8720 breakpoint: &Breakpoint,
8721 edit_action: BreakpointPromptEditAction,
8722 window: &mut Window,
8723 cx: &mut Context<Self>,
8724 ) {
8725 let weak_editor = cx.weak_entity();
8726 let bp_prompt = cx.new(|cx| {
8727 BreakpointPromptEditor::new(
8728 weak_editor,
8729 anchor,
8730 breakpoint.clone(),
8731 edit_action,
8732 window,
8733 cx,
8734 )
8735 });
8736
8737 let height = bp_prompt.update(cx, |this, cx| {
8738 this.prompt
8739 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8740 });
8741 let cloned_prompt = bp_prompt.clone();
8742 let blocks = vec![BlockProperties {
8743 style: BlockStyle::Sticky,
8744 placement: BlockPlacement::Above(anchor),
8745 height: Some(height),
8746 render: Arc::new(move |cx| {
8747 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8748 cloned_prompt.clone().into_any_element()
8749 }),
8750 priority: 0,
8751 }];
8752
8753 let focus_handle = bp_prompt.focus_handle(cx);
8754 window.focus(&focus_handle);
8755
8756 let block_ids = self.insert_blocks(blocks, None, cx);
8757 bp_prompt.update(cx, |prompt, _| {
8758 prompt.add_block_ids(block_ids);
8759 });
8760 }
8761
8762 fn breakpoint_at_cursor_head(
8763 &self,
8764 window: &mut Window,
8765 cx: &mut Context<Self>,
8766 ) -> Option<(Anchor, Breakpoint)> {
8767 let cursor_position: Point = self.selections.newest(cx).head();
8768 self.breakpoint_at_row(cursor_position.row, window, cx)
8769 }
8770
8771 pub(crate) fn breakpoint_at_row(
8772 &self,
8773 row: u32,
8774 window: &mut Window,
8775 cx: &mut Context<Self>,
8776 ) -> Option<(Anchor, Breakpoint)> {
8777 let snapshot = self.snapshot(window, cx);
8778 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
8779
8780 let project = self.project.clone()?;
8781
8782 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
8783 snapshot
8784 .buffer_snapshot
8785 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
8786 })?;
8787
8788 let enclosing_excerpt = breakpoint_position.excerpt_id;
8789 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8790 let buffer_snapshot = buffer.read(cx).snapshot();
8791
8792 let row = buffer_snapshot
8793 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
8794 .row;
8795
8796 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
8797 let anchor_end = snapshot
8798 .buffer_snapshot
8799 .anchor_after(Point::new(row, line_len));
8800
8801 let bp = self
8802 .breakpoint_store
8803 .as_ref()?
8804 .read_with(cx, |breakpoint_store, cx| {
8805 breakpoint_store
8806 .breakpoints(
8807 &buffer,
8808 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
8809 &buffer_snapshot,
8810 cx,
8811 )
8812 .next()
8813 .and_then(|(anchor, bp)| {
8814 let breakpoint_row = buffer_snapshot
8815 .summary_for_anchor::<text::PointUtf16>(anchor)
8816 .row;
8817
8818 if breakpoint_row == row {
8819 snapshot
8820 .buffer_snapshot
8821 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8822 .map(|anchor| (anchor, bp.clone()))
8823 } else {
8824 None
8825 }
8826 })
8827 });
8828 bp
8829 }
8830
8831 pub fn edit_log_breakpoint(
8832 &mut self,
8833 _: &EditLogBreakpoint,
8834 window: &mut Window,
8835 cx: &mut Context<Self>,
8836 ) {
8837 let (anchor, bp) = self
8838 .breakpoint_at_cursor_head(window, cx)
8839 .unwrap_or_else(|| {
8840 let cursor_position: Point = self.selections.newest(cx).head();
8841
8842 let breakpoint_position = self
8843 .snapshot(window, cx)
8844 .display_snapshot
8845 .buffer_snapshot
8846 .anchor_after(Point::new(cursor_position.row, 0));
8847
8848 (
8849 breakpoint_position,
8850 Breakpoint {
8851 message: None,
8852 state: BreakpointState::Enabled,
8853 condition: None,
8854 hit_condition: None,
8855 },
8856 )
8857 });
8858
8859 self.add_edit_breakpoint_block(anchor, &bp, BreakpointPromptEditAction::Log, window, cx);
8860 }
8861
8862 pub fn enable_breakpoint(
8863 &mut self,
8864 _: &crate::actions::EnableBreakpoint,
8865 window: &mut Window,
8866 cx: &mut Context<Self>,
8867 ) {
8868 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8869 if breakpoint.is_disabled() {
8870 self.edit_breakpoint_at_anchor(
8871 anchor,
8872 breakpoint,
8873 BreakpointEditAction::InvertState,
8874 cx,
8875 );
8876 }
8877 }
8878 }
8879
8880 pub fn disable_breakpoint(
8881 &mut self,
8882 _: &crate::actions::DisableBreakpoint,
8883 window: &mut Window,
8884 cx: &mut Context<Self>,
8885 ) {
8886 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8887 if breakpoint.is_enabled() {
8888 self.edit_breakpoint_at_anchor(
8889 anchor,
8890 breakpoint,
8891 BreakpointEditAction::InvertState,
8892 cx,
8893 );
8894 }
8895 }
8896 }
8897
8898 pub fn toggle_breakpoint(
8899 &mut self,
8900 _: &crate::actions::ToggleBreakpoint,
8901 window: &mut Window,
8902 cx: &mut Context<Self>,
8903 ) {
8904 let edit_action = BreakpointEditAction::Toggle;
8905
8906 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8907 self.edit_breakpoint_at_anchor(anchor, breakpoint, edit_action, cx);
8908 } else {
8909 let cursor_position: Point = self.selections.newest(cx).head();
8910
8911 let breakpoint_position = self
8912 .snapshot(window, cx)
8913 .display_snapshot
8914 .buffer_snapshot
8915 .anchor_after(Point::new(cursor_position.row, 0));
8916
8917 self.edit_breakpoint_at_anchor(
8918 breakpoint_position,
8919 Breakpoint::new_standard(),
8920 edit_action,
8921 cx,
8922 );
8923 }
8924 }
8925
8926 pub fn edit_breakpoint_at_anchor(
8927 &mut self,
8928 breakpoint_position: Anchor,
8929 breakpoint: Breakpoint,
8930 edit_action: BreakpointEditAction,
8931 cx: &mut Context<Self>,
8932 ) {
8933 let Some(breakpoint_store) = &self.breakpoint_store else {
8934 return;
8935 };
8936
8937 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8938 if breakpoint_position == Anchor::min() {
8939 self.buffer()
8940 .read(cx)
8941 .excerpt_buffer_ids()
8942 .into_iter()
8943 .next()
8944 } else {
8945 None
8946 }
8947 }) else {
8948 return;
8949 };
8950
8951 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8952 return;
8953 };
8954
8955 breakpoint_store.update(cx, |breakpoint_store, cx| {
8956 breakpoint_store.toggle_breakpoint(
8957 buffer,
8958 (breakpoint_position.text_anchor, breakpoint),
8959 edit_action,
8960 cx,
8961 );
8962 });
8963
8964 cx.notify();
8965 }
8966
8967 #[cfg(any(test, feature = "test-support"))]
8968 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
8969 self.breakpoint_store.clone()
8970 }
8971
8972 pub fn prepare_restore_change(
8973 &self,
8974 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
8975 hunk: &MultiBufferDiffHunk,
8976 cx: &mut App,
8977 ) -> Option<()> {
8978 if hunk.is_created_file() {
8979 return None;
8980 }
8981 let buffer = self.buffer.read(cx);
8982 let diff = buffer.diff_for(hunk.buffer_id)?;
8983 let buffer = buffer.buffer(hunk.buffer_id)?;
8984 let buffer = buffer.read(cx);
8985 let original_text = diff
8986 .read(cx)
8987 .base_text()
8988 .as_rope()
8989 .slice(hunk.diff_base_byte_range.clone());
8990 let buffer_snapshot = buffer.snapshot();
8991 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
8992 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
8993 probe
8994 .0
8995 .start
8996 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
8997 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
8998 }) {
8999 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9000 Some(())
9001 } else {
9002 None
9003 }
9004 }
9005
9006 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9007 self.manipulate_lines(window, cx, |lines| lines.reverse())
9008 }
9009
9010 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9011 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9012 }
9013
9014 fn manipulate_lines<Fn>(
9015 &mut self,
9016 window: &mut Window,
9017 cx: &mut Context<Self>,
9018 mut callback: Fn,
9019 ) where
9020 Fn: FnMut(&mut Vec<&str>),
9021 {
9022 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9023
9024 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9025 let buffer = self.buffer.read(cx).snapshot(cx);
9026
9027 let mut edits = Vec::new();
9028
9029 let selections = self.selections.all::<Point>(cx);
9030 let mut selections = selections.iter().peekable();
9031 let mut contiguous_row_selections = Vec::new();
9032 let mut new_selections = Vec::new();
9033 let mut added_lines = 0;
9034 let mut removed_lines = 0;
9035
9036 while let Some(selection) = selections.next() {
9037 let (start_row, end_row) = consume_contiguous_rows(
9038 &mut contiguous_row_selections,
9039 selection,
9040 &display_map,
9041 &mut selections,
9042 );
9043
9044 let start_point = Point::new(start_row.0, 0);
9045 let end_point = Point::new(
9046 end_row.previous_row().0,
9047 buffer.line_len(end_row.previous_row()),
9048 );
9049 let text = buffer
9050 .text_for_range(start_point..end_point)
9051 .collect::<String>();
9052
9053 let mut lines = text.split('\n').collect_vec();
9054
9055 let lines_before = lines.len();
9056 callback(&mut lines);
9057 let lines_after = lines.len();
9058
9059 edits.push((start_point..end_point, lines.join("\n")));
9060
9061 // Selections must change based on added and removed line count
9062 let start_row =
9063 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9064 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9065 new_selections.push(Selection {
9066 id: selection.id,
9067 start: start_row,
9068 end: end_row,
9069 goal: SelectionGoal::None,
9070 reversed: selection.reversed,
9071 });
9072
9073 if lines_after > lines_before {
9074 added_lines += lines_after - lines_before;
9075 } else if lines_before > lines_after {
9076 removed_lines += lines_before - lines_after;
9077 }
9078 }
9079
9080 self.transact(window, cx, |this, window, cx| {
9081 let buffer = this.buffer.update(cx, |buffer, cx| {
9082 buffer.edit(edits, None, cx);
9083 buffer.snapshot(cx)
9084 });
9085
9086 // Recalculate offsets on newly edited buffer
9087 let new_selections = new_selections
9088 .iter()
9089 .map(|s| {
9090 let start_point = Point::new(s.start.0, 0);
9091 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9092 Selection {
9093 id: s.id,
9094 start: buffer.point_to_offset(start_point),
9095 end: buffer.point_to_offset(end_point),
9096 goal: s.goal,
9097 reversed: s.reversed,
9098 }
9099 })
9100 .collect();
9101
9102 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9103 s.select(new_selections);
9104 });
9105
9106 this.request_autoscroll(Autoscroll::fit(), cx);
9107 });
9108 }
9109
9110 pub fn convert_to_upper_case(
9111 &mut self,
9112 _: &ConvertToUpperCase,
9113 window: &mut Window,
9114 cx: &mut Context<Self>,
9115 ) {
9116 self.manipulate_text(window, cx, |text| text.to_uppercase())
9117 }
9118
9119 pub fn convert_to_lower_case(
9120 &mut self,
9121 _: &ConvertToLowerCase,
9122 window: &mut Window,
9123 cx: &mut Context<Self>,
9124 ) {
9125 self.manipulate_text(window, cx, |text| text.to_lowercase())
9126 }
9127
9128 pub fn convert_to_title_case(
9129 &mut self,
9130 _: &ConvertToTitleCase,
9131 window: &mut Window,
9132 cx: &mut Context<Self>,
9133 ) {
9134 self.manipulate_text(window, cx, |text| {
9135 text.split('\n')
9136 .map(|line| line.to_case(Case::Title))
9137 .join("\n")
9138 })
9139 }
9140
9141 pub fn convert_to_snake_case(
9142 &mut self,
9143 _: &ConvertToSnakeCase,
9144 window: &mut Window,
9145 cx: &mut Context<Self>,
9146 ) {
9147 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9148 }
9149
9150 pub fn convert_to_kebab_case(
9151 &mut self,
9152 _: &ConvertToKebabCase,
9153 window: &mut Window,
9154 cx: &mut Context<Self>,
9155 ) {
9156 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9157 }
9158
9159 pub fn convert_to_upper_camel_case(
9160 &mut self,
9161 _: &ConvertToUpperCamelCase,
9162 window: &mut Window,
9163 cx: &mut Context<Self>,
9164 ) {
9165 self.manipulate_text(window, cx, |text| {
9166 text.split('\n')
9167 .map(|line| line.to_case(Case::UpperCamel))
9168 .join("\n")
9169 })
9170 }
9171
9172 pub fn convert_to_lower_camel_case(
9173 &mut self,
9174 _: &ConvertToLowerCamelCase,
9175 window: &mut Window,
9176 cx: &mut Context<Self>,
9177 ) {
9178 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9179 }
9180
9181 pub fn convert_to_opposite_case(
9182 &mut self,
9183 _: &ConvertToOppositeCase,
9184 window: &mut Window,
9185 cx: &mut Context<Self>,
9186 ) {
9187 self.manipulate_text(window, cx, |text| {
9188 text.chars()
9189 .fold(String::with_capacity(text.len()), |mut t, c| {
9190 if c.is_uppercase() {
9191 t.extend(c.to_lowercase());
9192 } else {
9193 t.extend(c.to_uppercase());
9194 }
9195 t
9196 })
9197 })
9198 }
9199
9200 pub fn convert_to_rot13(
9201 &mut self,
9202 _: &ConvertToRot13,
9203 window: &mut Window,
9204 cx: &mut Context<Self>,
9205 ) {
9206 self.manipulate_text(window, cx, |text| {
9207 text.chars()
9208 .map(|c| match c {
9209 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9210 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9211 _ => c,
9212 })
9213 .collect()
9214 })
9215 }
9216
9217 pub fn convert_to_rot47(
9218 &mut self,
9219 _: &ConvertToRot47,
9220 window: &mut Window,
9221 cx: &mut Context<Self>,
9222 ) {
9223 self.manipulate_text(window, cx, |text| {
9224 text.chars()
9225 .map(|c| {
9226 let code_point = c as u32;
9227 if code_point >= 33 && code_point <= 126 {
9228 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9229 }
9230 c
9231 })
9232 .collect()
9233 })
9234 }
9235
9236 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9237 where
9238 Fn: FnMut(&str) -> String,
9239 {
9240 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9241 let buffer = self.buffer.read(cx).snapshot(cx);
9242
9243 let mut new_selections = Vec::new();
9244 let mut edits = Vec::new();
9245 let mut selection_adjustment = 0i32;
9246
9247 for selection in self.selections.all::<usize>(cx) {
9248 let selection_is_empty = selection.is_empty();
9249
9250 let (start, end) = if selection_is_empty {
9251 let word_range = movement::surrounding_word(
9252 &display_map,
9253 selection.start.to_display_point(&display_map),
9254 );
9255 let start = word_range.start.to_offset(&display_map, Bias::Left);
9256 let end = word_range.end.to_offset(&display_map, Bias::Left);
9257 (start, end)
9258 } else {
9259 (selection.start, selection.end)
9260 };
9261
9262 let text = buffer.text_for_range(start..end).collect::<String>();
9263 let old_length = text.len() as i32;
9264 let text = callback(&text);
9265
9266 new_selections.push(Selection {
9267 start: (start as i32 - selection_adjustment) as usize,
9268 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9269 goal: SelectionGoal::None,
9270 ..selection
9271 });
9272
9273 selection_adjustment += old_length - text.len() as i32;
9274
9275 edits.push((start..end, text));
9276 }
9277
9278 self.transact(window, cx, |this, window, cx| {
9279 this.buffer.update(cx, |buffer, cx| {
9280 buffer.edit(edits, None, cx);
9281 });
9282
9283 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9284 s.select(new_selections);
9285 });
9286
9287 this.request_autoscroll(Autoscroll::fit(), cx);
9288 });
9289 }
9290
9291 pub fn duplicate(
9292 &mut self,
9293 upwards: bool,
9294 whole_lines: bool,
9295 window: &mut Window,
9296 cx: &mut Context<Self>,
9297 ) {
9298 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9299
9300 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9301 let buffer = &display_map.buffer_snapshot;
9302 let selections = self.selections.all::<Point>(cx);
9303
9304 let mut edits = Vec::new();
9305 let mut selections_iter = selections.iter().peekable();
9306 while let Some(selection) = selections_iter.next() {
9307 let mut rows = selection.spanned_rows(false, &display_map);
9308 // duplicate line-wise
9309 if whole_lines || selection.start == selection.end {
9310 // Avoid duplicating the same lines twice.
9311 while let Some(next_selection) = selections_iter.peek() {
9312 let next_rows = next_selection.spanned_rows(false, &display_map);
9313 if next_rows.start < rows.end {
9314 rows.end = next_rows.end;
9315 selections_iter.next().unwrap();
9316 } else {
9317 break;
9318 }
9319 }
9320
9321 // Copy the text from the selected row region and splice it either at the start
9322 // or end of the region.
9323 let start = Point::new(rows.start.0, 0);
9324 let end = Point::new(
9325 rows.end.previous_row().0,
9326 buffer.line_len(rows.end.previous_row()),
9327 );
9328 let text = buffer
9329 .text_for_range(start..end)
9330 .chain(Some("\n"))
9331 .collect::<String>();
9332 let insert_location = if upwards {
9333 Point::new(rows.end.0, 0)
9334 } else {
9335 start
9336 };
9337 edits.push((insert_location..insert_location, text));
9338 } else {
9339 // duplicate character-wise
9340 let start = selection.start;
9341 let end = selection.end;
9342 let text = buffer.text_for_range(start..end).collect::<String>();
9343 edits.push((selection.end..selection.end, text));
9344 }
9345 }
9346
9347 self.transact(window, cx, |this, _, cx| {
9348 this.buffer.update(cx, |buffer, cx| {
9349 buffer.edit(edits, None, cx);
9350 });
9351
9352 this.request_autoscroll(Autoscroll::fit(), cx);
9353 });
9354 }
9355
9356 pub fn duplicate_line_up(
9357 &mut self,
9358 _: &DuplicateLineUp,
9359 window: &mut Window,
9360 cx: &mut Context<Self>,
9361 ) {
9362 self.duplicate(true, true, window, cx);
9363 }
9364
9365 pub fn duplicate_line_down(
9366 &mut self,
9367 _: &DuplicateLineDown,
9368 window: &mut Window,
9369 cx: &mut Context<Self>,
9370 ) {
9371 self.duplicate(false, true, window, cx);
9372 }
9373
9374 pub fn duplicate_selection(
9375 &mut self,
9376 _: &DuplicateSelection,
9377 window: &mut Window,
9378 cx: &mut Context<Self>,
9379 ) {
9380 self.duplicate(false, false, window, cx);
9381 }
9382
9383 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9384 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9385
9386 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9387 let buffer = self.buffer.read(cx).snapshot(cx);
9388
9389 let mut edits = Vec::new();
9390 let mut unfold_ranges = Vec::new();
9391 let mut refold_creases = Vec::new();
9392
9393 let selections = self.selections.all::<Point>(cx);
9394 let mut selections = selections.iter().peekable();
9395 let mut contiguous_row_selections = Vec::new();
9396 let mut new_selections = Vec::new();
9397
9398 while let Some(selection) = selections.next() {
9399 // Find all the selections that span a contiguous row range
9400 let (start_row, end_row) = consume_contiguous_rows(
9401 &mut contiguous_row_selections,
9402 selection,
9403 &display_map,
9404 &mut selections,
9405 );
9406
9407 // Move the text spanned by the row range to be before the line preceding the row range
9408 if start_row.0 > 0 {
9409 let range_to_move = Point::new(
9410 start_row.previous_row().0,
9411 buffer.line_len(start_row.previous_row()),
9412 )
9413 ..Point::new(
9414 end_row.previous_row().0,
9415 buffer.line_len(end_row.previous_row()),
9416 );
9417 let insertion_point = display_map
9418 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9419 .0;
9420
9421 // Don't move lines across excerpts
9422 if buffer
9423 .excerpt_containing(insertion_point..range_to_move.end)
9424 .is_some()
9425 {
9426 let text = buffer
9427 .text_for_range(range_to_move.clone())
9428 .flat_map(|s| s.chars())
9429 .skip(1)
9430 .chain(['\n'])
9431 .collect::<String>();
9432
9433 edits.push((
9434 buffer.anchor_after(range_to_move.start)
9435 ..buffer.anchor_before(range_to_move.end),
9436 String::new(),
9437 ));
9438 let insertion_anchor = buffer.anchor_after(insertion_point);
9439 edits.push((insertion_anchor..insertion_anchor, text));
9440
9441 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9442
9443 // Move selections up
9444 new_selections.extend(contiguous_row_selections.drain(..).map(
9445 |mut selection| {
9446 selection.start.row -= row_delta;
9447 selection.end.row -= row_delta;
9448 selection
9449 },
9450 ));
9451
9452 // Move folds up
9453 unfold_ranges.push(range_to_move.clone());
9454 for fold in display_map.folds_in_range(
9455 buffer.anchor_before(range_to_move.start)
9456 ..buffer.anchor_after(range_to_move.end),
9457 ) {
9458 let mut start = fold.range.start.to_point(&buffer);
9459 let mut end = fold.range.end.to_point(&buffer);
9460 start.row -= row_delta;
9461 end.row -= row_delta;
9462 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9463 }
9464 }
9465 }
9466
9467 // If we didn't move line(s), preserve the existing selections
9468 new_selections.append(&mut contiguous_row_selections);
9469 }
9470
9471 self.transact(window, cx, |this, window, cx| {
9472 this.unfold_ranges(&unfold_ranges, true, true, cx);
9473 this.buffer.update(cx, |buffer, cx| {
9474 for (range, text) in edits {
9475 buffer.edit([(range, text)], None, cx);
9476 }
9477 });
9478 this.fold_creases(refold_creases, true, window, cx);
9479 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9480 s.select(new_selections);
9481 })
9482 });
9483 }
9484
9485 pub fn move_line_down(
9486 &mut self,
9487 _: &MoveLineDown,
9488 window: &mut Window,
9489 cx: &mut Context<Self>,
9490 ) {
9491 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9492
9493 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9494 let buffer = self.buffer.read(cx).snapshot(cx);
9495
9496 let mut edits = Vec::new();
9497 let mut unfold_ranges = Vec::new();
9498 let mut refold_creases = Vec::new();
9499
9500 let selections = self.selections.all::<Point>(cx);
9501 let mut selections = selections.iter().peekable();
9502 let mut contiguous_row_selections = Vec::new();
9503 let mut new_selections = Vec::new();
9504
9505 while let Some(selection) = selections.next() {
9506 // Find all the selections that span a contiguous row range
9507 let (start_row, end_row) = consume_contiguous_rows(
9508 &mut contiguous_row_selections,
9509 selection,
9510 &display_map,
9511 &mut selections,
9512 );
9513
9514 // Move the text spanned by the row range to be after the last line of the row range
9515 if end_row.0 <= buffer.max_point().row {
9516 let range_to_move =
9517 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9518 let insertion_point = display_map
9519 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9520 .0;
9521
9522 // Don't move lines across excerpt boundaries
9523 if buffer
9524 .excerpt_containing(range_to_move.start..insertion_point)
9525 .is_some()
9526 {
9527 let mut text = String::from("\n");
9528 text.extend(buffer.text_for_range(range_to_move.clone()));
9529 text.pop(); // Drop trailing newline
9530 edits.push((
9531 buffer.anchor_after(range_to_move.start)
9532 ..buffer.anchor_before(range_to_move.end),
9533 String::new(),
9534 ));
9535 let insertion_anchor = buffer.anchor_after(insertion_point);
9536 edits.push((insertion_anchor..insertion_anchor, text));
9537
9538 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9539
9540 // Move selections down
9541 new_selections.extend(contiguous_row_selections.drain(..).map(
9542 |mut selection| {
9543 selection.start.row += row_delta;
9544 selection.end.row += row_delta;
9545 selection
9546 },
9547 ));
9548
9549 // Move folds down
9550 unfold_ranges.push(range_to_move.clone());
9551 for fold in display_map.folds_in_range(
9552 buffer.anchor_before(range_to_move.start)
9553 ..buffer.anchor_after(range_to_move.end),
9554 ) {
9555 let mut start = fold.range.start.to_point(&buffer);
9556 let mut end = fold.range.end.to_point(&buffer);
9557 start.row += row_delta;
9558 end.row += row_delta;
9559 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9560 }
9561 }
9562 }
9563
9564 // If we didn't move line(s), preserve the existing selections
9565 new_selections.append(&mut contiguous_row_selections);
9566 }
9567
9568 self.transact(window, cx, |this, window, cx| {
9569 this.unfold_ranges(&unfold_ranges, true, true, cx);
9570 this.buffer.update(cx, |buffer, cx| {
9571 for (range, text) in edits {
9572 buffer.edit([(range, text)], None, cx);
9573 }
9574 });
9575 this.fold_creases(refold_creases, true, window, cx);
9576 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9577 s.select(new_selections)
9578 });
9579 });
9580 }
9581
9582 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9583 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9584 let text_layout_details = &self.text_layout_details(window);
9585 self.transact(window, cx, |this, window, cx| {
9586 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9587 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9588 s.move_with(|display_map, selection| {
9589 if !selection.is_empty() {
9590 return;
9591 }
9592
9593 let mut head = selection.head();
9594 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9595 if head.column() == display_map.line_len(head.row()) {
9596 transpose_offset = display_map
9597 .buffer_snapshot
9598 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9599 }
9600
9601 if transpose_offset == 0 {
9602 return;
9603 }
9604
9605 *head.column_mut() += 1;
9606 head = display_map.clip_point(head, Bias::Right);
9607 let goal = SelectionGoal::HorizontalPosition(
9608 display_map
9609 .x_for_display_point(head, text_layout_details)
9610 .into(),
9611 );
9612 selection.collapse_to(head, goal);
9613
9614 let transpose_start = display_map
9615 .buffer_snapshot
9616 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9617 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9618 let transpose_end = display_map
9619 .buffer_snapshot
9620 .clip_offset(transpose_offset + 1, Bias::Right);
9621 if let Some(ch) =
9622 display_map.buffer_snapshot.chars_at(transpose_start).next()
9623 {
9624 edits.push((transpose_start..transpose_offset, String::new()));
9625 edits.push((transpose_end..transpose_end, ch.to_string()));
9626 }
9627 }
9628 });
9629 edits
9630 });
9631 this.buffer
9632 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9633 let selections = this.selections.all::<usize>(cx);
9634 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9635 s.select(selections);
9636 });
9637 });
9638 }
9639
9640 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9641 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9642 self.rewrap_impl(RewrapOptions::default(), cx)
9643 }
9644
9645 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9646 let buffer = self.buffer.read(cx).snapshot(cx);
9647 let selections = self.selections.all::<Point>(cx);
9648 let mut selections = selections.iter().peekable();
9649
9650 let mut edits = Vec::new();
9651 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9652
9653 while let Some(selection) = selections.next() {
9654 let mut start_row = selection.start.row;
9655 let mut end_row = selection.end.row;
9656
9657 // Skip selections that overlap with a range that has already been rewrapped.
9658 let selection_range = start_row..end_row;
9659 if rewrapped_row_ranges
9660 .iter()
9661 .any(|range| range.overlaps(&selection_range))
9662 {
9663 continue;
9664 }
9665
9666 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9667
9668 // Since not all lines in the selection may be at the same indent
9669 // level, choose the indent size that is the most common between all
9670 // of the lines.
9671 //
9672 // If there is a tie, we use the deepest indent.
9673 let (indent_size, indent_end) = {
9674 let mut indent_size_occurrences = HashMap::default();
9675 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9676
9677 for row in start_row..=end_row {
9678 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9679 rows_by_indent_size.entry(indent).or_default().push(row);
9680 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9681 }
9682
9683 let indent_size = indent_size_occurrences
9684 .into_iter()
9685 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9686 .map(|(indent, _)| indent)
9687 .unwrap_or_default();
9688 let row = rows_by_indent_size[&indent_size][0];
9689 let indent_end = Point::new(row, indent_size.len);
9690
9691 (indent_size, indent_end)
9692 };
9693
9694 let mut line_prefix = indent_size.chars().collect::<String>();
9695
9696 let mut inside_comment = false;
9697 if let Some(comment_prefix) =
9698 buffer
9699 .language_scope_at(selection.head())
9700 .and_then(|language| {
9701 language
9702 .line_comment_prefixes()
9703 .iter()
9704 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9705 .cloned()
9706 })
9707 {
9708 line_prefix.push_str(&comment_prefix);
9709 inside_comment = true;
9710 }
9711
9712 let language_settings = buffer.language_settings_at(selection.head(), cx);
9713 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9714 RewrapBehavior::InComments => inside_comment,
9715 RewrapBehavior::InSelections => !selection.is_empty(),
9716 RewrapBehavior::Anywhere => true,
9717 };
9718
9719 let should_rewrap = options.override_language_settings
9720 || allow_rewrap_based_on_language
9721 || self.hard_wrap.is_some();
9722 if !should_rewrap {
9723 continue;
9724 }
9725
9726 if selection.is_empty() {
9727 'expand_upwards: while start_row > 0 {
9728 let prev_row = start_row - 1;
9729 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9730 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9731 {
9732 start_row = prev_row;
9733 } else {
9734 break 'expand_upwards;
9735 }
9736 }
9737
9738 'expand_downwards: while end_row < buffer.max_point().row {
9739 let next_row = end_row + 1;
9740 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9741 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9742 {
9743 end_row = next_row;
9744 } else {
9745 break 'expand_downwards;
9746 }
9747 }
9748 }
9749
9750 let start = Point::new(start_row, 0);
9751 let start_offset = start.to_offset(&buffer);
9752 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9753 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9754 let Some(lines_without_prefixes) = selection_text
9755 .lines()
9756 .map(|line| {
9757 line.strip_prefix(&line_prefix)
9758 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9759 .ok_or_else(|| {
9760 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9761 })
9762 })
9763 .collect::<Result<Vec<_>, _>>()
9764 .log_err()
9765 else {
9766 continue;
9767 };
9768
9769 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9770 buffer
9771 .language_settings_at(Point::new(start_row, 0), cx)
9772 .preferred_line_length as usize
9773 });
9774 let wrapped_text = wrap_with_prefix(
9775 line_prefix,
9776 lines_without_prefixes.join("\n"),
9777 wrap_column,
9778 tab_size,
9779 options.preserve_existing_whitespace,
9780 );
9781
9782 // TODO: should always use char-based diff while still supporting cursor behavior that
9783 // matches vim.
9784 let mut diff_options = DiffOptions::default();
9785 if options.override_language_settings {
9786 diff_options.max_word_diff_len = 0;
9787 diff_options.max_word_diff_line_count = 0;
9788 } else {
9789 diff_options.max_word_diff_len = usize::MAX;
9790 diff_options.max_word_diff_line_count = usize::MAX;
9791 }
9792
9793 for (old_range, new_text) in
9794 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9795 {
9796 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9797 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9798 edits.push((edit_start..edit_end, new_text));
9799 }
9800
9801 rewrapped_row_ranges.push(start_row..=end_row);
9802 }
9803
9804 self.buffer
9805 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9806 }
9807
9808 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9809 let mut text = String::new();
9810 let buffer = self.buffer.read(cx).snapshot(cx);
9811 let mut selections = self.selections.all::<Point>(cx);
9812 let mut clipboard_selections = Vec::with_capacity(selections.len());
9813 {
9814 let max_point = buffer.max_point();
9815 let mut is_first = true;
9816 for selection in &mut selections {
9817 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9818 if is_entire_line {
9819 selection.start = Point::new(selection.start.row, 0);
9820 if !selection.is_empty() && selection.end.column == 0 {
9821 selection.end = cmp::min(max_point, selection.end);
9822 } else {
9823 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9824 }
9825 selection.goal = SelectionGoal::None;
9826 }
9827 if is_first {
9828 is_first = false;
9829 } else {
9830 text += "\n";
9831 }
9832 let mut len = 0;
9833 for chunk in buffer.text_for_range(selection.start..selection.end) {
9834 text.push_str(chunk);
9835 len += chunk.len();
9836 }
9837 clipboard_selections.push(ClipboardSelection {
9838 len,
9839 is_entire_line,
9840 first_line_indent: buffer
9841 .indent_size_for_line(MultiBufferRow(selection.start.row))
9842 .len,
9843 });
9844 }
9845 }
9846
9847 self.transact(window, cx, |this, window, cx| {
9848 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9849 s.select(selections);
9850 });
9851 this.insert("", window, cx);
9852 });
9853 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9854 }
9855
9856 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9857 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9858 let item = self.cut_common(window, cx);
9859 cx.write_to_clipboard(item);
9860 }
9861
9862 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9863 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9864 self.change_selections(None, window, cx, |s| {
9865 s.move_with(|snapshot, sel| {
9866 if sel.is_empty() {
9867 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9868 }
9869 });
9870 });
9871 let item = self.cut_common(window, cx);
9872 cx.set_global(KillRing(item))
9873 }
9874
9875 pub fn kill_ring_yank(
9876 &mut self,
9877 _: &KillRingYank,
9878 window: &mut Window,
9879 cx: &mut Context<Self>,
9880 ) {
9881 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9882 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9883 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9884 (kill_ring.text().to_string(), kill_ring.metadata_json())
9885 } else {
9886 return;
9887 }
9888 } else {
9889 return;
9890 };
9891 self.do_paste(&text, metadata, false, window, cx);
9892 }
9893
9894 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9895 self.do_copy(true, cx);
9896 }
9897
9898 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9899 self.do_copy(false, cx);
9900 }
9901
9902 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9903 let selections = self.selections.all::<Point>(cx);
9904 let buffer = self.buffer.read(cx).read(cx);
9905 let mut text = String::new();
9906
9907 let mut clipboard_selections = Vec::with_capacity(selections.len());
9908 {
9909 let max_point = buffer.max_point();
9910 let mut is_first = true;
9911 for selection in &selections {
9912 let mut start = selection.start;
9913 let mut end = selection.end;
9914 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9915 if is_entire_line {
9916 start = Point::new(start.row, 0);
9917 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9918 }
9919
9920 let mut trimmed_selections = Vec::new();
9921 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
9922 let row = MultiBufferRow(start.row);
9923 let first_indent = buffer.indent_size_for_line(row);
9924 if first_indent.len == 0 || start.column > first_indent.len {
9925 trimmed_selections.push(start..end);
9926 } else {
9927 trimmed_selections.push(
9928 Point::new(row.0, first_indent.len)
9929 ..Point::new(row.0, buffer.line_len(row)),
9930 );
9931 for row in start.row + 1..=end.row {
9932 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
9933 if row_indent_size.len >= first_indent.len {
9934 trimmed_selections.push(
9935 Point::new(row, first_indent.len)
9936 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
9937 );
9938 } else {
9939 trimmed_selections.clear();
9940 trimmed_selections.push(start..end);
9941 break;
9942 }
9943 }
9944 }
9945 } else {
9946 trimmed_selections.push(start..end);
9947 }
9948
9949 for trimmed_range in trimmed_selections {
9950 if is_first {
9951 is_first = false;
9952 } else {
9953 text += "\n";
9954 }
9955 let mut len = 0;
9956 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
9957 text.push_str(chunk);
9958 len += chunk.len();
9959 }
9960 clipboard_selections.push(ClipboardSelection {
9961 len,
9962 is_entire_line,
9963 first_line_indent: buffer
9964 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
9965 .len,
9966 });
9967 }
9968 }
9969 }
9970
9971 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
9972 text,
9973 clipboard_selections,
9974 ));
9975 }
9976
9977 pub fn do_paste(
9978 &mut self,
9979 text: &String,
9980 clipboard_selections: Option<Vec<ClipboardSelection>>,
9981 handle_entire_lines: bool,
9982 window: &mut Window,
9983 cx: &mut Context<Self>,
9984 ) {
9985 if self.read_only(cx) {
9986 return;
9987 }
9988
9989 let clipboard_text = Cow::Borrowed(text);
9990
9991 self.transact(window, cx, |this, window, cx| {
9992 if let Some(mut clipboard_selections) = clipboard_selections {
9993 let old_selections = this.selections.all::<usize>(cx);
9994 let all_selections_were_entire_line =
9995 clipboard_selections.iter().all(|s| s.is_entire_line);
9996 let first_selection_indent_column =
9997 clipboard_selections.first().map(|s| s.first_line_indent);
9998 if clipboard_selections.len() != old_selections.len() {
9999 clipboard_selections.drain(..);
10000 }
10001 let cursor_offset = this.selections.last::<usize>(cx).head();
10002 let mut auto_indent_on_paste = true;
10003
10004 this.buffer.update(cx, |buffer, cx| {
10005 let snapshot = buffer.read(cx);
10006 auto_indent_on_paste = snapshot
10007 .language_settings_at(cursor_offset, cx)
10008 .auto_indent_on_paste;
10009
10010 let mut start_offset = 0;
10011 let mut edits = Vec::new();
10012 let mut original_indent_columns = Vec::new();
10013 for (ix, selection) in old_selections.iter().enumerate() {
10014 let to_insert;
10015 let entire_line;
10016 let original_indent_column;
10017 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10018 let end_offset = start_offset + clipboard_selection.len;
10019 to_insert = &clipboard_text[start_offset..end_offset];
10020 entire_line = clipboard_selection.is_entire_line;
10021 start_offset = end_offset + 1;
10022 original_indent_column = Some(clipboard_selection.first_line_indent);
10023 } else {
10024 to_insert = clipboard_text.as_str();
10025 entire_line = all_selections_were_entire_line;
10026 original_indent_column = first_selection_indent_column
10027 }
10028
10029 // If the corresponding selection was empty when this slice of the
10030 // clipboard text was written, then the entire line containing the
10031 // selection was copied. If this selection is also currently empty,
10032 // then paste the line before the current line of the buffer.
10033 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10034 let column = selection.start.to_point(&snapshot).column as usize;
10035 let line_start = selection.start - column;
10036 line_start..line_start
10037 } else {
10038 selection.range()
10039 };
10040
10041 edits.push((range, to_insert));
10042 original_indent_columns.push(original_indent_column);
10043 }
10044 drop(snapshot);
10045
10046 buffer.edit(
10047 edits,
10048 if auto_indent_on_paste {
10049 Some(AutoindentMode::Block {
10050 original_indent_columns,
10051 })
10052 } else {
10053 None
10054 },
10055 cx,
10056 );
10057 });
10058
10059 let selections = this.selections.all::<usize>(cx);
10060 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10061 s.select(selections)
10062 });
10063 } else {
10064 this.insert(&clipboard_text, window, cx);
10065 }
10066 });
10067 }
10068
10069 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10070 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10071 if let Some(item) = cx.read_from_clipboard() {
10072 let entries = item.entries();
10073
10074 match entries.first() {
10075 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10076 // of all the pasted entries.
10077 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10078 .do_paste(
10079 clipboard_string.text(),
10080 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10081 true,
10082 window,
10083 cx,
10084 ),
10085 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10086 }
10087 }
10088 }
10089
10090 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10091 if self.read_only(cx) {
10092 return;
10093 }
10094
10095 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10096
10097 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10098 if let Some((selections, _)) =
10099 self.selection_history.transaction(transaction_id).cloned()
10100 {
10101 self.change_selections(None, window, cx, |s| {
10102 s.select_anchors(selections.to_vec());
10103 });
10104 } else {
10105 log::error!(
10106 "No entry in selection_history found for undo. \
10107 This may correspond to a bug where undo does not update the selection. \
10108 If this is occurring, please add details to \
10109 https://github.com/zed-industries/zed/issues/22692"
10110 );
10111 }
10112 self.request_autoscroll(Autoscroll::fit(), cx);
10113 self.unmark_text(window, cx);
10114 self.refresh_inline_completion(true, false, window, cx);
10115 cx.emit(EditorEvent::Edited { transaction_id });
10116 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10117 }
10118 }
10119
10120 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10121 if self.read_only(cx) {
10122 return;
10123 }
10124
10125 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10126
10127 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10128 if let Some((_, Some(selections))) =
10129 self.selection_history.transaction(transaction_id).cloned()
10130 {
10131 self.change_selections(None, window, cx, |s| {
10132 s.select_anchors(selections.to_vec());
10133 });
10134 } else {
10135 log::error!(
10136 "No entry in selection_history found for redo. \
10137 This may correspond to a bug where undo does not update the selection. \
10138 If this is occurring, please add details to \
10139 https://github.com/zed-industries/zed/issues/22692"
10140 );
10141 }
10142 self.request_autoscroll(Autoscroll::fit(), cx);
10143 self.unmark_text(window, cx);
10144 self.refresh_inline_completion(true, false, window, cx);
10145 cx.emit(EditorEvent::Edited { transaction_id });
10146 }
10147 }
10148
10149 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10150 self.buffer
10151 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10152 }
10153
10154 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10155 self.buffer
10156 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10157 }
10158
10159 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10160 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10161 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10162 s.move_with(|map, selection| {
10163 let cursor = if selection.is_empty() {
10164 movement::left(map, selection.start)
10165 } else {
10166 selection.start
10167 };
10168 selection.collapse_to(cursor, SelectionGoal::None);
10169 });
10170 })
10171 }
10172
10173 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10174 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10175 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10176 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10177 })
10178 }
10179
10180 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10181 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10182 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10183 s.move_with(|map, selection| {
10184 let cursor = if selection.is_empty() {
10185 movement::right(map, selection.end)
10186 } else {
10187 selection.end
10188 };
10189 selection.collapse_to(cursor, SelectionGoal::None)
10190 });
10191 })
10192 }
10193
10194 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10195 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10196 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10197 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10198 })
10199 }
10200
10201 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10202 if self.take_rename(true, window, cx).is_some() {
10203 return;
10204 }
10205
10206 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10207 cx.propagate();
10208 return;
10209 }
10210
10211 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10212
10213 let text_layout_details = &self.text_layout_details(window);
10214 let selection_count = self.selections.count();
10215 let first_selection = self.selections.first_anchor();
10216
10217 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10218 s.move_with(|map, selection| {
10219 if !selection.is_empty() {
10220 selection.goal = SelectionGoal::None;
10221 }
10222 let (cursor, goal) = movement::up(
10223 map,
10224 selection.start,
10225 selection.goal,
10226 false,
10227 text_layout_details,
10228 );
10229 selection.collapse_to(cursor, goal);
10230 });
10231 });
10232
10233 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10234 {
10235 cx.propagate();
10236 }
10237 }
10238
10239 pub fn move_up_by_lines(
10240 &mut self,
10241 action: &MoveUpByLines,
10242 window: &mut Window,
10243 cx: &mut Context<Self>,
10244 ) {
10245 if self.take_rename(true, window, cx).is_some() {
10246 return;
10247 }
10248
10249 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10250 cx.propagate();
10251 return;
10252 }
10253
10254 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10255
10256 let text_layout_details = &self.text_layout_details(window);
10257
10258 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10259 s.move_with(|map, selection| {
10260 if !selection.is_empty() {
10261 selection.goal = SelectionGoal::None;
10262 }
10263 let (cursor, goal) = movement::up_by_rows(
10264 map,
10265 selection.start,
10266 action.lines,
10267 selection.goal,
10268 false,
10269 text_layout_details,
10270 );
10271 selection.collapse_to(cursor, goal);
10272 });
10273 })
10274 }
10275
10276 pub fn move_down_by_lines(
10277 &mut self,
10278 action: &MoveDownByLines,
10279 window: &mut Window,
10280 cx: &mut Context<Self>,
10281 ) {
10282 if self.take_rename(true, window, cx).is_some() {
10283 return;
10284 }
10285
10286 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10287 cx.propagate();
10288 return;
10289 }
10290
10291 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10292
10293 let text_layout_details = &self.text_layout_details(window);
10294
10295 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10296 s.move_with(|map, selection| {
10297 if !selection.is_empty() {
10298 selection.goal = SelectionGoal::None;
10299 }
10300 let (cursor, goal) = movement::down_by_rows(
10301 map,
10302 selection.start,
10303 action.lines,
10304 selection.goal,
10305 false,
10306 text_layout_details,
10307 );
10308 selection.collapse_to(cursor, goal);
10309 });
10310 })
10311 }
10312
10313 pub fn select_down_by_lines(
10314 &mut self,
10315 action: &SelectDownByLines,
10316 window: &mut Window,
10317 cx: &mut Context<Self>,
10318 ) {
10319 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10320 let text_layout_details = &self.text_layout_details(window);
10321 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10322 s.move_heads_with(|map, head, goal| {
10323 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10324 })
10325 })
10326 }
10327
10328 pub fn select_up_by_lines(
10329 &mut self,
10330 action: &SelectUpByLines,
10331 window: &mut Window,
10332 cx: &mut Context<Self>,
10333 ) {
10334 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10335 let text_layout_details = &self.text_layout_details(window);
10336 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10337 s.move_heads_with(|map, head, goal| {
10338 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10339 })
10340 })
10341 }
10342
10343 pub fn select_page_up(
10344 &mut self,
10345 _: &SelectPageUp,
10346 window: &mut Window,
10347 cx: &mut Context<Self>,
10348 ) {
10349 let Some(row_count) = self.visible_row_count() else {
10350 return;
10351 };
10352
10353 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10354
10355 let text_layout_details = &self.text_layout_details(window);
10356
10357 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10358 s.move_heads_with(|map, head, goal| {
10359 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10360 })
10361 })
10362 }
10363
10364 pub fn move_page_up(
10365 &mut self,
10366 action: &MovePageUp,
10367 window: &mut Window,
10368 cx: &mut Context<Self>,
10369 ) {
10370 if self.take_rename(true, window, cx).is_some() {
10371 return;
10372 }
10373
10374 if self
10375 .context_menu
10376 .borrow_mut()
10377 .as_mut()
10378 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10379 .unwrap_or(false)
10380 {
10381 return;
10382 }
10383
10384 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10385 cx.propagate();
10386 return;
10387 }
10388
10389 let Some(row_count) = self.visible_row_count() else {
10390 return;
10391 };
10392
10393 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10394
10395 let autoscroll = if action.center_cursor {
10396 Autoscroll::center()
10397 } else {
10398 Autoscroll::fit()
10399 };
10400
10401 let text_layout_details = &self.text_layout_details(window);
10402
10403 self.change_selections(Some(autoscroll), window, cx, |s| {
10404 s.move_with(|map, selection| {
10405 if !selection.is_empty() {
10406 selection.goal = SelectionGoal::None;
10407 }
10408 let (cursor, goal) = movement::up_by_rows(
10409 map,
10410 selection.end,
10411 row_count,
10412 selection.goal,
10413 false,
10414 text_layout_details,
10415 );
10416 selection.collapse_to(cursor, goal);
10417 });
10418 });
10419 }
10420
10421 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10422 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10423 let text_layout_details = &self.text_layout_details(window);
10424 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10425 s.move_heads_with(|map, head, goal| {
10426 movement::up(map, head, goal, false, text_layout_details)
10427 })
10428 })
10429 }
10430
10431 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10432 self.take_rename(true, window, cx);
10433
10434 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10435 cx.propagate();
10436 return;
10437 }
10438
10439 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10440
10441 let text_layout_details = &self.text_layout_details(window);
10442 let selection_count = self.selections.count();
10443 let first_selection = self.selections.first_anchor();
10444
10445 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10446 s.move_with(|map, selection| {
10447 if !selection.is_empty() {
10448 selection.goal = SelectionGoal::None;
10449 }
10450 let (cursor, goal) = movement::down(
10451 map,
10452 selection.end,
10453 selection.goal,
10454 false,
10455 text_layout_details,
10456 );
10457 selection.collapse_to(cursor, goal);
10458 });
10459 });
10460
10461 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10462 {
10463 cx.propagate();
10464 }
10465 }
10466
10467 pub fn select_page_down(
10468 &mut self,
10469 _: &SelectPageDown,
10470 window: &mut Window,
10471 cx: &mut Context<Self>,
10472 ) {
10473 let Some(row_count) = self.visible_row_count() else {
10474 return;
10475 };
10476
10477 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10478
10479 let text_layout_details = &self.text_layout_details(window);
10480
10481 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10482 s.move_heads_with(|map, head, goal| {
10483 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10484 })
10485 })
10486 }
10487
10488 pub fn move_page_down(
10489 &mut self,
10490 action: &MovePageDown,
10491 window: &mut Window,
10492 cx: &mut Context<Self>,
10493 ) {
10494 if self.take_rename(true, window, cx).is_some() {
10495 return;
10496 }
10497
10498 if self
10499 .context_menu
10500 .borrow_mut()
10501 .as_mut()
10502 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10503 .unwrap_or(false)
10504 {
10505 return;
10506 }
10507
10508 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10509 cx.propagate();
10510 return;
10511 }
10512
10513 let Some(row_count) = self.visible_row_count() else {
10514 return;
10515 };
10516
10517 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10518
10519 let autoscroll = if action.center_cursor {
10520 Autoscroll::center()
10521 } else {
10522 Autoscroll::fit()
10523 };
10524
10525 let text_layout_details = &self.text_layout_details(window);
10526 self.change_selections(Some(autoscroll), window, cx, |s| {
10527 s.move_with(|map, selection| {
10528 if !selection.is_empty() {
10529 selection.goal = SelectionGoal::None;
10530 }
10531 let (cursor, goal) = movement::down_by_rows(
10532 map,
10533 selection.end,
10534 row_count,
10535 selection.goal,
10536 false,
10537 text_layout_details,
10538 );
10539 selection.collapse_to(cursor, goal);
10540 });
10541 });
10542 }
10543
10544 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10545 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10546 let text_layout_details = &self.text_layout_details(window);
10547 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10548 s.move_heads_with(|map, head, goal| {
10549 movement::down(map, head, goal, false, text_layout_details)
10550 })
10551 });
10552 }
10553
10554 pub fn context_menu_first(
10555 &mut self,
10556 _: &ContextMenuFirst,
10557 _window: &mut Window,
10558 cx: &mut Context<Self>,
10559 ) {
10560 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10561 context_menu.select_first(self.completion_provider.as_deref(), cx);
10562 }
10563 }
10564
10565 pub fn context_menu_prev(
10566 &mut self,
10567 _: &ContextMenuPrevious,
10568 _window: &mut Window,
10569 cx: &mut Context<Self>,
10570 ) {
10571 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10572 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10573 }
10574 }
10575
10576 pub fn context_menu_next(
10577 &mut self,
10578 _: &ContextMenuNext,
10579 _window: &mut Window,
10580 cx: &mut Context<Self>,
10581 ) {
10582 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10583 context_menu.select_next(self.completion_provider.as_deref(), cx);
10584 }
10585 }
10586
10587 pub fn context_menu_last(
10588 &mut self,
10589 _: &ContextMenuLast,
10590 _window: &mut Window,
10591 cx: &mut Context<Self>,
10592 ) {
10593 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10594 context_menu.select_last(self.completion_provider.as_deref(), cx);
10595 }
10596 }
10597
10598 pub fn move_to_previous_word_start(
10599 &mut self,
10600 _: &MoveToPreviousWordStart,
10601 window: &mut Window,
10602 cx: &mut Context<Self>,
10603 ) {
10604 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10605 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10606 s.move_cursors_with(|map, head, _| {
10607 (
10608 movement::previous_word_start(map, head),
10609 SelectionGoal::None,
10610 )
10611 });
10612 })
10613 }
10614
10615 pub fn move_to_previous_subword_start(
10616 &mut self,
10617 _: &MoveToPreviousSubwordStart,
10618 window: &mut Window,
10619 cx: &mut Context<Self>,
10620 ) {
10621 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10622 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10623 s.move_cursors_with(|map, head, _| {
10624 (
10625 movement::previous_subword_start(map, head),
10626 SelectionGoal::None,
10627 )
10628 });
10629 })
10630 }
10631
10632 pub fn select_to_previous_word_start(
10633 &mut self,
10634 _: &SelectToPreviousWordStart,
10635 window: &mut Window,
10636 cx: &mut Context<Self>,
10637 ) {
10638 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10639 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10640 s.move_heads_with(|map, head, _| {
10641 (
10642 movement::previous_word_start(map, head),
10643 SelectionGoal::None,
10644 )
10645 });
10646 })
10647 }
10648
10649 pub fn select_to_previous_subword_start(
10650 &mut self,
10651 _: &SelectToPreviousSubwordStart,
10652 window: &mut Window,
10653 cx: &mut Context<Self>,
10654 ) {
10655 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10656 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10657 s.move_heads_with(|map, head, _| {
10658 (
10659 movement::previous_subword_start(map, head),
10660 SelectionGoal::None,
10661 )
10662 });
10663 })
10664 }
10665
10666 pub fn delete_to_previous_word_start(
10667 &mut self,
10668 action: &DeleteToPreviousWordStart,
10669 window: &mut Window,
10670 cx: &mut Context<Self>,
10671 ) {
10672 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10673 self.transact(window, cx, |this, window, cx| {
10674 this.select_autoclose_pair(window, cx);
10675 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10676 s.move_with(|map, selection| {
10677 if selection.is_empty() {
10678 let cursor = if action.ignore_newlines {
10679 movement::previous_word_start(map, selection.head())
10680 } else {
10681 movement::previous_word_start_or_newline(map, selection.head())
10682 };
10683 selection.set_head(cursor, SelectionGoal::None);
10684 }
10685 });
10686 });
10687 this.insert("", window, cx);
10688 });
10689 }
10690
10691 pub fn delete_to_previous_subword_start(
10692 &mut self,
10693 _: &DeleteToPreviousSubwordStart,
10694 window: &mut Window,
10695 cx: &mut Context<Self>,
10696 ) {
10697 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10698 self.transact(window, cx, |this, window, cx| {
10699 this.select_autoclose_pair(window, cx);
10700 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10701 s.move_with(|map, selection| {
10702 if selection.is_empty() {
10703 let cursor = movement::previous_subword_start(map, selection.head());
10704 selection.set_head(cursor, SelectionGoal::None);
10705 }
10706 });
10707 });
10708 this.insert("", window, cx);
10709 });
10710 }
10711
10712 pub fn move_to_next_word_end(
10713 &mut self,
10714 _: &MoveToNextWordEnd,
10715 window: &mut Window,
10716 cx: &mut Context<Self>,
10717 ) {
10718 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10719 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10720 s.move_cursors_with(|map, head, _| {
10721 (movement::next_word_end(map, head), SelectionGoal::None)
10722 });
10723 })
10724 }
10725
10726 pub fn move_to_next_subword_end(
10727 &mut self,
10728 _: &MoveToNextSubwordEnd,
10729 window: &mut Window,
10730 cx: &mut Context<Self>,
10731 ) {
10732 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10733 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10734 s.move_cursors_with(|map, head, _| {
10735 (movement::next_subword_end(map, head), SelectionGoal::None)
10736 });
10737 })
10738 }
10739
10740 pub fn select_to_next_word_end(
10741 &mut self,
10742 _: &SelectToNextWordEnd,
10743 window: &mut Window,
10744 cx: &mut Context<Self>,
10745 ) {
10746 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10747 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10748 s.move_heads_with(|map, head, _| {
10749 (movement::next_word_end(map, head), SelectionGoal::None)
10750 });
10751 })
10752 }
10753
10754 pub fn select_to_next_subword_end(
10755 &mut self,
10756 _: &SelectToNextSubwordEnd,
10757 window: &mut Window,
10758 cx: &mut Context<Self>,
10759 ) {
10760 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10761 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10762 s.move_heads_with(|map, head, _| {
10763 (movement::next_subword_end(map, head), SelectionGoal::None)
10764 });
10765 })
10766 }
10767
10768 pub fn delete_to_next_word_end(
10769 &mut self,
10770 action: &DeleteToNextWordEnd,
10771 window: &mut Window,
10772 cx: &mut Context<Self>,
10773 ) {
10774 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10775 self.transact(window, cx, |this, window, cx| {
10776 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10777 s.move_with(|map, selection| {
10778 if selection.is_empty() {
10779 let cursor = if action.ignore_newlines {
10780 movement::next_word_end(map, selection.head())
10781 } else {
10782 movement::next_word_end_or_newline(map, selection.head())
10783 };
10784 selection.set_head(cursor, SelectionGoal::None);
10785 }
10786 });
10787 });
10788 this.insert("", window, cx);
10789 });
10790 }
10791
10792 pub fn delete_to_next_subword_end(
10793 &mut self,
10794 _: &DeleteToNextSubwordEnd,
10795 window: &mut Window,
10796 cx: &mut Context<Self>,
10797 ) {
10798 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10799 self.transact(window, cx, |this, window, cx| {
10800 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10801 s.move_with(|map, selection| {
10802 if selection.is_empty() {
10803 let cursor = movement::next_subword_end(map, selection.head());
10804 selection.set_head(cursor, SelectionGoal::None);
10805 }
10806 });
10807 });
10808 this.insert("", window, cx);
10809 });
10810 }
10811
10812 pub fn move_to_beginning_of_line(
10813 &mut self,
10814 action: &MoveToBeginningOfLine,
10815 window: &mut Window,
10816 cx: &mut Context<Self>,
10817 ) {
10818 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10819 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10820 s.move_cursors_with(|map, head, _| {
10821 (
10822 movement::indented_line_beginning(
10823 map,
10824 head,
10825 action.stop_at_soft_wraps,
10826 action.stop_at_indent,
10827 ),
10828 SelectionGoal::None,
10829 )
10830 });
10831 })
10832 }
10833
10834 pub fn select_to_beginning_of_line(
10835 &mut self,
10836 action: &SelectToBeginningOfLine,
10837 window: &mut Window,
10838 cx: &mut Context<Self>,
10839 ) {
10840 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10841 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10842 s.move_heads_with(|map, head, _| {
10843 (
10844 movement::indented_line_beginning(
10845 map,
10846 head,
10847 action.stop_at_soft_wraps,
10848 action.stop_at_indent,
10849 ),
10850 SelectionGoal::None,
10851 )
10852 });
10853 });
10854 }
10855
10856 pub fn delete_to_beginning_of_line(
10857 &mut self,
10858 action: &DeleteToBeginningOfLine,
10859 window: &mut Window,
10860 cx: &mut Context<Self>,
10861 ) {
10862 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10863 self.transact(window, cx, |this, window, cx| {
10864 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10865 s.move_with(|_, selection| {
10866 selection.reversed = true;
10867 });
10868 });
10869
10870 this.select_to_beginning_of_line(
10871 &SelectToBeginningOfLine {
10872 stop_at_soft_wraps: false,
10873 stop_at_indent: action.stop_at_indent,
10874 },
10875 window,
10876 cx,
10877 );
10878 this.backspace(&Backspace, window, cx);
10879 });
10880 }
10881
10882 pub fn move_to_end_of_line(
10883 &mut self,
10884 action: &MoveToEndOfLine,
10885 window: &mut Window,
10886 cx: &mut Context<Self>,
10887 ) {
10888 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10889 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10890 s.move_cursors_with(|map, head, _| {
10891 (
10892 movement::line_end(map, head, action.stop_at_soft_wraps),
10893 SelectionGoal::None,
10894 )
10895 });
10896 })
10897 }
10898
10899 pub fn select_to_end_of_line(
10900 &mut self,
10901 action: &SelectToEndOfLine,
10902 window: &mut Window,
10903 cx: &mut Context<Self>,
10904 ) {
10905 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10906 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10907 s.move_heads_with(|map, head, _| {
10908 (
10909 movement::line_end(map, head, action.stop_at_soft_wraps),
10910 SelectionGoal::None,
10911 )
10912 });
10913 })
10914 }
10915
10916 pub fn delete_to_end_of_line(
10917 &mut self,
10918 _: &DeleteToEndOfLine,
10919 window: &mut Window,
10920 cx: &mut Context<Self>,
10921 ) {
10922 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10923 self.transact(window, cx, |this, window, cx| {
10924 this.select_to_end_of_line(
10925 &SelectToEndOfLine {
10926 stop_at_soft_wraps: false,
10927 },
10928 window,
10929 cx,
10930 );
10931 this.delete(&Delete, window, cx);
10932 });
10933 }
10934
10935 pub fn cut_to_end_of_line(
10936 &mut self,
10937 _: &CutToEndOfLine,
10938 window: &mut Window,
10939 cx: &mut Context<Self>,
10940 ) {
10941 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10942 self.transact(window, cx, |this, window, cx| {
10943 this.select_to_end_of_line(
10944 &SelectToEndOfLine {
10945 stop_at_soft_wraps: false,
10946 },
10947 window,
10948 cx,
10949 );
10950 this.cut(&Cut, window, cx);
10951 });
10952 }
10953
10954 pub fn move_to_start_of_paragraph(
10955 &mut self,
10956 _: &MoveToStartOfParagraph,
10957 window: &mut Window,
10958 cx: &mut Context<Self>,
10959 ) {
10960 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10961 cx.propagate();
10962 return;
10963 }
10964 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10965 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10966 s.move_with(|map, selection| {
10967 selection.collapse_to(
10968 movement::start_of_paragraph(map, selection.head(), 1),
10969 SelectionGoal::None,
10970 )
10971 });
10972 })
10973 }
10974
10975 pub fn move_to_end_of_paragraph(
10976 &mut self,
10977 _: &MoveToEndOfParagraph,
10978 window: &mut Window,
10979 cx: &mut Context<Self>,
10980 ) {
10981 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10982 cx.propagate();
10983 return;
10984 }
10985 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10986 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10987 s.move_with(|map, selection| {
10988 selection.collapse_to(
10989 movement::end_of_paragraph(map, selection.head(), 1),
10990 SelectionGoal::None,
10991 )
10992 });
10993 })
10994 }
10995
10996 pub fn select_to_start_of_paragraph(
10997 &mut self,
10998 _: &SelectToStartOfParagraph,
10999 window: &mut Window,
11000 cx: &mut Context<Self>,
11001 ) {
11002 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11003 cx.propagate();
11004 return;
11005 }
11006 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11007 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11008 s.move_heads_with(|map, head, _| {
11009 (
11010 movement::start_of_paragraph(map, head, 1),
11011 SelectionGoal::None,
11012 )
11013 });
11014 })
11015 }
11016
11017 pub fn select_to_end_of_paragraph(
11018 &mut self,
11019 _: &SelectToEndOfParagraph,
11020 window: &mut Window,
11021 cx: &mut Context<Self>,
11022 ) {
11023 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11024 cx.propagate();
11025 return;
11026 }
11027 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11028 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11029 s.move_heads_with(|map, head, _| {
11030 (
11031 movement::end_of_paragraph(map, head, 1),
11032 SelectionGoal::None,
11033 )
11034 });
11035 })
11036 }
11037
11038 pub fn move_to_start_of_excerpt(
11039 &mut self,
11040 _: &MoveToStartOfExcerpt,
11041 window: &mut Window,
11042 cx: &mut Context<Self>,
11043 ) {
11044 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11045 cx.propagate();
11046 return;
11047 }
11048 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11049 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11050 s.move_with(|map, selection| {
11051 selection.collapse_to(
11052 movement::start_of_excerpt(
11053 map,
11054 selection.head(),
11055 workspace::searchable::Direction::Prev,
11056 ),
11057 SelectionGoal::None,
11058 )
11059 });
11060 })
11061 }
11062
11063 pub fn move_to_start_of_next_excerpt(
11064 &mut self,
11065 _: &MoveToStartOfNextExcerpt,
11066 window: &mut Window,
11067 cx: &mut Context<Self>,
11068 ) {
11069 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11070 cx.propagate();
11071 return;
11072 }
11073
11074 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11075 s.move_with(|map, selection| {
11076 selection.collapse_to(
11077 movement::start_of_excerpt(
11078 map,
11079 selection.head(),
11080 workspace::searchable::Direction::Next,
11081 ),
11082 SelectionGoal::None,
11083 )
11084 });
11085 })
11086 }
11087
11088 pub fn move_to_end_of_excerpt(
11089 &mut self,
11090 _: &MoveToEndOfExcerpt,
11091 window: &mut Window,
11092 cx: &mut Context<Self>,
11093 ) {
11094 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11095 cx.propagate();
11096 return;
11097 }
11098 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11099 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11100 s.move_with(|map, selection| {
11101 selection.collapse_to(
11102 movement::end_of_excerpt(
11103 map,
11104 selection.head(),
11105 workspace::searchable::Direction::Next,
11106 ),
11107 SelectionGoal::None,
11108 )
11109 });
11110 })
11111 }
11112
11113 pub fn move_to_end_of_previous_excerpt(
11114 &mut self,
11115 _: &MoveToEndOfPreviousExcerpt,
11116 window: &mut Window,
11117 cx: &mut Context<Self>,
11118 ) {
11119 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11120 cx.propagate();
11121 return;
11122 }
11123 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11124 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11125 s.move_with(|map, selection| {
11126 selection.collapse_to(
11127 movement::end_of_excerpt(
11128 map,
11129 selection.head(),
11130 workspace::searchable::Direction::Prev,
11131 ),
11132 SelectionGoal::None,
11133 )
11134 });
11135 })
11136 }
11137
11138 pub fn select_to_start_of_excerpt(
11139 &mut self,
11140 _: &SelectToStartOfExcerpt,
11141 window: &mut Window,
11142 cx: &mut Context<Self>,
11143 ) {
11144 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11145 cx.propagate();
11146 return;
11147 }
11148 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11149 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11150 s.move_heads_with(|map, head, _| {
11151 (
11152 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11153 SelectionGoal::None,
11154 )
11155 });
11156 })
11157 }
11158
11159 pub fn select_to_start_of_next_excerpt(
11160 &mut self,
11161 _: &SelectToStartOfNextExcerpt,
11162 window: &mut Window,
11163 cx: &mut Context<Self>,
11164 ) {
11165 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11166 cx.propagate();
11167 return;
11168 }
11169 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11170 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11171 s.move_heads_with(|map, head, _| {
11172 (
11173 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11174 SelectionGoal::None,
11175 )
11176 });
11177 })
11178 }
11179
11180 pub fn select_to_end_of_excerpt(
11181 &mut self,
11182 _: &SelectToEndOfExcerpt,
11183 window: &mut Window,
11184 cx: &mut Context<Self>,
11185 ) {
11186 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11187 cx.propagate();
11188 return;
11189 }
11190 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11191 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11192 s.move_heads_with(|map, head, _| {
11193 (
11194 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11195 SelectionGoal::None,
11196 )
11197 });
11198 })
11199 }
11200
11201 pub fn select_to_end_of_previous_excerpt(
11202 &mut self,
11203 _: &SelectToEndOfPreviousExcerpt,
11204 window: &mut Window,
11205 cx: &mut Context<Self>,
11206 ) {
11207 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11208 cx.propagate();
11209 return;
11210 }
11211 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11212 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11213 s.move_heads_with(|map, head, _| {
11214 (
11215 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11216 SelectionGoal::None,
11217 )
11218 });
11219 })
11220 }
11221
11222 pub fn move_to_beginning(
11223 &mut self,
11224 _: &MoveToBeginning,
11225 window: &mut Window,
11226 cx: &mut Context<Self>,
11227 ) {
11228 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11229 cx.propagate();
11230 return;
11231 }
11232 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11233 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11234 s.select_ranges(vec![0..0]);
11235 });
11236 }
11237
11238 pub fn select_to_beginning(
11239 &mut self,
11240 _: &SelectToBeginning,
11241 window: &mut Window,
11242 cx: &mut Context<Self>,
11243 ) {
11244 let mut selection = self.selections.last::<Point>(cx);
11245 selection.set_head(Point::zero(), SelectionGoal::None);
11246 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11247 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11248 s.select(vec![selection]);
11249 });
11250 }
11251
11252 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11253 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11254 cx.propagate();
11255 return;
11256 }
11257 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11258 let cursor = self.buffer.read(cx).read(cx).len();
11259 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11260 s.select_ranges(vec![cursor..cursor])
11261 });
11262 }
11263
11264 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11265 self.nav_history = nav_history;
11266 }
11267
11268 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11269 self.nav_history.as_ref()
11270 }
11271
11272 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11273 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11274 }
11275
11276 fn push_to_nav_history(
11277 &mut self,
11278 cursor_anchor: Anchor,
11279 new_position: Option<Point>,
11280 is_deactivate: bool,
11281 cx: &mut Context<Self>,
11282 ) {
11283 if let Some(nav_history) = self.nav_history.as_mut() {
11284 let buffer = self.buffer.read(cx).read(cx);
11285 let cursor_position = cursor_anchor.to_point(&buffer);
11286 let scroll_state = self.scroll_manager.anchor();
11287 let scroll_top_row = scroll_state.top_row(&buffer);
11288 drop(buffer);
11289
11290 if let Some(new_position) = new_position {
11291 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11292 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11293 return;
11294 }
11295 }
11296
11297 nav_history.push(
11298 Some(NavigationData {
11299 cursor_anchor,
11300 cursor_position,
11301 scroll_anchor: scroll_state,
11302 scroll_top_row,
11303 }),
11304 cx,
11305 );
11306 cx.emit(EditorEvent::PushedToNavHistory {
11307 anchor: cursor_anchor,
11308 is_deactivate,
11309 })
11310 }
11311 }
11312
11313 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11314 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11315 let buffer = self.buffer.read(cx).snapshot(cx);
11316 let mut selection = self.selections.first::<usize>(cx);
11317 selection.set_head(buffer.len(), SelectionGoal::None);
11318 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11319 s.select(vec![selection]);
11320 });
11321 }
11322
11323 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11324 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11325 let end = self.buffer.read(cx).read(cx).len();
11326 self.change_selections(None, window, cx, |s| {
11327 s.select_ranges(vec![0..end]);
11328 });
11329 }
11330
11331 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11332 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11333 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11334 let mut selections = self.selections.all::<Point>(cx);
11335 let max_point = display_map.buffer_snapshot.max_point();
11336 for selection in &mut selections {
11337 let rows = selection.spanned_rows(true, &display_map);
11338 selection.start = Point::new(rows.start.0, 0);
11339 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11340 selection.reversed = false;
11341 }
11342 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11343 s.select(selections);
11344 });
11345 }
11346
11347 pub fn split_selection_into_lines(
11348 &mut self,
11349 _: &SplitSelectionIntoLines,
11350 window: &mut Window,
11351 cx: &mut Context<Self>,
11352 ) {
11353 let selections = self
11354 .selections
11355 .all::<Point>(cx)
11356 .into_iter()
11357 .map(|selection| selection.start..selection.end)
11358 .collect::<Vec<_>>();
11359 self.unfold_ranges(&selections, true, true, cx);
11360
11361 let mut new_selection_ranges = Vec::new();
11362 {
11363 let buffer = self.buffer.read(cx).read(cx);
11364 for selection in selections {
11365 for row in selection.start.row..selection.end.row {
11366 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11367 new_selection_ranges.push(cursor..cursor);
11368 }
11369
11370 let is_multiline_selection = selection.start.row != selection.end.row;
11371 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11372 // so this action feels more ergonomic when paired with other selection operations
11373 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11374 if !should_skip_last {
11375 new_selection_ranges.push(selection.end..selection.end);
11376 }
11377 }
11378 }
11379 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11380 s.select_ranges(new_selection_ranges);
11381 });
11382 }
11383
11384 pub fn add_selection_above(
11385 &mut self,
11386 _: &AddSelectionAbove,
11387 window: &mut Window,
11388 cx: &mut Context<Self>,
11389 ) {
11390 self.add_selection(true, window, cx);
11391 }
11392
11393 pub fn add_selection_below(
11394 &mut self,
11395 _: &AddSelectionBelow,
11396 window: &mut Window,
11397 cx: &mut Context<Self>,
11398 ) {
11399 self.add_selection(false, window, cx);
11400 }
11401
11402 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11403 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11404
11405 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11406 let mut selections = self.selections.all::<Point>(cx);
11407 let text_layout_details = self.text_layout_details(window);
11408 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11409 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11410 let range = oldest_selection.display_range(&display_map).sorted();
11411
11412 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11413 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11414 let positions = start_x.min(end_x)..start_x.max(end_x);
11415
11416 selections.clear();
11417 let mut stack = Vec::new();
11418 for row in range.start.row().0..=range.end.row().0 {
11419 if let Some(selection) = self.selections.build_columnar_selection(
11420 &display_map,
11421 DisplayRow(row),
11422 &positions,
11423 oldest_selection.reversed,
11424 &text_layout_details,
11425 ) {
11426 stack.push(selection.id);
11427 selections.push(selection);
11428 }
11429 }
11430
11431 if above {
11432 stack.reverse();
11433 }
11434
11435 AddSelectionsState { above, stack }
11436 });
11437
11438 let last_added_selection = *state.stack.last().unwrap();
11439 let mut new_selections = Vec::new();
11440 if above == state.above {
11441 let end_row = if above {
11442 DisplayRow(0)
11443 } else {
11444 display_map.max_point().row()
11445 };
11446
11447 'outer: for selection in selections {
11448 if selection.id == last_added_selection {
11449 let range = selection.display_range(&display_map).sorted();
11450 debug_assert_eq!(range.start.row(), range.end.row());
11451 let mut row = range.start.row();
11452 let positions =
11453 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11454 px(start)..px(end)
11455 } else {
11456 let start_x =
11457 display_map.x_for_display_point(range.start, &text_layout_details);
11458 let end_x =
11459 display_map.x_for_display_point(range.end, &text_layout_details);
11460 start_x.min(end_x)..start_x.max(end_x)
11461 };
11462
11463 while row != end_row {
11464 if above {
11465 row.0 -= 1;
11466 } else {
11467 row.0 += 1;
11468 }
11469
11470 if let Some(new_selection) = self.selections.build_columnar_selection(
11471 &display_map,
11472 row,
11473 &positions,
11474 selection.reversed,
11475 &text_layout_details,
11476 ) {
11477 state.stack.push(new_selection.id);
11478 if above {
11479 new_selections.push(new_selection);
11480 new_selections.push(selection);
11481 } else {
11482 new_selections.push(selection);
11483 new_selections.push(new_selection);
11484 }
11485
11486 continue 'outer;
11487 }
11488 }
11489 }
11490
11491 new_selections.push(selection);
11492 }
11493 } else {
11494 new_selections = selections;
11495 new_selections.retain(|s| s.id != last_added_selection);
11496 state.stack.pop();
11497 }
11498
11499 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11500 s.select(new_selections);
11501 });
11502 if state.stack.len() > 1 {
11503 self.add_selections_state = Some(state);
11504 }
11505 }
11506
11507 pub fn select_next_match_internal(
11508 &mut self,
11509 display_map: &DisplaySnapshot,
11510 replace_newest: bool,
11511 autoscroll: Option<Autoscroll>,
11512 window: &mut Window,
11513 cx: &mut Context<Self>,
11514 ) -> Result<()> {
11515 fn select_next_match_ranges(
11516 this: &mut Editor,
11517 range: Range<usize>,
11518 replace_newest: bool,
11519 auto_scroll: Option<Autoscroll>,
11520 window: &mut Window,
11521 cx: &mut Context<Editor>,
11522 ) {
11523 this.unfold_ranges(&[range.clone()], false, true, cx);
11524 this.change_selections(auto_scroll, window, cx, |s| {
11525 if replace_newest {
11526 s.delete(s.newest_anchor().id);
11527 }
11528 s.insert_range(range.clone());
11529 });
11530 }
11531
11532 let buffer = &display_map.buffer_snapshot;
11533 let mut selections = self.selections.all::<usize>(cx);
11534 if let Some(mut select_next_state) = self.select_next_state.take() {
11535 let query = &select_next_state.query;
11536 if !select_next_state.done {
11537 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11538 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11539 let mut next_selected_range = None;
11540
11541 let bytes_after_last_selection =
11542 buffer.bytes_in_range(last_selection.end..buffer.len());
11543 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11544 let query_matches = query
11545 .stream_find_iter(bytes_after_last_selection)
11546 .map(|result| (last_selection.end, result))
11547 .chain(
11548 query
11549 .stream_find_iter(bytes_before_first_selection)
11550 .map(|result| (0, result)),
11551 );
11552
11553 for (start_offset, query_match) in query_matches {
11554 let query_match = query_match.unwrap(); // can only fail due to I/O
11555 let offset_range =
11556 start_offset + query_match.start()..start_offset + query_match.end();
11557 let display_range = offset_range.start.to_display_point(display_map)
11558 ..offset_range.end.to_display_point(display_map);
11559
11560 if !select_next_state.wordwise
11561 || (!movement::is_inside_word(display_map, display_range.start)
11562 && !movement::is_inside_word(display_map, display_range.end))
11563 {
11564 // TODO: This is n^2, because we might check all the selections
11565 if !selections
11566 .iter()
11567 .any(|selection| selection.range().overlaps(&offset_range))
11568 {
11569 next_selected_range = Some(offset_range);
11570 break;
11571 }
11572 }
11573 }
11574
11575 if let Some(next_selected_range) = next_selected_range {
11576 select_next_match_ranges(
11577 self,
11578 next_selected_range,
11579 replace_newest,
11580 autoscroll,
11581 window,
11582 cx,
11583 );
11584 } else {
11585 select_next_state.done = true;
11586 }
11587 }
11588
11589 self.select_next_state = Some(select_next_state);
11590 } else {
11591 let mut only_carets = true;
11592 let mut same_text_selected = true;
11593 let mut selected_text = None;
11594
11595 let mut selections_iter = selections.iter().peekable();
11596 while let Some(selection) = selections_iter.next() {
11597 if selection.start != selection.end {
11598 only_carets = false;
11599 }
11600
11601 if same_text_selected {
11602 if selected_text.is_none() {
11603 selected_text =
11604 Some(buffer.text_for_range(selection.range()).collect::<String>());
11605 }
11606
11607 if let Some(next_selection) = selections_iter.peek() {
11608 if next_selection.range().len() == selection.range().len() {
11609 let next_selected_text = buffer
11610 .text_for_range(next_selection.range())
11611 .collect::<String>();
11612 if Some(next_selected_text) != selected_text {
11613 same_text_selected = false;
11614 selected_text = None;
11615 }
11616 } else {
11617 same_text_selected = false;
11618 selected_text = None;
11619 }
11620 }
11621 }
11622 }
11623
11624 if only_carets {
11625 for selection in &mut selections {
11626 let word_range = movement::surrounding_word(
11627 display_map,
11628 selection.start.to_display_point(display_map),
11629 );
11630 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11631 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11632 selection.goal = SelectionGoal::None;
11633 selection.reversed = false;
11634 select_next_match_ranges(
11635 self,
11636 selection.start..selection.end,
11637 replace_newest,
11638 autoscroll,
11639 window,
11640 cx,
11641 );
11642 }
11643
11644 if selections.len() == 1 {
11645 let selection = selections
11646 .last()
11647 .expect("ensured that there's only one selection");
11648 let query = buffer
11649 .text_for_range(selection.start..selection.end)
11650 .collect::<String>();
11651 let is_empty = query.is_empty();
11652 let select_state = SelectNextState {
11653 query: AhoCorasick::new(&[query])?,
11654 wordwise: true,
11655 done: is_empty,
11656 };
11657 self.select_next_state = Some(select_state);
11658 } else {
11659 self.select_next_state = None;
11660 }
11661 } else if let Some(selected_text) = selected_text {
11662 self.select_next_state = Some(SelectNextState {
11663 query: AhoCorasick::new(&[selected_text])?,
11664 wordwise: false,
11665 done: false,
11666 });
11667 self.select_next_match_internal(
11668 display_map,
11669 replace_newest,
11670 autoscroll,
11671 window,
11672 cx,
11673 )?;
11674 }
11675 }
11676 Ok(())
11677 }
11678
11679 pub fn select_all_matches(
11680 &mut self,
11681 _action: &SelectAllMatches,
11682 window: &mut Window,
11683 cx: &mut Context<Self>,
11684 ) -> Result<()> {
11685 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11686
11687 self.push_to_selection_history();
11688 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11689
11690 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11691 let Some(select_next_state) = self.select_next_state.as_mut() else {
11692 return Ok(());
11693 };
11694 if select_next_state.done {
11695 return Ok(());
11696 }
11697
11698 let mut new_selections = self.selections.all::<usize>(cx);
11699
11700 let buffer = &display_map.buffer_snapshot;
11701 let query_matches = select_next_state
11702 .query
11703 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11704
11705 for query_match in query_matches {
11706 let query_match = query_match.unwrap(); // can only fail due to I/O
11707 let offset_range = query_match.start()..query_match.end();
11708 let display_range = offset_range.start.to_display_point(&display_map)
11709 ..offset_range.end.to_display_point(&display_map);
11710
11711 if !select_next_state.wordwise
11712 || (!movement::is_inside_word(&display_map, display_range.start)
11713 && !movement::is_inside_word(&display_map, display_range.end))
11714 {
11715 self.selections.change_with(cx, |selections| {
11716 new_selections.push(Selection {
11717 id: selections.new_selection_id(),
11718 start: offset_range.start,
11719 end: offset_range.end,
11720 reversed: false,
11721 goal: SelectionGoal::None,
11722 });
11723 });
11724 }
11725 }
11726
11727 new_selections.sort_by_key(|selection| selection.start);
11728 let mut ix = 0;
11729 while ix + 1 < new_selections.len() {
11730 let current_selection = &new_selections[ix];
11731 let next_selection = &new_selections[ix + 1];
11732 if current_selection.range().overlaps(&next_selection.range()) {
11733 if current_selection.id < next_selection.id {
11734 new_selections.remove(ix + 1);
11735 } else {
11736 new_selections.remove(ix);
11737 }
11738 } else {
11739 ix += 1;
11740 }
11741 }
11742
11743 let reversed = self.selections.oldest::<usize>(cx).reversed;
11744
11745 for selection in new_selections.iter_mut() {
11746 selection.reversed = reversed;
11747 }
11748
11749 select_next_state.done = true;
11750 self.unfold_ranges(
11751 &new_selections
11752 .iter()
11753 .map(|selection| selection.range())
11754 .collect::<Vec<_>>(),
11755 false,
11756 false,
11757 cx,
11758 );
11759 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11760 selections.select(new_selections)
11761 });
11762
11763 Ok(())
11764 }
11765
11766 pub fn select_next(
11767 &mut self,
11768 action: &SelectNext,
11769 window: &mut Window,
11770 cx: &mut Context<Self>,
11771 ) -> Result<()> {
11772 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11773 self.push_to_selection_history();
11774 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11775 self.select_next_match_internal(
11776 &display_map,
11777 action.replace_newest,
11778 Some(Autoscroll::newest()),
11779 window,
11780 cx,
11781 )?;
11782 Ok(())
11783 }
11784
11785 pub fn select_previous(
11786 &mut self,
11787 action: &SelectPrevious,
11788 window: &mut Window,
11789 cx: &mut Context<Self>,
11790 ) -> Result<()> {
11791 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11792 self.push_to_selection_history();
11793 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11794 let buffer = &display_map.buffer_snapshot;
11795 let mut selections = self.selections.all::<usize>(cx);
11796 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11797 let query = &select_prev_state.query;
11798 if !select_prev_state.done {
11799 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11800 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11801 let mut next_selected_range = None;
11802 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11803 let bytes_before_last_selection =
11804 buffer.reversed_bytes_in_range(0..last_selection.start);
11805 let bytes_after_first_selection =
11806 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11807 let query_matches = query
11808 .stream_find_iter(bytes_before_last_selection)
11809 .map(|result| (last_selection.start, result))
11810 .chain(
11811 query
11812 .stream_find_iter(bytes_after_first_selection)
11813 .map(|result| (buffer.len(), result)),
11814 );
11815 for (end_offset, query_match) in query_matches {
11816 let query_match = query_match.unwrap(); // can only fail due to I/O
11817 let offset_range =
11818 end_offset - query_match.end()..end_offset - query_match.start();
11819 let display_range = offset_range.start.to_display_point(&display_map)
11820 ..offset_range.end.to_display_point(&display_map);
11821
11822 if !select_prev_state.wordwise
11823 || (!movement::is_inside_word(&display_map, display_range.start)
11824 && !movement::is_inside_word(&display_map, display_range.end))
11825 {
11826 next_selected_range = Some(offset_range);
11827 break;
11828 }
11829 }
11830
11831 if let Some(next_selected_range) = next_selected_range {
11832 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11833 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11834 if action.replace_newest {
11835 s.delete(s.newest_anchor().id);
11836 }
11837 s.insert_range(next_selected_range);
11838 });
11839 } else {
11840 select_prev_state.done = true;
11841 }
11842 }
11843
11844 self.select_prev_state = Some(select_prev_state);
11845 } else {
11846 let mut only_carets = true;
11847 let mut same_text_selected = true;
11848 let mut selected_text = None;
11849
11850 let mut selections_iter = selections.iter().peekable();
11851 while let Some(selection) = selections_iter.next() {
11852 if selection.start != selection.end {
11853 only_carets = false;
11854 }
11855
11856 if same_text_selected {
11857 if selected_text.is_none() {
11858 selected_text =
11859 Some(buffer.text_for_range(selection.range()).collect::<String>());
11860 }
11861
11862 if let Some(next_selection) = selections_iter.peek() {
11863 if next_selection.range().len() == selection.range().len() {
11864 let next_selected_text = buffer
11865 .text_for_range(next_selection.range())
11866 .collect::<String>();
11867 if Some(next_selected_text) != selected_text {
11868 same_text_selected = false;
11869 selected_text = None;
11870 }
11871 } else {
11872 same_text_selected = false;
11873 selected_text = None;
11874 }
11875 }
11876 }
11877 }
11878
11879 if only_carets {
11880 for selection in &mut selections {
11881 let word_range = movement::surrounding_word(
11882 &display_map,
11883 selection.start.to_display_point(&display_map),
11884 );
11885 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11886 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11887 selection.goal = SelectionGoal::None;
11888 selection.reversed = false;
11889 }
11890 if selections.len() == 1 {
11891 let selection = selections
11892 .last()
11893 .expect("ensured that there's only one selection");
11894 let query = buffer
11895 .text_for_range(selection.start..selection.end)
11896 .collect::<String>();
11897 let is_empty = query.is_empty();
11898 let select_state = SelectNextState {
11899 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11900 wordwise: true,
11901 done: is_empty,
11902 };
11903 self.select_prev_state = Some(select_state);
11904 } else {
11905 self.select_prev_state = None;
11906 }
11907
11908 self.unfold_ranges(
11909 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11910 false,
11911 true,
11912 cx,
11913 );
11914 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11915 s.select(selections);
11916 });
11917 } else if let Some(selected_text) = selected_text {
11918 self.select_prev_state = Some(SelectNextState {
11919 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11920 wordwise: false,
11921 done: false,
11922 });
11923 self.select_previous(action, window, cx)?;
11924 }
11925 }
11926 Ok(())
11927 }
11928
11929 pub fn toggle_comments(
11930 &mut self,
11931 action: &ToggleComments,
11932 window: &mut Window,
11933 cx: &mut Context<Self>,
11934 ) {
11935 if self.read_only(cx) {
11936 return;
11937 }
11938 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11939 let text_layout_details = &self.text_layout_details(window);
11940 self.transact(window, cx, |this, window, cx| {
11941 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11942 let mut edits = Vec::new();
11943 let mut selection_edit_ranges = Vec::new();
11944 let mut last_toggled_row = None;
11945 let snapshot = this.buffer.read(cx).read(cx);
11946 let empty_str: Arc<str> = Arc::default();
11947 let mut suffixes_inserted = Vec::new();
11948 let ignore_indent = action.ignore_indent;
11949
11950 fn comment_prefix_range(
11951 snapshot: &MultiBufferSnapshot,
11952 row: MultiBufferRow,
11953 comment_prefix: &str,
11954 comment_prefix_whitespace: &str,
11955 ignore_indent: bool,
11956 ) -> Range<Point> {
11957 let indent_size = if ignore_indent {
11958 0
11959 } else {
11960 snapshot.indent_size_for_line(row).len
11961 };
11962
11963 let start = Point::new(row.0, indent_size);
11964
11965 let mut line_bytes = snapshot
11966 .bytes_in_range(start..snapshot.max_point())
11967 .flatten()
11968 .copied();
11969
11970 // If this line currently begins with the line comment prefix, then record
11971 // the range containing the prefix.
11972 if line_bytes
11973 .by_ref()
11974 .take(comment_prefix.len())
11975 .eq(comment_prefix.bytes())
11976 {
11977 // Include any whitespace that matches the comment prefix.
11978 let matching_whitespace_len = line_bytes
11979 .zip(comment_prefix_whitespace.bytes())
11980 .take_while(|(a, b)| a == b)
11981 .count() as u32;
11982 let end = Point::new(
11983 start.row,
11984 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
11985 );
11986 start..end
11987 } else {
11988 start..start
11989 }
11990 }
11991
11992 fn comment_suffix_range(
11993 snapshot: &MultiBufferSnapshot,
11994 row: MultiBufferRow,
11995 comment_suffix: &str,
11996 comment_suffix_has_leading_space: bool,
11997 ) -> Range<Point> {
11998 let end = Point::new(row.0, snapshot.line_len(row));
11999 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12000
12001 let mut line_end_bytes = snapshot
12002 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12003 .flatten()
12004 .copied();
12005
12006 let leading_space_len = if suffix_start_column > 0
12007 && line_end_bytes.next() == Some(b' ')
12008 && comment_suffix_has_leading_space
12009 {
12010 1
12011 } else {
12012 0
12013 };
12014
12015 // If this line currently begins with the line comment prefix, then record
12016 // the range containing the prefix.
12017 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12018 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12019 start..end
12020 } else {
12021 end..end
12022 }
12023 }
12024
12025 // TODO: Handle selections that cross excerpts
12026 for selection in &mut selections {
12027 let start_column = snapshot
12028 .indent_size_for_line(MultiBufferRow(selection.start.row))
12029 .len;
12030 let language = if let Some(language) =
12031 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12032 {
12033 language
12034 } else {
12035 continue;
12036 };
12037
12038 selection_edit_ranges.clear();
12039
12040 // If multiple selections contain a given row, avoid processing that
12041 // row more than once.
12042 let mut start_row = MultiBufferRow(selection.start.row);
12043 if last_toggled_row == Some(start_row) {
12044 start_row = start_row.next_row();
12045 }
12046 let end_row =
12047 if selection.end.row > selection.start.row && selection.end.column == 0 {
12048 MultiBufferRow(selection.end.row - 1)
12049 } else {
12050 MultiBufferRow(selection.end.row)
12051 };
12052 last_toggled_row = Some(end_row);
12053
12054 if start_row > end_row {
12055 continue;
12056 }
12057
12058 // If the language has line comments, toggle those.
12059 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12060
12061 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12062 if ignore_indent {
12063 full_comment_prefixes = full_comment_prefixes
12064 .into_iter()
12065 .map(|s| Arc::from(s.trim_end()))
12066 .collect();
12067 }
12068
12069 if !full_comment_prefixes.is_empty() {
12070 let first_prefix = full_comment_prefixes
12071 .first()
12072 .expect("prefixes is non-empty");
12073 let prefix_trimmed_lengths = full_comment_prefixes
12074 .iter()
12075 .map(|p| p.trim_end_matches(' ').len())
12076 .collect::<SmallVec<[usize; 4]>>();
12077
12078 let mut all_selection_lines_are_comments = true;
12079
12080 for row in start_row.0..=end_row.0 {
12081 let row = MultiBufferRow(row);
12082 if start_row < end_row && snapshot.is_line_blank(row) {
12083 continue;
12084 }
12085
12086 let prefix_range = full_comment_prefixes
12087 .iter()
12088 .zip(prefix_trimmed_lengths.iter().copied())
12089 .map(|(prefix, trimmed_prefix_len)| {
12090 comment_prefix_range(
12091 snapshot.deref(),
12092 row,
12093 &prefix[..trimmed_prefix_len],
12094 &prefix[trimmed_prefix_len..],
12095 ignore_indent,
12096 )
12097 })
12098 .max_by_key(|range| range.end.column - range.start.column)
12099 .expect("prefixes is non-empty");
12100
12101 if prefix_range.is_empty() {
12102 all_selection_lines_are_comments = false;
12103 }
12104
12105 selection_edit_ranges.push(prefix_range);
12106 }
12107
12108 if all_selection_lines_are_comments {
12109 edits.extend(
12110 selection_edit_ranges
12111 .iter()
12112 .cloned()
12113 .map(|range| (range, empty_str.clone())),
12114 );
12115 } else {
12116 let min_column = selection_edit_ranges
12117 .iter()
12118 .map(|range| range.start.column)
12119 .min()
12120 .unwrap_or(0);
12121 edits.extend(selection_edit_ranges.iter().map(|range| {
12122 let position = Point::new(range.start.row, min_column);
12123 (position..position, first_prefix.clone())
12124 }));
12125 }
12126 } else if let Some((full_comment_prefix, comment_suffix)) =
12127 language.block_comment_delimiters()
12128 {
12129 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12130 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12131 let prefix_range = comment_prefix_range(
12132 snapshot.deref(),
12133 start_row,
12134 comment_prefix,
12135 comment_prefix_whitespace,
12136 ignore_indent,
12137 );
12138 let suffix_range = comment_suffix_range(
12139 snapshot.deref(),
12140 end_row,
12141 comment_suffix.trim_start_matches(' '),
12142 comment_suffix.starts_with(' '),
12143 );
12144
12145 if prefix_range.is_empty() || suffix_range.is_empty() {
12146 edits.push((
12147 prefix_range.start..prefix_range.start,
12148 full_comment_prefix.clone(),
12149 ));
12150 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12151 suffixes_inserted.push((end_row, comment_suffix.len()));
12152 } else {
12153 edits.push((prefix_range, empty_str.clone()));
12154 edits.push((suffix_range, empty_str.clone()));
12155 }
12156 } else {
12157 continue;
12158 }
12159 }
12160
12161 drop(snapshot);
12162 this.buffer.update(cx, |buffer, cx| {
12163 buffer.edit(edits, None, cx);
12164 });
12165
12166 // Adjust selections so that they end before any comment suffixes that
12167 // were inserted.
12168 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12169 let mut selections = this.selections.all::<Point>(cx);
12170 let snapshot = this.buffer.read(cx).read(cx);
12171 for selection in &mut selections {
12172 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12173 match row.cmp(&MultiBufferRow(selection.end.row)) {
12174 Ordering::Less => {
12175 suffixes_inserted.next();
12176 continue;
12177 }
12178 Ordering::Greater => break,
12179 Ordering::Equal => {
12180 if selection.end.column == snapshot.line_len(row) {
12181 if selection.is_empty() {
12182 selection.start.column -= suffix_len as u32;
12183 }
12184 selection.end.column -= suffix_len as u32;
12185 }
12186 break;
12187 }
12188 }
12189 }
12190 }
12191
12192 drop(snapshot);
12193 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12194 s.select(selections)
12195 });
12196
12197 let selections = this.selections.all::<Point>(cx);
12198 let selections_on_single_row = selections.windows(2).all(|selections| {
12199 selections[0].start.row == selections[1].start.row
12200 && selections[0].end.row == selections[1].end.row
12201 && selections[0].start.row == selections[0].end.row
12202 });
12203 let selections_selecting = selections
12204 .iter()
12205 .any(|selection| selection.start != selection.end);
12206 let advance_downwards = action.advance_downwards
12207 && selections_on_single_row
12208 && !selections_selecting
12209 && !matches!(this.mode, EditorMode::SingleLine { .. });
12210
12211 if advance_downwards {
12212 let snapshot = this.buffer.read(cx).snapshot(cx);
12213
12214 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12215 s.move_cursors_with(|display_snapshot, display_point, _| {
12216 let mut point = display_point.to_point(display_snapshot);
12217 point.row += 1;
12218 point = snapshot.clip_point(point, Bias::Left);
12219 let display_point = point.to_display_point(display_snapshot);
12220 let goal = SelectionGoal::HorizontalPosition(
12221 display_snapshot
12222 .x_for_display_point(display_point, text_layout_details)
12223 .into(),
12224 );
12225 (display_point, goal)
12226 })
12227 });
12228 }
12229 });
12230 }
12231
12232 pub fn select_enclosing_symbol(
12233 &mut self,
12234 _: &SelectEnclosingSymbol,
12235 window: &mut Window,
12236 cx: &mut Context<Self>,
12237 ) {
12238 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12239
12240 let buffer = self.buffer.read(cx).snapshot(cx);
12241 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12242
12243 fn update_selection(
12244 selection: &Selection<usize>,
12245 buffer_snap: &MultiBufferSnapshot,
12246 ) -> Option<Selection<usize>> {
12247 let cursor = selection.head();
12248 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12249 for symbol in symbols.iter().rev() {
12250 let start = symbol.range.start.to_offset(buffer_snap);
12251 let end = symbol.range.end.to_offset(buffer_snap);
12252 let new_range = start..end;
12253 if start < selection.start || end > selection.end {
12254 return Some(Selection {
12255 id: selection.id,
12256 start: new_range.start,
12257 end: new_range.end,
12258 goal: SelectionGoal::None,
12259 reversed: selection.reversed,
12260 });
12261 }
12262 }
12263 None
12264 }
12265
12266 let mut selected_larger_symbol = false;
12267 let new_selections = old_selections
12268 .iter()
12269 .map(|selection| match update_selection(selection, &buffer) {
12270 Some(new_selection) => {
12271 if new_selection.range() != selection.range() {
12272 selected_larger_symbol = true;
12273 }
12274 new_selection
12275 }
12276 None => selection.clone(),
12277 })
12278 .collect::<Vec<_>>();
12279
12280 if selected_larger_symbol {
12281 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12282 s.select(new_selections);
12283 });
12284 }
12285 }
12286
12287 pub fn select_larger_syntax_node(
12288 &mut self,
12289 _: &SelectLargerSyntaxNode,
12290 window: &mut Window,
12291 cx: &mut Context<Self>,
12292 ) {
12293 let Some(visible_row_count) = self.visible_row_count() else {
12294 return;
12295 };
12296 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12297 if old_selections.is_empty() {
12298 return;
12299 }
12300
12301 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12302
12303 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12304 let buffer = self.buffer.read(cx).snapshot(cx);
12305
12306 let mut selected_larger_node = false;
12307 let mut new_selections = old_selections
12308 .iter()
12309 .map(|selection| {
12310 let old_range = selection.start..selection.end;
12311 let mut new_range = old_range.clone();
12312 let mut new_node = None;
12313 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12314 {
12315 new_node = Some(node);
12316 new_range = match containing_range {
12317 MultiOrSingleBufferOffsetRange::Single(_) => break,
12318 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12319 };
12320 if !display_map.intersects_fold(new_range.start)
12321 && !display_map.intersects_fold(new_range.end)
12322 {
12323 break;
12324 }
12325 }
12326
12327 if let Some(node) = new_node {
12328 // Log the ancestor, to support using this action as a way to explore TreeSitter
12329 // nodes. Parent and grandparent are also logged because this operation will not
12330 // visit nodes that have the same range as their parent.
12331 log::info!("Node: {node:?}");
12332 let parent = node.parent();
12333 log::info!("Parent: {parent:?}");
12334 let grandparent = parent.and_then(|x| x.parent());
12335 log::info!("Grandparent: {grandparent:?}");
12336 }
12337
12338 selected_larger_node |= new_range != old_range;
12339 Selection {
12340 id: selection.id,
12341 start: new_range.start,
12342 end: new_range.end,
12343 goal: SelectionGoal::None,
12344 reversed: selection.reversed,
12345 }
12346 })
12347 .collect::<Vec<_>>();
12348
12349 if !selected_larger_node {
12350 return; // don't put this call in the history
12351 }
12352
12353 // scroll based on transformation done to the last selection created by the user
12354 let (last_old, last_new) = old_selections
12355 .last()
12356 .zip(new_selections.last().cloned())
12357 .expect("old_selections isn't empty");
12358
12359 // revert selection
12360 let is_selection_reversed = {
12361 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12362 new_selections.last_mut().expect("checked above").reversed =
12363 should_newest_selection_be_reversed;
12364 should_newest_selection_be_reversed
12365 };
12366
12367 if selected_larger_node {
12368 self.select_syntax_node_history.disable_clearing = true;
12369 self.change_selections(None, window, cx, |s| {
12370 s.select(new_selections.clone());
12371 });
12372 self.select_syntax_node_history.disable_clearing = false;
12373 }
12374
12375 let start_row = last_new.start.to_display_point(&display_map).row().0;
12376 let end_row = last_new.end.to_display_point(&display_map).row().0;
12377 let selection_height = end_row - start_row + 1;
12378 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12379
12380 // if fits on screen (considering margin), keep it in the middle, else, scroll to selection head
12381 let scroll_behavior = if visible_row_count >= selection_height + scroll_margin_rows * 2 {
12382 let middle_row = (end_row + start_row) / 2;
12383 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
12384 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
12385 SelectSyntaxNodeScrollBehavior::CenterSelection
12386 } else if is_selection_reversed {
12387 self.scroll_cursor_top(&Default::default(), window, cx);
12388 SelectSyntaxNodeScrollBehavior::CursorTop
12389 } else {
12390 self.scroll_cursor_bottom(&Default::default(), window, cx);
12391 SelectSyntaxNodeScrollBehavior::CursorBottom
12392 };
12393
12394 self.select_syntax_node_history.push((
12395 old_selections,
12396 scroll_behavior,
12397 is_selection_reversed,
12398 ));
12399 }
12400
12401 pub fn select_smaller_syntax_node(
12402 &mut self,
12403 _: &SelectSmallerSyntaxNode,
12404 window: &mut Window,
12405 cx: &mut Context<Self>,
12406 ) {
12407 let Some(visible_row_count) = self.visible_row_count() else {
12408 return;
12409 };
12410
12411 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12412
12413 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12414 self.select_syntax_node_history.pop()
12415 {
12416 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12417
12418 if let Some(selection) = selections.last_mut() {
12419 selection.reversed = is_selection_reversed;
12420 }
12421
12422 self.select_syntax_node_history.disable_clearing = true;
12423 self.change_selections(None, window, cx, |s| {
12424 s.select(selections.to_vec());
12425 });
12426 self.select_syntax_node_history.disable_clearing = false;
12427
12428 let newest = self.selections.newest::<usize>(cx);
12429 let start_row = newest.start.to_display_point(&display_map).row().0;
12430 let end_row = newest.end.to_display_point(&display_map).row().0;
12431
12432 match scroll_behavior {
12433 SelectSyntaxNodeScrollBehavior::CursorTop => {
12434 self.scroll_cursor_top(&Default::default(), window, cx);
12435 }
12436 SelectSyntaxNodeScrollBehavior::CenterSelection => {
12437 let middle_row = (end_row + start_row) / 2;
12438 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
12439 // centralize the selection, not the cursor
12440 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
12441 }
12442 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12443 self.scroll_cursor_bottom(&Default::default(), window, cx);
12444 }
12445 }
12446 }
12447 }
12448
12449 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12450 if !EditorSettings::get_global(cx).gutter.runnables {
12451 self.clear_tasks();
12452 return Task::ready(());
12453 }
12454 let project = self.project.as_ref().map(Entity::downgrade);
12455 cx.spawn_in(window, async move |this, cx| {
12456 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12457 let Some(project) = project.and_then(|p| p.upgrade()) else {
12458 return;
12459 };
12460 let Ok(display_snapshot) = this.update(cx, |this, cx| {
12461 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12462 }) else {
12463 return;
12464 };
12465
12466 let hide_runnables = project
12467 .update(cx, |project, cx| {
12468 // Do not display any test indicators in non-dev server remote projects.
12469 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12470 })
12471 .unwrap_or(true);
12472 if hide_runnables {
12473 return;
12474 }
12475 let new_rows =
12476 cx.background_spawn({
12477 let snapshot = display_snapshot.clone();
12478 async move {
12479 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12480 }
12481 })
12482 .await;
12483
12484 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12485 this.update(cx, |this, _| {
12486 this.clear_tasks();
12487 for (key, value) in rows {
12488 this.insert_tasks(key, value);
12489 }
12490 })
12491 .ok();
12492 })
12493 }
12494 fn fetch_runnable_ranges(
12495 snapshot: &DisplaySnapshot,
12496 range: Range<Anchor>,
12497 ) -> Vec<language::RunnableRange> {
12498 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12499 }
12500
12501 fn runnable_rows(
12502 project: Entity<Project>,
12503 snapshot: DisplaySnapshot,
12504 runnable_ranges: Vec<RunnableRange>,
12505 mut cx: AsyncWindowContext,
12506 ) -> Vec<((BufferId, u32), RunnableTasks)> {
12507 runnable_ranges
12508 .into_iter()
12509 .filter_map(|mut runnable| {
12510 let tasks = cx
12511 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12512 .ok()?;
12513 if tasks.is_empty() {
12514 return None;
12515 }
12516
12517 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12518
12519 let row = snapshot
12520 .buffer_snapshot
12521 .buffer_line_for_row(MultiBufferRow(point.row))?
12522 .1
12523 .start
12524 .row;
12525
12526 let context_range =
12527 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12528 Some((
12529 (runnable.buffer_id, row),
12530 RunnableTasks {
12531 templates: tasks,
12532 offset: snapshot
12533 .buffer_snapshot
12534 .anchor_before(runnable.run_range.start),
12535 context_range,
12536 column: point.column,
12537 extra_variables: runnable.extra_captures,
12538 },
12539 ))
12540 })
12541 .collect()
12542 }
12543
12544 fn templates_with_tags(
12545 project: &Entity<Project>,
12546 runnable: &mut Runnable,
12547 cx: &mut App,
12548 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12549 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12550 let (worktree_id, file) = project
12551 .buffer_for_id(runnable.buffer, cx)
12552 .and_then(|buffer| buffer.read(cx).file())
12553 .map(|file| (file.worktree_id(cx), file.clone()))
12554 .unzip();
12555
12556 (
12557 project.task_store().read(cx).task_inventory().cloned(),
12558 worktree_id,
12559 file,
12560 )
12561 });
12562
12563 let tags = mem::take(&mut runnable.tags);
12564 let mut tags: Vec<_> = tags
12565 .into_iter()
12566 .flat_map(|tag| {
12567 let tag = tag.0.clone();
12568 inventory
12569 .as_ref()
12570 .into_iter()
12571 .flat_map(|inventory| {
12572 inventory.read(cx).list_tasks(
12573 file.clone(),
12574 Some(runnable.language.clone()),
12575 worktree_id,
12576 cx,
12577 )
12578 })
12579 .filter(move |(_, template)| {
12580 template.tags.iter().any(|source_tag| source_tag == &tag)
12581 })
12582 })
12583 .sorted_by_key(|(kind, _)| kind.to_owned())
12584 .collect();
12585 if let Some((leading_tag_source, _)) = tags.first() {
12586 // Strongest source wins; if we have worktree tag binding, prefer that to
12587 // global and language bindings;
12588 // if we have a global binding, prefer that to language binding.
12589 let first_mismatch = tags
12590 .iter()
12591 .position(|(tag_source, _)| tag_source != leading_tag_source);
12592 if let Some(index) = first_mismatch {
12593 tags.truncate(index);
12594 }
12595 }
12596
12597 tags
12598 }
12599
12600 pub fn move_to_enclosing_bracket(
12601 &mut self,
12602 _: &MoveToEnclosingBracket,
12603 window: &mut Window,
12604 cx: &mut Context<Self>,
12605 ) {
12606 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12607 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12608 s.move_offsets_with(|snapshot, selection| {
12609 let Some(enclosing_bracket_ranges) =
12610 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12611 else {
12612 return;
12613 };
12614
12615 let mut best_length = usize::MAX;
12616 let mut best_inside = false;
12617 let mut best_in_bracket_range = false;
12618 let mut best_destination = None;
12619 for (open, close) in enclosing_bracket_ranges {
12620 let close = close.to_inclusive();
12621 let length = close.end() - open.start;
12622 let inside = selection.start >= open.end && selection.end <= *close.start();
12623 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12624 || close.contains(&selection.head());
12625
12626 // If best is next to a bracket and current isn't, skip
12627 if !in_bracket_range && best_in_bracket_range {
12628 continue;
12629 }
12630
12631 // Prefer smaller lengths unless best is inside and current isn't
12632 if length > best_length && (best_inside || !inside) {
12633 continue;
12634 }
12635
12636 best_length = length;
12637 best_inside = inside;
12638 best_in_bracket_range = in_bracket_range;
12639 best_destination = Some(
12640 if close.contains(&selection.start) && close.contains(&selection.end) {
12641 if inside { open.end } else { open.start }
12642 } else if inside {
12643 *close.start()
12644 } else {
12645 *close.end()
12646 },
12647 );
12648 }
12649
12650 if let Some(destination) = best_destination {
12651 selection.collapse_to(destination, SelectionGoal::None);
12652 }
12653 })
12654 });
12655 }
12656
12657 pub fn undo_selection(
12658 &mut self,
12659 _: &UndoSelection,
12660 window: &mut Window,
12661 cx: &mut Context<Self>,
12662 ) {
12663 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12664 self.end_selection(window, cx);
12665 self.selection_history.mode = SelectionHistoryMode::Undoing;
12666 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12667 self.change_selections(None, window, cx, |s| {
12668 s.select_anchors(entry.selections.to_vec())
12669 });
12670 self.select_next_state = entry.select_next_state;
12671 self.select_prev_state = entry.select_prev_state;
12672 self.add_selections_state = entry.add_selections_state;
12673 self.request_autoscroll(Autoscroll::newest(), cx);
12674 }
12675 self.selection_history.mode = SelectionHistoryMode::Normal;
12676 }
12677
12678 pub fn redo_selection(
12679 &mut self,
12680 _: &RedoSelection,
12681 window: &mut Window,
12682 cx: &mut Context<Self>,
12683 ) {
12684 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12685 self.end_selection(window, cx);
12686 self.selection_history.mode = SelectionHistoryMode::Redoing;
12687 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12688 self.change_selections(None, window, cx, |s| {
12689 s.select_anchors(entry.selections.to_vec())
12690 });
12691 self.select_next_state = entry.select_next_state;
12692 self.select_prev_state = entry.select_prev_state;
12693 self.add_selections_state = entry.add_selections_state;
12694 self.request_autoscroll(Autoscroll::newest(), cx);
12695 }
12696 self.selection_history.mode = SelectionHistoryMode::Normal;
12697 }
12698
12699 pub fn expand_excerpts(
12700 &mut self,
12701 action: &ExpandExcerpts,
12702 _: &mut Window,
12703 cx: &mut Context<Self>,
12704 ) {
12705 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12706 }
12707
12708 pub fn expand_excerpts_down(
12709 &mut self,
12710 action: &ExpandExcerptsDown,
12711 _: &mut Window,
12712 cx: &mut Context<Self>,
12713 ) {
12714 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12715 }
12716
12717 pub fn expand_excerpts_up(
12718 &mut self,
12719 action: &ExpandExcerptsUp,
12720 _: &mut Window,
12721 cx: &mut Context<Self>,
12722 ) {
12723 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12724 }
12725
12726 pub fn expand_excerpts_for_direction(
12727 &mut self,
12728 lines: u32,
12729 direction: ExpandExcerptDirection,
12730
12731 cx: &mut Context<Self>,
12732 ) {
12733 let selections = self.selections.disjoint_anchors();
12734
12735 let lines = if lines == 0 {
12736 EditorSettings::get_global(cx).expand_excerpt_lines
12737 } else {
12738 lines
12739 };
12740
12741 self.buffer.update(cx, |buffer, cx| {
12742 let snapshot = buffer.snapshot(cx);
12743 let mut excerpt_ids = selections
12744 .iter()
12745 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12746 .collect::<Vec<_>>();
12747 excerpt_ids.sort();
12748 excerpt_ids.dedup();
12749 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12750 })
12751 }
12752
12753 pub fn expand_excerpt(
12754 &mut self,
12755 excerpt: ExcerptId,
12756 direction: ExpandExcerptDirection,
12757 window: &mut Window,
12758 cx: &mut Context<Self>,
12759 ) {
12760 let current_scroll_position = self.scroll_position(cx);
12761 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
12762 self.buffer.update(cx, |buffer, cx| {
12763 buffer.expand_excerpts([excerpt], lines, direction, cx)
12764 });
12765 if direction == ExpandExcerptDirection::Down {
12766 let new_scroll_position = current_scroll_position + gpui::Point::new(0.0, lines as f32);
12767 self.set_scroll_position(new_scroll_position, window, cx);
12768 }
12769 }
12770
12771 pub fn go_to_singleton_buffer_point(
12772 &mut self,
12773 point: Point,
12774 window: &mut Window,
12775 cx: &mut Context<Self>,
12776 ) {
12777 self.go_to_singleton_buffer_range(point..point, window, cx);
12778 }
12779
12780 pub fn go_to_singleton_buffer_range(
12781 &mut self,
12782 range: Range<Point>,
12783 window: &mut Window,
12784 cx: &mut Context<Self>,
12785 ) {
12786 let multibuffer = self.buffer().read(cx);
12787 let Some(buffer) = multibuffer.as_singleton() else {
12788 return;
12789 };
12790 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12791 return;
12792 };
12793 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12794 return;
12795 };
12796 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12797 s.select_anchor_ranges([start..end])
12798 });
12799 }
12800
12801 fn go_to_diagnostic(
12802 &mut self,
12803 _: &GoToDiagnostic,
12804 window: &mut Window,
12805 cx: &mut Context<Self>,
12806 ) {
12807 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12808 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12809 }
12810
12811 fn go_to_prev_diagnostic(
12812 &mut self,
12813 _: &GoToPreviousDiagnostic,
12814 window: &mut Window,
12815 cx: &mut Context<Self>,
12816 ) {
12817 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12818 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12819 }
12820
12821 pub fn go_to_diagnostic_impl(
12822 &mut self,
12823 direction: Direction,
12824 window: &mut Window,
12825 cx: &mut Context<Self>,
12826 ) {
12827 let buffer = self.buffer.read(cx).snapshot(cx);
12828 let selection = self.selections.newest::<usize>(cx);
12829 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12830 if direction == Direction::Next {
12831 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12832 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12833 return;
12834 };
12835 self.activate_diagnostics(
12836 buffer_id,
12837 popover.local_diagnostic.diagnostic.group_id,
12838 window,
12839 cx,
12840 );
12841 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12842 let primary_range_start = active_diagnostics.primary_range.start;
12843 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12844 let mut new_selection = s.newest_anchor().clone();
12845 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12846 s.select_anchors(vec![new_selection.clone()]);
12847 });
12848 self.refresh_inline_completion(false, true, window, cx);
12849 }
12850 return;
12851 }
12852 }
12853
12854 let active_group_id = self
12855 .active_diagnostics
12856 .as_ref()
12857 .map(|active_group| active_group.group_id);
12858 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12859 active_diagnostics
12860 .primary_range
12861 .to_offset(&buffer)
12862 .to_inclusive()
12863 });
12864 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12865 if active_primary_range.contains(&selection.head()) {
12866 *active_primary_range.start()
12867 } else {
12868 selection.head()
12869 }
12870 } else {
12871 selection.head()
12872 };
12873
12874 let snapshot = self.snapshot(window, cx);
12875 let primary_diagnostics_before = buffer
12876 .diagnostics_in_range::<usize>(0..search_start)
12877 .filter(|entry| entry.diagnostic.is_primary)
12878 .filter(|entry| entry.range.start != entry.range.end)
12879 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12880 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12881 .collect::<Vec<_>>();
12882 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12883 primary_diagnostics_before
12884 .iter()
12885 .position(|entry| entry.diagnostic.group_id == active_group_id)
12886 });
12887
12888 let primary_diagnostics_after = buffer
12889 .diagnostics_in_range::<usize>(search_start..buffer.len())
12890 .filter(|entry| entry.diagnostic.is_primary)
12891 .filter(|entry| entry.range.start != entry.range.end)
12892 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12893 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12894 .collect::<Vec<_>>();
12895 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12896 primary_diagnostics_after
12897 .iter()
12898 .enumerate()
12899 .rev()
12900 .find_map(|(i, entry)| {
12901 if entry.diagnostic.group_id == active_group_id {
12902 Some(i)
12903 } else {
12904 None
12905 }
12906 })
12907 });
12908
12909 let next_primary_diagnostic = match direction {
12910 Direction::Prev => primary_diagnostics_before
12911 .iter()
12912 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
12913 .rev()
12914 .next(),
12915 Direction::Next => primary_diagnostics_after
12916 .iter()
12917 .skip(
12918 last_same_group_diagnostic_after
12919 .map(|index| index + 1)
12920 .unwrap_or(0),
12921 )
12922 .next(),
12923 };
12924
12925 // Cycle around to the start of the buffer, potentially moving back to the start of
12926 // the currently active diagnostic.
12927 let cycle_around = || match direction {
12928 Direction::Prev => primary_diagnostics_after
12929 .iter()
12930 .rev()
12931 .chain(primary_diagnostics_before.iter().rev())
12932 .next(),
12933 Direction::Next => primary_diagnostics_before
12934 .iter()
12935 .chain(primary_diagnostics_after.iter())
12936 .next(),
12937 };
12938
12939 if let Some((primary_range, group_id)) = next_primary_diagnostic
12940 .or_else(cycle_around)
12941 .map(|entry| (&entry.range, entry.diagnostic.group_id))
12942 {
12943 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
12944 return;
12945 };
12946 self.activate_diagnostics(buffer_id, group_id, window, cx);
12947 if self.active_diagnostics.is_some() {
12948 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12949 s.select(vec![Selection {
12950 id: selection.id,
12951 start: primary_range.start,
12952 end: primary_range.start,
12953 reversed: false,
12954 goal: SelectionGoal::None,
12955 }]);
12956 });
12957 self.refresh_inline_completion(false, true, window, cx);
12958 }
12959 }
12960 }
12961
12962 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
12963 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12964 let snapshot = self.snapshot(window, cx);
12965 let selection = self.selections.newest::<Point>(cx);
12966 self.go_to_hunk_before_or_after_position(
12967 &snapshot,
12968 selection.head(),
12969 Direction::Next,
12970 window,
12971 cx,
12972 );
12973 }
12974
12975 pub fn go_to_hunk_before_or_after_position(
12976 &mut self,
12977 snapshot: &EditorSnapshot,
12978 position: Point,
12979 direction: Direction,
12980 window: &mut Window,
12981 cx: &mut Context<Editor>,
12982 ) {
12983 let row = if direction == Direction::Next {
12984 self.hunk_after_position(snapshot, position)
12985 .map(|hunk| hunk.row_range.start)
12986 } else {
12987 self.hunk_before_position(snapshot, position)
12988 };
12989
12990 if let Some(row) = row {
12991 let destination = Point::new(row.0, 0);
12992 let autoscroll = Autoscroll::center();
12993
12994 self.unfold_ranges(&[destination..destination], false, false, cx);
12995 self.change_selections(Some(autoscroll), window, cx, |s| {
12996 s.select_ranges([destination..destination]);
12997 });
12998 }
12999 }
13000
13001 fn hunk_after_position(
13002 &mut self,
13003 snapshot: &EditorSnapshot,
13004 position: Point,
13005 ) -> Option<MultiBufferDiffHunk> {
13006 snapshot
13007 .buffer_snapshot
13008 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13009 .find(|hunk| hunk.row_range.start.0 > position.row)
13010 .or_else(|| {
13011 snapshot
13012 .buffer_snapshot
13013 .diff_hunks_in_range(Point::zero()..position)
13014 .find(|hunk| hunk.row_range.end.0 < position.row)
13015 })
13016 }
13017
13018 fn go_to_prev_hunk(
13019 &mut self,
13020 _: &GoToPreviousHunk,
13021 window: &mut Window,
13022 cx: &mut Context<Self>,
13023 ) {
13024 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13025 let snapshot = self.snapshot(window, cx);
13026 let selection = self.selections.newest::<Point>(cx);
13027 self.go_to_hunk_before_or_after_position(
13028 &snapshot,
13029 selection.head(),
13030 Direction::Prev,
13031 window,
13032 cx,
13033 );
13034 }
13035
13036 fn hunk_before_position(
13037 &mut self,
13038 snapshot: &EditorSnapshot,
13039 position: Point,
13040 ) -> Option<MultiBufferRow> {
13041 snapshot
13042 .buffer_snapshot
13043 .diff_hunk_before(position)
13044 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13045 }
13046
13047 fn go_to_line<T: 'static>(
13048 &mut self,
13049 position: Anchor,
13050 highlight_color: Option<Hsla>,
13051 window: &mut Window,
13052 cx: &mut Context<Self>,
13053 ) {
13054 let snapshot = self.snapshot(window, cx).display_snapshot;
13055 let position = position.to_point(&snapshot.buffer_snapshot);
13056 let start = snapshot
13057 .buffer_snapshot
13058 .clip_point(Point::new(position.row, 0), Bias::Left);
13059 let end = start + Point::new(1, 0);
13060 let start = snapshot.buffer_snapshot.anchor_before(start);
13061 let end = snapshot.buffer_snapshot.anchor_before(end);
13062
13063 self.highlight_rows::<T>(
13064 start..end,
13065 highlight_color
13066 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13067 false,
13068 cx,
13069 );
13070 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13071 }
13072
13073 pub fn go_to_definition(
13074 &mut self,
13075 _: &GoToDefinition,
13076 window: &mut Window,
13077 cx: &mut Context<Self>,
13078 ) -> Task<Result<Navigated>> {
13079 let definition =
13080 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13081 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13082 cx.spawn_in(window, async move |editor, cx| {
13083 if definition.await? == Navigated::Yes {
13084 return Ok(Navigated::Yes);
13085 }
13086 match fallback_strategy {
13087 GoToDefinitionFallback::None => Ok(Navigated::No),
13088 GoToDefinitionFallback::FindAllReferences => {
13089 match editor.update_in(cx, |editor, window, cx| {
13090 editor.find_all_references(&FindAllReferences, window, cx)
13091 })? {
13092 Some(references) => references.await,
13093 None => Ok(Navigated::No),
13094 }
13095 }
13096 }
13097 })
13098 }
13099
13100 pub fn go_to_declaration(
13101 &mut self,
13102 _: &GoToDeclaration,
13103 window: &mut Window,
13104 cx: &mut Context<Self>,
13105 ) -> Task<Result<Navigated>> {
13106 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13107 }
13108
13109 pub fn go_to_declaration_split(
13110 &mut self,
13111 _: &GoToDeclaration,
13112 window: &mut Window,
13113 cx: &mut Context<Self>,
13114 ) -> Task<Result<Navigated>> {
13115 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13116 }
13117
13118 pub fn go_to_implementation(
13119 &mut self,
13120 _: &GoToImplementation,
13121 window: &mut Window,
13122 cx: &mut Context<Self>,
13123 ) -> Task<Result<Navigated>> {
13124 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13125 }
13126
13127 pub fn go_to_implementation_split(
13128 &mut self,
13129 _: &GoToImplementationSplit,
13130 window: &mut Window,
13131 cx: &mut Context<Self>,
13132 ) -> Task<Result<Navigated>> {
13133 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13134 }
13135
13136 pub fn go_to_type_definition(
13137 &mut self,
13138 _: &GoToTypeDefinition,
13139 window: &mut Window,
13140 cx: &mut Context<Self>,
13141 ) -> Task<Result<Navigated>> {
13142 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13143 }
13144
13145 pub fn go_to_definition_split(
13146 &mut self,
13147 _: &GoToDefinitionSplit,
13148 window: &mut Window,
13149 cx: &mut Context<Self>,
13150 ) -> Task<Result<Navigated>> {
13151 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13152 }
13153
13154 pub fn go_to_type_definition_split(
13155 &mut self,
13156 _: &GoToTypeDefinitionSplit,
13157 window: &mut Window,
13158 cx: &mut Context<Self>,
13159 ) -> Task<Result<Navigated>> {
13160 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13161 }
13162
13163 fn go_to_definition_of_kind(
13164 &mut self,
13165 kind: GotoDefinitionKind,
13166 split: bool,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) -> Task<Result<Navigated>> {
13170 let Some(provider) = self.semantics_provider.clone() else {
13171 return Task::ready(Ok(Navigated::No));
13172 };
13173 let head = self.selections.newest::<usize>(cx).head();
13174 let buffer = self.buffer.read(cx);
13175 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13176 text_anchor
13177 } else {
13178 return Task::ready(Ok(Navigated::No));
13179 };
13180
13181 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13182 return Task::ready(Ok(Navigated::No));
13183 };
13184
13185 cx.spawn_in(window, async move |editor, cx| {
13186 let definitions = definitions.await?;
13187 let navigated = editor
13188 .update_in(cx, |editor, window, cx| {
13189 editor.navigate_to_hover_links(
13190 Some(kind),
13191 definitions
13192 .into_iter()
13193 .filter(|location| {
13194 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13195 })
13196 .map(HoverLink::Text)
13197 .collect::<Vec<_>>(),
13198 split,
13199 window,
13200 cx,
13201 )
13202 })?
13203 .await?;
13204 anyhow::Ok(navigated)
13205 })
13206 }
13207
13208 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13209 let selection = self.selections.newest_anchor();
13210 let head = selection.head();
13211 let tail = selection.tail();
13212
13213 let Some((buffer, start_position)) =
13214 self.buffer.read(cx).text_anchor_for_position(head, cx)
13215 else {
13216 return;
13217 };
13218
13219 let end_position = if head != tail {
13220 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13221 return;
13222 };
13223 Some(pos)
13224 } else {
13225 None
13226 };
13227
13228 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13229 let url = if let Some(end_pos) = end_position {
13230 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13231 } else {
13232 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13233 };
13234
13235 if let Some(url) = url {
13236 editor.update(cx, |_, cx| {
13237 cx.open_url(&url);
13238 })
13239 } else {
13240 Ok(())
13241 }
13242 });
13243
13244 url_finder.detach();
13245 }
13246
13247 pub fn open_selected_filename(
13248 &mut self,
13249 _: &OpenSelectedFilename,
13250 window: &mut Window,
13251 cx: &mut Context<Self>,
13252 ) {
13253 let Some(workspace) = self.workspace() else {
13254 return;
13255 };
13256
13257 let position = self.selections.newest_anchor().head();
13258
13259 let Some((buffer, buffer_position)) =
13260 self.buffer.read(cx).text_anchor_for_position(position, cx)
13261 else {
13262 return;
13263 };
13264
13265 let project = self.project.clone();
13266
13267 cx.spawn_in(window, async move |_, cx| {
13268 let result = find_file(&buffer, project, buffer_position, cx).await;
13269
13270 if let Some((_, path)) = result {
13271 workspace
13272 .update_in(cx, |workspace, window, cx| {
13273 workspace.open_resolved_path(path, window, cx)
13274 })?
13275 .await?;
13276 }
13277 anyhow::Ok(())
13278 })
13279 .detach();
13280 }
13281
13282 pub(crate) fn navigate_to_hover_links(
13283 &mut self,
13284 kind: Option<GotoDefinitionKind>,
13285 mut definitions: Vec<HoverLink>,
13286 split: bool,
13287 window: &mut Window,
13288 cx: &mut Context<Editor>,
13289 ) -> Task<Result<Navigated>> {
13290 // If there is one definition, just open it directly
13291 if definitions.len() == 1 {
13292 let definition = definitions.pop().unwrap();
13293
13294 enum TargetTaskResult {
13295 Location(Option<Location>),
13296 AlreadyNavigated,
13297 }
13298
13299 let target_task = match definition {
13300 HoverLink::Text(link) => {
13301 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13302 }
13303 HoverLink::InlayHint(lsp_location, server_id) => {
13304 let computation =
13305 self.compute_target_location(lsp_location, server_id, window, cx);
13306 cx.background_spawn(async move {
13307 let location = computation.await?;
13308 Ok(TargetTaskResult::Location(location))
13309 })
13310 }
13311 HoverLink::Url(url) => {
13312 cx.open_url(&url);
13313 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13314 }
13315 HoverLink::File(path) => {
13316 if let Some(workspace) = self.workspace() {
13317 cx.spawn_in(window, async move |_, cx| {
13318 workspace
13319 .update_in(cx, |workspace, window, cx| {
13320 workspace.open_resolved_path(path, window, cx)
13321 })?
13322 .await
13323 .map(|_| TargetTaskResult::AlreadyNavigated)
13324 })
13325 } else {
13326 Task::ready(Ok(TargetTaskResult::Location(None)))
13327 }
13328 }
13329 };
13330 cx.spawn_in(window, async move |editor, cx| {
13331 let target = match target_task.await.context("target resolution task")? {
13332 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13333 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13334 TargetTaskResult::Location(Some(target)) => target,
13335 };
13336
13337 editor.update_in(cx, |editor, window, cx| {
13338 let Some(workspace) = editor.workspace() else {
13339 return Navigated::No;
13340 };
13341 let pane = workspace.read(cx).active_pane().clone();
13342
13343 let range = target.range.to_point(target.buffer.read(cx));
13344 let range = editor.range_for_match(&range);
13345 let range = collapse_multiline_range(range);
13346
13347 if !split
13348 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13349 {
13350 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13351 } else {
13352 window.defer(cx, move |window, cx| {
13353 let target_editor: Entity<Self> =
13354 workspace.update(cx, |workspace, cx| {
13355 let pane = if split {
13356 workspace.adjacent_pane(window, cx)
13357 } else {
13358 workspace.active_pane().clone()
13359 };
13360
13361 workspace.open_project_item(
13362 pane,
13363 target.buffer.clone(),
13364 true,
13365 true,
13366 window,
13367 cx,
13368 )
13369 });
13370 target_editor.update(cx, |target_editor, cx| {
13371 // When selecting a definition in a different buffer, disable the nav history
13372 // to avoid creating a history entry at the previous cursor location.
13373 pane.update(cx, |pane, _| pane.disable_history());
13374 target_editor.go_to_singleton_buffer_range(range, window, cx);
13375 pane.update(cx, |pane, _| pane.enable_history());
13376 });
13377 });
13378 }
13379 Navigated::Yes
13380 })
13381 })
13382 } else if !definitions.is_empty() {
13383 cx.spawn_in(window, async move |editor, cx| {
13384 let (title, location_tasks, workspace) = editor
13385 .update_in(cx, |editor, window, cx| {
13386 let tab_kind = match kind {
13387 Some(GotoDefinitionKind::Implementation) => "Implementations",
13388 _ => "Definitions",
13389 };
13390 let title = definitions
13391 .iter()
13392 .find_map(|definition| match definition {
13393 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13394 let buffer = origin.buffer.read(cx);
13395 format!(
13396 "{} for {}",
13397 tab_kind,
13398 buffer
13399 .text_for_range(origin.range.clone())
13400 .collect::<String>()
13401 )
13402 }),
13403 HoverLink::InlayHint(_, _) => None,
13404 HoverLink::Url(_) => None,
13405 HoverLink::File(_) => None,
13406 })
13407 .unwrap_or(tab_kind.to_string());
13408 let location_tasks = definitions
13409 .into_iter()
13410 .map(|definition| match definition {
13411 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13412 HoverLink::InlayHint(lsp_location, server_id) => editor
13413 .compute_target_location(lsp_location, server_id, window, cx),
13414 HoverLink::Url(_) => Task::ready(Ok(None)),
13415 HoverLink::File(_) => Task::ready(Ok(None)),
13416 })
13417 .collect::<Vec<_>>();
13418 (title, location_tasks, editor.workspace().clone())
13419 })
13420 .context("location tasks preparation")?;
13421
13422 let locations = future::join_all(location_tasks)
13423 .await
13424 .into_iter()
13425 .filter_map(|location| location.transpose())
13426 .collect::<Result<_>>()
13427 .context("location tasks")?;
13428
13429 let Some(workspace) = workspace else {
13430 return Ok(Navigated::No);
13431 };
13432 let opened = workspace
13433 .update_in(cx, |workspace, window, cx| {
13434 Self::open_locations_in_multibuffer(
13435 workspace,
13436 locations,
13437 title,
13438 split,
13439 MultibufferSelectionMode::First,
13440 window,
13441 cx,
13442 )
13443 })
13444 .ok();
13445
13446 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13447 })
13448 } else {
13449 Task::ready(Ok(Navigated::No))
13450 }
13451 }
13452
13453 fn compute_target_location(
13454 &self,
13455 lsp_location: lsp::Location,
13456 server_id: LanguageServerId,
13457 window: &mut Window,
13458 cx: &mut Context<Self>,
13459 ) -> Task<anyhow::Result<Option<Location>>> {
13460 let Some(project) = self.project.clone() else {
13461 return Task::ready(Ok(None));
13462 };
13463
13464 cx.spawn_in(window, async move |editor, cx| {
13465 let location_task = editor.update(cx, |_, cx| {
13466 project.update(cx, |project, cx| {
13467 let language_server_name = project
13468 .language_server_statuses(cx)
13469 .find(|(id, _)| server_id == *id)
13470 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13471 language_server_name.map(|language_server_name| {
13472 project.open_local_buffer_via_lsp(
13473 lsp_location.uri.clone(),
13474 server_id,
13475 language_server_name,
13476 cx,
13477 )
13478 })
13479 })
13480 })?;
13481 let location = match location_task {
13482 Some(task) => Some({
13483 let target_buffer_handle = task.await.context("open local buffer")?;
13484 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13485 let target_start = target_buffer
13486 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13487 let target_end = target_buffer
13488 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13489 target_buffer.anchor_after(target_start)
13490 ..target_buffer.anchor_before(target_end)
13491 })?;
13492 Location {
13493 buffer: target_buffer_handle,
13494 range,
13495 }
13496 }),
13497 None => None,
13498 };
13499 Ok(location)
13500 })
13501 }
13502
13503 pub fn find_all_references(
13504 &mut self,
13505 _: &FindAllReferences,
13506 window: &mut Window,
13507 cx: &mut Context<Self>,
13508 ) -> Option<Task<Result<Navigated>>> {
13509 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13510
13511 let selection = self.selections.newest::<usize>(cx);
13512 let multi_buffer = self.buffer.read(cx);
13513 let head = selection.head();
13514
13515 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13516 let head_anchor = multi_buffer_snapshot.anchor_at(
13517 head,
13518 if head < selection.tail() {
13519 Bias::Right
13520 } else {
13521 Bias::Left
13522 },
13523 );
13524
13525 match self
13526 .find_all_references_task_sources
13527 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13528 {
13529 Ok(_) => {
13530 log::info!(
13531 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13532 );
13533 return None;
13534 }
13535 Err(i) => {
13536 self.find_all_references_task_sources.insert(i, head_anchor);
13537 }
13538 }
13539
13540 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13541 let workspace = self.workspace()?;
13542 let project = workspace.read(cx).project().clone();
13543 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13544 Some(cx.spawn_in(window, async move |editor, cx| {
13545 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13546 if let Ok(i) = editor
13547 .find_all_references_task_sources
13548 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13549 {
13550 editor.find_all_references_task_sources.remove(i);
13551 }
13552 });
13553
13554 let locations = references.await?;
13555 if locations.is_empty() {
13556 return anyhow::Ok(Navigated::No);
13557 }
13558
13559 workspace.update_in(cx, |workspace, window, cx| {
13560 let title = locations
13561 .first()
13562 .as_ref()
13563 .map(|location| {
13564 let buffer = location.buffer.read(cx);
13565 format!(
13566 "References to `{}`",
13567 buffer
13568 .text_for_range(location.range.clone())
13569 .collect::<String>()
13570 )
13571 })
13572 .unwrap();
13573 Self::open_locations_in_multibuffer(
13574 workspace,
13575 locations,
13576 title,
13577 false,
13578 MultibufferSelectionMode::First,
13579 window,
13580 cx,
13581 );
13582 Navigated::Yes
13583 })
13584 }))
13585 }
13586
13587 /// Opens a multibuffer with the given project locations in it
13588 pub fn open_locations_in_multibuffer(
13589 workspace: &mut Workspace,
13590 mut locations: Vec<Location>,
13591 title: String,
13592 split: bool,
13593 multibuffer_selection_mode: MultibufferSelectionMode,
13594 window: &mut Window,
13595 cx: &mut Context<Workspace>,
13596 ) {
13597 // If there are multiple definitions, open them in a multibuffer
13598 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13599 let mut locations = locations.into_iter().peekable();
13600 let mut ranges: Vec<Range<Anchor>> = Vec::new();
13601 let capability = workspace.project().read(cx).capability();
13602
13603 let excerpt_buffer = cx.new(|cx| {
13604 let mut multibuffer = MultiBuffer::new(capability);
13605 while let Some(location) = locations.next() {
13606 let buffer = location.buffer.read(cx);
13607 let mut ranges_for_buffer = Vec::new();
13608 let range = location.range.to_point(buffer);
13609 ranges_for_buffer.push(range.clone());
13610
13611 while let Some(next_location) = locations.peek() {
13612 if next_location.buffer == location.buffer {
13613 ranges_for_buffer.push(next_location.range.to_point(buffer));
13614 locations.next();
13615 } else {
13616 break;
13617 }
13618 }
13619
13620 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13621 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
13622 PathKey::for_buffer(&location.buffer, cx),
13623 location.buffer.clone(),
13624 ranges_for_buffer,
13625 DEFAULT_MULTIBUFFER_CONTEXT,
13626 cx,
13627 );
13628 ranges.extend(new_ranges)
13629 }
13630
13631 multibuffer.with_title(title)
13632 });
13633
13634 let editor = cx.new(|cx| {
13635 Editor::for_multibuffer(
13636 excerpt_buffer,
13637 Some(workspace.project().clone()),
13638 window,
13639 cx,
13640 )
13641 });
13642 editor.update(cx, |editor, cx| {
13643 match multibuffer_selection_mode {
13644 MultibufferSelectionMode::First => {
13645 if let Some(first_range) = ranges.first() {
13646 editor.change_selections(None, window, cx, |selections| {
13647 selections.clear_disjoint();
13648 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13649 });
13650 }
13651 editor.highlight_background::<Self>(
13652 &ranges,
13653 |theme| theme.editor_highlighted_line_background,
13654 cx,
13655 );
13656 }
13657 MultibufferSelectionMode::All => {
13658 editor.change_selections(None, window, cx, |selections| {
13659 selections.clear_disjoint();
13660 selections.select_anchor_ranges(ranges);
13661 });
13662 }
13663 }
13664 editor.register_buffers_with_language_servers(cx);
13665 });
13666
13667 let item = Box::new(editor);
13668 let item_id = item.item_id();
13669
13670 if split {
13671 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13672 } else {
13673 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13674 let (preview_item_id, preview_item_idx) =
13675 workspace.active_pane().update(cx, |pane, _| {
13676 (pane.preview_item_id(), pane.preview_item_idx())
13677 });
13678
13679 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13680
13681 if let Some(preview_item_id) = preview_item_id {
13682 workspace.active_pane().update(cx, |pane, cx| {
13683 pane.remove_item(preview_item_id, false, false, window, cx);
13684 });
13685 }
13686 } else {
13687 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13688 }
13689 }
13690 workspace.active_pane().update(cx, |pane, cx| {
13691 pane.set_preview_item_id(Some(item_id), cx);
13692 });
13693 }
13694
13695 pub fn rename(
13696 &mut self,
13697 _: &Rename,
13698 window: &mut Window,
13699 cx: &mut Context<Self>,
13700 ) -> Option<Task<Result<()>>> {
13701 use language::ToOffset as _;
13702
13703 let provider = self.semantics_provider.clone()?;
13704 let selection = self.selections.newest_anchor().clone();
13705 let (cursor_buffer, cursor_buffer_position) = self
13706 .buffer
13707 .read(cx)
13708 .text_anchor_for_position(selection.head(), cx)?;
13709 let (tail_buffer, cursor_buffer_position_end) = self
13710 .buffer
13711 .read(cx)
13712 .text_anchor_for_position(selection.tail(), cx)?;
13713 if tail_buffer != cursor_buffer {
13714 return None;
13715 }
13716
13717 let snapshot = cursor_buffer.read(cx).snapshot();
13718 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13719 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13720 let prepare_rename = provider
13721 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13722 .unwrap_or_else(|| Task::ready(Ok(None)));
13723 drop(snapshot);
13724
13725 Some(cx.spawn_in(window, async move |this, cx| {
13726 let rename_range = if let Some(range) = prepare_rename.await? {
13727 Some(range)
13728 } else {
13729 this.update(cx, |this, cx| {
13730 let buffer = this.buffer.read(cx).snapshot(cx);
13731 let mut buffer_highlights = this
13732 .document_highlights_for_position(selection.head(), &buffer)
13733 .filter(|highlight| {
13734 highlight.start.excerpt_id == selection.head().excerpt_id
13735 && highlight.end.excerpt_id == selection.head().excerpt_id
13736 });
13737 buffer_highlights
13738 .next()
13739 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13740 })?
13741 };
13742 if let Some(rename_range) = rename_range {
13743 this.update_in(cx, |this, window, cx| {
13744 let snapshot = cursor_buffer.read(cx).snapshot();
13745 let rename_buffer_range = rename_range.to_offset(&snapshot);
13746 let cursor_offset_in_rename_range =
13747 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13748 let cursor_offset_in_rename_range_end =
13749 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13750
13751 this.take_rename(false, window, cx);
13752 let buffer = this.buffer.read(cx).read(cx);
13753 let cursor_offset = selection.head().to_offset(&buffer);
13754 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13755 let rename_end = rename_start + rename_buffer_range.len();
13756 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13757 let mut old_highlight_id = None;
13758 let old_name: Arc<str> = buffer
13759 .chunks(rename_start..rename_end, true)
13760 .map(|chunk| {
13761 if old_highlight_id.is_none() {
13762 old_highlight_id = chunk.syntax_highlight_id;
13763 }
13764 chunk.text
13765 })
13766 .collect::<String>()
13767 .into();
13768
13769 drop(buffer);
13770
13771 // Position the selection in the rename editor so that it matches the current selection.
13772 this.show_local_selections = false;
13773 let rename_editor = cx.new(|cx| {
13774 let mut editor = Editor::single_line(window, cx);
13775 editor.buffer.update(cx, |buffer, cx| {
13776 buffer.edit([(0..0, old_name.clone())], None, cx)
13777 });
13778 let rename_selection_range = match cursor_offset_in_rename_range
13779 .cmp(&cursor_offset_in_rename_range_end)
13780 {
13781 Ordering::Equal => {
13782 editor.select_all(&SelectAll, window, cx);
13783 return editor;
13784 }
13785 Ordering::Less => {
13786 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13787 }
13788 Ordering::Greater => {
13789 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13790 }
13791 };
13792 if rename_selection_range.end > old_name.len() {
13793 editor.select_all(&SelectAll, window, cx);
13794 } else {
13795 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13796 s.select_ranges([rename_selection_range]);
13797 });
13798 }
13799 editor
13800 });
13801 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13802 if e == &EditorEvent::Focused {
13803 cx.emit(EditorEvent::FocusedIn)
13804 }
13805 })
13806 .detach();
13807
13808 let write_highlights =
13809 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13810 let read_highlights =
13811 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13812 let ranges = write_highlights
13813 .iter()
13814 .flat_map(|(_, ranges)| ranges.iter())
13815 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13816 .cloned()
13817 .collect();
13818
13819 this.highlight_text::<Rename>(
13820 ranges,
13821 HighlightStyle {
13822 fade_out: Some(0.6),
13823 ..Default::default()
13824 },
13825 cx,
13826 );
13827 let rename_focus_handle = rename_editor.focus_handle(cx);
13828 window.focus(&rename_focus_handle);
13829 let block_id = this.insert_blocks(
13830 [BlockProperties {
13831 style: BlockStyle::Flex,
13832 placement: BlockPlacement::Below(range.start),
13833 height: Some(1),
13834 render: Arc::new({
13835 let rename_editor = rename_editor.clone();
13836 move |cx: &mut BlockContext| {
13837 let mut text_style = cx.editor_style.text.clone();
13838 if let Some(highlight_style) = old_highlight_id
13839 .and_then(|h| h.style(&cx.editor_style.syntax))
13840 {
13841 text_style = text_style.highlight(highlight_style);
13842 }
13843 div()
13844 .block_mouse_down()
13845 .pl(cx.anchor_x)
13846 .child(EditorElement::new(
13847 &rename_editor,
13848 EditorStyle {
13849 background: cx.theme().system().transparent,
13850 local_player: cx.editor_style.local_player,
13851 text: text_style,
13852 scrollbar_width: cx.editor_style.scrollbar_width,
13853 syntax: cx.editor_style.syntax.clone(),
13854 status: cx.editor_style.status.clone(),
13855 inlay_hints_style: HighlightStyle {
13856 font_weight: Some(FontWeight::BOLD),
13857 ..make_inlay_hints_style(cx.app)
13858 },
13859 inline_completion_styles: make_suggestion_styles(
13860 cx.app,
13861 ),
13862 ..EditorStyle::default()
13863 },
13864 ))
13865 .into_any_element()
13866 }
13867 }),
13868 priority: 0,
13869 }],
13870 Some(Autoscroll::fit()),
13871 cx,
13872 )[0];
13873 this.pending_rename = Some(RenameState {
13874 range,
13875 old_name,
13876 editor: rename_editor,
13877 block_id,
13878 });
13879 })?;
13880 }
13881
13882 Ok(())
13883 }))
13884 }
13885
13886 pub fn confirm_rename(
13887 &mut self,
13888 _: &ConfirmRename,
13889 window: &mut Window,
13890 cx: &mut Context<Self>,
13891 ) -> Option<Task<Result<()>>> {
13892 let rename = self.take_rename(false, window, cx)?;
13893 let workspace = self.workspace()?.downgrade();
13894 let (buffer, start) = self
13895 .buffer
13896 .read(cx)
13897 .text_anchor_for_position(rename.range.start, cx)?;
13898 let (end_buffer, _) = self
13899 .buffer
13900 .read(cx)
13901 .text_anchor_for_position(rename.range.end, cx)?;
13902 if buffer != end_buffer {
13903 return None;
13904 }
13905
13906 let old_name = rename.old_name;
13907 let new_name = rename.editor.read(cx).text(cx);
13908
13909 let rename = self.semantics_provider.as_ref()?.perform_rename(
13910 &buffer,
13911 start,
13912 new_name.clone(),
13913 cx,
13914 )?;
13915
13916 Some(cx.spawn_in(window, async move |editor, cx| {
13917 let project_transaction = rename.await?;
13918 Self::open_project_transaction(
13919 &editor,
13920 workspace,
13921 project_transaction,
13922 format!("Rename: {} → {}", old_name, new_name),
13923 cx,
13924 )
13925 .await?;
13926
13927 editor.update(cx, |editor, cx| {
13928 editor.refresh_document_highlights(cx);
13929 })?;
13930 Ok(())
13931 }))
13932 }
13933
13934 fn take_rename(
13935 &mut self,
13936 moving_cursor: bool,
13937 window: &mut Window,
13938 cx: &mut Context<Self>,
13939 ) -> Option<RenameState> {
13940 let rename = self.pending_rename.take()?;
13941 if rename.editor.focus_handle(cx).is_focused(window) {
13942 window.focus(&self.focus_handle);
13943 }
13944
13945 self.remove_blocks(
13946 [rename.block_id].into_iter().collect(),
13947 Some(Autoscroll::fit()),
13948 cx,
13949 );
13950 self.clear_highlights::<Rename>(cx);
13951 self.show_local_selections = true;
13952
13953 if moving_cursor {
13954 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
13955 editor.selections.newest::<usize>(cx).head()
13956 });
13957
13958 // Update the selection to match the position of the selection inside
13959 // the rename editor.
13960 let snapshot = self.buffer.read(cx).read(cx);
13961 let rename_range = rename.range.to_offset(&snapshot);
13962 let cursor_in_editor = snapshot
13963 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
13964 .min(rename_range.end);
13965 drop(snapshot);
13966
13967 self.change_selections(None, window, cx, |s| {
13968 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
13969 });
13970 } else {
13971 self.refresh_document_highlights(cx);
13972 }
13973
13974 Some(rename)
13975 }
13976
13977 pub fn pending_rename(&self) -> Option<&RenameState> {
13978 self.pending_rename.as_ref()
13979 }
13980
13981 fn format(
13982 &mut self,
13983 _: &Format,
13984 window: &mut Window,
13985 cx: &mut Context<Self>,
13986 ) -> Option<Task<Result<()>>> {
13987 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13988
13989 let project = match &self.project {
13990 Some(project) => project.clone(),
13991 None => return None,
13992 };
13993
13994 Some(self.perform_format(
13995 project,
13996 FormatTrigger::Manual,
13997 FormatTarget::Buffers,
13998 window,
13999 cx,
14000 ))
14001 }
14002
14003 fn format_selections(
14004 &mut self,
14005 _: &FormatSelections,
14006 window: &mut Window,
14007 cx: &mut Context<Self>,
14008 ) -> Option<Task<Result<()>>> {
14009 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14010
14011 let project = match &self.project {
14012 Some(project) => project.clone(),
14013 None => return None,
14014 };
14015
14016 let ranges = self
14017 .selections
14018 .all_adjusted(cx)
14019 .into_iter()
14020 .map(|selection| selection.range())
14021 .collect_vec();
14022
14023 Some(self.perform_format(
14024 project,
14025 FormatTrigger::Manual,
14026 FormatTarget::Ranges(ranges),
14027 window,
14028 cx,
14029 ))
14030 }
14031
14032 fn perform_format(
14033 &mut self,
14034 project: Entity<Project>,
14035 trigger: FormatTrigger,
14036 target: FormatTarget,
14037 window: &mut Window,
14038 cx: &mut Context<Self>,
14039 ) -> Task<Result<()>> {
14040 let buffer = self.buffer.clone();
14041 let (buffers, target) = match target {
14042 FormatTarget::Buffers => {
14043 let mut buffers = buffer.read(cx).all_buffers();
14044 if trigger == FormatTrigger::Save {
14045 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14046 }
14047 (buffers, LspFormatTarget::Buffers)
14048 }
14049 FormatTarget::Ranges(selection_ranges) => {
14050 let multi_buffer = buffer.read(cx);
14051 let snapshot = multi_buffer.read(cx);
14052 let mut buffers = HashSet::default();
14053 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14054 BTreeMap::new();
14055 for selection_range in selection_ranges {
14056 for (buffer, buffer_range, _) in
14057 snapshot.range_to_buffer_ranges(selection_range)
14058 {
14059 let buffer_id = buffer.remote_id();
14060 let start = buffer.anchor_before(buffer_range.start);
14061 let end = buffer.anchor_after(buffer_range.end);
14062 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14063 buffer_id_to_ranges
14064 .entry(buffer_id)
14065 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14066 .or_insert_with(|| vec![start..end]);
14067 }
14068 }
14069 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14070 }
14071 };
14072
14073 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14074 let format = project.update(cx, |project, cx| {
14075 project.format(buffers, target, true, trigger, cx)
14076 });
14077
14078 cx.spawn_in(window, async move |_, cx| {
14079 let transaction = futures::select_biased! {
14080 transaction = format.log_err().fuse() => transaction,
14081 () = timeout => {
14082 log::warn!("timed out waiting for formatting");
14083 None
14084 }
14085 };
14086
14087 buffer
14088 .update(cx, |buffer, cx| {
14089 if let Some(transaction) = transaction {
14090 if !buffer.is_singleton() {
14091 buffer.push_transaction(&transaction.0, cx);
14092 }
14093 }
14094 cx.notify();
14095 })
14096 .ok();
14097
14098 Ok(())
14099 })
14100 }
14101
14102 fn organize_imports(
14103 &mut self,
14104 _: &OrganizeImports,
14105 window: &mut Window,
14106 cx: &mut Context<Self>,
14107 ) -> Option<Task<Result<()>>> {
14108 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14109 let project = match &self.project {
14110 Some(project) => project.clone(),
14111 None => return None,
14112 };
14113 Some(self.perform_code_action_kind(
14114 project,
14115 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14116 window,
14117 cx,
14118 ))
14119 }
14120
14121 fn perform_code_action_kind(
14122 &mut self,
14123 project: Entity<Project>,
14124 kind: CodeActionKind,
14125 window: &mut Window,
14126 cx: &mut Context<Self>,
14127 ) -> Task<Result<()>> {
14128 let buffer = self.buffer.clone();
14129 let buffers = buffer.read(cx).all_buffers();
14130 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14131 let apply_action = project.update(cx, |project, cx| {
14132 project.apply_code_action_kind(buffers, kind, true, cx)
14133 });
14134 cx.spawn_in(window, async move |_, cx| {
14135 let transaction = futures::select_biased! {
14136 () = timeout => {
14137 log::warn!("timed out waiting for executing code action");
14138 None
14139 }
14140 transaction = apply_action.log_err().fuse() => transaction,
14141 };
14142 buffer
14143 .update(cx, |buffer, cx| {
14144 // check if we need this
14145 if let Some(transaction) = transaction {
14146 if !buffer.is_singleton() {
14147 buffer.push_transaction(&transaction.0, cx);
14148 }
14149 }
14150 cx.notify();
14151 })
14152 .ok();
14153 Ok(())
14154 })
14155 }
14156
14157 fn restart_language_server(
14158 &mut self,
14159 _: &RestartLanguageServer,
14160 _: &mut Window,
14161 cx: &mut Context<Self>,
14162 ) {
14163 if let Some(project) = self.project.clone() {
14164 self.buffer.update(cx, |multi_buffer, cx| {
14165 project.update(cx, |project, cx| {
14166 project.restart_language_servers_for_buffers(
14167 multi_buffer.all_buffers().into_iter().collect(),
14168 cx,
14169 );
14170 });
14171 })
14172 }
14173 }
14174
14175 fn cancel_language_server_work(
14176 workspace: &mut Workspace,
14177 _: &actions::CancelLanguageServerWork,
14178 _: &mut Window,
14179 cx: &mut Context<Workspace>,
14180 ) {
14181 let project = workspace.project();
14182 let buffers = workspace
14183 .active_item(cx)
14184 .and_then(|item| item.act_as::<Editor>(cx))
14185 .map_or(HashSet::default(), |editor| {
14186 editor.read(cx).buffer.read(cx).all_buffers()
14187 });
14188 project.update(cx, |project, cx| {
14189 project.cancel_language_server_work_for_buffers(buffers, cx);
14190 });
14191 }
14192
14193 fn show_character_palette(
14194 &mut self,
14195 _: &ShowCharacterPalette,
14196 window: &mut Window,
14197 _: &mut Context<Self>,
14198 ) {
14199 window.show_character_palette();
14200 }
14201
14202 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14203 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
14204 let buffer = self.buffer.read(cx).snapshot(cx);
14205 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
14206 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
14207 let is_valid = buffer
14208 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14209 .any(|entry| {
14210 entry.diagnostic.is_primary
14211 && !entry.range.is_empty()
14212 && entry.range.start == primary_range_start
14213 && entry.diagnostic.message == active_diagnostics.primary_message
14214 });
14215
14216 if is_valid != active_diagnostics.is_valid {
14217 active_diagnostics.is_valid = is_valid;
14218 if is_valid {
14219 let mut new_styles = HashMap::default();
14220 for (block_id, diagnostic) in &active_diagnostics.blocks {
14221 new_styles.insert(
14222 *block_id,
14223 diagnostic_block_renderer(diagnostic.clone(), None, true),
14224 );
14225 }
14226 self.display_map.update(cx, |display_map, _cx| {
14227 display_map.replace_blocks(new_styles);
14228 });
14229 } else {
14230 self.dismiss_diagnostics(cx);
14231 }
14232 }
14233 }
14234 }
14235
14236 fn activate_diagnostics(
14237 &mut self,
14238 buffer_id: BufferId,
14239 group_id: usize,
14240 window: &mut Window,
14241 cx: &mut Context<Self>,
14242 ) {
14243 self.dismiss_diagnostics(cx);
14244 let snapshot = self.snapshot(window, cx);
14245 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
14246 let buffer = self.buffer.read(cx).snapshot(cx);
14247
14248 let mut primary_range = None;
14249 let mut primary_message = None;
14250 let diagnostic_group = buffer
14251 .diagnostic_group(buffer_id, group_id)
14252 .filter_map(|entry| {
14253 let start = entry.range.start;
14254 let end = entry.range.end;
14255 if snapshot.is_line_folded(MultiBufferRow(start.row))
14256 && (start.row == end.row
14257 || snapshot.is_line_folded(MultiBufferRow(end.row)))
14258 {
14259 return None;
14260 }
14261 if entry.diagnostic.is_primary {
14262 primary_range = Some(entry.range.clone());
14263 primary_message = Some(entry.diagnostic.message.clone());
14264 }
14265 Some(entry)
14266 })
14267 .collect::<Vec<_>>();
14268 let primary_range = primary_range?;
14269 let primary_message = primary_message?;
14270
14271 let blocks = display_map
14272 .insert_blocks(
14273 diagnostic_group.iter().map(|entry| {
14274 let diagnostic = entry.diagnostic.clone();
14275 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
14276 BlockProperties {
14277 style: BlockStyle::Fixed,
14278 placement: BlockPlacement::Below(
14279 buffer.anchor_after(entry.range.start),
14280 ),
14281 height: Some(message_height),
14282 render: diagnostic_block_renderer(diagnostic, None, true),
14283 priority: 0,
14284 }
14285 }),
14286 cx,
14287 )
14288 .into_iter()
14289 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
14290 .collect();
14291
14292 Some(ActiveDiagnosticGroup {
14293 primary_range: buffer.anchor_before(primary_range.start)
14294 ..buffer.anchor_after(primary_range.end),
14295 primary_message,
14296 group_id,
14297 blocks,
14298 is_valid: true,
14299 })
14300 });
14301 }
14302
14303 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14304 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
14305 self.display_map.update(cx, |display_map, cx| {
14306 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
14307 });
14308 cx.notify();
14309 }
14310 }
14311
14312 /// Disable inline diagnostics rendering for this editor.
14313 pub fn disable_inline_diagnostics(&mut self) {
14314 self.inline_diagnostics_enabled = false;
14315 self.inline_diagnostics_update = Task::ready(());
14316 self.inline_diagnostics.clear();
14317 }
14318
14319 pub fn inline_diagnostics_enabled(&self) -> bool {
14320 self.inline_diagnostics_enabled
14321 }
14322
14323 pub fn show_inline_diagnostics(&self) -> bool {
14324 self.show_inline_diagnostics
14325 }
14326
14327 pub fn toggle_inline_diagnostics(
14328 &mut self,
14329 _: &ToggleInlineDiagnostics,
14330 window: &mut Window,
14331 cx: &mut Context<Editor>,
14332 ) {
14333 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14334 self.refresh_inline_diagnostics(false, window, cx);
14335 }
14336
14337 fn refresh_inline_diagnostics(
14338 &mut self,
14339 debounce: bool,
14340 window: &mut Window,
14341 cx: &mut Context<Self>,
14342 ) {
14343 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14344 self.inline_diagnostics_update = Task::ready(());
14345 self.inline_diagnostics.clear();
14346 return;
14347 }
14348
14349 let debounce_ms = ProjectSettings::get_global(cx)
14350 .diagnostics
14351 .inline
14352 .update_debounce_ms;
14353 let debounce = if debounce && debounce_ms > 0 {
14354 Some(Duration::from_millis(debounce_ms))
14355 } else {
14356 None
14357 };
14358 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14359 if let Some(debounce) = debounce {
14360 cx.background_executor().timer(debounce).await;
14361 }
14362 let Some(snapshot) = editor
14363 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14364 .ok()
14365 else {
14366 return;
14367 };
14368
14369 let new_inline_diagnostics = cx
14370 .background_spawn(async move {
14371 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14372 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14373 let message = diagnostic_entry
14374 .diagnostic
14375 .message
14376 .split_once('\n')
14377 .map(|(line, _)| line)
14378 .map(SharedString::new)
14379 .unwrap_or_else(|| {
14380 SharedString::from(diagnostic_entry.diagnostic.message)
14381 });
14382 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14383 let (Ok(i) | Err(i)) = inline_diagnostics
14384 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14385 inline_diagnostics.insert(
14386 i,
14387 (
14388 start_anchor,
14389 InlineDiagnostic {
14390 message,
14391 group_id: diagnostic_entry.diagnostic.group_id,
14392 start: diagnostic_entry.range.start.to_point(&snapshot),
14393 is_primary: diagnostic_entry.diagnostic.is_primary,
14394 severity: diagnostic_entry.diagnostic.severity,
14395 },
14396 ),
14397 );
14398 }
14399 inline_diagnostics
14400 })
14401 .await;
14402
14403 editor
14404 .update(cx, |editor, cx| {
14405 editor.inline_diagnostics = new_inline_diagnostics;
14406 cx.notify();
14407 })
14408 .ok();
14409 });
14410 }
14411
14412 pub fn set_selections_from_remote(
14413 &mut self,
14414 selections: Vec<Selection<Anchor>>,
14415 pending_selection: Option<Selection<Anchor>>,
14416 window: &mut Window,
14417 cx: &mut Context<Self>,
14418 ) {
14419 let old_cursor_position = self.selections.newest_anchor().head();
14420 self.selections.change_with(cx, |s| {
14421 s.select_anchors(selections);
14422 if let Some(pending_selection) = pending_selection {
14423 s.set_pending(pending_selection, SelectMode::Character);
14424 } else {
14425 s.clear_pending();
14426 }
14427 });
14428 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14429 }
14430
14431 fn push_to_selection_history(&mut self) {
14432 self.selection_history.push(SelectionHistoryEntry {
14433 selections: self.selections.disjoint_anchors(),
14434 select_next_state: self.select_next_state.clone(),
14435 select_prev_state: self.select_prev_state.clone(),
14436 add_selections_state: self.add_selections_state.clone(),
14437 });
14438 }
14439
14440 pub fn transact(
14441 &mut self,
14442 window: &mut Window,
14443 cx: &mut Context<Self>,
14444 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14445 ) -> Option<TransactionId> {
14446 self.start_transaction_at(Instant::now(), window, cx);
14447 update(self, window, cx);
14448 self.end_transaction_at(Instant::now(), cx)
14449 }
14450
14451 pub fn start_transaction_at(
14452 &mut self,
14453 now: Instant,
14454 window: &mut Window,
14455 cx: &mut Context<Self>,
14456 ) {
14457 self.end_selection(window, cx);
14458 if let Some(tx_id) = self
14459 .buffer
14460 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14461 {
14462 self.selection_history
14463 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14464 cx.emit(EditorEvent::TransactionBegun {
14465 transaction_id: tx_id,
14466 })
14467 }
14468 }
14469
14470 pub fn end_transaction_at(
14471 &mut self,
14472 now: Instant,
14473 cx: &mut Context<Self>,
14474 ) -> Option<TransactionId> {
14475 if let Some(transaction_id) = self
14476 .buffer
14477 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14478 {
14479 if let Some((_, end_selections)) =
14480 self.selection_history.transaction_mut(transaction_id)
14481 {
14482 *end_selections = Some(self.selections.disjoint_anchors());
14483 } else {
14484 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14485 }
14486
14487 cx.emit(EditorEvent::Edited { transaction_id });
14488 Some(transaction_id)
14489 } else {
14490 None
14491 }
14492 }
14493
14494 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14495 if self.selection_mark_mode {
14496 self.change_selections(None, window, cx, |s| {
14497 s.move_with(|_, sel| {
14498 sel.collapse_to(sel.head(), SelectionGoal::None);
14499 });
14500 })
14501 }
14502 self.selection_mark_mode = true;
14503 cx.notify();
14504 }
14505
14506 pub fn swap_selection_ends(
14507 &mut self,
14508 _: &actions::SwapSelectionEnds,
14509 window: &mut Window,
14510 cx: &mut Context<Self>,
14511 ) {
14512 self.change_selections(None, window, cx, |s| {
14513 s.move_with(|_, sel| {
14514 if sel.start != sel.end {
14515 sel.reversed = !sel.reversed
14516 }
14517 });
14518 });
14519 self.request_autoscroll(Autoscroll::newest(), cx);
14520 cx.notify();
14521 }
14522
14523 pub fn toggle_fold(
14524 &mut self,
14525 _: &actions::ToggleFold,
14526 window: &mut Window,
14527 cx: &mut Context<Self>,
14528 ) {
14529 if self.is_singleton(cx) {
14530 let selection = self.selections.newest::<Point>(cx);
14531
14532 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14533 let range = if selection.is_empty() {
14534 let point = selection.head().to_display_point(&display_map);
14535 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14536 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14537 .to_point(&display_map);
14538 start..end
14539 } else {
14540 selection.range()
14541 };
14542 if display_map.folds_in_range(range).next().is_some() {
14543 self.unfold_lines(&Default::default(), window, cx)
14544 } else {
14545 self.fold(&Default::default(), window, cx)
14546 }
14547 } else {
14548 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14549 let buffer_ids: HashSet<_> = self
14550 .selections
14551 .disjoint_anchor_ranges()
14552 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14553 .collect();
14554
14555 let should_unfold = buffer_ids
14556 .iter()
14557 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14558
14559 for buffer_id in buffer_ids {
14560 if should_unfold {
14561 self.unfold_buffer(buffer_id, cx);
14562 } else {
14563 self.fold_buffer(buffer_id, cx);
14564 }
14565 }
14566 }
14567 }
14568
14569 pub fn toggle_fold_recursive(
14570 &mut self,
14571 _: &actions::ToggleFoldRecursive,
14572 window: &mut Window,
14573 cx: &mut Context<Self>,
14574 ) {
14575 let selection = self.selections.newest::<Point>(cx);
14576
14577 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14578 let range = if selection.is_empty() {
14579 let point = selection.head().to_display_point(&display_map);
14580 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14581 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14582 .to_point(&display_map);
14583 start..end
14584 } else {
14585 selection.range()
14586 };
14587 if display_map.folds_in_range(range).next().is_some() {
14588 self.unfold_recursive(&Default::default(), window, cx)
14589 } else {
14590 self.fold_recursive(&Default::default(), window, cx)
14591 }
14592 }
14593
14594 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14595 if self.is_singleton(cx) {
14596 let mut to_fold = Vec::new();
14597 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14598 let selections = self.selections.all_adjusted(cx);
14599
14600 for selection in selections {
14601 let range = selection.range().sorted();
14602 let buffer_start_row = range.start.row;
14603
14604 if range.start.row != range.end.row {
14605 let mut found = false;
14606 let mut row = range.start.row;
14607 while row <= range.end.row {
14608 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14609 {
14610 found = true;
14611 row = crease.range().end.row + 1;
14612 to_fold.push(crease);
14613 } else {
14614 row += 1
14615 }
14616 }
14617 if found {
14618 continue;
14619 }
14620 }
14621
14622 for row in (0..=range.start.row).rev() {
14623 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14624 if crease.range().end.row >= buffer_start_row {
14625 to_fold.push(crease);
14626 if row <= range.start.row {
14627 break;
14628 }
14629 }
14630 }
14631 }
14632 }
14633
14634 self.fold_creases(to_fold, true, window, cx);
14635 } else {
14636 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14637 let buffer_ids = self
14638 .selections
14639 .disjoint_anchor_ranges()
14640 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14641 .collect::<HashSet<_>>();
14642 for buffer_id in buffer_ids {
14643 self.fold_buffer(buffer_id, cx);
14644 }
14645 }
14646 }
14647
14648 fn fold_at_level(
14649 &mut self,
14650 fold_at: &FoldAtLevel,
14651 window: &mut Window,
14652 cx: &mut Context<Self>,
14653 ) {
14654 if !self.buffer.read(cx).is_singleton() {
14655 return;
14656 }
14657
14658 let fold_at_level = fold_at.0;
14659 let snapshot = self.buffer.read(cx).snapshot(cx);
14660 let mut to_fold = Vec::new();
14661 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14662
14663 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14664 while start_row < end_row {
14665 match self
14666 .snapshot(window, cx)
14667 .crease_for_buffer_row(MultiBufferRow(start_row))
14668 {
14669 Some(crease) => {
14670 let nested_start_row = crease.range().start.row + 1;
14671 let nested_end_row = crease.range().end.row;
14672
14673 if current_level < fold_at_level {
14674 stack.push((nested_start_row, nested_end_row, current_level + 1));
14675 } else if current_level == fold_at_level {
14676 to_fold.push(crease);
14677 }
14678
14679 start_row = nested_end_row + 1;
14680 }
14681 None => start_row += 1,
14682 }
14683 }
14684 }
14685
14686 self.fold_creases(to_fold, true, window, cx);
14687 }
14688
14689 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14690 if self.buffer.read(cx).is_singleton() {
14691 let mut fold_ranges = Vec::new();
14692 let snapshot = self.buffer.read(cx).snapshot(cx);
14693
14694 for row in 0..snapshot.max_row().0 {
14695 if let Some(foldable_range) = self
14696 .snapshot(window, cx)
14697 .crease_for_buffer_row(MultiBufferRow(row))
14698 {
14699 fold_ranges.push(foldable_range);
14700 }
14701 }
14702
14703 self.fold_creases(fold_ranges, true, window, cx);
14704 } else {
14705 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14706 editor
14707 .update_in(cx, |editor, _, cx| {
14708 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14709 editor.fold_buffer(buffer_id, cx);
14710 }
14711 })
14712 .ok();
14713 });
14714 }
14715 }
14716
14717 pub fn fold_function_bodies(
14718 &mut self,
14719 _: &actions::FoldFunctionBodies,
14720 window: &mut Window,
14721 cx: &mut Context<Self>,
14722 ) {
14723 let snapshot = self.buffer.read(cx).snapshot(cx);
14724
14725 let ranges = snapshot
14726 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14727 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14728 .collect::<Vec<_>>();
14729
14730 let creases = ranges
14731 .into_iter()
14732 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14733 .collect();
14734
14735 self.fold_creases(creases, true, window, cx);
14736 }
14737
14738 pub fn fold_recursive(
14739 &mut self,
14740 _: &actions::FoldRecursive,
14741 window: &mut Window,
14742 cx: &mut Context<Self>,
14743 ) {
14744 let mut to_fold = Vec::new();
14745 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14746 let selections = self.selections.all_adjusted(cx);
14747
14748 for selection in selections {
14749 let range = selection.range().sorted();
14750 let buffer_start_row = range.start.row;
14751
14752 if range.start.row != range.end.row {
14753 let mut found = false;
14754 for row in range.start.row..=range.end.row {
14755 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14756 found = true;
14757 to_fold.push(crease);
14758 }
14759 }
14760 if found {
14761 continue;
14762 }
14763 }
14764
14765 for row in (0..=range.start.row).rev() {
14766 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14767 if crease.range().end.row >= buffer_start_row {
14768 to_fold.push(crease);
14769 } else {
14770 break;
14771 }
14772 }
14773 }
14774 }
14775
14776 self.fold_creases(to_fold, true, window, cx);
14777 }
14778
14779 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14780 let buffer_row = fold_at.buffer_row;
14781 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14782
14783 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14784 let autoscroll = self
14785 .selections
14786 .all::<Point>(cx)
14787 .iter()
14788 .any(|selection| crease.range().overlaps(&selection.range()));
14789
14790 self.fold_creases(vec![crease], autoscroll, window, cx);
14791 }
14792 }
14793
14794 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14795 if self.is_singleton(cx) {
14796 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14797 let buffer = &display_map.buffer_snapshot;
14798 let selections = self.selections.all::<Point>(cx);
14799 let ranges = selections
14800 .iter()
14801 .map(|s| {
14802 let range = s.display_range(&display_map).sorted();
14803 let mut start = range.start.to_point(&display_map);
14804 let mut end = range.end.to_point(&display_map);
14805 start.column = 0;
14806 end.column = buffer.line_len(MultiBufferRow(end.row));
14807 start..end
14808 })
14809 .collect::<Vec<_>>();
14810
14811 self.unfold_ranges(&ranges, true, true, cx);
14812 } else {
14813 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14814 let buffer_ids = self
14815 .selections
14816 .disjoint_anchor_ranges()
14817 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14818 .collect::<HashSet<_>>();
14819 for buffer_id in buffer_ids {
14820 self.unfold_buffer(buffer_id, cx);
14821 }
14822 }
14823 }
14824
14825 pub fn unfold_recursive(
14826 &mut self,
14827 _: &UnfoldRecursive,
14828 _window: &mut Window,
14829 cx: &mut Context<Self>,
14830 ) {
14831 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14832 let selections = self.selections.all::<Point>(cx);
14833 let ranges = selections
14834 .iter()
14835 .map(|s| {
14836 let mut range = s.display_range(&display_map).sorted();
14837 *range.start.column_mut() = 0;
14838 *range.end.column_mut() = display_map.line_len(range.end.row());
14839 let start = range.start.to_point(&display_map);
14840 let end = range.end.to_point(&display_map);
14841 start..end
14842 })
14843 .collect::<Vec<_>>();
14844
14845 self.unfold_ranges(&ranges, true, true, cx);
14846 }
14847
14848 pub fn unfold_at(
14849 &mut self,
14850 unfold_at: &UnfoldAt,
14851 _window: &mut Window,
14852 cx: &mut Context<Self>,
14853 ) {
14854 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14855
14856 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14857 ..Point::new(
14858 unfold_at.buffer_row.0,
14859 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14860 );
14861
14862 let autoscroll = self
14863 .selections
14864 .all::<Point>(cx)
14865 .iter()
14866 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14867
14868 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14869 }
14870
14871 pub fn unfold_all(
14872 &mut self,
14873 _: &actions::UnfoldAll,
14874 _window: &mut Window,
14875 cx: &mut Context<Self>,
14876 ) {
14877 if self.buffer.read(cx).is_singleton() {
14878 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14879 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
14880 } else {
14881 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
14882 editor
14883 .update(cx, |editor, cx| {
14884 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14885 editor.unfold_buffer(buffer_id, cx);
14886 }
14887 })
14888 .ok();
14889 });
14890 }
14891 }
14892
14893 pub fn fold_selected_ranges(
14894 &mut self,
14895 _: &FoldSelectedRanges,
14896 window: &mut Window,
14897 cx: &mut Context<Self>,
14898 ) {
14899 let selections = self.selections.all_adjusted(cx);
14900 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14901 let ranges = selections
14902 .into_iter()
14903 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
14904 .collect::<Vec<_>>();
14905 self.fold_creases(ranges, true, window, cx);
14906 }
14907
14908 pub fn fold_ranges<T: ToOffset + Clone>(
14909 &mut self,
14910 ranges: Vec<Range<T>>,
14911 auto_scroll: bool,
14912 window: &mut Window,
14913 cx: &mut Context<Self>,
14914 ) {
14915 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14916 let ranges = ranges
14917 .into_iter()
14918 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
14919 .collect::<Vec<_>>();
14920 self.fold_creases(ranges, auto_scroll, window, cx);
14921 }
14922
14923 pub fn fold_creases<T: ToOffset + Clone>(
14924 &mut self,
14925 creases: Vec<Crease<T>>,
14926 auto_scroll: bool,
14927 window: &mut Window,
14928 cx: &mut Context<Self>,
14929 ) {
14930 if creases.is_empty() {
14931 return;
14932 }
14933
14934 let mut buffers_affected = HashSet::default();
14935 let multi_buffer = self.buffer().read(cx);
14936 for crease in &creases {
14937 if let Some((_, buffer, _)) =
14938 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
14939 {
14940 buffers_affected.insert(buffer.read(cx).remote_id());
14941 };
14942 }
14943
14944 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
14945
14946 if auto_scroll {
14947 self.request_autoscroll(Autoscroll::fit(), cx);
14948 }
14949
14950 cx.notify();
14951
14952 if let Some(active_diagnostics) = self.active_diagnostics.take() {
14953 // Clear diagnostics block when folding a range that contains it.
14954 let snapshot = self.snapshot(window, cx);
14955 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
14956 drop(snapshot);
14957 self.active_diagnostics = Some(active_diagnostics);
14958 self.dismiss_diagnostics(cx);
14959 } else {
14960 self.active_diagnostics = Some(active_diagnostics);
14961 }
14962 }
14963
14964 self.scrollbar_marker_state.dirty = true;
14965 self.folds_did_change(cx);
14966 }
14967
14968 /// Removes any folds whose ranges intersect any of the given ranges.
14969 pub fn unfold_ranges<T: ToOffset + Clone>(
14970 &mut self,
14971 ranges: &[Range<T>],
14972 inclusive: bool,
14973 auto_scroll: bool,
14974 cx: &mut Context<Self>,
14975 ) {
14976 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14977 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
14978 });
14979 self.folds_did_change(cx);
14980 }
14981
14982 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14983 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
14984 return;
14985 }
14986 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14987 self.display_map.update(cx, |display_map, cx| {
14988 display_map.fold_buffers([buffer_id], cx)
14989 });
14990 cx.emit(EditorEvent::BufferFoldToggled {
14991 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
14992 folded: true,
14993 });
14994 cx.notify();
14995 }
14996
14997 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14998 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
14999 return;
15000 }
15001 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15002 self.display_map.update(cx, |display_map, cx| {
15003 display_map.unfold_buffers([buffer_id], cx);
15004 });
15005 cx.emit(EditorEvent::BufferFoldToggled {
15006 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15007 folded: false,
15008 });
15009 cx.notify();
15010 }
15011
15012 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15013 self.display_map.read(cx).is_buffer_folded(buffer)
15014 }
15015
15016 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15017 self.display_map.read(cx).folded_buffers()
15018 }
15019
15020 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15021 self.display_map.update(cx, |display_map, cx| {
15022 display_map.disable_header_for_buffer(buffer_id, cx);
15023 });
15024 cx.notify();
15025 }
15026
15027 /// Removes any folds with the given ranges.
15028 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15029 &mut self,
15030 ranges: &[Range<T>],
15031 type_id: TypeId,
15032 auto_scroll: bool,
15033 cx: &mut Context<Self>,
15034 ) {
15035 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15036 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15037 });
15038 self.folds_did_change(cx);
15039 }
15040
15041 fn remove_folds_with<T: ToOffset + Clone>(
15042 &mut self,
15043 ranges: &[Range<T>],
15044 auto_scroll: bool,
15045 cx: &mut Context<Self>,
15046 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15047 ) {
15048 if ranges.is_empty() {
15049 return;
15050 }
15051
15052 let mut buffers_affected = HashSet::default();
15053 let multi_buffer = self.buffer().read(cx);
15054 for range in ranges {
15055 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15056 buffers_affected.insert(buffer.read(cx).remote_id());
15057 };
15058 }
15059
15060 self.display_map.update(cx, update);
15061
15062 if auto_scroll {
15063 self.request_autoscroll(Autoscroll::fit(), cx);
15064 }
15065
15066 cx.notify();
15067 self.scrollbar_marker_state.dirty = true;
15068 self.active_indent_guides_state.dirty = true;
15069 }
15070
15071 pub fn update_fold_widths(
15072 &mut self,
15073 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15074 cx: &mut Context<Self>,
15075 ) -> bool {
15076 self.display_map
15077 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15078 }
15079
15080 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15081 self.display_map.read(cx).fold_placeholder.clone()
15082 }
15083
15084 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15085 self.buffer.update(cx, |buffer, cx| {
15086 buffer.set_all_diff_hunks_expanded(cx);
15087 });
15088 }
15089
15090 pub fn expand_all_diff_hunks(
15091 &mut self,
15092 _: &ExpandAllDiffHunks,
15093 _window: &mut Window,
15094 cx: &mut Context<Self>,
15095 ) {
15096 self.buffer.update(cx, |buffer, cx| {
15097 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15098 });
15099 }
15100
15101 pub fn toggle_selected_diff_hunks(
15102 &mut self,
15103 _: &ToggleSelectedDiffHunks,
15104 _window: &mut Window,
15105 cx: &mut Context<Self>,
15106 ) {
15107 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15108 self.toggle_diff_hunks_in_ranges(ranges, cx);
15109 }
15110
15111 pub fn diff_hunks_in_ranges<'a>(
15112 &'a self,
15113 ranges: &'a [Range<Anchor>],
15114 buffer: &'a MultiBufferSnapshot,
15115 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15116 ranges.iter().flat_map(move |range| {
15117 let end_excerpt_id = range.end.excerpt_id;
15118 let range = range.to_point(buffer);
15119 let mut peek_end = range.end;
15120 if range.end.row < buffer.max_row().0 {
15121 peek_end = Point::new(range.end.row + 1, 0);
15122 }
15123 buffer
15124 .diff_hunks_in_range(range.start..peek_end)
15125 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15126 })
15127 }
15128
15129 pub fn has_stageable_diff_hunks_in_ranges(
15130 &self,
15131 ranges: &[Range<Anchor>],
15132 snapshot: &MultiBufferSnapshot,
15133 ) -> bool {
15134 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15135 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15136 }
15137
15138 pub fn toggle_staged_selected_diff_hunks(
15139 &mut self,
15140 _: &::git::ToggleStaged,
15141 _: &mut Window,
15142 cx: &mut Context<Self>,
15143 ) {
15144 let snapshot = self.buffer.read(cx).snapshot(cx);
15145 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15146 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15147 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15148 }
15149
15150 pub fn set_render_diff_hunk_controls(
15151 &mut self,
15152 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15153 cx: &mut Context<Self>,
15154 ) {
15155 self.render_diff_hunk_controls = render_diff_hunk_controls;
15156 cx.notify();
15157 }
15158
15159 pub fn stage_and_next(
15160 &mut self,
15161 _: &::git::StageAndNext,
15162 window: &mut Window,
15163 cx: &mut Context<Self>,
15164 ) {
15165 self.do_stage_or_unstage_and_next(true, window, cx);
15166 }
15167
15168 pub fn unstage_and_next(
15169 &mut self,
15170 _: &::git::UnstageAndNext,
15171 window: &mut Window,
15172 cx: &mut Context<Self>,
15173 ) {
15174 self.do_stage_or_unstage_and_next(false, window, cx);
15175 }
15176
15177 pub fn stage_or_unstage_diff_hunks(
15178 &mut self,
15179 stage: bool,
15180 ranges: Vec<Range<Anchor>>,
15181 cx: &mut Context<Self>,
15182 ) {
15183 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15184 cx.spawn(async move |this, cx| {
15185 task.await?;
15186 this.update(cx, |this, cx| {
15187 let snapshot = this.buffer.read(cx).snapshot(cx);
15188 let chunk_by = this
15189 .diff_hunks_in_ranges(&ranges, &snapshot)
15190 .chunk_by(|hunk| hunk.buffer_id);
15191 for (buffer_id, hunks) in &chunk_by {
15192 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15193 }
15194 })
15195 })
15196 .detach_and_log_err(cx);
15197 }
15198
15199 fn save_buffers_for_ranges_if_needed(
15200 &mut self,
15201 ranges: &[Range<Anchor>],
15202 cx: &mut Context<Editor>,
15203 ) -> Task<Result<()>> {
15204 let multibuffer = self.buffer.read(cx);
15205 let snapshot = multibuffer.read(cx);
15206 let buffer_ids: HashSet<_> = ranges
15207 .iter()
15208 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15209 .collect();
15210 drop(snapshot);
15211
15212 let mut buffers = HashSet::default();
15213 for buffer_id in buffer_ids {
15214 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15215 let buffer = buffer_entity.read(cx);
15216 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15217 {
15218 buffers.insert(buffer_entity);
15219 }
15220 }
15221 }
15222
15223 if let Some(project) = &self.project {
15224 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15225 } else {
15226 Task::ready(Ok(()))
15227 }
15228 }
15229
15230 fn do_stage_or_unstage_and_next(
15231 &mut self,
15232 stage: bool,
15233 window: &mut Window,
15234 cx: &mut Context<Self>,
15235 ) {
15236 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15237
15238 if ranges.iter().any(|range| range.start != range.end) {
15239 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15240 return;
15241 }
15242
15243 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15244 let snapshot = self.snapshot(window, cx);
15245 let position = self.selections.newest::<Point>(cx).head();
15246 let mut row = snapshot
15247 .buffer_snapshot
15248 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15249 .find(|hunk| hunk.row_range.start.0 > position.row)
15250 .map(|hunk| hunk.row_range.start);
15251
15252 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15253 // Outside of the project diff editor, wrap around to the beginning.
15254 if !all_diff_hunks_expanded {
15255 row = row.or_else(|| {
15256 snapshot
15257 .buffer_snapshot
15258 .diff_hunks_in_range(Point::zero()..position)
15259 .find(|hunk| hunk.row_range.end.0 < position.row)
15260 .map(|hunk| hunk.row_range.start)
15261 });
15262 }
15263
15264 if let Some(row) = row {
15265 let destination = Point::new(row.0, 0);
15266 let autoscroll = Autoscroll::center();
15267
15268 self.unfold_ranges(&[destination..destination], false, false, cx);
15269 self.change_selections(Some(autoscroll), window, cx, |s| {
15270 s.select_ranges([destination..destination]);
15271 });
15272 }
15273 }
15274
15275 fn do_stage_or_unstage(
15276 &self,
15277 stage: bool,
15278 buffer_id: BufferId,
15279 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15280 cx: &mut App,
15281 ) -> Option<()> {
15282 let project = self.project.as_ref()?;
15283 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15284 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15285 let buffer_snapshot = buffer.read(cx).snapshot();
15286 let file_exists = buffer_snapshot
15287 .file()
15288 .is_some_and(|file| file.disk_state().exists());
15289 diff.update(cx, |diff, cx| {
15290 diff.stage_or_unstage_hunks(
15291 stage,
15292 &hunks
15293 .map(|hunk| buffer_diff::DiffHunk {
15294 buffer_range: hunk.buffer_range,
15295 diff_base_byte_range: hunk.diff_base_byte_range,
15296 secondary_status: hunk.secondary_status,
15297 range: Point::zero()..Point::zero(), // unused
15298 })
15299 .collect::<Vec<_>>(),
15300 &buffer_snapshot,
15301 file_exists,
15302 cx,
15303 )
15304 });
15305 None
15306 }
15307
15308 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15309 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15310 self.buffer
15311 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15312 }
15313
15314 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15315 self.buffer.update(cx, |buffer, cx| {
15316 let ranges = vec![Anchor::min()..Anchor::max()];
15317 if !buffer.all_diff_hunks_expanded()
15318 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15319 {
15320 buffer.collapse_diff_hunks(ranges, cx);
15321 true
15322 } else {
15323 false
15324 }
15325 })
15326 }
15327
15328 fn toggle_diff_hunks_in_ranges(
15329 &mut self,
15330 ranges: Vec<Range<Anchor>>,
15331 cx: &mut Context<Editor>,
15332 ) {
15333 self.buffer.update(cx, |buffer, cx| {
15334 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15335 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15336 })
15337 }
15338
15339 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15340 self.buffer.update(cx, |buffer, cx| {
15341 let snapshot = buffer.snapshot(cx);
15342 let excerpt_id = range.end.excerpt_id;
15343 let point_range = range.to_point(&snapshot);
15344 let expand = !buffer.single_hunk_is_expanded(range, cx);
15345 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15346 })
15347 }
15348
15349 pub(crate) fn apply_all_diff_hunks(
15350 &mut self,
15351 _: &ApplyAllDiffHunks,
15352 window: &mut Window,
15353 cx: &mut Context<Self>,
15354 ) {
15355 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15356
15357 let buffers = self.buffer.read(cx).all_buffers();
15358 for branch_buffer in buffers {
15359 branch_buffer.update(cx, |branch_buffer, cx| {
15360 branch_buffer.merge_into_base(Vec::new(), cx);
15361 });
15362 }
15363
15364 if let Some(project) = self.project.clone() {
15365 self.save(true, project, window, cx).detach_and_log_err(cx);
15366 }
15367 }
15368
15369 pub(crate) fn apply_selected_diff_hunks(
15370 &mut self,
15371 _: &ApplyDiffHunk,
15372 window: &mut Window,
15373 cx: &mut Context<Self>,
15374 ) {
15375 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15376 let snapshot = self.snapshot(window, cx);
15377 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15378 let mut ranges_by_buffer = HashMap::default();
15379 self.transact(window, cx, |editor, _window, cx| {
15380 for hunk in hunks {
15381 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15382 ranges_by_buffer
15383 .entry(buffer.clone())
15384 .or_insert_with(Vec::new)
15385 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15386 }
15387 }
15388
15389 for (buffer, ranges) in ranges_by_buffer {
15390 buffer.update(cx, |buffer, cx| {
15391 buffer.merge_into_base(ranges, cx);
15392 });
15393 }
15394 });
15395
15396 if let Some(project) = self.project.clone() {
15397 self.save(true, project, window, cx).detach_and_log_err(cx);
15398 }
15399 }
15400
15401 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15402 if hovered != self.gutter_hovered {
15403 self.gutter_hovered = hovered;
15404 cx.notify();
15405 }
15406 }
15407
15408 pub fn insert_blocks(
15409 &mut self,
15410 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15411 autoscroll: Option<Autoscroll>,
15412 cx: &mut Context<Self>,
15413 ) -> Vec<CustomBlockId> {
15414 let blocks = self
15415 .display_map
15416 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15417 if let Some(autoscroll) = autoscroll {
15418 self.request_autoscroll(autoscroll, cx);
15419 }
15420 cx.notify();
15421 blocks
15422 }
15423
15424 pub fn resize_blocks(
15425 &mut self,
15426 heights: HashMap<CustomBlockId, u32>,
15427 autoscroll: Option<Autoscroll>,
15428 cx: &mut Context<Self>,
15429 ) {
15430 self.display_map
15431 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15432 if let Some(autoscroll) = autoscroll {
15433 self.request_autoscroll(autoscroll, cx);
15434 }
15435 cx.notify();
15436 }
15437
15438 pub fn replace_blocks(
15439 &mut self,
15440 renderers: HashMap<CustomBlockId, RenderBlock>,
15441 autoscroll: Option<Autoscroll>,
15442 cx: &mut Context<Self>,
15443 ) {
15444 self.display_map
15445 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15446 if let Some(autoscroll) = autoscroll {
15447 self.request_autoscroll(autoscroll, cx);
15448 }
15449 cx.notify();
15450 }
15451
15452 pub fn remove_blocks(
15453 &mut self,
15454 block_ids: HashSet<CustomBlockId>,
15455 autoscroll: Option<Autoscroll>,
15456 cx: &mut Context<Self>,
15457 ) {
15458 self.display_map.update(cx, |display_map, cx| {
15459 display_map.remove_blocks(block_ids, cx)
15460 });
15461 if let Some(autoscroll) = autoscroll {
15462 self.request_autoscroll(autoscroll, cx);
15463 }
15464 cx.notify();
15465 }
15466
15467 pub fn row_for_block(
15468 &self,
15469 block_id: CustomBlockId,
15470 cx: &mut Context<Self>,
15471 ) -> Option<DisplayRow> {
15472 self.display_map
15473 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15474 }
15475
15476 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15477 self.focused_block = Some(focused_block);
15478 }
15479
15480 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15481 self.focused_block.take()
15482 }
15483
15484 pub fn insert_creases(
15485 &mut self,
15486 creases: impl IntoIterator<Item = Crease<Anchor>>,
15487 cx: &mut Context<Self>,
15488 ) -> Vec<CreaseId> {
15489 self.display_map
15490 .update(cx, |map, cx| map.insert_creases(creases, cx))
15491 }
15492
15493 pub fn remove_creases(
15494 &mut self,
15495 ids: impl IntoIterator<Item = CreaseId>,
15496 cx: &mut Context<Self>,
15497 ) {
15498 self.display_map
15499 .update(cx, |map, cx| map.remove_creases(ids, cx));
15500 }
15501
15502 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15503 self.display_map
15504 .update(cx, |map, cx| map.snapshot(cx))
15505 .longest_row()
15506 }
15507
15508 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15509 self.display_map
15510 .update(cx, |map, cx| map.snapshot(cx))
15511 .max_point()
15512 }
15513
15514 pub fn text(&self, cx: &App) -> String {
15515 self.buffer.read(cx).read(cx).text()
15516 }
15517
15518 pub fn is_empty(&self, cx: &App) -> bool {
15519 self.buffer.read(cx).read(cx).is_empty()
15520 }
15521
15522 pub fn text_option(&self, cx: &App) -> Option<String> {
15523 let text = self.text(cx);
15524 let text = text.trim();
15525
15526 if text.is_empty() {
15527 return None;
15528 }
15529
15530 Some(text.to_string())
15531 }
15532
15533 pub fn set_text(
15534 &mut self,
15535 text: impl Into<Arc<str>>,
15536 window: &mut Window,
15537 cx: &mut Context<Self>,
15538 ) {
15539 self.transact(window, cx, |this, _, cx| {
15540 this.buffer
15541 .read(cx)
15542 .as_singleton()
15543 .expect("you can only call set_text on editors for singleton buffers")
15544 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15545 });
15546 }
15547
15548 pub fn display_text(&self, cx: &mut App) -> String {
15549 self.display_map
15550 .update(cx, |map, cx| map.snapshot(cx))
15551 .text()
15552 }
15553
15554 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15555 let mut wrap_guides = smallvec::smallvec![];
15556
15557 if self.show_wrap_guides == Some(false) {
15558 return wrap_guides;
15559 }
15560
15561 let settings = self.buffer.read(cx).language_settings(cx);
15562 if settings.show_wrap_guides {
15563 match self.soft_wrap_mode(cx) {
15564 SoftWrap::Column(soft_wrap) => {
15565 wrap_guides.push((soft_wrap as usize, true));
15566 }
15567 SoftWrap::Bounded(soft_wrap) => {
15568 wrap_guides.push((soft_wrap as usize, true));
15569 }
15570 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15571 }
15572 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15573 }
15574
15575 wrap_guides
15576 }
15577
15578 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15579 let settings = self.buffer.read(cx).language_settings(cx);
15580 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15581 match mode {
15582 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15583 SoftWrap::None
15584 }
15585 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15586 language_settings::SoftWrap::PreferredLineLength => {
15587 SoftWrap::Column(settings.preferred_line_length)
15588 }
15589 language_settings::SoftWrap::Bounded => {
15590 SoftWrap::Bounded(settings.preferred_line_length)
15591 }
15592 }
15593 }
15594
15595 pub fn set_soft_wrap_mode(
15596 &mut self,
15597 mode: language_settings::SoftWrap,
15598
15599 cx: &mut Context<Self>,
15600 ) {
15601 self.soft_wrap_mode_override = Some(mode);
15602 cx.notify();
15603 }
15604
15605 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15606 self.hard_wrap = hard_wrap;
15607 cx.notify();
15608 }
15609
15610 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15611 self.text_style_refinement = Some(style);
15612 }
15613
15614 /// called by the Element so we know what style we were most recently rendered with.
15615 pub(crate) fn set_style(
15616 &mut self,
15617 style: EditorStyle,
15618 window: &mut Window,
15619 cx: &mut Context<Self>,
15620 ) {
15621 let rem_size = window.rem_size();
15622 self.display_map.update(cx, |map, cx| {
15623 map.set_font(
15624 style.text.font(),
15625 style.text.font_size.to_pixels(rem_size),
15626 cx,
15627 )
15628 });
15629 self.style = Some(style);
15630 }
15631
15632 pub fn style(&self) -> Option<&EditorStyle> {
15633 self.style.as_ref()
15634 }
15635
15636 // Called by the element. This method is not designed to be called outside of the editor
15637 // element's layout code because it does not notify when rewrapping is computed synchronously.
15638 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15639 self.display_map
15640 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15641 }
15642
15643 pub fn set_soft_wrap(&mut self) {
15644 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15645 }
15646
15647 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15648 if self.soft_wrap_mode_override.is_some() {
15649 self.soft_wrap_mode_override.take();
15650 } else {
15651 let soft_wrap = match self.soft_wrap_mode(cx) {
15652 SoftWrap::GitDiff => return,
15653 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15654 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15655 language_settings::SoftWrap::None
15656 }
15657 };
15658 self.soft_wrap_mode_override = Some(soft_wrap);
15659 }
15660 cx.notify();
15661 }
15662
15663 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15664 let Some(workspace) = self.workspace() else {
15665 return;
15666 };
15667 let fs = workspace.read(cx).app_state().fs.clone();
15668 let current_show = TabBarSettings::get_global(cx).show;
15669 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15670 setting.show = Some(!current_show);
15671 });
15672 }
15673
15674 pub fn toggle_indent_guides(
15675 &mut self,
15676 _: &ToggleIndentGuides,
15677 _: &mut Window,
15678 cx: &mut Context<Self>,
15679 ) {
15680 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15681 self.buffer
15682 .read(cx)
15683 .language_settings(cx)
15684 .indent_guides
15685 .enabled
15686 });
15687 self.show_indent_guides = Some(!currently_enabled);
15688 cx.notify();
15689 }
15690
15691 fn should_show_indent_guides(&self) -> Option<bool> {
15692 self.show_indent_guides
15693 }
15694
15695 pub fn toggle_line_numbers(
15696 &mut self,
15697 _: &ToggleLineNumbers,
15698 _: &mut Window,
15699 cx: &mut Context<Self>,
15700 ) {
15701 let mut editor_settings = EditorSettings::get_global(cx).clone();
15702 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15703 EditorSettings::override_global(editor_settings, cx);
15704 }
15705
15706 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15707 if let Some(show_line_numbers) = self.show_line_numbers {
15708 return show_line_numbers;
15709 }
15710 EditorSettings::get_global(cx).gutter.line_numbers
15711 }
15712
15713 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15714 self.use_relative_line_numbers
15715 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15716 }
15717
15718 pub fn toggle_relative_line_numbers(
15719 &mut self,
15720 _: &ToggleRelativeLineNumbers,
15721 _: &mut Window,
15722 cx: &mut Context<Self>,
15723 ) {
15724 let is_relative = self.should_use_relative_line_numbers(cx);
15725 self.set_relative_line_number(Some(!is_relative), cx)
15726 }
15727
15728 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15729 self.use_relative_line_numbers = is_relative;
15730 cx.notify();
15731 }
15732
15733 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15734 self.show_gutter = show_gutter;
15735 cx.notify();
15736 }
15737
15738 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15739 self.show_scrollbars = show_scrollbars;
15740 cx.notify();
15741 }
15742
15743 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15744 self.show_line_numbers = Some(show_line_numbers);
15745 cx.notify();
15746 }
15747
15748 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15749 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15750 cx.notify();
15751 }
15752
15753 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15754 self.show_code_actions = Some(show_code_actions);
15755 cx.notify();
15756 }
15757
15758 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15759 self.show_runnables = Some(show_runnables);
15760 cx.notify();
15761 }
15762
15763 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15764 self.show_breakpoints = Some(show_breakpoints);
15765 cx.notify();
15766 }
15767
15768 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15769 if self.display_map.read(cx).masked != masked {
15770 self.display_map.update(cx, |map, _| map.masked = masked);
15771 }
15772 cx.notify()
15773 }
15774
15775 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15776 self.show_wrap_guides = Some(show_wrap_guides);
15777 cx.notify();
15778 }
15779
15780 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15781 self.show_indent_guides = Some(show_indent_guides);
15782 cx.notify();
15783 }
15784
15785 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15786 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15787 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15788 if let Some(dir) = file.abs_path(cx).parent() {
15789 return Some(dir.to_owned());
15790 }
15791 }
15792
15793 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15794 return Some(project_path.path.to_path_buf());
15795 }
15796 }
15797
15798 None
15799 }
15800
15801 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15802 self.active_excerpt(cx)?
15803 .1
15804 .read(cx)
15805 .file()
15806 .and_then(|f| f.as_local())
15807 }
15808
15809 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15810 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15811 let buffer = buffer.read(cx);
15812 if let Some(project_path) = buffer.project_path(cx) {
15813 let project = self.project.as_ref()?.read(cx);
15814 project.absolute_path(&project_path, cx)
15815 } else {
15816 buffer
15817 .file()
15818 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15819 }
15820 })
15821 }
15822
15823 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15824 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15825 let project_path = buffer.read(cx).project_path(cx)?;
15826 let project = self.project.as_ref()?.read(cx);
15827 let entry = project.entry_for_path(&project_path, cx)?;
15828 let path = entry.path.to_path_buf();
15829 Some(path)
15830 })
15831 }
15832
15833 pub fn reveal_in_finder(
15834 &mut self,
15835 _: &RevealInFileManager,
15836 _window: &mut Window,
15837 cx: &mut Context<Self>,
15838 ) {
15839 if let Some(target) = self.target_file(cx) {
15840 cx.reveal_path(&target.abs_path(cx));
15841 }
15842 }
15843
15844 pub fn copy_path(
15845 &mut self,
15846 _: &zed_actions::workspace::CopyPath,
15847 _window: &mut Window,
15848 cx: &mut Context<Self>,
15849 ) {
15850 if let Some(path) = self.target_file_abs_path(cx) {
15851 if let Some(path) = path.to_str() {
15852 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15853 }
15854 }
15855 }
15856
15857 pub fn copy_relative_path(
15858 &mut self,
15859 _: &zed_actions::workspace::CopyRelativePath,
15860 _window: &mut Window,
15861 cx: &mut Context<Self>,
15862 ) {
15863 if let Some(path) = self.target_file_path(cx) {
15864 if let Some(path) = path.to_str() {
15865 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15866 }
15867 }
15868 }
15869
15870 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
15871 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15872 buffer.read(cx).project_path(cx)
15873 } else {
15874 None
15875 }
15876 }
15877
15878 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) {
15879 let _ = maybe!({
15880 let breakpoint_store = self.breakpoint_store.as_ref()?;
15881
15882 let Some((_, _, active_position)) =
15883 breakpoint_store.read(cx).active_position().cloned()
15884 else {
15885 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15886 return None;
15887 };
15888
15889 let snapshot = self
15890 .project
15891 .as_ref()?
15892 .read(cx)
15893 .buffer_for_id(active_position.buffer_id?, cx)?
15894 .read(cx)
15895 .snapshot();
15896
15897 for (id, ExcerptRange { context, .. }) in self
15898 .buffer
15899 .read(cx)
15900 .excerpts_for_buffer(active_position.buffer_id?, cx)
15901 {
15902 if context.start.cmp(&active_position, &snapshot).is_ge()
15903 || context.end.cmp(&active_position, &snapshot).is_lt()
15904 {
15905 continue;
15906 }
15907 let snapshot = self.buffer.read(cx).snapshot(cx);
15908 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
15909
15910 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15911 self.go_to_line::<DebugCurrentRowHighlight>(
15912 multibuffer_anchor,
15913 Some(cx.theme().colors().editor_debugger_active_line_background),
15914 window,
15915 cx,
15916 );
15917
15918 cx.notify();
15919 }
15920
15921 Some(())
15922 });
15923 }
15924
15925 pub fn copy_file_name_without_extension(
15926 &mut self,
15927 _: &CopyFileNameWithoutExtension,
15928 _: &mut Window,
15929 cx: &mut Context<Self>,
15930 ) {
15931 if let Some(file) = self.target_file(cx) {
15932 if let Some(file_stem) = file.path().file_stem() {
15933 if let Some(name) = file_stem.to_str() {
15934 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15935 }
15936 }
15937 }
15938 }
15939
15940 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
15941 if let Some(file) = self.target_file(cx) {
15942 if let Some(file_name) = file.path().file_name() {
15943 if let Some(name) = file_name.to_str() {
15944 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15945 }
15946 }
15947 }
15948 }
15949
15950 pub fn toggle_git_blame(
15951 &mut self,
15952 _: &::git::Blame,
15953 window: &mut Window,
15954 cx: &mut Context<Self>,
15955 ) {
15956 self.show_git_blame_gutter = !self.show_git_blame_gutter;
15957
15958 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
15959 self.start_git_blame(true, window, cx);
15960 }
15961
15962 cx.notify();
15963 }
15964
15965 pub fn toggle_git_blame_inline(
15966 &mut self,
15967 _: &ToggleGitBlameInline,
15968 window: &mut Window,
15969 cx: &mut Context<Self>,
15970 ) {
15971 self.toggle_git_blame_inline_internal(true, window, cx);
15972 cx.notify();
15973 }
15974
15975 pub fn open_git_blame_commit(
15976 &mut self,
15977 _: &OpenGitBlameCommit,
15978 window: &mut Window,
15979 cx: &mut Context<Self>,
15980 ) {
15981 self.open_git_blame_commit_internal(window, cx);
15982 }
15983
15984 fn open_git_blame_commit_internal(
15985 &mut self,
15986 window: &mut Window,
15987 cx: &mut Context<Self>,
15988 ) -> Option<()> {
15989 let blame = self.blame.as_ref()?;
15990 let snapshot = self.snapshot(window, cx);
15991 let cursor = self.selections.newest::<Point>(cx).head();
15992 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
15993 let blame_entry = blame
15994 .update(cx, |blame, cx| {
15995 blame
15996 .blame_for_rows(
15997 &[RowInfo {
15998 buffer_id: Some(buffer.remote_id()),
15999 buffer_row: Some(point.row),
16000 ..Default::default()
16001 }],
16002 cx,
16003 )
16004 .next()
16005 })
16006 .flatten()?;
16007 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16008 let repo = blame.read(cx).repository(cx)?;
16009 let workspace = self.workspace()?.downgrade();
16010 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16011 None
16012 }
16013
16014 pub fn git_blame_inline_enabled(&self) -> bool {
16015 self.git_blame_inline_enabled
16016 }
16017
16018 pub fn toggle_selection_menu(
16019 &mut self,
16020 _: &ToggleSelectionMenu,
16021 _: &mut Window,
16022 cx: &mut Context<Self>,
16023 ) {
16024 self.show_selection_menu = self
16025 .show_selection_menu
16026 .map(|show_selections_menu| !show_selections_menu)
16027 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16028
16029 cx.notify();
16030 }
16031
16032 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16033 self.show_selection_menu
16034 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16035 }
16036
16037 fn start_git_blame(
16038 &mut self,
16039 user_triggered: bool,
16040 window: &mut Window,
16041 cx: &mut Context<Self>,
16042 ) {
16043 if let Some(project) = self.project.as_ref() {
16044 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16045 return;
16046 };
16047
16048 if buffer.read(cx).file().is_none() {
16049 return;
16050 }
16051
16052 let focused = self.focus_handle(cx).contains_focused(window, cx);
16053
16054 let project = project.clone();
16055 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16056 self.blame_subscription =
16057 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16058 self.blame = Some(blame);
16059 }
16060 }
16061
16062 fn toggle_git_blame_inline_internal(
16063 &mut self,
16064 user_triggered: bool,
16065 window: &mut Window,
16066 cx: &mut Context<Self>,
16067 ) {
16068 if self.git_blame_inline_enabled {
16069 self.git_blame_inline_enabled = false;
16070 self.show_git_blame_inline = false;
16071 self.show_git_blame_inline_delay_task.take();
16072 } else {
16073 self.git_blame_inline_enabled = true;
16074 self.start_git_blame_inline(user_triggered, window, cx);
16075 }
16076
16077 cx.notify();
16078 }
16079
16080 fn start_git_blame_inline(
16081 &mut self,
16082 user_triggered: bool,
16083 window: &mut Window,
16084 cx: &mut Context<Self>,
16085 ) {
16086 self.start_git_blame(user_triggered, window, cx);
16087
16088 if ProjectSettings::get_global(cx)
16089 .git
16090 .inline_blame_delay()
16091 .is_some()
16092 {
16093 self.start_inline_blame_timer(window, cx);
16094 } else {
16095 self.show_git_blame_inline = true
16096 }
16097 }
16098
16099 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16100 self.blame.as_ref()
16101 }
16102
16103 pub fn show_git_blame_gutter(&self) -> bool {
16104 self.show_git_blame_gutter
16105 }
16106
16107 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16108 self.show_git_blame_gutter && self.has_blame_entries(cx)
16109 }
16110
16111 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16112 self.show_git_blame_inline
16113 && (self.focus_handle.is_focused(window)
16114 || self
16115 .git_blame_inline_tooltip
16116 .as_ref()
16117 .and_then(|t| t.upgrade())
16118 .is_some())
16119 && !self.newest_selection_head_on_empty_line(cx)
16120 && self.has_blame_entries(cx)
16121 }
16122
16123 fn has_blame_entries(&self, cx: &App) -> bool {
16124 self.blame()
16125 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16126 }
16127
16128 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16129 let cursor_anchor = self.selections.newest_anchor().head();
16130
16131 let snapshot = self.buffer.read(cx).snapshot(cx);
16132 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16133
16134 snapshot.line_len(buffer_row) == 0
16135 }
16136
16137 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16138 let buffer_and_selection = maybe!({
16139 let selection = self.selections.newest::<Point>(cx);
16140 let selection_range = selection.range();
16141
16142 let multi_buffer = self.buffer().read(cx);
16143 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16144 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16145
16146 let (buffer, range, _) = if selection.reversed {
16147 buffer_ranges.first()
16148 } else {
16149 buffer_ranges.last()
16150 }?;
16151
16152 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16153 ..text::ToPoint::to_point(&range.end, &buffer).row;
16154 Some((
16155 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16156 selection,
16157 ))
16158 });
16159
16160 let Some((buffer, selection)) = buffer_and_selection else {
16161 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16162 };
16163
16164 let Some(project) = self.project.as_ref() else {
16165 return Task::ready(Err(anyhow!("editor does not have project")));
16166 };
16167
16168 project.update(cx, |project, cx| {
16169 project.get_permalink_to_line(&buffer, selection, cx)
16170 })
16171 }
16172
16173 pub fn copy_permalink_to_line(
16174 &mut self,
16175 _: &CopyPermalinkToLine,
16176 window: &mut Window,
16177 cx: &mut Context<Self>,
16178 ) {
16179 let permalink_task = self.get_permalink_to_line(cx);
16180 let workspace = self.workspace();
16181
16182 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16183 Ok(permalink) => {
16184 cx.update(|_, cx| {
16185 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16186 })
16187 .ok();
16188 }
16189 Err(err) => {
16190 let message = format!("Failed to copy permalink: {err}");
16191
16192 Err::<(), anyhow::Error>(err).log_err();
16193
16194 if let Some(workspace) = workspace {
16195 workspace
16196 .update_in(cx, |workspace, _, cx| {
16197 struct CopyPermalinkToLine;
16198
16199 workspace.show_toast(
16200 Toast::new(
16201 NotificationId::unique::<CopyPermalinkToLine>(),
16202 message,
16203 ),
16204 cx,
16205 )
16206 })
16207 .ok();
16208 }
16209 }
16210 })
16211 .detach();
16212 }
16213
16214 pub fn copy_file_location(
16215 &mut self,
16216 _: &CopyFileLocation,
16217 _: &mut Window,
16218 cx: &mut Context<Self>,
16219 ) {
16220 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16221 if let Some(file) = self.target_file(cx) {
16222 if let Some(path) = file.path().to_str() {
16223 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16224 }
16225 }
16226 }
16227
16228 pub fn open_permalink_to_line(
16229 &mut self,
16230 _: &OpenPermalinkToLine,
16231 window: &mut Window,
16232 cx: &mut Context<Self>,
16233 ) {
16234 let permalink_task = self.get_permalink_to_line(cx);
16235 let workspace = self.workspace();
16236
16237 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16238 Ok(permalink) => {
16239 cx.update(|_, cx| {
16240 cx.open_url(permalink.as_ref());
16241 })
16242 .ok();
16243 }
16244 Err(err) => {
16245 let message = format!("Failed to open permalink: {err}");
16246
16247 Err::<(), anyhow::Error>(err).log_err();
16248
16249 if let Some(workspace) = workspace {
16250 workspace
16251 .update(cx, |workspace, cx| {
16252 struct OpenPermalinkToLine;
16253
16254 workspace.show_toast(
16255 Toast::new(
16256 NotificationId::unique::<OpenPermalinkToLine>(),
16257 message,
16258 ),
16259 cx,
16260 )
16261 })
16262 .ok();
16263 }
16264 }
16265 })
16266 .detach();
16267 }
16268
16269 pub fn insert_uuid_v4(
16270 &mut self,
16271 _: &InsertUuidV4,
16272 window: &mut Window,
16273 cx: &mut Context<Self>,
16274 ) {
16275 self.insert_uuid(UuidVersion::V4, window, cx);
16276 }
16277
16278 pub fn insert_uuid_v7(
16279 &mut self,
16280 _: &InsertUuidV7,
16281 window: &mut Window,
16282 cx: &mut Context<Self>,
16283 ) {
16284 self.insert_uuid(UuidVersion::V7, window, cx);
16285 }
16286
16287 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16288 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16289 self.transact(window, cx, |this, window, cx| {
16290 let edits = this
16291 .selections
16292 .all::<Point>(cx)
16293 .into_iter()
16294 .map(|selection| {
16295 let uuid = match version {
16296 UuidVersion::V4 => uuid::Uuid::new_v4(),
16297 UuidVersion::V7 => uuid::Uuid::now_v7(),
16298 };
16299
16300 (selection.range(), uuid.to_string())
16301 });
16302 this.edit(edits, cx);
16303 this.refresh_inline_completion(true, false, window, cx);
16304 });
16305 }
16306
16307 pub fn open_selections_in_multibuffer(
16308 &mut self,
16309 _: &OpenSelectionsInMultibuffer,
16310 window: &mut Window,
16311 cx: &mut Context<Self>,
16312 ) {
16313 let multibuffer = self.buffer.read(cx);
16314
16315 let Some(buffer) = multibuffer.as_singleton() else {
16316 return;
16317 };
16318
16319 let Some(workspace) = self.workspace() else {
16320 return;
16321 };
16322
16323 let locations = self
16324 .selections
16325 .disjoint_anchors()
16326 .iter()
16327 .map(|range| Location {
16328 buffer: buffer.clone(),
16329 range: range.start.text_anchor..range.end.text_anchor,
16330 })
16331 .collect::<Vec<_>>();
16332
16333 let title = multibuffer.title(cx).to_string();
16334
16335 cx.spawn_in(window, async move |_, cx| {
16336 workspace.update_in(cx, |workspace, window, cx| {
16337 Self::open_locations_in_multibuffer(
16338 workspace,
16339 locations,
16340 format!("Selections for '{title}'"),
16341 false,
16342 MultibufferSelectionMode::All,
16343 window,
16344 cx,
16345 );
16346 })
16347 })
16348 .detach();
16349 }
16350
16351 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16352 /// last highlight added will be used.
16353 ///
16354 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16355 pub fn highlight_rows<T: 'static>(
16356 &mut self,
16357 range: Range<Anchor>,
16358 color: Hsla,
16359 should_autoscroll: bool,
16360 cx: &mut Context<Self>,
16361 ) {
16362 let snapshot = self.buffer().read(cx).snapshot(cx);
16363 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16364 let ix = row_highlights.binary_search_by(|highlight| {
16365 Ordering::Equal
16366 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16367 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16368 });
16369
16370 if let Err(mut ix) = ix {
16371 let index = post_inc(&mut self.highlight_order);
16372
16373 // If this range intersects with the preceding highlight, then merge it with
16374 // the preceding highlight. Otherwise insert a new highlight.
16375 let mut merged = false;
16376 if ix > 0 {
16377 let prev_highlight = &mut row_highlights[ix - 1];
16378 if prev_highlight
16379 .range
16380 .end
16381 .cmp(&range.start, &snapshot)
16382 .is_ge()
16383 {
16384 ix -= 1;
16385 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16386 prev_highlight.range.end = range.end;
16387 }
16388 merged = true;
16389 prev_highlight.index = index;
16390 prev_highlight.color = color;
16391 prev_highlight.should_autoscroll = should_autoscroll;
16392 }
16393 }
16394
16395 if !merged {
16396 row_highlights.insert(
16397 ix,
16398 RowHighlight {
16399 range: range.clone(),
16400 index,
16401 color,
16402 should_autoscroll,
16403 },
16404 );
16405 }
16406
16407 // If any of the following highlights intersect with this one, merge them.
16408 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16409 let highlight = &row_highlights[ix];
16410 if next_highlight
16411 .range
16412 .start
16413 .cmp(&highlight.range.end, &snapshot)
16414 .is_le()
16415 {
16416 if next_highlight
16417 .range
16418 .end
16419 .cmp(&highlight.range.end, &snapshot)
16420 .is_gt()
16421 {
16422 row_highlights[ix].range.end = next_highlight.range.end;
16423 }
16424 row_highlights.remove(ix + 1);
16425 } else {
16426 break;
16427 }
16428 }
16429 }
16430 }
16431
16432 /// Remove any highlighted row ranges of the given type that intersect the
16433 /// given ranges.
16434 pub fn remove_highlighted_rows<T: 'static>(
16435 &mut self,
16436 ranges_to_remove: Vec<Range<Anchor>>,
16437 cx: &mut Context<Self>,
16438 ) {
16439 let snapshot = self.buffer().read(cx).snapshot(cx);
16440 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16441 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16442 row_highlights.retain(|highlight| {
16443 while let Some(range_to_remove) = ranges_to_remove.peek() {
16444 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16445 Ordering::Less | Ordering::Equal => {
16446 ranges_to_remove.next();
16447 }
16448 Ordering::Greater => {
16449 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16450 Ordering::Less | Ordering::Equal => {
16451 return false;
16452 }
16453 Ordering::Greater => break,
16454 }
16455 }
16456 }
16457 }
16458
16459 true
16460 })
16461 }
16462
16463 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16464 pub fn clear_row_highlights<T: 'static>(&mut self) {
16465 self.highlighted_rows.remove(&TypeId::of::<T>());
16466 }
16467
16468 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16469 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16470 self.highlighted_rows
16471 .get(&TypeId::of::<T>())
16472 .map_or(&[] as &[_], |vec| vec.as_slice())
16473 .iter()
16474 .map(|highlight| (highlight.range.clone(), highlight.color))
16475 }
16476
16477 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16478 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16479 /// Allows to ignore certain kinds of highlights.
16480 pub fn highlighted_display_rows(
16481 &self,
16482 window: &mut Window,
16483 cx: &mut App,
16484 ) -> BTreeMap<DisplayRow, LineHighlight> {
16485 let snapshot = self.snapshot(window, cx);
16486 let mut used_highlight_orders = HashMap::default();
16487 self.highlighted_rows
16488 .iter()
16489 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16490 .fold(
16491 BTreeMap::<DisplayRow, LineHighlight>::new(),
16492 |mut unique_rows, highlight| {
16493 let start = highlight.range.start.to_display_point(&snapshot);
16494 let end = highlight.range.end.to_display_point(&snapshot);
16495 let start_row = start.row().0;
16496 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16497 && end.column() == 0
16498 {
16499 end.row().0.saturating_sub(1)
16500 } else {
16501 end.row().0
16502 };
16503 for row in start_row..=end_row {
16504 let used_index =
16505 used_highlight_orders.entry(row).or_insert(highlight.index);
16506 if highlight.index >= *used_index {
16507 *used_index = highlight.index;
16508 unique_rows.insert(DisplayRow(row), highlight.color.into());
16509 }
16510 }
16511 unique_rows
16512 },
16513 )
16514 }
16515
16516 pub fn highlighted_display_row_for_autoscroll(
16517 &self,
16518 snapshot: &DisplaySnapshot,
16519 ) -> Option<DisplayRow> {
16520 self.highlighted_rows
16521 .values()
16522 .flat_map(|highlighted_rows| highlighted_rows.iter())
16523 .filter_map(|highlight| {
16524 if highlight.should_autoscroll {
16525 Some(highlight.range.start.to_display_point(snapshot).row())
16526 } else {
16527 None
16528 }
16529 })
16530 .min()
16531 }
16532
16533 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16534 self.highlight_background::<SearchWithinRange>(
16535 ranges,
16536 |colors| colors.editor_document_highlight_read_background,
16537 cx,
16538 )
16539 }
16540
16541 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16542 self.breadcrumb_header = Some(new_header);
16543 }
16544
16545 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16546 self.clear_background_highlights::<SearchWithinRange>(cx);
16547 }
16548
16549 pub fn highlight_background<T: 'static>(
16550 &mut self,
16551 ranges: &[Range<Anchor>],
16552 color_fetcher: fn(&ThemeColors) -> Hsla,
16553 cx: &mut Context<Self>,
16554 ) {
16555 self.background_highlights
16556 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16557 self.scrollbar_marker_state.dirty = true;
16558 cx.notify();
16559 }
16560
16561 pub fn clear_background_highlights<T: 'static>(
16562 &mut self,
16563 cx: &mut Context<Self>,
16564 ) -> Option<BackgroundHighlight> {
16565 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16566 if !text_highlights.1.is_empty() {
16567 self.scrollbar_marker_state.dirty = true;
16568 cx.notify();
16569 }
16570 Some(text_highlights)
16571 }
16572
16573 pub fn highlight_gutter<T: 'static>(
16574 &mut self,
16575 ranges: &[Range<Anchor>],
16576 color_fetcher: fn(&App) -> Hsla,
16577 cx: &mut Context<Self>,
16578 ) {
16579 self.gutter_highlights
16580 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16581 cx.notify();
16582 }
16583
16584 pub fn clear_gutter_highlights<T: 'static>(
16585 &mut self,
16586 cx: &mut Context<Self>,
16587 ) -> Option<GutterHighlight> {
16588 cx.notify();
16589 self.gutter_highlights.remove(&TypeId::of::<T>())
16590 }
16591
16592 #[cfg(feature = "test-support")]
16593 pub fn all_text_background_highlights(
16594 &self,
16595 window: &mut Window,
16596 cx: &mut Context<Self>,
16597 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16598 let snapshot = self.snapshot(window, cx);
16599 let buffer = &snapshot.buffer_snapshot;
16600 let start = buffer.anchor_before(0);
16601 let end = buffer.anchor_after(buffer.len());
16602 let theme = cx.theme().colors();
16603 self.background_highlights_in_range(start..end, &snapshot, theme)
16604 }
16605
16606 #[cfg(feature = "test-support")]
16607 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16608 let snapshot = self.buffer().read(cx).snapshot(cx);
16609
16610 let highlights = self
16611 .background_highlights
16612 .get(&TypeId::of::<items::BufferSearchHighlights>());
16613
16614 if let Some((_color, ranges)) = highlights {
16615 ranges
16616 .iter()
16617 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16618 .collect_vec()
16619 } else {
16620 vec![]
16621 }
16622 }
16623
16624 fn document_highlights_for_position<'a>(
16625 &'a self,
16626 position: Anchor,
16627 buffer: &'a MultiBufferSnapshot,
16628 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16629 let read_highlights = self
16630 .background_highlights
16631 .get(&TypeId::of::<DocumentHighlightRead>())
16632 .map(|h| &h.1);
16633 let write_highlights = self
16634 .background_highlights
16635 .get(&TypeId::of::<DocumentHighlightWrite>())
16636 .map(|h| &h.1);
16637 let left_position = position.bias_left(buffer);
16638 let right_position = position.bias_right(buffer);
16639 read_highlights
16640 .into_iter()
16641 .chain(write_highlights)
16642 .flat_map(move |ranges| {
16643 let start_ix = match ranges.binary_search_by(|probe| {
16644 let cmp = probe.end.cmp(&left_position, buffer);
16645 if cmp.is_ge() {
16646 Ordering::Greater
16647 } else {
16648 Ordering::Less
16649 }
16650 }) {
16651 Ok(i) | Err(i) => i,
16652 };
16653
16654 ranges[start_ix..]
16655 .iter()
16656 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16657 })
16658 }
16659
16660 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16661 self.background_highlights
16662 .get(&TypeId::of::<T>())
16663 .map_or(false, |(_, highlights)| !highlights.is_empty())
16664 }
16665
16666 pub fn background_highlights_in_range(
16667 &self,
16668 search_range: Range<Anchor>,
16669 display_snapshot: &DisplaySnapshot,
16670 theme: &ThemeColors,
16671 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16672 let mut results = Vec::new();
16673 for (color_fetcher, ranges) in self.background_highlights.values() {
16674 let color = color_fetcher(theme);
16675 let start_ix = match ranges.binary_search_by(|probe| {
16676 let cmp = probe
16677 .end
16678 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16679 if cmp.is_gt() {
16680 Ordering::Greater
16681 } else {
16682 Ordering::Less
16683 }
16684 }) {
16685 Ok(i) | Err(i) => i,
16686 };
16687 for range in &ranges[start_ix..] {
16688 if range
16689 .start
16690 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16691 .is_ge()
16692 {
16693 break;
16694 }
16695
16696 let start = range.start.to_display_point(display_snapshot);
16697 let end = range.end.to_display_point(display_snapshot);
16698 results.push((start..end, color))
16699 }
16700 }
16701 results
16702 }
16703
16704 pub fn background_highlight_row_ranges<T: 'static>(
16705 &self,
16706 search_range: Range<Anchor>,
16707 display_snapshot: &DisplaySnapshot,
16708 count: usize,
16709 ) -> Vec<RangeInclusive<DisplayPoint>> {
16710 let mut results = Vec::new();
16711 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16712 return vec![];
16713 };
16714
16715 let start_ix = match ranges.binary_search_by(|probe| {
16716 let cmp = probe
16717 .end
16718 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16719 if cmp.is_gt() {
16720 Ordering::Greater
16721 } else {
16722 Ordering::Less
16723 }
16724 }) {
16725 Ok(i) | Err(i) => i,
16726 };
16727 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16728 if let (Some(start_display), Some(end_display)) = (start, end) {
16729 results.push(
16730 start_display.to_display_point(display_snapshot)
16731 ..=end_display.to_display_point(display_snapshot),
16732 );
16733 }
16734 };
16735 let mut start_row: Option<Point> = None;
16736 let mut end_row: Option<Point> = None;
16737 if ranges.len() > count {
16738 return Vec::new();
16739 }
16740 for range in &ranges[start_ix..] {
16741 if range
16742 .start
16743 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16744 .is_ge()
16745 {
16746 break;
16747 }
16748 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16749 if let Some(current_row) = &end_row {
16750 if end.row == current_row.row {
16751 continue;
16752 }
16753 }
16754 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16755 if start_row.is_none() {
16756 assert_eq!(end_row, None);
16757 start_row = Some(start);
16758 end_row = Some(end);
16759 continue;
16760 }
16761 if let Some(current_end) = end_row.as_mut() {
16762 if start.row > current_end.row + 1 {
16763 push_region(start_row, end_row);
16764 start_row = Some(start);
16765 end_row = Some(end);
16766 } else {
16767 // Merge two hunks.
16768 *current_end = end;
16769 }
16770 } else {
16771 unreachable!();
16772 }
16773 }
16774 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16775 push_region(start_row, end_row);
16776 results
16777 }
16778
16779 pub fn gutter_highlights_in_range(
16780 &self,
16781 search_range: Range<Anchor>,
16782 display_snapshot: &DisplaySnapshot,
16783 cx: &App,
16784 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16785 let mut results = Vec::new();
16786 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16787 let color = color_fetcher(cx);
16788 let start_ix = match ranges.binary_search_by(|probe| {
16789 let cmp = probe
16790 .end
16791 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16792 if cmp.is_gt() {
16793 Ordering::Greater
16794 } else {
16795 Ordering::Less
16796 }
16797 }) {
16798 Ok(i) | Err(i) => i,
16799 };
16800 for range in &ranges[start_ix..] {
16801 if range
16802 .start
16803 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16804 .is_ge()
16805 {
16806 break;
16807 }
16808
16809 let start = range.start.to_display_point(display_snapshot);
16810 let end = range.end.to_display_point(display_snapshot);
16811 results.push((start..end, color))
16812 }
16813 }
16814 results
16815 }
16816
16817 /// Get the text ranges corresponding to the redaction query
16818 pub fn redacted_ranges(
16819 &self,
16820 search_range: Range<Anchor>,
16821 display_snapshot: &DisplaySnapshot,
16822 cx: &App,
16823 ) -> Vec<Range<DisplayPoint>> {
16824 display_snapshot
16825 .buffer_snapshot
16826 .redacted_ranges(search_range, |file| {
16827 if let Some(file) = file {
16828 file.is_private()
16829 && EditorSettings::get(
16830 Some(SettingsLocation {
16831 worktree_id: file.worktree_id(cx),
16832 path: file.path().as_ref(),
16833 }),
16834 cx,
16835 )
16836 .redact_private_values
16837 } else {
16838 false
16839 }
16840 })
16841 .map(|range| {
16842 range.start.to_display_point(display_snapshot)
16843 ..range.end.to_display_point(display_snapshot)
16844 })
16845 .collect()
16846 }
16847
16848 pub fn highlight_text<T: 'static>(
16849 &mut self,
16850 ranges: Vec<Range<Anchor>>,
16851 style: HighlightStyle,
16852 cx: &mut Context<Self>,
16853 ) {
16854 self.display_map.update(cx, |map, _| {
16855 map.highlight_text(TypeId::of::<T>(), ranges, style)
16856 });
16857 cx.notify();
16858 }
16859
16860 pub(crate) fn highlight_inlays<T: 'static>(
16861 &mut self,
16862 highlights: Vec<InlayHighlight>,
16863 style: HighlightStyle,
16864 cx: &mut Context<Self>,
16865 ) {
16866 self.display_map.update(cx, |map, _| {
16867 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16868 });
16869 cx.notify();
16870 }
16871
16872 pub fn text_highlights<'a, T: 'static>(
16873 &'a self,
16874 cx: &'a App,
16875 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
16876 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
16877 }
16878
16879 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
16880 let cleared = self
16881 .display_map
16882 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
16883 if cleared {
16884 cx.notify();
16885 }
16886 }
16887
16888 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
16889 (self.read_only(cx) || self.blink_manager.read(cx).visible())
16890 && self.focus_handle.is_focused(window)
16891 }
16892
16893 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
16894 self.show_cursor_when_unfocused = is_enabled;
16895 cx.notify();
16896 }
16897
16898 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
16899 cx.notify();
16900 }
16901
16902 fn on_buffer_event(
16903 &mut self,
16904 multibuffer: &Entity<MultiBuffer>,
16905 event: &multi_buffer::Event,
16906 window: &mut Window,
16907 cx: &mut Context<Self>,
16908 ) {
16909 match event {
16910 multi_buffer::Event::Edited {
16911 singleton_buffer_edited,
16912 edited_buffer: buffer_edited,
16913 } => {
16914 self.scrollbar_marker_state.dirty = true;
16915 self.active_indent_guides_state.dirty = true;
16916 self.refresh_active_diagnostics(cx);
16917 self.refresh_code_actions(window, cx);
16918 if self.has_active_inline_completion() {
16919 self.update_visible_inline_completion(window, cx);
16920 }
16921 if let Some(buffer) = buffer_edited {
16922 let buffer_id = buffer.read(cx).remote_id();
16923 if !self.registered_buffers.contains_key(&buffer_id) {
16924 if let Some(project) = self.project.as_ref() {
16925 project.update(cx, |project, cx| {
16926 self.registered_buffers.insert(
16927 buffer_id,
16928 project.register_buffer_with_language_servers(&buffer, cx),
16929 );
16930 })
16931 }
16932 }
16933 }
16934 cx.emit(EditorEvent::BufferEdited);
16935 cx.emit(SearchEvent::MatchesInvalidated);
16936 if *singleton_buffer_edited {
16937 if let Some(project) = &self.project {
16938 #[allow(clippy::mutable_key_type)]
16939 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
16940 multibuffer
16941 .all_buffers()
16942 .into_iter()
16943 .filter_map(|buffer| {
16944 buffer.update(cx, |buffer, cx| {
16945 let language = buffer.language()?;
16946 let should_discard = project.update(cx, |project, cx| {
16947 project.is_local()
16948 && !project.has_language_servers_for(buffer, cx)
16949 });
16950 should_discard.not().then_some(language.clone())
16951 })
16952 })
16953 .collect::<HashSet<_>>()
16954 });
16955 if !languages_affected.is_empty() {
16956 self.refresh_inlay_hints(
16957 InlayHintRefreshReason::BufferEdited(languages_affected),
16958 cx,
16959 );
16960 }
16961 }
16962 }
16963
16964 let Some(project) = &self.project else { return };
16965 let (telemetry, is_via_ssh) = {
16966 let project = project.read(cx);
16967 let telemetry = project.client().telemetry().clone();
16968 let is_via_ssh = project.is_via_ssh();
16969 (telemetry, is_via_ssh)
16970 };
16971 refresh_linked_ranges(self, window, cx);
16972 telemetry.log_edit_event("editor", is_via_ssh);
16973 }
16974 multi_buffer::Event::ExcerptsAdded {
16975 buffer,
16976 predecessor,
16977 excerpts,
16978 } => {
16979 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16980 let buffer_id = buffer.read(cx).remote_id();
16981 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
16982 if let Some(project) = &self.project {
16983 get_uncommitted_diff_for_buffer(
16984 project,
16985 [buffer.clone()],
16986 self.buffer.clone(),
16987 cx,
16988 )
16989 .detach();
16990 }
16991 }
16992 cx.emit(EditorEvent::ExcerptsAdded {
16993 buffer: buffer.clone(),
16994 predecessor: *predecessor,
16995 excerpts: excerpts.clone(),
16996 });
16997 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16998 }
16999 multi_buffer::Event::ExcerptsRemoved { ids } => {
17000 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17001 let buffer = self.buffer.read(cx);
17002 self.registered_buffers
17003 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17004 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17005 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
17006 }
17007 multi_buffer::Event::ExcerptsEdited {
17008 excerpt_ids,
17009 buffer_ids,
17010 } => {
17011 self.display_map.update(cx, |map, cx| {
17012 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17013 });
17014 cx.emit(EditorEvent::ExcerptsEdited {
17015 ids: excerpt_ids.clone(),
17016 })
17017 }
17018 multi_buffer::Event::ExcerptsExpanded { ids } => {
17019 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17020 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17021 }
17022 multi_buffer::Event::Reparsed(buffer_id) => {
17023 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17024 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17025
17026 cx.emit(EditorEvent::Reparsed(*buffer_id));
17027 }
17028 multi_buffer::Event::DiffHunksToggled => {
17029 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17030 }
17031 multi_buffer::Event::LanguageChanged(buffer_id) => {
17032 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17033 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17034 cx.emit(EditorEvent::Reparsed(*buffer_id));
17035 cx.notify();
17036 }
17037 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17038 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17039 multi_buffer::Event::FileHandleChanged
17040 | multi_buffer::Event::Reloaded
17041 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17042 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17043 multi_buffer::Event::DiagnosticsUpdated => {
17044 self.refresh_active_diagnostics(cx);
17045 self.refresh_inline_diagnostics(true, window, cx);
17046 self.scrollbar_marker_state.dirty = true;
17047 cx.notify();
17048 }
17049 _ => {}
17050 };
17051 }
17052
17053 fn on_display_map_changed(
17054 &mut self,
17055 _: Entity<DisplayMap>,
17056 _: &mut Window,
17057 cx: &mut Context<Self>,
17058 ) {
17059 cx.notify();
17060 }
17061
17062 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17063 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17064 self.update_edit_prediction_settings(cx);
17065 self.refresh_inline_completion(true, false, window, cx);
17066 self.refresh_inlay_hints(
17067 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17068 self.selections.newest_anchor().head(),
17069 &self.buffer.read(cx).snapshot(cx),
17070 cx,
17071 )),
17072 cx,
17073 );
17074
17075 let old_cursor_shape = self.cursor_shape;
17076
17077 {
17078 let editor_settings = EditorSettings::get_global(cx);
17079 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17080 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17081 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17082 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17083 }
17084
17085 if old_cursor_shape != self.cursor_shape {
17086 cx.emit(EditorEvent::CursorShapeChanged);
17087 }
17088
17089 let project_settings = ProjectSettings::get_global(cx);
17090 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17091
17092 if self.mode == EditorMode::Full {
17093 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17094 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17095 if self.show_inline_diagnostics != show_inline_diagnostics {
17096 self.show_inline_diagnostics = show_inline_diagnostics;
17097 self.refresh_inline_diagnostics(false, window, cx);
17098 }
17099
17100 if self.git_blame_inline_enabled != inline_blame_enabled {
17101 self.toggle_git_blame_inline_internal(false, window, cx);
17102 }
17103 }
17104
17105 cx.notify();
17106 }
17107
17108 pub fn set_searchable(&mut self, searchable: bool) {
17109 self.searchable = searchable;
17110 }
17111
17112 pub fn searchable(&self) -> bool {
17113 self.searchable
17114 }
17115
17116 fn open_proposed_changes_editor(
17117 &mut self,
17118 _: &OpenProposedChangesEditor,
17119 window: &mut Window,
17120 cx: &mut Context<Self>,
17121 ) {
17122 let Some(workspace) = self.workspace() else {
17123 cx.propagate();
17124 return;
17125 };
17126
17127 let selections = self.selections.all::<usize>(cx);
17128 let multi_buffer = self.buffer.read(cx);
17129 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17130 let mut new_selections_by_buffer = HashMap::default();
17131 for selection in selections {
17132 for (buffer, range, _) in
17133 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17134 {
17135 let mut range = range.to_point(buffer);
17136 range.start.column = 0;
17137 range.end.column = buffer.line_len(range.end.row);
17138 new_selections_by_buffer
17139 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17140 .or_insert(Vec::new())
17141 .push(range)
17142 }
17143 }
17144
17145 let proposed_changes_buffers = new_selections_by_buffer
17146 .into_iter()
17147 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17148 .collect::<Vec<_>>();
17149 let proposed_changes_editor = cx.new(|cx| {
17150 ProposedChangesEditor::new(
17151 "Proposed changes",
17152 proposed_changes_buffers,
17153 self.project.clone(),
17154 window,
17155 cx,
17156 )
17157 });
17158
17159 window.defer(cx, move |window, cx| {
17160 workspace.update(cx, |workspace, cx| {
17161 workspace.active_pane().update(cx, |pane, cx| {
17162 pane.add_item(
17163 Box::new(proposed_changes_editor),
17164 true,
17165 true,
17166 None,
17167 window,
17168 cx,
17169 );
17170 });
17171 });
17172 });
17173 }
17174
17175 pub fn open_excerpts_in_split(
17176 &mut self,
17177 _: &OpenExcerptsSplit,
17178 window: &mut Window,
17179 cx: &mut Context<Self>,
17180 ) {
17181 self.open_excerpts_common(None, true, window, cx)
17182 }
17183
17184 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17185 self.open_excerpts_common(None, false, window, cx)
17186 }
17187
17188 fn open_excerpts_common(
17189 &mut self,
17190 jump_data: Option<JumpData>,
17191 split: bool,
17192 window: &mut Window,
17193 cx: &mut Context<Self>,
17194 ) {
17195 let Some(workspace) = self.workspace() else {
17196 cx.propagate();
17197 return;
17198 };
17199
17200 if self.buffer.read(cx).is_singleton() {
17201 cx.propagate();
17202 return;
17203 }
17204
17205 let mut new_selections_by_buffer = HashMap::default();
17206 match &jump_data {
17207 Some(JumpData::MultiBufferPoint {
17208 excerpt_id,
17209 position,
17210 anchor,
17211 line_offset_from_top,
17212 }) => {
17213 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17214 if let Some(buffer) = multi_buffer_snapshot
17215 .buffer_id_for_excerpt(*excerpt_id)
17216 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17217 {
17218 let buffer_snapshot = buffer.read(cx).snapshot();
17219 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17220 language::ToPoint::to_point(anchor, &buffer_snapshot)
17221 } else {
17222 buffer_snapshot.clip_point(*position, Bias::Left)
17223 };
17224 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17225 new_selections_by_buffer.insert(
17226 buffer,
17227 (
17228 vec![jump_to_offset..jump_to_offset],
17229 Some(*line_offset_from_top),
17230 ),
17231 );
17232 }
17233 }
17234 Some(JumpData::MultiBufferRow {
17235 row,
17236 line_offset_from_top,
17237 }) => {
17238 let point = MultiBufferPoint::new(row.0, 0);
17239 if let Some((buffer, buffer_point, _)) =
17240 self.buffer.read(cx).point_to_buffer_point(point, cx)
17241 {
17242 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17243 new_selections_by_buffer
17244 .entry(buffer)
17245 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17246 .0
17247 .push(buffer_offset..buffer_offset)
17248 }
17249 }
17250 None => {
17251 let selections = self.selections.all::<usize>(cx);
17252 let multi_buffer = self.buffer.read(cx);
17253 for selection in selections {
17254 for (snapshot, range, _, anchor) in multi_buffer
17255 .snapshot(cx)
17256 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17257 {
17258 if let Some(anchor) = anchor {
17259 // selection is in a deleted hunk
17260 let Some(buffer_id) = anchor.buffer_id else {
17261 continue;
17262 };
17263 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17264 continue;
17265 };
17266 let offset = text::ToOffset::to_offset(
17267 &anchor.text_anchor,
17268 &buffer_handle.read(cx).snapshot(),
17269 );
17270 let range = offset..offset;
17271 new_selections_by_buffer
17272 .entry(buffer_handle)
17273 .or_insert((Vec::new(), None))
17274 .0
17275 .push(range)
17276 } else {
17277 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17278 else {
17279 continue;
17280 };
17281 new_selections_by_buffer
17282 .entry(buffer_handle)
17283 .or_insert((Vec::new(), None))
17284 .0
17285 .push(range)
17286 }
17287 }
17288 }
17289 }
17290 }
17291
17292 new_selections_by_buffer
17293 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
17294
17295 if new_selections_by_buffer.is_empty() {
17296 return;
17297 }
17298
17299 // We defer the pane interaction because we ourselves are a workspace item
17300 // and activating a new item causes the pane to call a method on us reentrantly,
17301 // which panics if we're on the stack.
17302 window.defer(cx, move |window, cx| {
17303 workspace.update(cx, |workspace, cx| {
17304 let pane = if split {
17305 workspace.adjacent_pane(window, cx)
17306 } else {
17307 workspace.active_pane().clone()
17308 };
17309
17310 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17311 let editor = buffer
17312 .read(cx)
17313 .file()
17314 .is_none()
17315 .then(|| {
17316 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17317 // so `workspace.open_project_item` will never find them, always opening a new editor.
17318 // Instead, we try to activate the existing editor in the pane first.
17319 let (editor, pane_item_index) =
17320 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17321 let editor = item.downcast::<Editor>()?;
17322 let singleton_buffer =
17323 editor.read(cx).buffer().read(cx).as_singleton()?;
17324 if singleton_buffer == buffer {
17325 Some((editor, i))
17326 } else {
17327 None
17328 }
17329 })?;
17330 pane.update(cx, |pane, cx| {
17331 pane.activate_item(pane_item_index, true, true, window, cx)
17332 });
17333 Some(editor)
17334 })
17335 .flatten()
17336 .unwrap_or_else(|| {
17337 workspace.open_project_item::<Self>(
17338 pane.clone(),
17339 buffer,
17340 true,
17341 true,
17342 window,
17343 cx,
17344 )
17345 });
17346
17347 editor.update(cx, |editor, cx| {
17348 let autoscroll = match scroll_offset {
17349 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17350 None => Autoscroll::newest(),
17351 };
17352 let nav_history = editor.nav_history.take();
17353 editor.change_selections(Some(autoscroll), window, cx, |s| {
17354 s.select_ranges(ranges);
17355 });
17356 editor.nav_history = nav_history;
17357 });
17358 }
17359 })
17360 });
17361 }
17362
17363 // For now, don't allow opening excerpts in buffers that aren't backed by
17364 // regular project files.
17365 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
17366 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
17367 }
17368
17369 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17370 let snapshot = self.buffer.read(cx).read(cx);
17371 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17372 Some(
17373 ranges
17374 .iter()
17375 .map(move |range| {
17376 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17377 })
17378 .collect(),
17379 )
17380 }
17381
17382 fn selection_replacement_ranges(
17383 &self,
17384 range: Range<OffsetUtf16>,
17385 cx: &mut App,
17386 ) -> Vec<Range<OffsetUtf16>> {
17387 let selections = self.selections.all::<OffsetUtf16>(cx);
17388 let newest_selection = selections
17389 .iter()
17390 .max_by_key(|selection| selection.id)
17391 .unwrap();
17392 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17393 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17394 let snapshot = self.buffer.read(cx).read(cx);
17395 selections
17396 .into_iter()
17397 .map(|mut selection| {
17398 selection.start.0 =
17399 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17400 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17401 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17402 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17403 })
17404 .collect()
17405 }
17406
17407 fn report_editor_event(
17408 &self,
17409 event_type: &'static str,
17410 file_extension: Option<String>,
17411 cx: &App,
17412 ) {
17413 if cfg!(any(test, feature = "test-support")) {
17414 return;
17415 }
17416
17417 let Some(project) = &self.project else { return };
17418
17419 // If None, we are in a file without an extension
17420 let file = self
17421 .buffer
17422 .read(cx)
17423 .as_singleton()
17424 .and_then(|b| b.read(cx).file());
17425 let file_extension = file_extension.or(file
17426 .as_ref()
17427 .and_then(|file| Path::new(file.file_name(cx)).extension())
17428 .and_then(|e| e.to_str())
17429 .map(|a| a.to_string()));
17430
17431 let vim_mode = cx
17432 .global::<SettingsStore>()
17433 .raw_user_settings()
17434 .get("vim_mode")
17435 == Some(&serde_json::Value::Bool(true));
17436
17437 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17438 let copilot_enabled = edit_predictions_provider
17439 == language::language_settings::EditPredictionProvider::Copilot;
17440 let copilot_enabled_for_language = self
17441 .buffer
17442 .read(cx)
17443 .language_settings(cx)
17444 .show_edit_predictions;
17445
17446 let project = project.read(cx);
17447 telemetry::event!(
17448 event_type,
17449 file_extension,
17450 vim_mode,
17451 copilot_enabled,
17452 copilot_enabled_for_language,
17453 edit_predictions_provider,
17454 is_via_ssh = project.is_via_ssh(),
17455 );
17456 }
17457
17458 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17459 /// with each line being an array of {text, highlight} objects.
17460 fn copy_highlight_json(
17461 &mut self,
17462 _: &CopyHighlightJson,
17463 window: &mut Window,
17464 cx: &mut Context<Self>,
17465 ) {
17466 #[derive(Serialize)]
17467 struct Chunk<'a> {
17468 text: String,
17469 highlight: Option<&'a str>,
17470 }
17471
17472 let snapshot = self.buffer.read(cx).snapshot(cx);
17473 let range = self
17474 .selected_text_range(false, window, cx)
17475 .and_then(|selection| {
17476 if selection.range.is_empty() {
17477 None
17478 } else {
17479 Some(selection.range)
17480 }
17481 })
17482 .unwrap_or_else(|| 0..snapshot.len());
17483
17484 let chunks = snapshot.chunks(range, true);
17485 let mut lines = Vec::new();
17486 let mut line: VecDeque<Chunk> = VecDeque::new();
17487
17488 let Some(style) = self.style.as_ref() else {
17489 return;
17490 };
17491
17492 for chunk in chunks {
17493 let highlight = chunk
17494 .syntax_highlight_id
17495 .and_then(|id| id.name(&style.syntax));
17496 let mut chunk_lines = chunk.text.split('\n').peekable();
17497 while let Some(text) = chunk_lines.next() {
17498 let mut merged_with_last_token = false;
17499 if let Some(last_token) = line.back_mut() {
17500 if last_token.highlight == highlight {
17501 last_token.text.push_str(text);
17502 merged_with_last_token = true;
17503 }
17504 }
17505
17506 if !merged_with_last_token {
17507 line.push_back(Chunk {
17508 text: text.into(),
17509 highlight,
17510 });
17511 }
17512
17513 if chunk_lines.peek().is_some() {
17514 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17515 line.pop_front();
17516 }
17517 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17518 line.pop_back();
17519 }
17520
17521 lines.push(mem::take(&mut line));
17522 }
17523 }
17524 }
17525
17526 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17527 return;
17528 };
17529 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17530 }
17531
17532 pub fn open_context_menu(
17533 &mut self,
17534 _: &OpenContextMenu,
17535 window: &mut Window,
17536 cx: &mut Context<Self>,
17537 ) {
17538 self.request_autoscroll(Autoscroll::newest(), cx);
17539 let position = self.selections.newest_display(cx).start;
17540 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17541 }
17542
17543 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17544 &self.inlay_hint_cache
17545 }
17546
17547 pub fn replay_insert_event(
17548 &mut self,
17549 text: &str,
17550 relative_utf16_range: Option<Range<isize>>,
17551 window: &mut Window,
17552 cx: &mut Context<Self>,
17553 ) {
17554 if !self.input_enabled {
17555 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17556 return;
17557 }
17558 if let Some(relative_utf16_range) = relative_utf16_range {
17559 let selections = self.selections.all::<OffsetUtf16>(cx);
17560 self.change_selections(None, window, cx, |s| {
17561 let new_ranges = selections.into_iter().map(|range| {
17562 let start = OffsetUtf16(
17563 range
17564 .head()
17565 .0
17566 .saturating_add_signed(relative_utf16_range.start),
17567 );
17568 let end = OffsetUtf16(
17569 range
17570 .head()
17571 .0
17572 .saturating_add_signed(relative_utf16_range.end),
17573 );
17574 start..end
17575 });
17576 s.select_ranges(new_ranges);
17577 });
17578 }
17579
17580 self.handle_input(text, window, cx);
17581 }
17582
17583 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17584 let Some(provider) = self.semantics_provider.as_ref() else {
17585 return false;
17586 };
17587
17588 let mut supports = false;
17589 self.buffer().update(cx, |this, cx| {
17590 this.for_each_buffer(|buffer| {
17591 supports |= provider.supports_inlay_hints(buffer, cx);
17592 });
17593 });
17594
17595 supports
17596 }
17597
17598 pub fn is_focused(&self, window: &Window) -> bool {
17599 self.focus_handle.is_focused(window)
17600 }
17601
17602 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17603 cx.emit(EditorEvent::Focused);
17604
17605 if let Some(descendant) = self
17606 .last_focused_descendant
17607 .take()
17608 .and_then(|descendant| descendant.upgrade())
17609 {
17610 window.focus(&descendant);
17611 } else {
17612 if let Some(blame) = self.blame.as_ref() {
17613 blame.update(cx, GitBlame::focus)
17614 }
17615
17616 self.blink_manager.update(cx, BlinkManager::enable);
17617 self.show_cursor_names(window, cx);
17618 self.buffer.update(cx, |buffer, cx| {
17619 buffer.finalize_last_transaction(cx);
17620 if self.leader_peer_id.is_none() {
17621 buffer.set_active_selections(
17622 &self.selections.disjoint_anchors(),
17623 self.selections.line_mode,
17624 self.cursor_shape,
17625 cx,
17626 );
17627 }
17628 });
17629 }
17630 }
17631
17632 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17633 cx.emit(EditorEvent::FocusedIn)
17634 }
17635
17636 fn handle_focus_out(
17637 &mut self,
17638 event: FocusOutEvent,
17639 _window: &mut Window,
17640 cx: &mut Context<Self>,
17641 ) {
17642 if event.blurred != self.focus_handle {
17643 self.last_focused_descendant = Some(event.blurred);
17644 }
17645 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17646 }
17647
17648 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17649 self.blink_manager.update(cx, BlinkManager::disable);
17650 self.buffer
17651 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17652
17653 if let Some(blame) = self.blame.as_ref() {
17654 blame.update(cx, GitBlame::blur)
17655 }
17656 if !self.hover_state.focused(window, cx) {
17657 hide_hover(self, cx);
17658 }
17659 if !self
17660 .context_menu
17661 .borrow()
17662 .as_ref()
17663 .is_some_and(|context_menu| context_menu.focused(window, cx))
17664 {
17665 self.hide_context_menu(window, cx);
17666 }
17667 self.discard_inline_completion(false, cx);
17668 cx.emit(EditorEvent::Blurred);
17669 cx.notify();
17670 }
17671
17672 pub fn register_action<A: Action>(
17673 &mut self,
17674 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17675 ) -> Subscription {
17676 let id = self.next_editor_action_id.post_inc();
17677 let listener = Arc::new(listener);
17678 self.editor_actions.borrow_mut().insert(
17679 id,
17680 Box::new(move |window, _| {
17681 let listener = listener.clone();
17682 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17683 let action = action.downcast_ref().unwrap();
17684 if phase == DispatchPhase::Bubble {
17685 listener(action, window, cx)
17686 }
17687 })
17688 }),
17689 );
17690
17691 let editor_actions = self.editor_actions.clone();
17692 Subscription::new(move || {
17693 editor_actions.borrow_mut().remove(&id);
17694 })
17695 }
17696
17697 pub fn file_header_size(&self) -> u32 {
17698 FILE_HEADER_HEIGHT
17699 }
17700
17701 pub fn restore(
17702 &mut self,
17703 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17704 window: &mut Window,
17705 cx: &mut Context<Self>,
17706 ) {
17707 let workspace = self.workspace();
17708 let project = self.project.as_ref();
17709 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17710 let mut tasks = Vec::new();
17711 for (buffer_id, changes) in revert_changes {
17712 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17713 buffer.update(cx, |buffer, cx| {
17714 buffer.edit(
17715 changes
17716 .into_iter()
17717 .map(|(range, text)| (range, text.to_string())),
17718 None,
17719 cx,
17720 );
17721 });
17722
17723 if let Some(project) =
17724 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17725 {
17726 project.update(cx, |project, cx| {
17727 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17728 })
17729 }
17730 }
17731 }
17732 tasks
17733 });
17734 cx.spawn_in(window, async move |_, cx| {
17735 for (buffer, task) in save_tasks {
17736 let result = task.await;
17737 if result.is_err() {
17738 let Some(path) = buffer
17739 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17740 .ok()
17741 else {
17742 continue;
17743 };
17744 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17745 let Some(task) = cx
17746 .update_window_entity(&workspace, |workspace, window, cx| {
17747 workspace
17748 .open_path_preview(path, None, false, false, false, window, cx)
17749 })
17750 .ok()
17751 else {
17752 continue;
17753 };
17754 task.await.log_err();
17755 }
17756 }
17757 }
17758 })
17759 .detach();
17760 self.change_selections(None, window, cx, |selections| selections.refresh());
17761 }
17762
17763 pub fn to_pixel_point(
17764 &self,
17765 source: multi_buffer::Anchor,
17766 editor_snapshot: &EditorSnapshot,
17767 window: &mut Window,
17768 ) -> Option<gpui::Point<Pixels>> {
17769 let source_point = source.to_display_point(editor_snapshot);
17770 self.display_to_pixel_point(source_point, editor_snapshot, window)
17771 }
17772
17773 pub fn display_to_pixel_point(
17774 &self,
17775 source: DisplayPoint,
17776 editor_snapshot: &EditorSnapshot,
17777 window: &mut Window,
17778 ) -> Option<gpui::Point<Pixels>> {
17779 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17780 let text_layout_details = self.text_layout_details(window);
17781 let scroll_top = text_layout_details
17782 .scroll_anchor
17783 .scroll_position(editor_snapshot)
17784 .y;
17785
17786 if source.row().as_f32() < scroll_top.floor() {
17787 return None;
17788 }
17789 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17790 let source_y = line_height * (source.row().as_f32() - scroll_top);
17791 Some(gpui::Point::new(source_x, source_y))
17792 }
17793
17794 pub fn has_visible_completions_menu(&self) -> bool {
17795 !self.edit_prediction_preview_is_active()
17796 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17797 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17798 })
17799 }
17800
17801 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17802 self.addons
17803 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17804 }
17805
17806 pub fn unregister_addon<T: Addon>(&mut self) {
17807 self.addons.remove(&std::any::TypeId::of::<T>());
17808 }
17809
17810 pub fn addon<T: Addon>(&self) -> Option<&T> {
17811 let type_id = std::any::TypeId::of::<T>();
17812 self.addons
17813 .get(&type_id)
17814 .and_then(|item| item.to_any().downcast_ref::<T>())
17815 }
17816
17817 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17818 let text_layout_details = self.text_layout_details(window);
17819 let style = &text_layout_details.editor_style;
17820 let font_id = window.text_system().resolve_font(&style.text.font());
17821 let font_size = style.text.font_size.to_pixels(window.rem_size());
17822 let line_height = style.text.line_height_in_pixels(window.rem_size());
17823 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17824
17825 gpui::Size::new(em_width, line_height)
17826 }
17827
17828 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17829 self.load_diff_task.clone()
17830 }
17831
17832 fn read_metadata_from_db(
17833 &mut self,
17834 item_id: u64,
17835 workspace_id: WorkspaceId,
17836 window: &mut Window,
17837 cx: &mut Context<Editor>,
17838 ) {
17839 if self.is_singleton(cx)
17840 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
17841 {
17842 let buffer_snapshot = OnceCell::new();
17843
17844 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
17845 if !folds.is_empty() {
17846 let snapshot =
17847 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17848 self.fold_ranges(
17849 folds
17850 .into_iter()
17851 .map(|(start, end)| {
17852 snapshot.clip_offset(start, Bias::Left)
17853 ..snapshot.clip_offset(end, Bias::Right)
17854 })
17855 .collect(),
17856 false,
17857 window,
17858 cx,
17859 );
17860 }
17861 }
17862
17863 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
17864 if !selections.is_empty() {
17865 let snapshot =
17866 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17867 self.change_selections(None, window, cx, |s| {
17868 s.select_ranges(selections.into_iter().map(|(start, end)| {
17869 snapshot.clip_offset(start, Bias::Left)
17870 ..snapshot.clip_offset(end, Bias::Right)
17871 }));
17872 });
17873 }
17874 };
17875 }
17876
17877 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
17878 }
17879}
17880
17881fn insert_extra_newline_brackets(
17882 buffer: &MultiBufferSnapshot,
17883 range: Range<usize>,
17884 language: &language::LanguageScope,
17885) -> bool {
17886 let leading_whitespace_len = buffer
17887 .reversed_chars_at(range.start)
17888 .take_while(|c| c.is_whitespace() && *c != '\n')
17889 .map(|c| c.len_utf8())
17890 .sum::<usize>();
17891 let trailing_whitespace_len = buffer
17892 .chars_at(range.end)
17893 .take_while(|c| c.is_whitespace() && *c != '\n')
17894 .map(|c| c.len_utf8())
17895 .sum::<usize>();
17896 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
17897
17898 language.brackets().any(|(pair, enabled)| {
17899 let pair_start = pair.start.trim_end();
17900 let pair_end = pair.end.trim_start();
17901
17902 enabled
17903 && pair.newline
17904 && buffer.contains_str_at(range.end, pair_end)
17905 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
17906 })
17907}
17908
17909fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
17910 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
17911 [(buffer, range, _)] => (*buffer, range.clone()),
17912 _ => return false,
17913 };
17914 let pair = {
17915 let mut result: Option<BracketMatch> = None;
17916
17917 for pair in buffer
17918 .all_bracket_ranges(range.clone())
17919 .filter(move |pair| {
17920 pair.open_range.start <= range.start && pair.close_range.end >= range.end
17921 })
17922 {
17923 let len = pair.close_range.end - pair.open_range.start;
17924
17925 if let Some(existing) = &result {
17926 let existing_len = existing.close_range.end - existing.open_range.start;
17927 if len > existing_len {
17928 continue;
17929 }
17930 }
17931
17932 result = Some(pair);
17933 }
17934
17935 result
17936 };
17937 let Some(pair) = pair else {
17938 return false;
17939 };
17940 pair.newline_only
17941 && buffer
17942 .chars_for_range(pair.open_range.end..range.start)
17943 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
17944 .all(|c| c.is_whitespace() && c != '\n')
17945}
17946
17947fn get_uncommitted_diff_for_buffer(
17948 project: &Entity<Project>,
17949 buffers: impl IntoIterator<Item = Entity<Buffer>>,
17950 buffer: Entity<MultiBuffer>,
17951 cx: &mut App,
17952) -> Task<()> {
17953 let mut tasks = Vec::new();
17954 project.update(cx, |project, cx| {
17955 for buffer in buffers {
17956 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
17957 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
17958 }
17959 }
17960 });
17961 cx.spawn(async move |cx| {
17962 let diffs = future::join_all(tasks).await;
17963 buffer
17964 .update(cx, |buffer, cx| {
17965 for diff in diffs.into_iter().flatten() {
17966 buffer.add_diff(diff, cx);
17967 }
17968 })
17969 .ok();
17970 })
17971}
17972
17973fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
17974 let tab_size = tab_size.get() as usize;
17975 let mut width = offset;
17976
17977 for ch in text.chars() {
17978 width += if ch == '\t' {
17979 tab_size - (width % tab_size)
17980 } else {
17981 1
17982 };
17983 }
17984
17985 width - offset
17986}
17987
17988#[cfg(test)]
17989mod tests {
17990 use super::*;
17991
17992 #[test]
17993 fn test_string_size_with_expanded_tabs() {
17994 let nz = |val| NonZeroU32::new(val).unwrap();
17995 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
17996 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
17997 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
17998 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
17999 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18000 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18001 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18002 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18003 }
18004}
18005
18006/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18007struct WordBreakingTokenizer<'a> {
18008 input: &'a str,
18009}
18010
18011impl<'a> WordBreakingTokenizer<'a> {
18012 fn new(input: &'a str) -> Self {
18013 Self { input }
18014 }
18015}
18016
18017fn is_char_ideographic(ch: char) -> bool {
18018 use unicode_script::Script::*;
18019 use unicode_script::UnicodeScript;
18020 matches!(ch.script(), Han | Tangut | Yi)
18021}
18022
18023fn is_grapheme_ideographic(text: &str) -> bool {
18024 text.chars().any(is_char_ideographic)
18025}
18026
18027fn is_grapheme_whitespace(text: &str) -> bool {
18028 text.chars().any(|x| x.is_whitespace())
18029}
18030
18031fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18032 text.chars().next().map_or(false, |ch| {
18033 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18034 })
18035}
18036
18037#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18038enum WordBreakToken<'a> {
18039 Word { token: &'a str, grapheme_len: usize },
18040 InlineWhitespace { token: &'a str, grapheme_len: usize },
18041 Newline,
18042}
18043
18044impl<'a> Iterator for WordBreakingTokenizer<'a> {
18045 /// Yields a span, the count of graphemes in the token, and whether it was
18046 /// whitespace. Note that it also breaks at word boundaries.
18047 type Item = WordBreakToken<'a>;
18048
18049 fn next(&mut self) -> Option<Self::Item> {
18050 use unicode_segmentation::UnicodeSegmentation;
18051 if self.input.is_empty() {
18052 return None;
18053 }
18054
18055 let mut iter = self.input.graphemes(true).peekable();
18056 let mut offset = 0;
18057 let mut grapheme_len = 0;
18058 if let Some(first_grapheme) = iter.next() {
18059 let is_newline = first_grapheme == "\n";
18060 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18061 offset += first_grapheme.len();
18062 grapheme_len += 1;
18063 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18064 if let Some(grapheme) = iter.peek().copied() {
18065 if should_stay_with_preceding_ideograph(grapheme) {
18066 offset += grapheme.len();
18067 grapheme_len += 1;
18068 }
18069 }
18070 } else {
18071 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18072 let mut next_word_bound = words.peek().copied();
18073 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18074 next_word_bound = words.next();
18075 }
18076 while let Some(grapheme) = iter.peek().copied() {
18077 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18078 break;
18079 };
18080 if is_grapheme_whitespace(grapheme) != is_whitespace
18081 || (grapheme == "\n") != is_newline
18082 {
18083 break;
18084 };
18085 offset += grapheme.len();
18086 grapheme_len += 1;
18087 iter.next();
18088 }
18089 }
18090 let token = &self.input[..offset];
18091 self.input = &self.input[offset..];
18092 if token == "\n" {
18093 Some(WordBreakToken::Newline)
18094 } else if is_whitespace {
18095 Some(WordBreakToken::InlineWhitespace {
18096 token,
18097 grapheme_len,
18098 })
18099 } else {
18100 Some(WordBreakToken::Word {
18101 token,
18102 grapheme_len,
18103 })
18104 }
18105 } else {
18106 None
18107 }
18108 }
18109}
18110
18111#[test]
18112fn test_word_breaking_tokenizer() {
18113 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18114 ("", &[]),
18115 (" ", &[whitespace(" ", 2)]),
18116 ("Ʒ", &[word("Ʒ", 1)]),
18117 ("Ǽ", &[word("Ǽ", 1)]),
18118 ("⋑", &[word("⋑", 1)]),
18119 ("⋑⋑", &[word("⋑⋑", 2)]),
18120 (
18121 "原理,进而",
18122 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18123 ),
18124 (
18125 "hello world",
18126 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18127 ),
18128 (
18129 "hello, world",
18130 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18131 ),
18132 (
18133 " hello world",
18134 &[
18135 whitespace(" ", 2),
18136 word("hello", 5),
18137 whitespace(" ", 1),
18138 word("world", 5),
18139 ],
18140 ),
18141 (
18142 "这是什么 \n 钢笔",
18143 &[
18144 word("这", 1),
18145 word("是", 1),
18146 word("什", 1),
18147 word("么", 1),
18148 whitespace(" ", 1),
18149 newline(),
18150 whitespace(" ", 1),
18151 word("钢", 1),
18152 word("笔", 1),
18153 ],
18154 ),
18155 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
18156 ];
18157
18158 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18159 WordBreakToken::Word {
18160 token,
18161 grapheme_len,
18162 }
18163 }
18164
18165 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18166 WordBreakToken::InlineWhitespace {
18167 token,
18168 grapheme_len,
18169 }
18170 }
18171
18172 fn newline() -> WordBreakToken<'static> {
18173 WordBreakToken::Newline
18174 }
18175
18176 for (input, result) in tests {
18177 assert_eq!(
18178 WordBreakingTokenizer::new(input)
18179 .collect::<Vec<_>>()
18180 .as_slice(),
18181 *result,
18182 );
18183 }
18184}
18185
18186fn wrap_with_prefix(
18187 line_prefix: String,
18188 unwrapped_text: String,
18189 wrap_column: usize,
18190 tab_size: NonZeroU32,
18191 preserve_existing_whitespace: bool,
18192) -> String {
18193 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18194 let mut wrapped_text = String::new();
18195 let mut current_line = line_prefix.clone();
18196
18197 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18198 let mut current_line_len = line_prefix_len;
18199 let mut in_whitespace = false;
18200 for token in tokenizer {
18201 let have_preceding_whitespace = in_whitespace;
18202 match token {
18203 WordBreakToken::Word {
18204 token,
18205 grapheme_len,
18206 } => {
18207 in_whitespace = false;
18208 if current_line_len + grapheme_len > wrap_column
18209 && current_line_len != line_prefix_len
18210 {
18211 wrapped_text.push_str(current_line.trim_end());
18212 wrapped_text.push('\n');
18213 current_line.truncate(line_prefix.len());
18214 current_line_len = line_prefix_len;
18215 }
18216 current_line.push_str(token);
18217 current_line_len += grapheme_len;
18218 }
18219 WordBreakToken::InlineWhitespace {
18220 mut token,
18221 mut grapheme_len,
18222 } => {
18223 in_whitespace = true;
18224 if have_preceding_whitespace && !preserve_existing_whitespace {
18225 continue;
18226 }
18227 if !preserve_existing_whitespace {
18228 token = " ";
18229 grapheme_len = 1;
18230 }
18231 if current_line_len + grapheme_len > wrap_column {
18232 wrapped_text.push_str(current_line.trim_end());
18233 wrapped_text.push('\n');
18234 current_line.truncate(line_prefix.len());
18235 current_line_len = line_prefix_len;
18236 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18237 current_line.push_str(token);
18238 current_line_len += grapheme_len;
18239 }
18240 }
18241 WordBreakToken::Newline => {
18242 in_whitespace = true;
18243 if preserve_existing_whitespace {
18244 wrapped_text.push_str(current_line.trim_end());
18245 wrapped_text.push('\n');
18246 current_line.truncate(line_prefix.len());
18247 current_line_len = line_prefix_len;
18248 } else if have_preceding_whitespace {
18249 continue;
18250 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18251 {
18252 wrapped_text.push_str(current_line.trim_end());
18253 wrapped_text.push('\n');
18254 current_line.truncate(line_prefix.len());
18255 current_line_len = line_prefix_len;
18256 } else if current_line_len != line_prefix_len {
18257 current_line.push(' ');
18258 current_line_len += 1;
18259 }
18260 }
18261 }
18262 }
18263
18264 if !current_line.is_empty() {
18265 wrapped_text.push_str(¤t_line);
18266 }
18267 wrapped_text
18268}
18269
18270#[test]
18271fn test_wrap_with_prefix() {
18272 assert_eq!(
18273 wrap_with_prefix(
18274 "# ".to_string(),
18275 "abcdefg".to_string(),
18276 4,
18277 NonZeroU32::new(4).unwrap(),
18278 false,
18279 ),
18280 "# abcdefg"
18281 );
18282 assert_eq!(
18283 wrap_with_prefix(
18284 "".to_string(),
18285 "\thello world".to_string(),
18286 8,
18287 NonZeroU32::new(4).unwrap(),
18288 false,
18289 ),
18290 "hello\nworld"
18291 );
18292 assert_eq!(
18293 wrap_with_prefix(
18294 "// ".to_string(),
18295 "xx \nyy zz aa bb cc".to_string(),
18296 12,
18297 NonZeroU32::new(4).unwrap(),
18298 false,
18299 ),
18300 "// xx yy zz\n// aa bb cc"
18301 );
18302 assert_eq!(
18303 wrap_with_prefix(
18304 String::new(),
18305 "这是什么 \n 钢笔".to_string(),
18306 3,
18307 NonZeroU32::new(4).unwrap(),
18308 false,
18309 ),
18310 "这是什\n么 钢\n笔"
18311 );
18312}
18313
18314pub trait CollaborationHub {
18315 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18316 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18317 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18318}
18319
18320impl CollaborationHub for Entity<Project> {
18321 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18322 self.read(cx).collaborators()
18323 }
18324
18325 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18326 self.read(cx).user_store().read(cx).participant_indices()
18327 }
18328
18329 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18330 let this = self.read(cx);
18331 let user_ids = this.collaborators().values().map(|c| c.user_id);
18332 this.user_store().read_with(cx, |user_store, cx| {
18333 user_store.participant_names(user_ids, cx)
18334 })
18335 }
18336}
18337
18338pub trait SemanticsProvider {
18339 fn hover(
18340 &self,
18341 buffer: &Entity<Buffer>,
18342 position: text::Anchor,
18343 cx: &mut App,
18344 ) -> Option<Task<Vec<project::Hover>>>;
18345
18346 fn inlay_hints(
18347 &self,
18348 buffer_handle: Entity<Buffer>,
18349 range: Range<text::Anchor>,
18350 cx: &mut App,
18351 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18352
18353 fn resolve_inlay_hint(
18354 &self,
18355 hint: InlayHint,
18356 buffer_handle: Entity<Buffer>,
18357 server_id: LanguageServerId,
18358 cx: &mut App,
18359 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18360
18361 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18362
18363 fn document_highlights(
18364 &self,
18365 buffer: &Entity<Buffer>,
18366 position: text::Anchor,
18367 cx: &mut App,
18368 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18369
18370 fn definitions(
18371 &self,
18372 buffer: &Entity<Buffer>,
18373 position: text::Anchor,
18374 kind: GotoDefinitionKind,
18375 cx: &mut App,
18376 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18377
18378 fn range_for_rename(
18379 &self,
18380 buffer: &Entity<Buffer>,
18381 position: text::Anchor,
18382 cx: &mut App,
18383 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18384
18385 fn perform_rename(
18386 &self,
18387 buffer: &Entity<Buffer>,
18388 position: text::Anchor,
18389 new_name: String,
18390 cx: &mut App,
18391 ) -> Option<Task<Result<ProjectTransaction>>>;
18392}
18393
18394pub trait CompletionProvider {
18395 fn completions(
18396 &self,
18397 excerpt_id: ExcerptId,
18398 buffer: &Entity<Buffer>,
18399 buffer_position: text::Anchor,
18400 trigger: CompletionContext,
18401 window: &mut Window,
18402 cx: &mut Context<Editor>,
18403 ) -> Task<Result<Option<Vec<Completion>>>>;
18404
18405 fn resolve_completions(
18406 &self,
18407 buffer: Entity<Buffer>,
18408 completion_indices: Vec<usize>,
18409 completions: Rc<RefCell<Box<[Completion]>>>,
18410 cx: &mut Context<Editor>,
18411 ) -> Task<Result<bool>>;
18412
18413 fn apply_additional_edits_for_completion(
18414 &self,
18415 _buffer: Entity<Buffer>,
18416 _completions: Rc<RefCell<Box<[Completion]>>>,
18417 _completion_index: usize,
18418 _push_to_history: bool,
18419 _cx: &mut Context<Editor>,
18420 ) -> Task<Result<Option<language::Transaction>>> {
18421 Task::ready(Ok(None))
18422 }
18423
18424 fn is_completion_trigger(
18425 &self,
18426 buffer: &Entity<Buffer>,
18427 position: language::Anchor,
18428 text: &str,
18429 trigger_in_words: bool,
18430 cx: &mut Context<Editor>,
18431 ) -> bool;
18432
18433 fn sort_completions(&self) -> bool {
18434 true
18435 }
18436
18437 fn filter_completions(&self) -> bool {
18438 true
18439 }
18440}
18441
18442pub trait CodeActionProvider {
18443 fn id(&self) -> Arc<str>;
18444
18445 fn code_actions(
18446 &self,
18447 buffer: &Entity<Buffer>,
18448 range: Range<text::Anchor>,
18449 window: &mut Window,
18450 cx: &mut App,
18451 ) -> Task<Result<Vec<CodeAction>>>;
18452
18453 fn apply_code_action(
18454 &self,
18455 buffer_handle: Entity<Buffer>,
18456 action: CodeAction,
18457 excerpt_id: ExcerptId,
18458 push_to_history: bool,
18459 window: &mut Window,
18460 cx: &mut App,
18461 ) -> Task<Result<ProjectTransaction>>;
18462}
18463
18464impl CodeActionProvider for Entity<Project> {
18465 fn id(&self) -> Arc<str> {
18466 "project".into()
18467 }
18468
18469 fn code_actions(
18470 &self,
18471 buffer: &Entity<Buffer>,
18472 range: Range<text::Anchor>,
18473 _window: &mut Window,
18474 cx: &mut App,
18475 ) -> Task<Result<Vec<CodeAction>>> {
18476 self.update(cx, |project, cx| {
18477 let code_lens = project.code_lens(buffer, range.clone(), cx);
18478 let code_actions = project.code_actions(buffer, range, None, cx);
18479 cx.background_spawn(async move {
18480 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18481 Ok(code_lens
18482 .context("code lens fetch")?
18483 .into_iter()
18484 .chain(code_actions.context("code action fetch")?)
18485 .collect())
18486 })
18487 })
18488 }
18489
18490 fn apply_code_action(
18491 &self,
18492 buffer_handle: Entity<Buffer>,
18493 action: CodeAction,
18494 _excerpt_id: ExcerptId,
18495 push_to_history: bool,
18496 _window: &mut Window,
18497 cx: &mut App,
18498 ) -> Task<Result<ProjectTransaction>> {
18499 self.update(cx, |project, cx| {
18500 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18501 })
18502 }
18503}
18504
18505fn snippet_completions(
18506 project: &Project,
18507 buffer: &Entity<Buffer>,
18508 buffer_position: text::Anchor,
18509 cx: &mut App,
18510) -> Task<Result<Vec<Completion>>> {
18511 let language = buffer.read(cx).language_at(buffer_position);
18512 let language_name = language.as_ref().map(|language| language.lsp_id());
18513 let snippet_store = project.snippets().read(cx);
18514 let snippets = snippet_store.snippets_for(language_name, cx);
18515
18516 if snippets.is_empty() {
18517 return Task::ready(Ok(vec![]));
18518 }
18519 let snapshot = buffer.read(cx).text_snapshot();
18520 let chars: String = snapshot
18521 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18522 .collect();
18523
18524 let scope = language.map(|language| language.default_scope());
18525 let executor = cx.background_executor().clone();
18526
18527 cx.background_spawn(async move {
18528 let classifier = CharClassifier::new(scope).for_completion(true);
18529 let mut last_word = chars
18530 .chars()
18531 .take_while(|c| classifier.is_word(*c))
18532 .collect::<String>();
18533 last_word = last_word.chars().rev().collect();
18534
18535 if last_word.is_empty() {
18536 return Ok(vec![]);
18537 }
18538
18539 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18540 let to_lsp = |point: &text::Anchor| {
18541 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18542 point_to_lsp(end)
18543 };
18544 let lsp_end = to_lsp(&buffer_position);
18545
18546 let candidates = snippets
18547 .iter()
18548 .enumerate()
18549 .flat_map(|(ix, snippet)| {
18550 snippet
18551 .prefix
18552 .iter()
18553 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18554 })
18555 .collect::<Vec<StringMatchCandidate>>();
18556
18557 let mut matches = fuzzy::match_strings(
18558 &candidates,
18559 &last_word,
18560 last_word.chars().any(|c| c.is_uppercase()),
18561 100,
18562 &Default::default(),
18563 executor,
18564 )
18565 .await;
18566
18567 // Remove all candidates where the query's start does not match the start of any word in the candidate
18568 if let Some(query_start) = last_word.chars().next() {
18569 matches.retain(|string_match| {
18570 split_words(&string_match.string).any(|word| {
18571 // Check that the first codepoint of the word as lowercase matches the first
18572 // codepoint of the query as lowercase
18573 word.chars()
18574 .flat_map(|codepoint| codepoint.to_lowercase())
18575 .zip(query_start.to_lowercase())
18576 .all(|(word_cp, query_cp)| word_cp == query_cp)
18577 })
18578 });
18579 }
18580
18581 let matched_strings = matches
18582 .into_iter()
18583 .map(|m| m.string)
18584 .collect::<HashSet<_>>();
18585
18586 let result: Vec<Completion> = snippets
18587 .into_iter()
18588 .filter_map(|snippet| {
18589 let matching_prefix = snippet
18590 .prefix
18591 .iter()
18592 .find(|prefix| matched_strings.contains(*prefix))?;
18593 let start = as_offset - last_word.len();
18594 let start = snapshot.anchor_before(start);
18595 let range = start..buffer_position;
18596 let lsp_start = to_lsp(&start);
18597 let lsp_range = lsp::Range {
18598 start: lsp_start,
18599 end: lsp_end,
18600 };
18601 Some(Completion {
18602 old_range: range,
18603 new_text: snippet.body.clone(),
18604 source: CompletionSource::Lsp {
18605 server_id: LanguageServerId(usize::MAX),
18606 resolved: true,
18607 lsp_completion: Box::new(lsp::CompletionItem {
18608 label: snippet.prefix.first().unwrap().clone(),
18609 kind: Some(CompletionItemKind::SNIPPET),
18610 label_details: snippet.description.as_ref().map(|description| {
18611 lsp::CompletionItemLabelDetails {
18612 detail: Some(description.clone()),
18613 description: None,
18614 }
18615 }),
18616 insert_text_format: Some(InsertTextFormat::SNIPPET),
18617 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18618 lsp::InsertReplaceEdit {
18619 new_text: snippet.body.clone(),
18620 insert: lsp_range,
18621 replace: lsp_range,
18622 },
18623 )),
18624 filter_text: Some(snippet.body.clone()),
18625 sort_text: Some(char::MAX.to_string()),
18626 ..lsp::CompletionItem::default()
18627 }),
18628 lsp_defaults: None,
18629 },
18630 label: CodeLabel {
18631 text: matching_prefix.clone(),
18632 runs: Vec::new(),
18633 filter_range: 0..matching_prefix.len(),
18634 },
18635 icon_path: None,
18636 documentation: snippet
18637 .description
18638 .clone()
18639 .map(|description| CompletionDocumentation::SingleLine(description.into())),
18640 confirm: None,
18641 })
18642 })
18643 .collect();
18644
18645 Ok(result)
18646 })
18647}
18648
18649impl CompletionProvider for Entity<Project> {
18650 fn completions(
18651 &self,
18652 _excerpt_id: ExcerptId,
18653 buffer: &Entity<Buffer>,
18654 buffer_position: text::Anchor,
18655 options: CompletionContext,
18656 _window: &mut Window,
18657 cx: &mut Context<Editor>,
18658 ) -> Task<Result<Option<Vec<Completion>>>> {
18659 self.update(cx, |project, cx| {
18660 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18661 let project_completions = project.completions(buffer, buffer_position, options, cx);
18662 cx.background_spawn(async move {
18663 let snippets_completions = snippets.await?;
18664 match project_completions.await? {
18665 Some(mut completions) => {
18666 completions.extend(snippets_completions);
18667 Ok(Some(completions))
18668 }
18669 None => {
18670 if snippets_completions.is_empty() {
18671 Ok(None)
18672 } else {
18673 Ok(Some(snippets_completions))
18674 }
18675 }
18676 }
18677 })
18678 })
18679 }
18680
18681 fn resolve_completions(
18682 &self,
18683 buffer: Entity<Buffer>,
18684 completion_indices: Vec<usize>,
18685 completions: Rc<RefCell<Box<[Completion]>>>,
18686 cx: &mut Context<Editor>,
18687 ) -> Task<Result<bool>> {
18688 self.update(cx, |project, cx| {
18689 project.lsp_store().update(cx, |lsp_store, cx| {
18690 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18691 })
18692 })
18693 }
18694
18695 fn apply_additional_edits_for_completion(
18696 &self,
18697 buffer: Entity<Buffer>,
18698 completions: Rc<RefCell<Box<[Completion]>>>,
18699 completion_index: usize,
18700 push_to_history: bool,
18701 cx: &mut Context<Editor>,
18702 ) -> Task<Result<Option<language::Transaction>>> {
18703 self.update(cx, |project, cx| {
18704 project.lsp_store().update(cx, |lsp_store, cx| {
18705 lsp_store.apply_additional_edits_for_completion(
18706 buffer,
18707 completions,
18708 completion_index,
18709 push_to_history,
18710 cx,
18711 )
18712 })
18713 })
18714 }
18715
18716 fn is_completion_trigger(
18717 &self,
18718 buffer: &Entity<Buffer>,
18719 position: language::Anchor,
18720 text: &str,
18721 trigger_in_words: bool,
18722 cx: &mut Context<Editor>,
18723 ) -> bool {
18724 let mut chars = text.chars();
18725 let char = if let Some(char) = chars.next() {
18726 char
18727 } else {
18728 return false;
18729 };
18730 if chars.next().is_some() {
18731 return false;
18732 }
18733
18734 let buffer = buffer.read(cx);
18735 let snapshot = buffer.snapshot();
18736 if !snapshot.settings_at(position, cx).show_completions_on_input {
18737 return false;
18738 }
18739 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18740 if trigger_in_words && classifier.is_word(char) {
18741 return true;
18742 }
18743
18744 buffer.completion_triggers().contains(text)
18745 }
18746}
18747
18748impl SemanticsProvider for Entity<Project> {
18749 fn hover(
18750 &self,
18751 buffer: &Entity<Buffer>,
18752 position: text::Anchor,
18753 cx: &mut App,
18754 ) -> Option<Task<Vec<project::Hover>>> {
18755 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18756 }
18757
18758 fn document_highlights(
18759 &self,
18760 buffer: &Entity<Buffer>,
18761 position: text::Anchor,
18762 cx: &mut App,
18763 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18764 Some(self.update(cx, |project, cx| {
18765 project.document_highlights(buffer, position, cx)
18766 }))
18767 }
18768
18769 fn definitions(
18770 &self,
18771 buffer: &Entity<Buffer>,
18772 position: text::Anchor,
18773 kind: GotoDefinitionKind,
18774 cx: &mut App,
18775 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18776 Some(self.update(cx, |project, cx| match kind {
18777 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18778 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18779 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18780 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18781 }))
18782 }
18783
18784 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18785 // TODO: make this work for remote projects
18786 self.update(cx, |this, cx| {
18787 buffer.update(cx, |buffer, cx| {
18788 this.any_language_server_supports_inlay_hints(buffer, cx)
18789 })
18790 })
18791 }
18792
18793 fn inlay_hints(
18794 &self,
18795 buffer_handle: Entity<Buffer>,
18796 range: Range<text::Anchor>,
18797 cx: &mut App,
18798 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
18799 Some(self.update(cx, |project, cx| {
18800 project.inlay_hints(buffer_handle, range, cx)
18801 }))
18802 }
18803
18804 fn resolve_inlay_hint(
18805 &self,
18806 hint: InlayHint,
18807 buffer_handle: Entity<Buffer>,
18808 server_id: LanguageServerId,
18809 cx: &mut App,
18810 ) -> Option<Task<anyhow::Result<InlayHint>>> {
18811 Some(self.update(cx, |project, cx| {
18812 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
18813 }))
18814 }
18815
18816 fn range_for_rename(
18817 &self,
18818 buffer: &Entity<Buffer>,
18819 position: text::Anchor,
18820 cx: &mut App,
18821 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
18822 Some(self.update(cx, |project, cx| {
18823 let buffer = buffer.clone();
18824 let task = project.prepare_rename(buffer.clone(), position, cx);
18825 cx.spawn(async move |_, cx| {
18826 Ok(match task.await? {
18827 PrepareRenameResponse::Success(range) => Some(range),
18828 PrepareRenameResponse::InvalidPosition => None,
18829 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
18830 // Fallback on using TreeSitter info to determine identifier range
18831 buffer.update(cx, |buffer, _| {
18832 let snapshot = buffer.snapshot();
18833 let (range, kind) = snapshot.surrounding_word(position);
18834 if kind != Some(CharKind::Word) {
18835 return None;
18836 }
18837 Some(
18838 snapshot.anchor_before(range.start)
18839 ..snapshot.anchor_after(range.end),
18840 )
18841 })?
18842 }
18843 })
18844 })
18845 }))
18846 }
18847
18848 fn perform_rename(
18849 &self,
18850 buffer: &Entity<Buffer>,
18851 position: text::Anchor,
18852 new_name: String,
18853 cx: &mut App,
18854 ) -> Option<Task<Result<ProjectTransaction>>> {
18855 Some(self.update(cx, |project, cx| {
18856 project.perform_rename(buffer.clone(), position, new_name, cx)
18857 }))
18858 }
18859}
18860
18861fn inlay_hint_settings(
18862 location: Anchor,
18863 snapshot: &MultiBufferSnapshot,
18864 cx: &mut Context<Editor>,
18865) -> InlayHintSettings {
18866 let file = snapshot.file_at(location);
18867 let language = snapshot.language_at(location).map(|l| l.name());
18868 language_settings(language, file, cx).inlay_hints
18869}
18870
18871fn consume_contiguous_rows(
18872 contiguous_row_selections: &mut Vec<Selection<Point>>,
18873 selection: &Selection<Point>,
18874 display_map: &DisplaySnapshot,
18875 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
18876) -> (MultiBufferRow, MultiBufferRow) {
18877 contiguous_row_selections.push(selection.clone());
18878 let start_row = MultiBufferRow(selection.start.row);
18879 let mut end_row = ending_row(selection, display_map);
18880
18881 while let Some(next_selection) = selections.peek() {
18882 if next_selection.start.row <= end_row.0 {
18883 end_row = ending_row(next_selection, display_map);
18884 contiguous_row_selections.push(selections.next().unwrap().clone());
18885 } else {
18886 break;
18887 }
18888 }
18889 (start_row, end_row)
18890}
18891
18892fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
18893 if next_selection.end.column > 0 || next_selection.is_empty() {
18894 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
18895 } else {
18896 MultiBufferRow(next_selection.end.row)
18897 }
18898}
18899
18900impl EditorSnapshot {
18901 pub fn remote_selections_in_range<'a>(
18902 &'a self,
18903 range: &'a Range<Anchor>,
18904 collaboration_hub: &dyn CollaborationHub,
18905 cx: &'a App,
18906 ) -> impl 'a + Iterator<Item = RemoteSelection> {
18907 let participant_names = collaboration_hub.user_names(cx);
18908 let participant_indices = collaboration_hub.user_participant_indices(cx);
18909 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
18910 let collaborators_by_replica_id = collaborators_by_peer_id
18911 .iter()
18912 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
18913 .collect::<HashMap<_, _>>();
18914 self.buffer_snapshot
18915 .selections_in_range(range, false)
18916 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
18917 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
18918 let participant_index = participant_indices.get(&collaborator.user_id).copied();
18919 let user_name = participant_names.get(&collaborator.user_id).cloned();
18920 Some(RemoteSelection {
18921 replica_id,
18922 selection,
18923 cursor_shape,
18924 line_mode,
18925 participant_index,
18926 peer_id: collaborator.peer_id,
18927 user_name,
18928 })
18929 })
18930 }
18931
18932 pub fn hunks_for_ranges(
18933 &self,
18934 ranges: impl IntoIterator<Item = Range<Point>>,
18935 ) -> Vec<MultiBufferDiffHunk> {
18936 let mut hunks = Vec::new();
18937 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
18938 HashMap::default();
18939 for query_range in ranges {
18940 let query_rows =
18941 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
18942 for hunk in self.buffer_snapshot.diff_hunks_in_range(
18943 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
18944 ) {
18945 // Include deleted hunks that are adjacent to the query range, because
18946 // otherwise they would be missed.
18947 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
18948 if hunk.status().is_deleted() {
18949 intersects_range |= hunk.row_range.start == query_rows.end;
18950 intersects_range |= hunk.row_range.end == query_rows.start;
18951 }
18952 if intersects_range {
18953 if !processed_buffer_rows
18954 .entry(hunk.buffer_id)
18955 .or_default()
18956 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
18957 {
18958 continue;
18959 }
18960 hunks.push(hunk);
18961 }
18962 }
18963 }
18964
18965 hunks
18966 }
18967
18968 fn display_diff_hunks_for_rows<'a>(
18969 &'a self,
18970 display_rows: Range<DisplayRow>,
18971 folded_buffers: &'a HashSet<BufferId>,
18972 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
18973 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
18974 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
18975
18976 self.buffer_snapshot
18977 .diff_hunks_in_range(buffer_start..buffer_end)
18978 .filter_map(|hunk| {
18979 if folded_buffers.contains(&hunk.buffer_id) {
18980 return None;
18981 }
18982
18983 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
18984 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
18985
18986 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
18987 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
18988
18989 let display_hunk = if hunk_display_start.column() != 0 {
18990 DisplayDiffHunk::Folded {
18991 display_row: hunk_display_start.row(),
18992 }
18993 } else {
18994 let mut end_row = hunk_display_end.row();
18995 if hunk_display_end.column() > 0 {
18996 end_row.0 += 1;
18997 }
18998 let is_created_file = hunk.is_created_file();
18999 DisplayDiffHunk::Unfolded {
19000 status: hunk.status(),
19001 diff_base_byte_range: hunk.diff_base_byte_range,
19002 display_row_range: hunk_display_start.row()..end_row,
19003 multi_buffer_range: Anchor::range_in_buffer(
19004 hunk.excerpt_id,
19005 hunk.buffer_id,
19006 hunk.buffer_range,
19007 ),
19008 is_created_file,
19009 }
19010 };
19011
19012 Some(display_hunk)
19013 })
19014 }
19015
19016 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19017 self.display_snapshot.buffer_snapshot.language_at(position)
19018 }
19019
19020 pub fn is_focused(&self) -> bool {
19021 self.is_focused
19022 }
19023
19024 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19025 self.placeholder_text.as_ref()
19026 }
19027
19028 pub fn scroll_position(&self) -> gpui::Point<f32> {
19029 self.scroll_anchor.scroll_position(&self.display_snapshot)
19030 }
19031
19032 fn gutter_dimensions(
19033 &self,
19034 font_id: FontId,
19035 font_size: Pixels,
19036 max_line_number_width: Pixels,
19037 cx: &App,
19038 ) -> Option<GutterDimensions> {
19039 if !self.show_gutter {
19040 return None;
19041 }
19042
19043 let descent = cx.text_system().descent(font_id, font_size);
19044 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19045 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19046
19047 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19048 matches!(
19049 ProjectSettings::get_global(cx).git.git_gutter,
19050 Some(GitGutterSetting::TrackedFiles)
19051 )
19052 });
19053 let gutter_settings = EditorSettings::get_global(cx).gutter;
19054 let show_line_numbers = self
19055 .show_line_numbers
19056 .unwrap_or(gutter_settings.line_numbers);
19057 let line_gutter_width = if show_line_numbers {
19058 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19059 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19060 max_line_number_width.max(min_width_for_number_on_gutter)
19061 } else {
19062 0.0.into()
19063 };
19064
19065 let show_code_actions = self
19066 .show_code_actions
19067 .unwrap_or(gutter_settings.code_actions);
19068
19069 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19070 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19071
19072 let git_blame_entries_width =
19073 self.git_blame_gutter_max_author_length
19074 .map(|max_author_length| {
19075 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19076 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19077
19078 /// The number of characters to dedicate to gaps and margins.
19079 const SPACING_WIDTH: usize = 4;
19080
19081 let max_char_count = max_author_length.min(renderer.max_author_length())
19082 + ::git::SHORT_SHA_LENGTH
19083 + MAX_RELATIVE_TIMESTAMP.len()
19084 + SPACING_WIDTH;
19085
19086 em_advance * max_char_count
19087 });
19088
19089 let is_singleton = self.buffer_snapshot.is_singleton();
19090
19091 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19092 left_padding += if !is_singleton {
19093 em_width * 4.0
19094 } else if show_code_actions || show_runnables || show_breakpoints {
19095 em_width * 3.0
19096 } else if show_git_gutter && show_line_numbers {
19097 em_width * 2.0
19098 } else if show_git_gutter || show_line_numbers {
19099 em_width
19100 } else {
19101 px(0.)
19102 };
19103
19104 let shows_folds = is_singleton && gutter_settings.folds;
19105
19106 let right_padding = if shows_folds && show_line_numbers {
19107 em_width * 4.0
19108 } else if shows_folds || (!is_singleton && show_line_numbers) {
19109 em_width * 3.0
19110 } else if show_line_numbers {
19111 em_width
19112 } else {
19113 px(0.)
19114 };
19115
19116 Some(GutterDimensions {
19117 left_padding,
19118 right_padding,
19119 width: line_gutter_width + left_padding + right_padding,
19120 margin: -descent,
19121 git_blame_entries_width,
19122 })
19123 }
19124
19125 pub fn render_crease_toggle(
19126 &self,
19127 buffer_row: MultiBufferRow,
19128 row_contains_cursor: bool,
19129 editor: Entity<Editor>,
19130 window: &mut Window,
19131 cx: &mut App,
19132 ) -> Option<AnyElement> {
19133 let folded = self.is_line_folded(buffer_row);
19134 let mut is_foldable = false;
19135
19136 if let Some(crease) = self
19137 .crease_snapshot
19138 .query_row(buffer_row, &self.buffer_snapshot)
19139 {
19140 is_foldable = true;
19141 match crease {
19142 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
19143 if let Some(render_toggle) = render_toggle {
19144 let toggle_callback =
19145 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
19146 if folded {
19147 editor.update(cx, |editor, cx| {
19148 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
19149 });
19150 } else {
19151 editor.update(cx, |editor, cx| {
19152 editor.unfold_at(
19153 &crate::UnfoldAt { buffer_row },
19154 window,
19155 cx,
19156 )
19157 });
19158 }
19159 });
19160 return Some((render_toggle)(
19161 buffer_row,
19162 folded,
19163 toggle_callback,
19164 window,
19165 cx,
19166 ));
19167 }
19168 }
19169 }
19170 }
19171
19172 is_foldable |= self.starts_indent(buffer_row);
19173
19174 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19175 Some(
19176 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19177 .toggle_state(folded)
19178 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19179 if folded {
19180 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
19181 } else {
19182 this.fold_at(&FoldAt { buffer_row }, window, cx);
19183 }
19184 }))
19185 .into_any_element(),
19186 )
19187 } else {
19188 None
19189 }
19190 }
19191
19192 pub fn render_crease_trailer(
19193 &self,
19194 buffer_row: MultiBufferRow,
19195 window: &mut Window,
19196 cx: &mut App,
19197 ) -> Option<AnyElement> {
19198 let folded = self.is_line_folded(buffer_row);
19199 if let Crease::Inline { render_trailer, .. } = self
19200 .crease_snapshot
19201 .query_row(buffer_row, &self.buffer_snapshot)?
19202 {
19203 let render_trailer = render_trailer.as_ref()?;
19204 Some(render_trailer(buffer_row, folded, window, cx))
19205 } else {
19206 None
19207 }
19208 }
19209}
19210
19211impl Deref for EditorSnapshot {
19212 type Target = DisplaySnapshot;
19213
19214 fn deref(&self) -> &Self::Target {
19215 &self.display_snapshot
19216 }
19217}
19218
19219#[derive(Clone, Debug, PartialEq, Eq)]
19220pub enum EditorEvent {
19221 InputIgnored {
19222 text: Arc<str>,
19223 },
19224 InputHandled {
19225 utf16_range_to_replace: Option<Range<isize>>,
19226 text: Arc<str>,
19227 },
19228 ExcerptsAdded {
19229 buffer: Entity<Buffer>,
19230 predecessor: ExcerptId,
19231 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19232 },
19233 ExcerptsRemoved {
19234 ids: Vec<ExcerptId>,
19235 },
19236 BufferFoldToggled {
19237 ids: Vec<ExcerptId>,
19238 folded: bool,
19239 },
19240 ExcerptsEdited {
19241 ids: Vec<ExcerptId>,
19242 },
19243 ExcerptsExpanded {
19244 ids: Vec<ExcerptId>,
19245 },
19246 BufferEdited,
19247 Edited {
19248 transaction_id: clock::Lamport,
19249 },
19250 Reparsed(BufferId),
19251 Focused,
19252 FocusedIn,
19253 Blurred,
19254 DirtyChanged,
19255 Saved,
19256 TitleChanged,
19257 DiffBaseChanged,
19258 SelectionsChanged {
19259 local: bool,
19260 },
19261 ScrollPositionChanged {
19262 local: bool,
19263 autoscroll: bool,
19264 },
19265 Closed,
19266 TransactionUndone {
19267 transaction_id: clock::Lamport,
19268 },
19269 TransactionBegun {
19270 transaction_id: clock::Lamport,
19271 },
19272 Reloaded,
19273 CursorShapeChanged,
19274 PushedToNavHistory {
19275 anchor: Anchor,
19276 is_deactivate: bool,
19277 },
19278}
19279
19280impl EventEmitter<EditorEvent> for Editor {}
19281
19282impl Focusable for Editor {
19283 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19284 self.focus_handle.clone()
19285 }
19286}
19287
19288impl Render for Editor {
19289 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19290 let settings = ThemeSettings::get_global(cx);
19291
19292 let mut text_style = match self.mode {
19293 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19294 color: cx.theme().colors().editor_foreground,
19295 font_family: settings.ui_font.family.clone(),
19296 font_features: settings.ui_font.features.clone(),
19297 font_fallbacks: settings.ui_font.fallbacks.clone(),
19298 font_size: rems(0.875).into(),
19299 font_weight: settings.ui_font.weight,
19300 line_height: relative(settings.buffer_line_height.value()),
19301 ..Default::default()
19302 },
19303 EditorMode::Full => TextStyle {
19304 color: cx.theme().colors().editor_foreground,
19305 font_family: settings.buffer_font.family.clone(),
19306 font_features: settings.buffer_font.features.clone(),
19307 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19308 font_size: settings.buffer_font_size(cx).into(),
19309 font_weight: settings.buffer_font.weight,
19310 line_height: relative(settings.buffer_line_height.value()),
19311 ..Default::default()
19312 },
19313 };
19314 if let Some(text_style_refinement) = &self.text_style_refinement {
19315 text_style.refine(text_style_refinement)
19316 }
19317
19318 let background = match self.mode {
19319 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19320 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19321 EditorMode::Full => cx.theme().colors().editor_background,
19322 };
19323
19324 EditorElement::new(
19325 &cx.entity(),
19326 EditorStyle {
19327 background,
19328 local_player: cx.theme().players().local(),
19329 text: text_style,
19330 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19331 syntax: cx.theme().syntax().clone(),
19332 status: cx.theme().status().clone(),
19333 inlay_hints_style: make_inlay_hints_style(cx),
19334 inline_completion_styles: make_suggestion_styles(cx),
19335 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19336 },
19337 )
19338 }
19339}
19340
19341impl EntityInputHandler for Editor {
19342 fn text_for_range(
19343 &mut self,
19344 range_utf16: Range<usize>,
19345 adjusted_range: &mut Option<Range<usize>>,
19346 _: &mut Window,
19347 cx: &mut Context<Self>,
19348 ) -> Option<String> {
19349 let snapshot = self.buffer.read(cx).read(cx);
19350 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19351 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19352 if (start.0..end.0) != range_utf16 {
19353 adjusted_range.replace(start.0..end.0);
19354 }
19355 Some(snapshot.text_for_range(start..end).collect())
19356 }
19357
19358 fn selected_text_range(
19359 &mut self,
19360 ignore_disabled_input: bool,
19361 _: &mut Window,
19362 cx: &mut Context<Self>,
19363 ) -> Option<UTF16Selection> {
19364 // Prevent the IME menu from appearing when holding down an alphabetic key
19365 // while input is disabled.
19366 if !ignore_disabled_input && !self.input_enabled {
19367 return None;
19368 }
19369
19370 let selection = self.selections.newest::<OffsetUtf16>(cx);
19371 let range = selection.range();
19372
19373 Some(UTF16Selection {
19374 range: range.start.0..range.end.0,
19375 reversed: selection.reversed,
19376 })
19377 }
19378
19379 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19380 let snapshot = self.buffer.read(cx).read(cx);
19381 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19382 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19383 }
19384
19385 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19386 self.clear_highlights::<InputComposition>(cx);
19387 self.ime_transaction.take();
19388 }
19389
19390 fn replace_text_in_range(
19391 &mut self,
19392 range_utf16: Option<Range<usize>>,
19393 text: &str,
19394 window: &mut Window,
19395 cx: &mut Context<Self>,
19396 ) {
19397 if !self.input_enabled {
19398 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19399 return;
19400 }
19401
19402 self.transact(window, cx, |this, window, cx| {
19403 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19404 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19405 Some(this.selection_replacement_ranges(range_utf16, cx))
19406 } else {
19407 this.marked_text_ranges(cx)
19408 };
19409
19410 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19411 let newest_selection_id = this.selections.newest_anchor().id;
19412 this.selections
19413 .all::<OffsetUtf16>(cx)
19414 .iter()
19415 .zip(ranges_to_replace.iter())
19416 .find_map(|(selection, range)| {
19417 if selection.id == newest_selection_id {
19418 Some(
19419 (range.start.0 as isize - selection.head().0 as isize)
19420 ..(range.end.0 as isize - selection.head().0 as isize),
19421 )
19422 } else {
19423 None
19424 }
19425 })
19426 });
19427
19428 cx.emit(EditorEvent::InputHandled {
19429 utf16_range_to_replace: range_to_replace,
19430 text: text.into(),
19431 });
19432
19433 if let Some(new_selected_ranges) = new_selected_ranges {
19434 this.change_selections(None, window, cx, |selections| {
19435 selections.select_ranges(new_selected_ranges)
19436 });
19437 this.backspace(&Default::default(), window, cx);
19438 }
19439
19440 this.handle_input(text, window, cx);
19441 });
19442
19443 if let Some(transaction) = self.ime_transaction {
19444 self.buffer.update(cx, |buffer, cx| {
19445 buffer.group_until_transaction(transaction, cx);
19446 });
19447 }
19448
19449 self.unmark_text(window, cx);
19450 }
19451
19452 fn replace_and_mark_text_in_range(
19453 &mut self,
19454 range_utf16: Option<Range<usize>>,
19455 text: &str,
19456 new_selected_range_utf16: Option<Range<usize>>,
19457 window: &mut Window,
19458 cx: &mut Context<Self>,
19459 ) {
19460 if !self.input_enabled {
19461 return;
19462 }
19463
19464 let transaction = self.transact(window, cx, |this, window, cx| {
19465 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19466 let snapshot = this.buffer.read(cx).read(cx);
19467 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19468 for marked_range in &mut marked_ranges {
19469 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19470 marked_range.start.0 += relative_range_utf16.start;
19471 marked_range.start =
19472 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19473 marked_range.end =
19474 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19475 }
19476 }
19477 Some(marked_ranges)
19478 } else if let Some(range_utf16) = range_utf16 {
19479 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19480 Some(this.selection_replacement_ranges(range_utf16, cx))
19481 } else {
19482 None
19483 };
19484
19485 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19486 let newest_selection_id = this.selections.newest_anchor().id;
19487 this.selections
19488 .all::<OffsetUtf16>(cx)
19489 .iter()
19490 .zip(ranges_to_replace.iter())
19491 .find_map(|(selection, range)| {
19492 if selection.id == newest_selection_id {
19493 Some(
19494 (range.start.0 as isize - selection.head().0 as isize)
19495 ..(range.end.0 as isize - selection.head().0 as isize),
19496 )
19497 } else {
19498 None
19499 }
19500 })
19501 });
19502
19503 cx.emit(EditorEvent::InputHandled {
19504 utf16_range_to_replace: range_to_replace,
19505 text: text.into(),
19506 });
19507
19508 if let Some(ranges) = ranges_to_replace {
19509 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19510 }
19511
19512 let marked_ranges = {
19513 let snapshot = this.buffer.read(cx).read(cx);
19514 this.selections
19515 .disjoint_anchors()
19516 .iter()
19517 .map(|selection| {
19518 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19519 })
19520 .collect::<Vec<_>>()
19521 };
19522
19523 if text.is_empty() {
19524 this.unmark_text(window, cx);
19525 } else {
19526 this.highlight_text::<InputComposition>(
19527 marked_ranges.clone(),
19528 HighlightStyle {
19529 underline: Some(UnderlineStyle {
19530 thickness: px(1.),
19531 color: None,
19532 wavy: false,
19533 }),
19534 ..Default::default()
19535 },
19536 cx,
19537 );
19538 }
19539
19540 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19541 let use_autoclose = this.use_autoclose;
19542 let use_auto_surround = this.use_auto_surround;
19543 this.set_use_autoclose(false);
19544 this.set_use_auto_surround(false);
19545 this.handle_input(text, window, cx);
19546 this.set_use_autoclose(use_autoclose);
19547 this.set_use_auto_surround(use_auto_surround);
19548
19549 if let Some(new_selected_range) = new_selected_range_utf16 {
19550 let snapshot = this.buffer.read(cx).read(cx);
19551 let new_selected_ranges = marked_ranges
19552 .into_iter()
19553 .map(|marked_range| {
19554 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19555 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19556 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19557 snapshot.clip_offset_utf16(new_start, Bias::Left)
19558 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19559 })
19560 .collect::<Vec<_>>();
19561
19562 drop(snapshot);
19563 this.change_selections(None, window, cx, |selections| {
19564 selections.select_ranges(new_selected_ranges)
19565 });
19566 }
19567 });
19568
19569 self.ime_transaction = self.ime_transaction.or(transaction);
19570 if let Some(transaction) = self.ime_transaction {
19571 self.buffer.update(cx, |buffer, cx| {
19572 buffer.group_until_transaction(transaction, cx);
19573 });
19574 }
19575
19576 if self.text_highlights::<InputComposition>(cx).is_none() {
19577 self.ime_transaction.take();
19578 }
19579 }
19580
19581 fn bounds_for_range(
19582 &mut self,
19583 range_utf16: Range<usize>,
19584 element_bounds: gpui::Bounds<Pixels>,
19585 window: &mut Window,
19586 cx: &mut Context<Self>,
19587 ) -> Option<gpui::Bounds<Pixels>> {
19588 let text_layout_details = self.text_layout_details(window);
19589 let gpui::Size {
19590 width: em_width,
19591 height: line_height,
19592 } = self.character_size(window);
19593
19594 let snapshot = self.snapshot(window, cx);
19595 let scroll_position = snapshot.scroll_position();
19596 let scroll_left = scroll_position.x * em_width;
19597
19598 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19599 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19600 + self.gutter_dimensions.width
19601 + self.gutter_dimensions.margin;
19602 let y = line_height * (start.row().as_f32() - scroll_position.y);
19603
19604 Some(Bounds {
19605 origin: element_bounds.origin + point(x, y),
19606 size: size(em_width, line_height),
19607 })
19608 }
19609
19610 fn character_index_for_point(
19611 &mut self,
19612 point: gpui::Point<Pixels>,
19613 _window: &mut Window,
19614 _cx: &mut Context<Self>,
19615 ) -> Option<usize> {
19616 let position_map = self.last_position_map.as_ref()?;
19617 if !position_map.text_hitbox.contains(&point) {
19618 return None;
19619 }
19620 let display_point = position_map.point_for_position(point).previous_valid;
19621 let anchor = position_map
19622 .snapshot
19623 .display_point_to_anchor(display_point, Bias::Left);
19624 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19625 Some(utf16_offset.0)
19626 }
19627}
19628
19629trait SelectionExt {
19630 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19631 fn spanned_rows(
19632 &self,
19633 include_end_if_at_line_start: bool,
19634 map: &DisplaySnapshot,
19635 ) -> Range<MultiBufferRow>;
19636}
19637
19638impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19639 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19640 let start = self
19641 .start
19642 .to_point(&map.buffer_snapshot)
19643 .to_display_point(map);
19644 let end = self
19645 .end
19646 .to_point(&map.buffer_snapshot)
19647 .to_display_point(map);
19648 if self.reversed {
19649 end..start
19650 } else {
19651 start..end
19652 }
19653 }
19654
19655 fn spanned_rows(
19656 &self,
19657 include_end_if_at_line_start: bool,
19658 map: &DisplaySnapshot,
19659 ) -> Range<MultiBufferRow> {
19660 let start = self.start.to_point(&map.buffer_snapshot);
19661 let mut end = self.end.to_point(&map.buffer_snapshot);
19662 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19663 end.row -= 1;
19664 }
19665
19666 let buffer_start = map.prev_line_boundary(start).0;
19667 let buffer_end = map.next_line_boundary(end).0;
19668 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19669 }
19670}
19671
19672impl<T: InvalidationRegion> InvalidationStack<T> {
19673 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19674 where
19675 S: Clone + ToOffset,
19676 {
19677 while let Some(region) = self.last() {
19678 let all_selections_inside_invalidation_ranges =
19679 if selections.len() == region.ranges().len() {
19680 selections
19681 .iter()
19682 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19683 .all(|(selection, invalidation_range)| {
19684 let head = selection.head().to_offset(buffer);
19685 invalidation_range.start <= head && invalidation_range.end >= head
19686 })
19687 } else {
19688 false
19689 };
19690
19691 if all_selections_inside_invalidation_ranges {
19692 break;
19693 } else {
19694 self.pop();
19695 }
19696 }
19697 }
19698}
19699
19700impl<T> Default for InvalidationStack<T> {
19701 fn default() -> Self {
19702 Self(Default::default())
19703 }
19704}
19705
19706impl<T> Deref for InvalidationStack<T> {
19707 type Target = Vec<T>;
19708
19709 fn deref(&self) -> &Self::Target {
19710 &self.0
19711 }
19712}
19713
19714impl<T> DerefMut for InvalidationStack<T> {
19715 fn deref_mut(&mut self) -> &mut Self::Target {
19716 &mut self.0
19717 }
19718}
19719
19720impl InvalidationRegion for SnippetState {
19721 fn ranges(&self) -> &[Range<Anchor>] {
19722 &self.ranges[self.active_index]
19723 }
19724}
19725
19726pub fn diagnostic_block_renderer(
19727 diagnostic: Diagnostic,
19728 max_message_rows: Option<u8>,
19729 allow_closing: bool,
19730) -> RenderBlock {
19731 let (text_without_backticks, code_ranges) =
19732 highlight_diagnostic_message(&diagnostic, max_message_rows);
19733
19734 Arc::new(move |cx: &mut BlockContext| {
19735 let group_id: SharedString = cx.block_id.to_string().into();
19736
19737 let mut text_style = cx.window.text_style().clone();
19738 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19739 let theme_settings = ThemeSettings::get_global(cx);
19740 text_style.font_family = theme_settings.buffer_font.family.clone();
19741 text_style.font_style = theme_settings.buffer_font.style;
19742 text_style.font_features = theme_settings.buffer_font.features.clone();
19743 text_style.font_weight = theme_settings.buffer_font.weight;
19744
19745 let multi_line_diagnostic = diagnostic.message.contains('\n');
19746
19747 let buttons = |diagnostic: &Diagnostic| {
19748 if multi_line_diagnostic {
19749 v_flex()
19750 } else {
19751 h_flex()
19752 }
19753 .when(allow_closing, |div| {
19754 div.children(diagnostic.is_primary.then(|| {
19755 IconButton::new("close-block", IconName::XCircle)
19756 .icon_color(Color::Muted)
19757 .size(ButtonSize::Compact)
19758 .style(ButtonStyle::Transparent)
19759 .visible_on_hover(group_id.clone())
19760 .on_click(move |_click, window, cx| {
19761 window.dispatch_action(Box::new(Cancel), cx)
19762 })
19763 .tooltip(|window, cx| {
19764 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19765 })
19766 }))
19767 })
19768 .child(
19769 IconButton::new("copy-block", IconName::Copy)
19770 .icon_color(Color::Muted)
19771 .size(ButtonSize::Compact)
19772 .style(ButtonStyle::Transparent)
19773 .visible_on_hover(group_id.clone())
19774 .on_click({
19775 let message = diagnostic.message.clone();
19776 move |_click, _, cx| {
19777 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19778 }
19779 })
19780 .tooltip(Tooltip::text("Copy diagnostic message")),
19781 )
19782 };
19783
19784 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19785 AvailableSpace::min_size(),
19786 cx.window,
19787 cx.app,
19788 );
19789
19790 h_flex()
19791 .id(cx.block_id)
19792 .group(group_id.clone())
19793 .relative()
19794 .size_full()
19795 .block_mouse_down()
19796 .pl(cx.gutter_dimensions.width)
19797 .w(cx.max_width - cx.gutter_dimensions.full_width())
19798 .child(
19799 div()
19800 .flex()
19801 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
19802 .flex_shrink(),
19803 )
19804 .child(buttons(&diagnostic))
19805 .child(div().flex().flex_shrink_0().child(
19806 StyledText::new(text_without_backticks.clone()).with_default_highlights(
19807 &text_style,
19808 code_ranges.iter().map(|range| {
19809 (
19810 range.clone(),
19811 HighlightStyle {
19812 font_weight: Some(FontWeight::BOLD),
19813 ..Default::default()
19814 },
19815 )
19816 }),
19817 ),
19818 ))
19819 .into_any_element()
19820 })
19821}
19822
19823fn inline_completion_edit_text(
19824 current_snapshot: &BufferSnapshot,
19825 edits: &[(Range<Anchor>, String)],
19826 edit_preview: &EditPreview,
19827 include_deletions: bool,
19828 cx: &App,
19829) -> HighlightedText {
19830 let edits = edits
19831 .iter()
19832 .map(|(anchor, text)| {
19833 (
19834 anchor.start.text_anchor..anchor.end.text_anchor,
19835 text.clone(),
19836 )
19837 })
19838 .collect::<Vec<_>>();
19839
19840 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
19841}
19842
19843pub fn highlight_diagnostic_message(
19844 diagnostic: &Diagnostic,
19845 mut max_message_rows: Option<u8>,
19846) -> (SharedString, Vec<Range<usize>>) {
19847 let mut text_without_backticks = String::new();
19848 let mut code_ranges = Vec::new();
19849
19850 if let Some(source) = &diagnostic.source {
19851 text_without_backticks.push_str(source);
19852 code_ranges.push(0..source.len());
19853 text_without_backticks.push_str(": ");
19854 }
19855
19856 let mut prev_offset = 0;
19857 let mut in_code_block = false;
19858 let has_row_limit = max_message_rows.is_some();
19859 let mut newline_indices = diagnostic
19860 .message
19861 .match_indices('\n')
19862 .filter(|_| has_row_limit)
19863 .map(|(ix, _)| ix)
19864 .fuse()
19865 .peekable();
19866
19867 for (quote_ix, _) in diagnostic
19868 .message
19869 .match_indices('`')
19870 .chain([(diagnostic.message.len(), "")])
19871 {
19872 let mut first_newline_ix = None;
19873 let mut last_newline_ix = None;
19874 while let Some(newline_ix) = newline_indices.peek() {
19875 if *newline_ix < quote_ix {
19876 if first_newline_ix.is_none() {
19877 first_newline_ix = Some(*newline_ix);
19878 }
19879 last_newline_ix = Some(*newline_ix);
19880
19881 if let Some(rows_left) = &mut max_message_rows {
19882 if *rows_left == 0 {
19883 break;
19884 } else {
19885 *rows_left -= 1;
19886 }
19887 }
19888 let _ = newline_indices.next();
19889 } else {
19890 break;
19891 }
19892 }
19893 let prev_len = text_without_backticks.len();
19894 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
19895 text_without_backticks.push_str(new_text);
19896 if in_code_block {
19897 code_ranges.push(prev_len..text_without_backticks.len());
19898 }
19899 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
19900 in_code_block = !in_code_block;
19901 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
19902 text_without_backticks.push_str("...");
19903 break;
19904 }
19905 }
19906
19907 (text_without_backticks.into(), code_ranges)
19908}
19909
19910fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
19911 match severity {
19912 DiagnosticSeverity::ERROR => colors.error,
19913 DiagnosticSeverity::WARNING => colors.warning,
19914 DiagnosticSeverity::INFORMATION => colors.info,
19915 DiagnosticSeverity::HINT => colors.info,
19916 _ => colors.ignored,
19917 }
19918}
19919
19920pub fn styled_runs_for_code_label<'a>(
19921 label: &'a CodeLabel,
19922 syntax_theme: &'a theme::SyntaxTheme,
19923) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
19924 let fade_out = HighlightStyle {
19925 fade_out: Some(0.35),
19926 ..Default::default()
19927 };
19928
19929 let mut prev_end = label.filter_range.end;
19930 label
19931 .runs
19932 .iter()
19933 .enumerate()
19934 .flat_map(move |(ix, (range, highlight_id))| {
19935 let style = if let Some(style) = highlight_id.style(syntax_theme) {
19936 style
19937 } else {
19938 return Default::default();
19939 };
19940 let mut muted_style = style;
19941 muted_style.highlight(fade_out);
19942
19943 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
19944 if range.start >= label.filter_range.end {
19945 if range.start > prev_end {
19946 runs.push((prev_end..range.start, fade_out));
19947 }
19948 runs.push((range.clone(), muted_style));
19949 } else if range.end <= label.filter_range.end {
19950 runs.push((range.clone(), style));
19951 } else {
19952 runs.push((range.start..label.filter_range.end, style));
19953 runs.push((label.filter_range.end..range.end, muted_style));
19954 }
19955 prev_end = cmp::max(prev_end, range.end);
19956
19957 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
19958 runs.push((prev_end..label.text.len(), fade_out));
19959 }
19960
19961 runs
19962 })
19963}
19964
19965pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
19966 let mut prev_index = 0;
19967 let mut prev_codepoint: Option<char> = None;
19968 text.char_indices()
19969 .chain([(text.len(), '\0')])
19970 .filter_map(move |(index, codepoint)| {
19971 let prev_codepoint = prev_codepoint.replace(codepoint)?;
19972 let is_boundary = index == text.len()
19973 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
19974 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
19975 if is_boundary {
19976 let chunk = &text[prev_index..index];
19977 prev_index = index;
19978 Some(chunk)
19979 } else {
19980 None
19981 }
19982 })
19983}
19984
19985pub trait RangeToAnchorExt: Sized {
19986 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
19987
19988 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
19989 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
19990 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
19991 }
19992}
19993
19994impl<T: ToOffset> RangeToAnchorExt for Range<T> {
19995 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
19996 let start_offset = self.start.to_offset(snapshot);
19997 let end_offset = self.end.to_offset(snapshot);
19998 if start_offset == end_offset {
19999 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20000 } else {
20001 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20002 }
20003 }
20004}
20005
20006pub trait RowExt {
20007 fn as_f32(&self) -> f32;
20008
20009 fn next_row(&self) -> Self;
20010
20011 fn previous_row(&self) -> Self;
20012
20013 fn minus(&self, other: Self) -> u32;
20014}
20015
20016impl RowExt for DisplayRow {
20017 fn as_f32(&self) -> f32 {
20018 self.0 as f32
20019 }
20020
20021 fn next_row(&self) -> Self {
20022 Self(self.0 + 1)
20023 }
20024
20025 fn previous_row(&self) -> Self {
20026 Self(self.0.saturating_sub(1))
20027 }
20028
20029 fn minus(&self, other: Self) -> u32 {
20030 self.0 - other.0
20031 }
20032}
20033
20034impl RowExt for MultiBufferRow {
20035 fn as_f32(&self) -> f32 {
20036 self.0 as f32
20037 }
20038
20039 fn next_row(&self) -> Self {
20040 Self(self.0 + 1)
20041 }
20042
20043 fn previous_row(&self) -> Self {
20044 Self(self.0.saturating_sub(1))
20045 }
20046
20047 fn minus(&self, other: Self) -> u32 {
20048 self.0 - other.0
20049 }
20050}
20051
20052trait RowRangeExt {
20053 type Row;
20054
20055 fn len(&self) -> usize;
20056
20057 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20058}
20059
20060impl RowRangeExt for Range<MultiBufferRow> {
20061 type Row = MultiBufferRow;
20062
20063 fn len(&self) -> usize {
20064 (self.end.0 - self.start.0) as usize
20065 }
20066
20067 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20068 (self.start.0..self.end.0).map(MultiBufferRow)
20069 }
20070}
20071
20072impl RowRangeExt for Range<DisplayRow> {
20073 type Row = DisplayRow;
20074
20075 fn len(&self) -> usize {
20076 (self.end.0 - self.start.0) as usize
20077 }
20078
20079 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20080 (self.start.0..self.end.0).map(DisplayRow)
20081 }
20082}
20083
20084/// If select range has more than one line, we
20085/// just point the cursor to range.start.
20086fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20087 if range.start.row == range.end.row {
20088 range
20089 } else {
20090 range.start..range.start
20091 }
20092}
20093pub struct KillRing(ClipboardItem);
20094impl Global for KillRing {}
20095
20096const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20097
20098enum BreakpointPromptEditAction {
20099 Log,
20100 Condition,
20101 HitCondition,
20102}
20103
20104struct BreakpointPromptEditor {
20105 pub(crate) prompt: Entity<Editor>,
20106 editor: WeakEntity<Editor>,
20107 breakpoint_anchor: Anchor,
20108 breakpoint: Breakpoint,
20109 edit_action: BreakpointPromptEditAction,
20110 block_ids: HashSet<CustomBlockId>,
20111 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20112 _subscriptions: Vec<Subscription>,
20113}
20114
20115impl BreakpointPromptEditor {
20116 const MAX_LINES: u8 = 4;
20117
20118 fn new(
20119 editor: WeakEntity<Editor>,
20120 breakpoint_anchor: Anchor,
20121 breakpoint: Breakpoint,
20122 edit_action: BreakpointPromptEditAction,
20123 window: &mut Window,
20124 cx: &mut Context<Self>,
20125 ) -> Self {
20126 let base_text = match edit_action {
20127 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20128 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20129 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20130 }
20131 .map(|msg| msg.to_string())
20132 .unwrap_or_default();
20133
20134 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20135 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20136
20137 let prompt = cx.new(|cx| {
20138 let mut prompt = Editor::new(
20139 EditorMode::AutoHeight {
20140 max_lines: Self::MAX_LINES as usize,
20141 },
20142 buffer,
20143 None,
20144 window,
20145 cx,
20146 );
20147 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20148 prompt.set_show_cursor_when_unfocused(false, cx);
20149 prompt.set_placeholder_text(
20150 match edit_action {
20151 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20152 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20153 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20154 },
20155 cx,
20156 );
20157
20158 prompt
20159 });
20160
20161 Self {
20162 prompt,
20163 editor,
20164 breakpoint_anchor,
20165 breakpoint,
20166 edit_action,
20167 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20168 block_ids: Default::default(),
20169 _subscriptions: vec![],
20170 }
20171 }
20172
20173 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20174 self.block_ids.extend(block_ids)
20175 }
20176
20177 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20178 if let Some(editor) = self.editor.upgrade() {
20179 let message = self
20180 .prompt
20181 .read(cx)
20182 .buffer
20183 .read(cx)
20184 .as_singleton()
20185 .expect("A multi buffer in breakpoint prompt isn't possible")
20186 .read(cx)
20187 .as_rope()
20188 .to_string();
20189
20190 editor.update(cx, |editor, cx| {
20191 editor.edit_breakpoint_at_anchor(
20192 self.breakpoint_anchor,
20193 self.breakpoint.clone(),
20194 match self.edit_action {
20195 BreakpointPromptEditAction::Log => {
20196 BreakpointEditAction::EditLogMessage(message.into())
20197 }
20198 BreakpointPromptEditAction::Condition => {
20199 BreakpointEditAction::EditCondition(message.into())
20200 }
20201 BreakpointPromptEditAction::HitCondition => {
20202 BreakpointEditAction::EditHitCondition(message.into())
20203 }
20204 },
20205 cx,
20206 );
20207
20208 editor.remove_blocks(self.block_ids.clone(), None, cx);
20209 cx.focus_self(window);
20210 });
20211 }
20212 }
20213
20214 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20215 self.editor
20216 .update(cx, |editor, cx| {
20217 editor.remove_blocks(self.block_ids.clone(), None, cx);
20218 window.focus(&editor.focus_handle);
20219 })
20220 .log_err();
20221 }
20222
20223 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20224 let settings = ThemeSettings::get_global(cx);
20225 let text_style = TextStyle {
20226 color: if self.prompt.read(cx).read_only(cx) {
20227 cx.theme().colors().text_disabled
20228 } else {
20229 cx.theme().colors().text
20230 },
20231 font_family: settings.buffer_font.family.clone(),
20232 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20233 font_size: settings.buffer_font_size(cx).into(),
20234 font_weight: settings.buffer_font.weight,
20235 line_height: relative(settings.buffer_line_height.value()),
20236 ..Default::default()
20237 };
20238 EditorElement::new(
20239 &self.prompt,
20240 EditorStyle {
20241 background: cx.theme().colors().editor_background,
20242 local_player: cx.theme().players().local(),
20243 text: text_style,
20244 ..Default::default()
20245 },
20246 )
20247 }
20248}
20249
20250impl Render for BreakpointPromptEditor {
20251 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20252 let gutter_dimensions = *self.gutter_dimensions.lock();
20253 h_flex()
20254 .key_context("Editor")
20255 .bg(cx.theme().colors().editor_background)
20256 .border_y_1()
20257 .border_color(cx.theme().status().info_border)
20258 .size_full()
20259 .py(window.line_height() / 2.5)
20260 .on_action(cx.listener(Self::confirm))
20261 .on_action(cx.listener(Self::cancel))
20262 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20263 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20264 }
20265}
20266
20267impl Focusable for BreakpointPromptEditor {
20268 fn focus_handle(&self, cx: &App) -> FocusHandle {
20269 self.prompt.focus_handle(cx)
20270 }
20271}
20272
20273fn all_edits_insertions_or_deletions(
20274 edits: &Vec<(Range<Anchor>, String)>,
20275 snapshot: &MultiBufferSnapshot,
20276) -> bool {
20277 let mut all_insertions = true;
20278 let mut all_deletions = true;
20279
20280 for (range, new_text) in edits.iter() {
20281 let range_is_empty = range.to_offset(&snapshot).is_empty();
20282 let text_is_empty = new_text.is_empty();
20283
20284 if range_is_empty != text_is_empty {
20285 if range_is_empty {
20286 all_deletions = false;
20287 } else {
20288 all_insertions = false;
20289 }
20290 } else {
20291 return false;
20292 }
20293
20294 if !all_insertions && !all_deletions {
20295 return false;
20296 }
20297 }
20298 all_insertions || all_deletions
20299}
20300
20301struct MissingEditPredictionKeybindingTooltip;
20302
20303impl Render for MissingEditPredictionKeybindingTooltip {
20304 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20305 ui::tooltip_container(window, cx, |container, _, cx| {
20306 container
20307 .flex_shrink_0()
20308 .max_w_80()
20309 .min_h(rems_from_px(124.))
20310 .justify_between()
20311 .child(
20312 v_flex()
20313 .flex_1()
20314 .text_ui_sm(cx)
20315 .child(Label::new("Conflict with Accept Keybinding"))
20316 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20317 )
20318 .child(
20319 h_flex()
20320 .pb_1()
20321 .gap_1()
20322 .items_end()
20323 .w_full()
20324 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20325 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20326 }))
20327 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20328 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20329 })),
20330 )
20331 })
20332 }
20333}
20334
20335#[derive(Debug, Clone, Copy, PartialEq)]
20336pub struct LineHighlight {
20337 pub background: Background,
20338 pub border: Option<gpui::Hsla>,
20339}
20340
20341impl From<Hsla> for LineHighlight {
20342 fn from(hsla: Hsla) -> Self {
20343 Self {
20344 background: hsla.into(),
20345 border: None,
20346 }
20347 }
20348}
20349
20350impl From<Background> for LineHighlight {
20351 fn from(background: Background) -> Self {
20352 Self {
20353 background,
20354 border: None,
20355 }
20356 }
20357}
20358
20359fn render_diff_hunk_controls(
20360 row: u32,
20361 status: &DiffHunkStatus,
20362 hunk_range: Range<Anchor>,
20363 is_created_file: bool,
20364 line_height: Pixels,
20365 editor: &Entity<Editor>,
20366 _window: &mut Window,
20367 cx: &mut App,
20368) -> AnyElement {
20369 h_flex()
20370 .h(line_height)
20371 .mr_1()
20372 .gap_1()
20373 .px_0p5()
20374 .pb_1()
20375 .border_x_1()
20376 .border_b_1()
20377 .border_color(cx.theme().colors().border_variant)
20378 .rounded_b_lg()
20379 .bg(cx.theme().colors().editor_background)
20380 .gap_1()
20381 .occlude()
20382 .shadow_md()
20383 .child(if status.has_secondary_hunk() {
20384 Button::new(("stage", row as u64), "Stage")
20385 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20386 .tooltip({
20387 let focus_handle = editor.focus_handle(cx);
20388 move |window, cx| {
20389 Tooltip::for_action_in(
20390 "Stage Hunk",
20391 &::git::ToggleStaged,
20392 &focus_handle,
20393 window,
20394 cx,
20395 )
20396 }
20397 })
20398 .on_click({
20399 let editor = editor.clone();
20400 move |_event, _window, cx| {
20401 editor.update(cx, |editor, cx| {
20402 editor.stage_or_unstage_diff_hunks(
20403 true,
20404 vec![hunk_range.start..hunk_range.start],
20405 cx,
20406 );
20407 });
20408 }
20409 })
20410 } else {
20411 Button::new(("unstage", row as u64), "Unstage")
20412 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20413 .tooltip({
20414 let focus_handle = editor.focus_handle(cx);
20415 move |window, cx| {
20416 Tooltip::for_action_in(
20417 "Unstage Hunk",
20418 &::git::ToggleStaged,
20419 &focus_handle,
20420 window,
20421 cx,
20422 )
20423 }
20424 })
20425 .on_click({
20426 let editor = editor.clone();
20427 move |_event, _window, cx| {
20428 editor.update(cx, |editor, cx| {
20429 editor.stage_or_unstage_diff_hunks(
20430 false,
20431 vec![hunk_range.start..hunk_range.start],
20432 cx,
20433 );
20434 });
20435 }
20436 })
20437 })
20438 .child(
20439 Button::new(("restore", row as u64), "Restore")
20440 .tooltip({
20441 let focus_handle = editor.focus_handle(cx);
20442 move |window, cx| {
20443 Tooltip::for_action_in(
20444 "Restore Hunk",
20445 &::git::Restore,
20446 &focus_handle,
20447 window,
20448 cx,
20449 )
20450 }
20451 })
20452 .on_click({
20453 let editor = editor.clone();
20454 move |_event, window, cx| {
20455 editor.update(cx, |editor, cx| {
20456 let snapshot = editor.snapshot(window, cx);
20457 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20458 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20459 });
20460 }
20461 })
20462 .disabled(is_created_file),
20463 )
20464 .when(
20465 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20466 |el| {
20467 el.child(
20468 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20469 .shape(IconButtonShape::Square)
20470 .icon_size(IconSize::Small)
20471 // .disabled(!has_multiple_hunks)
20472 .tooltip({
20473 let focus_handle = editor.focus_handle(cx);
20474 move |window, cx| {
20475 Tooltip::for_action_in(
20476 "Next Hunk",
20477 &GoToHunk,
20478 &focus_handle,
20479 window,
20480 cx,
20481 )
20482 }
20483 })
20484 .on_click({
20485 let editor = editor.clone();
20486 move |_event, window, cx| {
20487 editor.update(cx, |editor, cx| {
20488 let snapshot = editor.snapshot(window, cx);
20489 let position =
20490 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20491 editor.go_to_hunk_before_or_after_position(
20492 &snapshot,
20493 position,
20494 Direction::Next,
20495 window,
20496 cx,
20497 );
20498 editor.expand_selected_diff_hunks(cx);
20499 });
20500 }
20501 }),
20502 )
20503 .child(
20504 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20505 .shape(IconButtonShape::Square)
20506 .icon_size(IconSize::Small)
20507 // .disabled(!has_multiple_hunks)
20508 .tooltip({
20509 let focus_handle = editor.focus_handle(cx);
20510 move |window, cx| {
20511 Tooltip::for_action_in(
20512 "Previous Hunk",
20513 &GoToPreviousHunk,
20514 &focus_handle,
20515 window,
20516 cx,
20517 )
20518 }
20519 })
20520 .on_click({
20521 let editor = editor.clone();
20522 move |_event, window, cx| {
20523 editor.update(cx, |editor, cx| {
20524 let snapshot = editor.snapshot(window, cx);
20525 let point =
20526 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20527 editor.go_to_hunk_before_or_after_position(
20528 &snapshot,
20529 point,
20530 Direction::Prev,
20531 window,
20532 cx,
20533 );
20534 editor.expand_selected_diff_hunks(cx);
20535 });
20536 }
20537 }),
20538 )
20539 },
20540 )
20541 .into_any_element()
20542}