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, LspInsertMode, RewrapBehavior, WordsCompletionMode,
113 all_language_settings, 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, RunnableTag, TaskTemplate, TaskVariables};
135
136pub use lsp::CompletionContext;
137use lsp::{
138 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
139 InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
140};
141
142use language::BufferSnapshot;
143pub use lsp_ext::lsp_tasks;
144use movement::TextLayoutDetails;
145pub use multi_buffer::{
146 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
147 ToOffset, ToPoint,
148};
149use multi_buffer::{
150 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
151 MultiOrSingleBufferOffsetRange, PathKey, ToOffsetUtf16,
152};
153use parking_lot::Mutex;
154use project::{
155 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
156 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
157 TaskSourceKind,
158 debugger::breakpoint_store::Breakpoint,
159 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
160 project_settings::{GitGutterSetting, ProjectSettings},
161};
162use rand::prelude::*;
163use rpc::{ErrorExt, proto::*};
164use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
165use selections_collection::{
166 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
167};
168use serde::{Deserialize, Serialize};
169use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
170use smallvec::SmallVec;
171use snippet::Snippet;
172use std::sync::Arc;
173use std::{
174 any::TypeId,
175 borrow::Cow,
176 cell::RefCell,
177 cmp::{self, Ordering, Reverse},
178 mem,
179 num::NonZeroU32,
180 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
181 path::{Path, PathBuf},
182 rc::Rc,
183 time::{Duration, Instant},
184};
185pub use sum_tree::Bias;
186use sum_tree::TreeMap;
187use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
188use theme::{
189 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
190 observe_buffer_font_size_adjustment,
191};
192use ui::{
193 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
194 IconSize, Key, Tooltip, h_flex, prelude::*,
195};
196use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
197use workspace::{
198 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
199 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
200 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
201 item::{ItemHandle, PreviewTabsSettings},
202 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
203 searchable::SearchEvent,
204};
205
206use crate::hover_links::{find_url, find_url_from_range};
207use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
208
209pub const FILE_HEADER_HEIGHT: u32 = 2;
210pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
211pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
212const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
213const MAX_LINE_LEN: usize = 1024;
214const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
215const MAX_SELECTION_HISTORY_LEN: usize = 1024;
216pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
217#[doc(hidden)]
218pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
219
220pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
221pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
222pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
223
224pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
225pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
226pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
227
228pub type RenderDiffHunkControlsFn = Arc<
229 dyn Fn(
230 u32,
231 &DiffHunkStatus,
232 Range<Anchor>,
233 bool,
234 Pixels,
235 &Entity<Editor>,
236 &mut Window,
237 &mut App,
238 ) -> AnyElement,
239>;
240
241const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
242 alt: true,
243 shift: true,
244 control: false,
245 platform: false,
246 function: false,
247};
248
249#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
250pub enum InlayId {
251 InlineCompletion(usize),
252 Hint(usize),
253}
254
255impl InlayId {
256 fn id(&self) -> usize {
257 match self {
258 Self::InlineCompletion(id) => *id,
259 Self::Hint(id) => *id,
260 }
261 }
262}
263
264pub enum DebugCurrentRowHighlight {}
265enum DocumentHighlightRead {}
266enum DocumentHighlightWrite {}
267enum InputComposition {}
268enum SelectedTextHighlight {}
269
270#[derive(Debug, Copy, Clone, PartialEq, Eq)]
271pub enum Navigated {
272 Yes,
273 No,
274}
275
276impl Navigated {
277 pub fn from_bool(yes: bool) -> Navigated {
278 if yes { Navigated::Yes } else { Navigated::No }
279 }
280}
281
282#[derive(Debug, Clone, PartialEq, Eq)]
283enum DisplayDiffHunk {
284 Folded {
285 display_row: DisplayRow,
286 },
287 Unfolded {
288 is_created_file: bool,
289 diff_base_byte_range: Range<usize>,
290 display_row_range: Range<DisplayRow>,
291 multi_buffer_range: Range<Anchor>,
292 status: DiffHunkStatus,
293 },
294}
295
296pub enum HideMouseCursorOrigin {
297 TypingAction,
298 MovementAction,
299}
300
301pub fn init_settings(cx: &mut App) {
302 EditorSettings::register(cx);
303}
304
305pub fn init(cx: &mut App) {
306 init_settings(cx);
307
308 cx.set_global(GlobalBlameRenderer(Arc::new(())));
309
310 workspace::register_project_item::<Editor>(cx);
311 workspace::FollowableViewRegistry::register::<Editor>(cx);
312 workspace::register_serializable_item::<Editor>(cx);
313
314 cx.observe_new(
315 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
316 workspace.register_action(Editor::new_file);
317 workspace.register_action(Editor::new_file_vertical);
318 workspace.register_action(Editor::new_file_horizontal);
319 workspace.register_action(Editor::cancel_language_server_work);
320 },
321 )
322 .detach();
323
324 cx.on_action(move |_: &workspace::NewFile, cx| {
325 let app_state = workspace::AppState::global(cx);
326 if let Some(app_state) = app_state.upgrade() {
327 workspace::open_new(
328 Default::default(),
329 app_state,
330 cx,
331 |workspace, window, cx| {
332 Editor::new_file(workspace, &Default::default(), window, cx)
333 },
334 )
335 .detach();
336 }
337 });
338 cx.on_action(move |_: &workspace::NewWindow, cx| {
339 let app_state = workspace::AppState::global(cx);
340 if let Some(app_state) = app_state.upgrade() {
341 workspace::open_new(
342 Default::default(),
343 app_state,
344 cx,
345 |workspace, window, cx| {
346 cx.activate(true);
347 Editor::new_file(workspace, &Default::default(), window, cx)
348 },
349 )
350 .detach();
351 }
352 });
353}
354
355pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
356 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
357}
358
359pub struct SearchWithinRange;
360
361trait InvalidationRegion {
362 fn ranges(&self) -> &[Range<Anchor>];
363}
364
365#[derive(Clone, Debug, PartialEq)]
366pub enum SelectPhase {
367 Begin {
368 position: DisplayPoint,
369 add: bool,
370 click_count: usize,
371 },
372 BeginColumnar {
373 position: DisplayPoint,
374 reset: bool,
375 goal_column: u32,
376 },
377 Extend {
378 position: DisplayPoint,
379 click_count: usize,
380 },
381 Update {
382 position: DisplayPoint,
383 goal_column: u32,
384 scroll_delta: gpui::Point<f32>,
385 },
386 End,
387}
388
389#[derive(Clone, Debug)]
390pub enum SelectMode {
391 Character,
392 Word(Range<Anchor>),
393 Line(Range<Anchor>),
394 All,
395}
396
397#[derive(Copy, Clone, PartialEq, Eq, Debug)]
398pub enum EditorMode {
399 SingleLine { auto_width: bool },
400 AutoHeight { max_lines: usize },
401 Full,
402}
403
404#[derive(Copy, Clone, Debug)]
405pub enum SoftWrap {
406 /// Prefer not to wrap at all.
407 ///
408 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
409 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
410 GitDiff,
411 /// Prefer a single line generally, unless an overly long line is encountered.
412 None,
413 /// Soft wrap lines that exceed the editor width.
414 EditorWidth,
415 /// Soft wrap lines at the preferred line length.
416 Column(u32),
417 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
418 Bounded(u32),
419}
420
421#[derive(Clone)]
422pub struct EditorStyle {
423 pub background: Hsla,
424 pub local_player: PlayerColor,
425 pub text: TextStyle,
426 pub scrollbar_width: Pixels,
427 pub syntax: Arc<SyntaxTheme>,
428 pub status: StatusColors,
429 pub inlay_hints_style: HighlightStyle,
430 pub inline_completion_styles: InlineCompletionStyles,
431 pub unnecessary_code_fade: f32,
432}
433
434impl Default for EditorStyle {
435 fn default() -> Self {
436 Self {
437 background: Hsla::default(),
438 local_player: PlayerColor::default(),
439 text: TextStyle::default(),
440 scrollbar_width: Pixels::default(),
441 syntax: Default::default(),
442 // HACK: Status colors don't have a real default.
443 // We should look into removing the status colors from the editor
444 // style and retrieve them directly from the theme.
445 status: StatusColors::dark(),
446 inlay_hints_style: HighlightStyle::default(),
447 inline_completion_styles: InlineCompletionStyles {
448 insertion: HighlightStyle::default(),
449 whitespace: HighlightStyle::default(),
450 },
451 unnecessary_code_fade: Default::default(),
452 }
453 }
454}
455
456pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
457 let show_background = language_settings::language_settings(None, None, cx)
458 .inlay_hints
459 .show_background;
460
461 HighlightStyle {
462 color: Some(cx.theme().status().hint),
463 background_color: show_background.then(|| cx.theme().status().hint_background),
464 ..HighlightStyle::default()
465 }
466}
467
468pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
469 InlineCompletionStyles {
470 insertion: HighlightStyle {
471 color: Some(cx.theme().status().predictive),
472 ..HighlightStyle::default()
473 },
474 whitespace: HighlightStyle {
475 background_color: Some(cx.theme().status().created_background),
476 ..HighlightStyle::default()
477 },
478 }
479}
480
481type CompletionId = usize;
482
483pub(crate) enum EditDisplayMode {
484 TabAccept,
485 DiffPopover,
486 Inline,
487}
488
489enum InlineCompletion {
490 Edit {
491 edits: Vec<(Range<Anchor>, String)>,
492 edit_preview: Option<EditPreview>,
493 display_mode: EditDisplayMode,
494 snapshot: BufferSnapshot,
495 },
496 Move {
497 target: Anchor,
498 snapshot: BufferSnapshot,
499 },
500}
501
502struct InlineCompletionState {
503 inlay_ids: Vec<InlayId>,
504 completion: InlineCompletion,
505 completion_id: Option<SharedString>,
506 invalidation_range: Range<Anchor>,
507}
508
509enum EditPredictionSettings {
510 Disabled,
511 Enabled {
512 show_in_menu: bool,
513 preview_requires_modifier: bool,
514 },
515}
516
517enum InlineCompletionHighlight {}
518
519#[derive(Debug, Clone)]
520struct InlineDiagnostic {
521 message: SharedString,
522 group_id: usize,
523 is_primary: bool,
524 start: Point,
525 severity: DiagnosticSeverity,
526}
527
528pub enum MenuInlineCompletionsPolicy {
529 Never,
530 ByProvider,
531}
532
533pub enum EditPredictionPreview {
534 /// Modifier is not pressed
535 Inactive { released_too_fast: bool },
536 /// Modifier pressed
537 Active {
538 since: Instant,
539 previous_scroll_position: Option<ScrollAnchor>,
540 },
541}
542
543impl EditPredictionPreview {
544 pub fn released_too_fast(&self) -> bool {
545 match self {
546 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
547 EditPredictionPreview::Active { .. } => false,
548 }
549 }
550
551 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
552 if let EditPredictionPreview::Active {
553 previous_scroll_position,
554 ..
555 } = self
556 {
557 *previous_scroll_position = scroll_position;
558 }
559 }
560}
561
562pub struct ContextMenuOptions {
563 pub min_entries_visible: usize,
564 pub max_entries_visible: usize,
565 pub placement: Option<ContextMenuPlacement>,
566}
567
568#[derive(Debug, Clone, PartialEq, Eq)]
569pub enum ContextMenuPlacement {
570 Above,
571 Below,
572}
573
574#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
575struct EditorActionId(usize);
576
577impl EditorActionId {
578 pub fn post_inc(&mut self) -> Self {
579 let answer = self.0;
580
581 *self = Self(answer + 1);
582
583 Self(answer)
584 }
585}
586
587// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
588// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
589
590type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
591type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
592
593#[derive(Default)]
594struct ScrollbarMarkerState {
595 scrollbar_size: Size<Pixels>,
596 dirty: bool,
597 markers: Arc<[PaintQuad]>,
598 pending_refresh: Option<Task<Result<()>>>,
599}
600
601impl ScrollbarMarkerState {
602 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
603 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
604 }
605}
606
607#[derive(Clone, Debug)]
608struct RunnableTasks {
609 templates: Vec<(TaskSourceKind, TaskTemplate)>,
610 offset: multi_buffer::Anchor,
611 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
612 column: u32,
613 // Values of all named captures, including those starting with '_'
614 extra_variables: HashMap<String, String>,
615 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
616 context_range: Range<BufferOffset>,
617}
618
619impl RunnableTasks {
620 fn resolve<'a>(
621 &'a self,
622 cx: &'a task::TaskContext,
623 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
624 self.templates.iter().filter_map(|(kind, template)| {
625 template
626 .resolve_task(&kind.to_id_base(), cx)
627 .map(|task| (kind.clone(), task))
628 })
629 }
630}
631
632#[derive(Clone)]
633struct ResolvedTasks {
634 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
635 position: Anchor,
636}
637
638#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
639struct BufferOffset(usize);
640
641// Addons allow storing per-editor state in other crates (e.g. Vim)
642pub trait Addon: 'static {
643 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
644
645 fn render_buffer_header_controls(
646 &self,
647 _: &ExcerptInfo,
648 _: &Window,
649 _: &App,
650 ) -> Option<AnyElement> {
651 None
652 }
653
654 fn to_any(&self) -> &dyn std::any::Any;
655}
656
657/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
658///
659/// See the [module level documentation](self) for more information.
660pub struct Editor {
661 focus_handle: FocusHandle,
662 last_focused_descendant: Option<WeakFocusHandle>,
663 /// The text buffer being edited
664 buffer: Entity<MultiBuffer>,
665 /// Map of how text in the buffer should be displayed.
666 /// Handles soft wraps, folds, fake inlay text insertions, etc.
667 pub display_map: Entity<DisplayMap>,
668 pub selections: SelectionsCollection,
669 pub scroll_manager: ScrollManager,
670 /// When inline assist editors are linked, they all render cursors because
671 /// typing enters text into each of them, even the ones that aren't focused.
672 pub(crate) show_cursor_when_unfocused: bool,
673 columnar_selection_tail: Option<Anchor>,
674 add_selections_state: Option<AddSelectionsState>,
675 select_next_state: Option<SelectNextState>,
676 select_prev_state: Option<SelectNextState>,
677 selection_history: SelectionHistory,
678 autoclose_regions: Vec<AutocloseRegion>,
679 snippet_stack: InvalidationStack<SnippetState>,
680 select_syntax_node_history: SelectSyntaxNodeHistory,
681 ime_transaction: Option<TransactionId>,
682 active_diagnostics: Option<ActiveDiagnosticGroup>,
683 show_inline_diagnostics: bool,
684 inline_diagnostics_update: Task<()>,
685 inline_diagnostics_enabled: bool,
686 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
687 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
688 hard_wrap: Option<usize>,
689
690 // TODO: make this a access method
691 pub project: Option<Entity<Project>>,
692 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
693 completion_provider: Option<Box<dyn CompletionProvider>>,
694 collaboration_hub: Option<Box<dyn CollaborationHub>>,
695 blink_manager: Entity<BlinkManager>,
696 show_cursor_names: bool,
697 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
698 pub show_local_selections: bool,
699 mode: EditorMode,
700 show_breadcrumbs: bool,
701 show_gutter: bool,
702 show_scrollbars: bool,
703 show_line_numbers: Option<bool>,
704 use_relative_line_numbers: Option<bool>,
705 show_git_diff_gutter: Option<bool>,
706 show_code_actions: Option<bool>,
707 show_runnables: Option<bool>,
708 show_breakpoints: Option<bool>,
709 show_wrap_guides: Option<bool>,
710 show_indent_guides: Option<bool>,
711 placeholder_text: Option<Arc<str>>,
712 highlight_order: usize,
713 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
714 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
715 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
716 scrollbar_marker_state: ScrollbarMarkerState,
717 active_indent_guides_state: ActiveIndentGuidesState,
718 nav_history: Option<ItemNavHistory>,
719 context_menu: RefCell<Option<CodeContextMenu>>,
720 context_menu_options: Option<ContextMenuOptions>,
721 mouse_context_menu: Option<MouseContextMenu>,
722 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
723 signature_help_state: SignatureHelpState,
724 auto_signature_help: Option<bool>,
725 find_all_references_task_sources: Vec<Anchor>,
726 next_completion_id: CompletionId,
727 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
728 code_actions_task: Option<Task<Result<()>>>,
729 selection_highlight_task: Option<Task<()>>,
730 document_highlights_task: Option<Task<()>>,
731 linked_editing_range_task: Option<Task<Option<()>>>,
732 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
733 pending_rename: Option<RenameState>,
734 searchable: bool,
735 cursor_shape: CursorShape,
736 current_line_highlight: Option<CurrentLineHighlight>,
737 collapse_matches: bool,
738 autoindent_mode: Option<AutoindentMode>,
739 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
740 input_enabled: bool,
741 use_modal_editing: bool,
742 read_only: bool,
743 leader_peer_id: Option<PeerId>,
744 remote_id: Option<ViewId>,
745 hover_state: HoverState,
746 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
747 gutter_hovered: bool,
748 hovered_link_state: Option<HoveredLinkState>,
749 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
750 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
751 active_inline_completion: Option<InlineCompletionState>,
752 /// Used to prevent flickering as the user types while the menu is open
753 stale_inline_completion_in_menu: Option<InlineCompletionState>,
754 edit_prediction_settings: EditPredictionSettings,
755 inline_completions_hidden_for_vim_mode: bool,
756 show_inline_completions_override: Option<bool>,
757 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
758 edit_prediction_preview: EditPredictionPreview,
759 edit_prediction_indent_conflict: bool,
760 edit_prediction_requires_modifier_in_indent_conflict: bool,
761 inlay_hint_cache: InlayHintCache,
762 next_inlay_id: usize,
763 _subscriptions: Vec<Subscription>,
764 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
765 gutter_dimensions: GutterDimensions,
766 style: Option<EditorStyle>,
767 text_style_refinement: Option<TextStyleRefinement>,
768 next_editor_action_id: EditorActionId,
769 editor_actions:
770 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
771 use_autoclose: bool,
772 use_auto_surround: bool,
773 auto_replace_emoji_shortcode: bool,
774 jsx_tag_auto_close_enabled_in_any_buffer: bool,
775 show_git_blame_gutter: bool,
776 show_git_blame_inline: bool,
777 show_git_blame_inline_delay_task: Option<Task<()>>,
778 pub git_blame_inline_tooltip: Option<AnyWeakEntity>,
779 git_blame_inline_enabled: bool,
780 render_diff_hunk_controls: RenderDiffHunkControlsFn,
781 serialize_dirty_buffers: bool,
782 show_selection_menu: Option<bool>,
783 blame: Option<Entity<GitBlame>>,
784 blame_subscription: Option<Subscription>,
785 custom_context_menu: Option<
786 Box<
787 dyn 'static
788 + Fn(
789 &mut Self,
790 DisplayPoint,
791 &mut Window,
792 &mut Context<Self>,
793 ) -> Option<Entity<ui::ContextMenu>>,
794 >,
795 >,
796 last_bounds: Option<Bounds<Pixels>>,
797 last_position_map: Option<Rc<PositionMap>>,
798 expect_bounds_change: Option<Bounds<Pixels>>,
799 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
800 tasks_update_task: Option<Task<()>>,
801 breakpoint_store: Option<Entity<BreakpointStore>>,
802 /// Allow's a user to create a breakpoint by selecting this indicator
803 /// It should be None while a user is not hovering over the gutter
804 /// Otherwise it represents the point that the breakpoint will be shown
805 gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
806 in_project_search: bool,
807 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
808 breadcrumb_header: Option<String>,
809 focused_block: Option<FocusedBlock>,
810 next_scroll_position: NextScrollCursorCenterTopBottom,
811 addons: HashMap<TypeId, Box<dyn Addon>>,
812 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
813 load_diff_task: Option<Shared<Task<()>>>,
814 selection_mark_mode: bool,
815 toggle_fold_multiple_buffers: Task<()>,
816 _scroll_cursor_center_top_bottom_task: Task<()>,
817 serialize_selections: Task<()>,
818 serialize_folds: Task<()>,
819 mouse_cursor_hidden: bool,
820 hide_mouse_mode: HideMouseMode,
821}
822
823#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
824enum NextScrollCursorCenterTopBottom {
825 #[default]
826 Center,
827 Top,
828 Bottom,
829}
830
831impl NextScrollCursorCenterTopBottom {
832 fn next(&self) -> Self {
833 match self {
834 Self::Center => Self::Top,
835 Self::Top => Self::Bottom,
836 Self::Bottom => Self::Center,
837 }
838 }
839}
840
841#[derive(Clone)]
842pub struct EditorSnapshot {
843 pub mode: EditorMode,
844 show_gutter: bool,
845 show_line_numbers: Option<bool>,
846 show_git_diff_gutter: Option<bool>,
847 show_code_actions: Option<bool>,
848 show_runnables: Option<bool>,
849 show_breakpoints: Option<bool>,
850 git_blame_gutter_max_author_length: Option<usize>,
851 pub display_snapshot: DisplaySnapshot,
852 pub placeholder_text: Option<Arc<str>>,
853 is_focused: bool,
854 scroll_anchor: ScrollAnchor,
855 ongoing_scroll: OngoingScroll,
856 current_line_highlight: CurrentLineHighlight,
857 gutter_hovered: bool,
858}
859
860#[derive(Default, Debug, Clone, Copy)]
861pub struct GutterDimensions {
862 pub left_padding: Pixels,
863 pub right_padding: Pixels,
864 pub width: Pixels,
865 pub margin: Pixels,
866 pub git_blame_entries_width: Option<Pixels>,
867}
868
869impl GutterDimensions {
870 /// The full width of the space taken up by the gutter.
871 pub fn full_width(&self) -> Pixels {
872 self.margin + self.width
873 }
874
875 /// The width of the space reserved for the fold indicators,
876 /// use alongside 'justify_end' and `gutter_width` to
877 /// right align content with the line numbers
878 pub fn fold_area_width(&self) -> Pixels {
879 self.margin + self.right_padding
880 }
881}
882
883#[derive(Debug)]
884pub struct RemoteSelection {
885 pub replica_id: ReplicaId,
886 pub selection: Selection<Anchor>,
887 pub cursor_shape: CursorShape,
888 pub peer_id: PeerId,
889 pub line_mode: bool,
890 pub participant_index: Option<ParticipantIndex>,
891 pub user_name: Option<SharedString>,
892}
893
894#[derive(Clone, Debug)]
895struct SelectionHistoryEntry {
896 selections: Arc<[Selection<Anchor>]>,
897 select_next_state: Option<SelectNextState>,
898 select_prev_state: Option<SelectNextState>,
899 add_selections_state: Option<AddSelectionsState>,
900}
901
902enum SelectionHistoryMode {
903 Normal,
904 Undoing,
905 Redoing,
906}
907
908#[derive(Clone, PartialEq, Eq, Hash)]
909struct HoveredCursor {
910 replica_id: u16,
911 selection_id: usize,
912}
913
914impl Default for SelectionHistoryMode {
915 fn default() -> Self {
916 Self::Normal
917 }
918}
919
920#[derive(Default)]
921struct SelectionHistory {
922 #[allow(clippy::type_complexity)]
923 selections_by_transaction:
924 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
925 mode: SelectionHistoryMode,
926 undo_stack: VecDeque<SelectionHistoryEntry>,
927 redo_stack: VecDeque<SelectionHistoryEntry>,
928}
929
930impl SelectionHistory {
931 fn insert_transaction(
932 &mut self,
933 transaction_id: TransactionId,
934 selections: Arc<[Selection<Anchor>]>,
935 ) {
936 self.selections_by_transaction
937 .insert(transaction_id, (selections, None));
938 }
939
940 #[allow(clippy::type_complexity)]
941 fn transaction(
942 &self,
943 transaction_id: TransactionId,
944 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
945 self.selections_by_transaction.get(&transaction_id)
946 }
947
948 #[allow(clippy::type_complexity)]
949 fn transaction_mut(
950 &mut self,
951 transaction_id: TransactionId,
952 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
953 self.selections_by_transaction.get_mut(&transaction_id)
954 }
955
956 fn push(&mut self, entry: SelectionHistoryEntry) {
957 if !entry.selections.is_empty() {
958 match self.mode {
959 SelectionHistoryMode::Normal => {
960 self.push_undo(entry);
961 self.redo_stack.clear();
962 }
963 SelectionHistoryMode::Undoing => self.push_redo(entry),
964 SelectionHistoryMode::Redoing => self.push_undo(entry),
965 }
966 }
967 }
968
969 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
970 if self
971 .undo_stack
972 .back()
973 .map_or(true, |e| e.selections != entry.selections)
974 {
975 self.undo_stack.push_back(entry);
976 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
977 self.undo_stack.pop_front();
978 }
979 }
980 }
981
982 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
983 if self
984 .redo_stack
985 .back()
986 .map_or(true, |e| e.selections != entry.selections)
987 {
988 self.redo_stack.push_back(entry);
989 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
990 self.redo_stack.pop_front();
991 }
992 }
993 }
994}
995
996struct RowHighlight {
997 index: usize,
998 range: Range<Anchor>,
999 color: Hsla,
1000 should_autoscroll: bool,
1001}
1002
1003#[derive(Clone, Debug)]
1004struct AddSelectionsState {
1005 above: bool,
1006 stack: Vec<usize>,
1007}
1008
1009#[derive(Clone)]
1010struct SelectNextState {
1011 query: AhoCorasick,
1012 wordwise: bool,
1013 done: bool,
1014}
1015
1016impl std::fmt::Debug for SelectNextState {
1017 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1018 f.debug_struct(std::any::type_name::<Self>())
1019 .field("wordwise", &self.wordwise)
1020 .field("done", &self.done)
1021 .finish()
1022 }
1023}
1024
1025#[derive(Debug)]
1026struct AutocloseRegion {
1027 selection_id: usize,
1028 range: Range<Anchor>,
1029 pair: BracketPair,
1030}
1031
1032#[derive(Debug)]
1033struct SnippetState {
1034 ranges: Vec<Vec<Range<Anchor>>>,
1035 active_index: usize,
1036 choices: Vec<Option<Vec<String>>>,
1037}
1038
1039#[doc(hidden)]
1040pub struct RenameState {
1041 pub range: Range<Anchor>,
1042 pub old_name: Arc<str>,
1043 pub editor: Entity<Editor>,
1044 block_id: CustomBlockId,
1045}
1046
1047struct InvalidationStack<T>(Vec<T>);
1048
1049struct RegisteredInlineCompletionProvider {
1050 provider: Arc<dyn InlineCompletionProviderHandle>,
1051 _subscription: Subscription,
1052}
1053
1054#[derive(Debug, PartialEq, Eq)]
1055struct ActiveDiagnosticGroup {
1056 primary_range: Range<Anchor>,
1057 primary_message: String,
1058 group_id: usize,
1059 blocks: HashMap<CustomBlockId, Diagnostic>,
1060 is_valid: bool,
1061}
1062
1063#[derive(Serialize, Deserialize, Clone, Debug)]
1064pub struct ClipboardSelection {
1065 /// The number of bytes in this selection.
1066 pub len: usize,
1067 /// Whether this was a full-line selection.
1068 pub is_entire_line: bool,
1069 /// The indentation of the first line when this content was originally copied.
1070 pub first_line_indent: u32,
1071}
1072
1073// selections, scroll behavior, was newest selection reversed
1074type SelectSyntaxNodeHistoryState = (
1075 Box<[Selection<usize>]>,
1076 SelectSyntaxNodeScrollBehavior,
1077 bool,
1078);
1079
1080#[derive(Default)]
1081struct SelectSyntaxNodeHistory {
1082 stack: Vec<SelectSyntaxNodeHistoryState>,
1083 // disable temporarily to allow changing selections without losing the stack
1084 pub disable_clearing: bool,
1085}
1086
1087impl SelectSyntaxNodeHistory {
1088 pub fn try_clear(&mut self) {
1089 if !self.disable_clearing {
1090 self.stack.clear();
1091 }
1092 }
1093
1094 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1095 self.stack.push(selection);
1096 }
1097
1098 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1099 self.stack.pop()
1100 }
1101}
1102
1103enum SelectSyntaxNodeScrollBehavior {
1104 CursorTop,
1105 FitSelection,
1106 CursorBottom,
1107}
1108
1109#[derive(Debug)]
1110pub(crate) struct NavigationData {
1111 cursor_anchor: Anchor,
1112 cursor_position: Point,
1113 scroll_anchor: ScrollAnchor,
1114 scroll_top_row: u32,
1115}
1116
1117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1118pub enum GotoDefinitionKind {
1119 Symbol,
1120 Declaration,
1121 Type,
1122 Implementation,
1123}
1124
1125#[derive(Debug, Clone)]
1126enum InlayHintRefreshReason {
1127 ModifiersChanged(bool),
1128 Toggle(bool),
1129 SettingsChange(InlayHintSettings),
1130 NewLinesShown,
1131 BufferEdited(HashSet<Arc<Language>>),
1132 RefreshRequested,
1133 ExcerptsRemoved(Vec<ExcerptId>),
1134}
1135
1136impl InlayHintRefreshReason {
1137 fn description(&self) -> &'static str {
1138 match self {
1139 Self::ModifiersChanged(_) => "modifiers changed",
1140 Self::Toggle(_) => "toggle",
1141 Self::SettingsChange(_) => "settings change",
1142 Self::NewLinesShown => "new lines shown",
1143 Self::BufferEdited(_) => "buffer edited",
1144 Self::RefreshRequested => "refresh requested",
1145 Self::ExcerptsRemoved(_) => "excerpts removed",
1146 }
1147 }
1148}
1149
1150pub enum FormatTarget {
1151 Buffers,
1152 Ranges(Vec<Range<MultiBufferPoint>>),
1153}
1154
1155pub(crate) struct FocusedBlock {
1156 id: BlockId,
1157 focus_handle: WeakFocusHandle,
1158}
1159
1160#[derive(Clone)]
1161enum JumpData {
1162 MultiBufferRow {
1163 row: MultiBufferRow,
1164 line_offset_from_top: u32,
1165 },
1166 MultiBufferPoint {
1167 excerpt_id: ExcerptId,
1168 position: Point,
1169 anchor: text::Anchor,
1170 line_offset_from_top: u32,
1171 },
1172}
1173
1174pub enum MultibufferSelectionMode {
1175 First,
1176 All,
1177}
1178
1179#[derive(Clone, Copy, Debug, Default)]
1180pub struct RewrapOptions {
1181 pub override_language_settings: bool,
1182 pub preserve_existing_whitespace: bool,
1183}
1184
1185impl Editor {
1186 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1187 let buffer = cx.new(|cx| Buffer::local("", cx));
1188 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1189 Self::new(
1190 EditorMode::SingleLine { auto_width: false },
1191 buffer,
1192 None,
1193 window,
1194 cx,
1195 )
1196 }
1197
1198 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1199 let buffer = cx.new(|cx| Buffer::local("", cx));
1200 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1201 Self::new(EditorMode::Full, buffer, None, window, cx)
1202 }
1203
1204 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1205 let buffer = cx.new(|cx| Buffer::local("", cx));
1206 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1207 Self::new(
1208 EditorMode::SingleLine { auto_width: true },
1209 buffer,
1210 None,
1211 window,
1212 cx,
1213 )
1214 }
1215
1216 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1217 let buffer = cx.new(|cx| Buffer::local("", cx));
1218 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1219 Self::new(
1220 EditorMode::AutoHeight { max_lines },
1221 buffer,
1222 None,
1223 window,
1224 cx,
1225 )
1226 }
1227
1228 pub fn for_buffer(
1229 buffer: Entity<Buffer>,
1230 project: Option<Entity<Project>>,
1231 window: &mut Window,
1232 cx: &mut Context<Self>,
1233 ) -> Self {
1234 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1235 Self::new(EditorMode::Full, buffer, project, window, cx)
1236 }
1237
1238 pub fn for_multibuffer(
1239 buffer: Entity<MultiBuffer>,
1240 project: Option<Entity<Project>>,
1241 window: &mut Window,
1242 cx: &mut Context<Self>,
1243 ) -> Self {
1244 Self::new(EditorMode::Full, buffer, project, window, cx)
1245 }
1246
1247 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1248 let mut clone = Self::new(
1249 self.mode,
1250 self.buffer.clone(),
1251 self.project.clone(),
1252 window,
1253 cx,
1254 );
1255 self.display_map.update(cx, |display_map, cx| {
1256 let snapshot = display_map.snapshot(cx);
1257 clone.display_map.update(cx, |display_map, cx| {
1258 display_map.set_state(&snapshot, cx);
1259 });
1260 });
1261 clone.folds_did_change(cx);
1262 clone.selections.clone_state(&self.selections);
1263 clone.scroll_manager.clone_state(&self.scroll_manager);
1264 clone.searchable = self.searchable;
1265 clone.read_only = self.read_only;
1266 clone
1267 }
1268
1269 pub fn new(
1270 mode: EditorMode,
1271 buffer: Entity<MultiBuffer>,
1272 project: Option<Entity<Project>>,
1273 window: &mut Window,
1274 cx: &mut Context<Self>,
1275 ) -> Self {
1276 let style = window.text_style();
1277 let font_size = style.font_size.to_pixels(window.rem_size());
1278 let editor = cx.entity().downgrade();
1279 let fold_placeholder = FoldPlaceholder {
1280 constrain_width: true,
1281 render: Arc::new(move |fold_id, fold_range, cx| {
1282 let editor = editor.clone();
1283 div()
1284 .id(fold_id)
1285 .bg(cx.theme().colors().ghost_element_background)
1286 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1287 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1288 .rounded_xs()
1289 .size_full()
1290 .cursor_pointer()
1291 .child("⋯")
1292 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1293 .on_click(move |_, _window, cx| {
1294 editor
1295 .update(cx, |editor, cx| {
1296 editor.unfold_ranges(
1297 &[fold_range.start..fold_range.end],
1298 true,
1299 false,
1300 cx,
1301 );
1302 cx.stop_propagation();
1303 })
1304 .ok();
1305 })
1306 .into_any()
1307 }),
1308 merge_adjacent: true,
1309 ..Default::default()
1310 };
1311 let display_map = cx.new(|cx| {
1312 DisplayMap::new(
1313 buffer.clone(),
1314 style.font(),
1315 font_size,
1316 None,
1317 FILE_HEADER_HEIGHT,
1318 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1319 fold_placeholder,
1320 cx,
1321 )
1322 });
1323
1324 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1325
1326 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1327
1328 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1329 .then(|| language_settings::SoftWrap::None);
1330
1331 let mut project_subscriptions = Vec::new();
1332 if mode == EditorMode::Full {
1333 if let Some(project) = project.as_ref() {
1334 project_subscriptions.push(cx.subscribe_in(
1335 project,
1336 window,
1337 |editor, _, event, window, cx| match event {
1338 project::Event::RefreshCodeLens => {
1339 // we always query lens with actions, without storing them, always refreshing them
1340 }
1341 project::Event::RefreshInlayHints => {
1342 editor
1343 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1344 }
1345 project::Event::SnippetEdit(id, snippet_edits) => {
1346 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1347 let focus_handle = editor.focus_handle(cx);
1348 if focus_handle.is_focused(window) {
1349 let snapshot = buffer.read(cx).snapshot();
1350 for (range, snippet) in snippet_edits {
1351 let editor_range =
1352 language::range_from_lsp(*range).to_offset(&snapshot);
1353 editor
1354 .insert_snippet(
1355 &[editor_range],
1356 snippet.clone(),
1357 window,
1358 cx,
1359 )
1360 .ok();
1361 }
1362 }
1363 }
1364 }
1365 _ => {}
1366 },
1367 ));
1368 if let Some(task_inventory) = project
1369 .read(cx)
1370 .task_store()
1371 .read(cx)
1372 .task_inventory()
1373 .cloned()
1374 {
1375 project_subscriptions.push(cx.observe_in(
1376 &task_inventory,
1377 window,
1378 |editor, _, window, cx| {
1379 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1380 },
1381 ));
1382 };
1383
1384 project_subscriptions.push(cx.subscribe_in(
1385 &project.read(cx).breakpoint_store(),
1386 window,
1387 |editor, _, event, window, cx| match event {
1388 BreakpointStoreEvent::ActiveDebugLineChanged => {
1389 if editor.go_to_active_debug_line(window, cx) {
1390 cx.stop_propagation();
1391 }
1392 }
1393 _ => {}
1394 },
1395 ));
1396 }
1397 }
1398
1399 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1400
1401 let inlay_hint_settings =
1402 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1403 let focus_handle = cx.focus_handle();
1404 cx.on_focus(&focus_handle, window, Self::handle_focus)
1405 .detach();
1406 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1407 .detach();
1408 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1409 .detach();
1410 cx.on_blur(&focus_handle, window, Self::handle_blur)
1411 .detach();
1412
1413 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1414 Some(false)
1415 } else {
1416 None
1417 };
1418
1419 let breakpoint_store = match (mode, project.as_ref()) {
1420 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1421 _ => None,
1422 };
1423
1424 let mut code_action_providers = Vec::new();
1425 let mut load_uncommitted_diff = None;
1426 if let Some(project) = project.clone() {
1427 load_uncommitted_diff = Some(
1428 get_uncommitted_diff_for_buffer(
1429 &project,
1430 buffer.read(cx).all_buffers(),
1431 buffer.clone(),
1432 cx,
1433 )
1434 .shared(),
1435 );
1436 code_action_providers.push(Rc::new(project) as Rc<_>);
1437 }
1438
1439 let mut this = Self {
1440 focus_handle,
1441 show_cursor_when_unfocused: false,
1442 last_focused_descendant: None,
1443 buffer: buffer.clone(),
1444 display_map: display_map.clone(),
1445 selections,
1446 scroll_manager: ScrollManager::new(cx),
1447 columnar_selection_tail: None,
1448 add_selections_state: None,
1449 select_next_state: None,
1450 select_prev_state: None,
1451 selection_history: Default::default(),
1452 autoclose_regions: Default::default(),
1453 snippet_stack: Default::default(),
1454 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1455 ime_transaction: Default::default(),
1456 active_diagnostics: None,
1457 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1458 inline_diagnostics_update: Task::ready(()),
1459 inline_diagnostics: Vec::new(),
1460 soft_wrap_mode_override,
1461 hard_wrap: None,
1462 completion_provider: project.clone().map(|project| Box::new(project) as _),
1463 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1464 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1465 project,
1466 blink_manager: blink_manager.clone(),
1467 show_local_selections: true,
1468 show_scrollbars: true,
1469 mode,
1470 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1471 show_gutter: mode == EditorMode::Full,
1472 show_line_numbers: None,
1473 use_relative_line_numbers: None,
1474 show_git_diff_gutter: None,
1475 show_code_actions: None,
1476 show_runnables: None,
1477 show_breakpoints: None,
1478 show_wrap_guides: None,
1479 show_indent_guides,
1480 placeholder_text: None,
1481 highlight_order: 0,
1482 highlighted_rows: HashMap::default(),
1483 background_highlights: Default::default(),
1484 gutter_highlights: TreeMap::default(),
1485 scrollbar_marker_state: ScrollbarMarkerState::default(),
1486 active_indent_guides_state: ActiveIndentGuidesState::default(),
1487 nav_history: None,
1488 context_menu: RefCell::new(None),
1489 context_menu_options: None,
1490 mouse_context_menu: None,
1491 completion_tasks: Default::default(),
1492 signature_help_state: SignatureHelpState::default(),
1493 auto_signature_help: None,
1494 find_all_references_task_sources: Vec::new(),
1495 next_completion_id: 0,
1496 next_inlay_id: 0,
1497 code_action_providers,
1498 available_code_actions: Default::default(),
1499 code_actions_task: Default::default(),
1500 selection_highlight_task: Default::default(),
1501 document_highlights_task: Default::default(),
1502 linked_editing_range_task: Default::default(),
1503 pending_rename: Default::default(),
1504 searchable: true,
1505 cursor_shape: EditorSettings::get_global(cx)
1506 .cursor_shape
1507 .unwrap_or_default(),
1508 current_line_highlight: None,
1509 autoindent_mode: Some(AutoindentMode::EachLine),
1510 collapse_matches: false,
1511 workspace: None,
1512 input_enabled: true,
1513 use_modal_editing: mode == EditorMode::Full,
1514 read_only: false,
1515 use_autoclose: true,
1516 use_auto_surround: true,
1517 auto_replace_emoji_shortcode: false,
1518 jsx_tag_auto_close_enabled_in_any_buffer: false,
1519 leader_peer_id: None,
1520 remote_id: None,
1521 hover_state: Default::default(),
1522 pending_mouse_down: None,
1523 hovered_link_state: Default::default(),
1524 edit_prediction_provider: None,
1525 active_inline_completion: None,
1526 stale_inline_completion_in_menu: None,
1527 edit_prediction_preview: EditPredictionPreview::Inactive {
1528 released_too_fast: false,
1529 },
1530 inline_diagnostics_enabled: mode == EditorMode::Full,
1531 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1532
1533 gutter_hovered: false,
1534 pixel_position_of_newest_cursor: None,
1535 last_bounds: None,
1536 last_position_map: None,
1537 expect_bounds_change: None,
1538 gutter_dimensions: GutterDimensions::default(),
1539 style: None,
1540 show_cursor_names: false,
1541 hovered_cursors: Default::default(),
1542 next_editor_action_id: EditorActionId::default(),
1543 editor_actions: Rc::default(),
1544 inline_completions_hidden_for_vim_mode: false,
1545 show_inline_completions_override: None,
1546 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1547 edit_prediction_settings: EditPredictionSettings::Disabled,
1548 edit_prediction_indent_conflict: false,
1549 edit_prediction_requires_modifier_in_indent_conflict: true,
1550 custom_context_menu: None,
1551 show_git_blame_gutter: false,
1552 show_git_blame_inline: false,
1553 show_selection_menu: None,
1554 show_git_blame_inline_delay_task: None,
1555 git_blame_inline_tooltip: None,
1556 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1557 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1558 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1559 .session
1560 .restore_unsaved_buffers,
1561 blame: None,
1562 blame_subscription: None,
1563 tasks: Default::default(),
1564
1565 breakpoint_store,
1566 gutter_breakpoint_indicator: (None, None),
1567 _subscriptions: vec![
1568 cx.observe(&buffer, Self::on_buffer_changed),
1569 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1570 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1571 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1572 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1573 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1574 cx.observe_window_activation(window, |editor, window, cx| {
1575 let active = window.is_window_active();
1576 editor.blink_manager.update(cx, |blink_manager, cx| {
1577 if active {
1578 blink_manager.enable(cx);
1579 } else {
1580 blink_manager.disable(cx);
1581 }
1582 });
1583 }),
1584 ],
1585 tasks_update_task: None,
1586 linked_edit_ranges: Default::default(),
1587 in_project_search: false,
1588 previous_search_ranges: None,
1589 breadcrumb_header: None,
1590 focused_block: None,
1591 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1592 addons: HashMap::default(),
1593 registered_buffers: HashMap::default(),
1594 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1595 selection_mark_mode: false,
1596 toggle_fold_multiple_buffers: Task::ready(()),
1597 serialize_selections: Task::ready(()),
1598 serialize_folds: Task::ready(()),
1599 text_style_refinement: None,
1600 load_diff_task: load_uncommitted_diff,
1601 mouse_cursor_hidden: false,
1602 hide_mouse_mode: EditorSettings::get_global(cx)
1603 .hide_mouse
1604 .unwrap_or_default(),
1605 };
1606 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1607 this._subscriptions
1608 .push(cx.observe(breakpoints, |_, _, cx| {
1609 cx.notify();
1610 }));
1611 }
1612 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1613 this._subscriptions.extend(project_subscriptions);
1614
1615 this._subscriptions.push(cx.subscribe_in(
1616 &cx.entity(),
1617 window,
1618 |editor, _, e: &EditorEvent, window, cx| {
1619 if let EditorEvent::SelectionsChanged { local } = e {
1620 if *local {
1621 let new_anchor = editor.scroll_manager.anchor();
1622 let snapshot = editor.snapshot(window, cx);
1623 editor.update_restoration_data(cx, move |data| {
1624 data.scroll_position = (
1625 new_anchor.top_row(&snapshot.buffer_snapshot),
1626 new_anchor.offset,
1627 );
1628 });
1629 }
1630 }
1631 },
1632 ));
1633
1634 this.end_selection(window, cx);
1635 this.scroll_manager.show_scrollbars(window, cx);
1636 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1637
1638 if mode == EditorMode::Full {
1639 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1640 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1641
1642 if this.git_blame_inline_enabled {
1643 this.git_blame_inline_enabled = true;
1644 this.start_git_blame_inline(false, window, cx);
1645 }
1646
1647 this.go_to_active_debug_line(window, cx);
1648
1649 if let Some(buffer) = buffer.read(cx).as_singleton() {
1650 if let Some(project) = this.project.as_ref() {
1651 let handle = project.update(cx, |project, cx| {
1652 project.register_buffer_with_language_servers(&buffer, cx)
1653 });
1654 this.registered_buffers
1655 .insert(buffer.read(cx).remote_id(), handle);
1656 }
1657 }
1658 }
1659
1660 this.report_editor_event("Editor Opened", None, cx);
1661 this
1662 }
1663
1664 pub fn deploy_mouse_context_menu(
1665 &mut self,
1666 position: gpui::Point<Pixels>,
1667 context_menu: Entity<ContextMenu>,
1668 window: &mut Window,
1669 cx: &mut Context<Self>,
1670 ) {
1671 self.mouse_context_menu = Some(MouseContextMenu::new(
1672 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1673 context_menu,
1674 window,
1675 cx,
1676 ));
1677 }
1678
1679 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1680 self.mouse_context_menu
1681 .as_ref()
1682 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1683 }
1684
1685 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1686 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1687 }
1688
1689 fn key_context_internal(
1690 &self,
1691 has_active_edit_prediction: bool,
1692 window: &Window,
1693 cx: &App,
1694 ) -> KeyContext {
1695 let mut key_context = KeyContext::new_with_defaults();
1696 key_context.add("Editor");
1697 let mode = match self.mode {
1698 EditorMode::SingleLine { .. } => "single_line",
1699 EditorMode::AutoHeight { .. } => "auto_height",
1700 EditorMode::Full => "full",
1701 };
1702
1703 if EditorSettings::jupyter_enabled(cx) {
1704 key_context.add("jupyter");
1705 }
1706
1707 key_context.set("mode", mode);
1708 if self.pending_rename.is_some() {
1709 key_context.add("renaming");
1710 }
1711
1712 match self.context_menu.borrow().as_ref() {
1713 Some(CodeContextMenu::Completions(_)) => {
1714 key_context.add("menu");
1715 key_context.add("showing_completions");
1716 }
1717 Some(CodeContextMenu::CodeActions(_)) => {
1718 key_context.add("menu");
1719 key_context.add("showing_code_actions")
1720 }
1721 None => {}
1722 }
1723
1724 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1725 if !self.focus_handle(cx).contains_focused(window, cx)
1726 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1727 {
1728 for addon in self.addons.values() {
1729 addon.extend_key_context(&mut key_context, cx)
1730 }
1731 }
1732
1733 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1734 if let Some(extension) = singleton_buffer
1735 .read(cx)
1736 .file()
1737 .and_then(|file| file.path().extension()?.to_str())
1738 {
1739 key_context.set("extension", extension.to_string());
1740 }
1741 } else {
1742 key_context.add("multibuffer");
1743 }
1744
1745 if has_active_edit_prediction {
1746 if self.edit_prediction_in_conflict() {
1747 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1748 } else {
1749 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1750 key_context.add("copilot_suggestion");
1751 }
1752 }
1753
1754 if self.selection_mark_mode {
1755 key_context.add("selection_mode");
1756 }
1757
1758 key_context
1759 }
1760
1761 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1762 self.mouse_cursor_hidden = match origin {
1763 HideMouseCursorOrigin::TypingAction => {
1764 matches!(
1765 self.hide_mouse_mode,
1766 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1767 )
1768 }
1769 HideMouseCursorOrigin::MovementAction => {
1770 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
1771 }
1772 };
1773 }
1774
1775 pub fn edit_prediction_in_conflict(&self) -> bool {
1776 if !self.show_edit_predictions_in_menu() {
1777 return false;
1778 }
1779
1780 let showing_completions = self
1781 .context_menu
1782 .borrow()
1783 .as_ref()
1784 .map_or(false, |context| {
1785 matches!(context, CodeContextMenu::Completions(_))
1786 });
1787
1788 showing_completions
1789 || self.edit_prediction_requires_modifier()
1790 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1791 // bindings to insert tab characters.
1792 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1793 }
1794
1795 pub fn accept_edit_prediction_keybind(
1796 &self,
1797 window: &Window,
1798 cx: &App,
1799 ) -> AcceptEditPredictionBinding {
1800 let key_context = self.key_context_internal(true, window, cx);
1801 let in_conflict = self.edit_prediction_in_conflict();
1802
1803 AcceptEditPredictionBinding(
1804 window
1805 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1806 .into_iter()
1807 .filter(|binding| {
1808 !in_conflict
1809 || binding
1810 .keystrokes()
1811 .first()
1812 .map_or(false, |keystroke| keystroke.modifiers.modified())
1813 })
1814 .rev()
1815 .min_by_key(|binding| {
1816 binding
1817 .keystrokes()
1818 .first()
1819 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1820 }),
1821 )
1822 }
1823
1824 pub fn new_file(
1825 workspace: &mut Workspace,
1826 _: &workspace::NewFile,
1827 window: &mut Window,
1828 cx: &mut Context<Workspace>,
1829 ) {
1830 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1831 "Failed to create buffer",
1832 window,
1833 cx,
1834 |e, _, _| match e.error_code() {
1835 ErrorCode::RemoteUpgradeRequired => Some(format!(
1836 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1837 e.error_tag("required").unwrap_or("the latest version")
1838 )),
1839 _ => None,
1840 },
1841 );
1842 }
1843
1844 pub fn new_in_workspace(
1845 workspace: &mut Workspace,
1846 window: &mut Window,
1847 cx: &mut Context<Workspace>,
1848 ) -> Task<Result<Entity<Editor>>> {
1849 let project = workspace.project().clone();
1850 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1851
1852 cx.spawn_in(window, async move |workspace, cx| {
1853 let buffer = create.await?;
1854 workspace.update_in(cx, |workspace, window, cx| {
1855 let editor =
1856 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1857 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1858 editor
1859 })
1860 })
1861 }
1862
1863 fn new_file_vertical(
1864 workspace: &mut Workspace,
1865 _: &workspace::NewFileSplitVertical,
1866 window: &mut Window,
1867 cx: &mut Context<Workspace>,
1868 ) {
1869 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1870 }
1871
1872 fn new_file_horizontal(
1873 workspace: &mut Workspace,
1874 _: &workspace::NewFileSplitHorizontal,
1875 window: &mut Window,
1876 cx: &mut Context<Workspace>,
1877 ) {
1878 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1879 }
1880
1881 fn new_file_in_direction(
1882 workspace: &mut Workspace,
1883 direction: SplitDirection,
1884 window: &mut Window,
1885 cx: &mut Context<Workspace>,
1886 ) {
1887 let project = workspace.project().clone();
1888 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1889
1890 cx.spawn_in(window, async move |workspace, cx| {
1891 let buffer = create.await?;
1892 workspace.update_in(cx, move |workspace, window, cx| {
1893 workspace.split_item(
1894 direction,
1895 Box::new(
1896 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1897 ),
1898 window,
1899 cx,
1900 )
1901 })?;
1902 anyhow::Ok(())
1903 })
1904 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1905 match e.error_code() {
1906 ErrorCode::RemoteUpgradeRequired => Some(format!(
1907 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1908 e.error_tag("required").unwrap_or("the latest version")
1909 )),
1910 _ => None,
1911 }
1912 });
1913 }
1914
1915 pub fn leader_peer_id(&self) -> Option<PeerId> {
1916 self.leader_peer_id
1917 }
1918
1919 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1920 &self.buffer
1921 }
1922
1923 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1924 self.workspace.as_ref()?.0.upgrade()
1925 }
1926
1927 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1928 self.buffer().read(cx).title(cx)
1929 }
1930
1931 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1932 let git_blame_gutter_max_author_length = self
1933 .render_git_blame_gutter(cx)
1934 .then(|| {
1935 if let Some(blame) = self.blame.as_ref() {
1936 let max_author_length =
1937 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1938 Some(max_author_length)
1939 } else {
1940 None
1941 }
1942 })
1943 .flatten();
1944
1945 EditorSnapshot {
1946 mode: self.mode,
1947 show_gutter: self.show_gutter,
1948 show_line_numbers: self.show_line_numbers,
1949 show_git_diff_gutter: self.show_git_diff_gutter,
1950 show_code_actions: self.show_code_actions,
1951 show_runnables: self.show_runnables,
1952 show_breakpoints: self.show_breakpoints,
1953 git_blame_gutter_max_author_length,
1954 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1955 scroll_anchor: self.scroll_manager.anchor(),
1956 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1957 placeholder_text: self.placeholder_text.clone(),
1958 is_focused: self.focus_handle.is_focused(window),
1959 current_line_highlight: self
1960 .current_line_highlight
1961 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1962 gutter_hovered: self.gutter_hovered,
1963 }
1964 }
1965
1966 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1967 self.buffer.read(cx).language_at(point, cx)
1968 }
1969
1970 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1971 self.buffer.read(cx).read(cx).file_at(point).cloned()
1972 }
1973
1974 pub fn active_excerpt(
1975 &self,
1976 cx: &App,
1977 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1978 self.buffer
1979 .read(cx)
1980 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1981 }
1982
1983 pub fn mode(&self) -> EditorMode {
1984 self.mode
1985 }
1986
1987 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1988 self.collaboration_hub.as_deref()
1989 }
1990
1991 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1992 self.collaboration_hub = Some(hub);
1993 }
1994
1995 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1996 self.in_project_search = in_project_search;
1997 }
1998
1999 pub fn set_custom_context_menu(
2000 &mut self,
2001 f: impl 'static
2002 + Fn(
2003 &mut Self,
2004 DisplayPoint,
2005 &mut Window,
2006 &mut Context<Self>,
2007 ) -> Option<Entity<ui::ContextMenu>>,
2008 ) {
2009 self.custom_context_menu = Some(Box::new(f))
2010 }
2011
2012 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2013 self.completion_provider = provider;
2014 }
2015
2016 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2017 self.semantics_provider.clone()
2018 }
2019
2020 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2021 self.semantics_provider = provider;
2022 }
2023
2024 pub fn set_edit_prediction_provider<T>(
2025 &mut self,
2026 provider: Option<Entity<T>>,
2027 window: &mut Window,
2028 cx: &mut Context<Self>,
2029 ) where
2030 T: EditPredictionProvider,
2031 {
2032 self.edit_prediction_provider =
2033 provider.map(|provider| RegisteredInlineCompletionProvider {
2034 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2035 if this.focus_handle.is_focused(window) {
2036 this.update_visible_inline_completion(window, cx);
2037 }
2038 }),
2039 provider: Arc::new(provider),
2040 });
2041 self.update_edit_prediction_settings(cx);
2042 self.refresh_inline_completion(false, false, window, cx);
2043 }
2044
2045 pub fn placeholder_text(&self) -> Option<&str> {
2046 self.placeholder_text.as_deref()
2047 }
2048
2049 pub fn set_placeholder_text(
2050 &mut self,
2051 placeholder_text: impl Into<Arc<str>>,
2052 cx: &mut Context<Self>,
2053 ) {
2054 let placeholder_text = Some(placeholder_text.into());
2055 if self.placeholder_text != placeholder_text {
2056 self.placeholder_text = placeholder_text;
2057 cx.notify();
2058 }
2059 }
2060
2061 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2062 self.cursor_shape = cursor_shape;
2063
2064 // Disrupt blink for immediate user feedback that the cursor shape has changed
2065 self.blink_manager.update(cx, BlinkManager::show_cursor);
2066
2067 cx.notify();
2068 }
2069
2070 pub fn set_current_line_highlight(
2071 &mut self,
2072 current_line_highlight: Option<CurrentLineHighlight>,
2073 ) {
2074 self.current_line_highlight = current_line_highlight;
2075 }
2076
2077 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2078 self.collapse_matches = collapse_matches;
2079 }
2080
2081 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2082 let buffers = self.buffer.read(cx).all_buffers();
2083 let Some(project) = self.project.as_ref() else {
2084 return;
2085 };
2086 project.update(cx, |project, cx| {
2087 for buffer in buffers {
2088 self.registered_buffers
2089 .entry(buffer.read(cx).remote_id())
2090 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2091 }
2092 })
2093 }
2094
2095 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2096 if self.collapse_matches {
2097 return range.start..range.start;
2098 }
2099 range.clone()
2100 }
2101
2102 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2103 if self.display_map.read(cx).clip_at_line_ends != clip {
2104 self.display_map
2105 .update(cx, |map, _| map.clip_at_line_ends = clip);
2106 }
2107 }
2108
2109 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2110 self.input_enabled = input_enabled;
2111 }
2112
2113 pub fn set_inline_completions_hidden_for_vim_mode(
2114 &mut self,
2115 hidden: bool,
2116 window: &mut Window,
2117 cx: &mut Context<Self>,
2118 ) {
2119 if hidden != self.inline_completions_hidden_for_vim_mode {
2120 self.inline_completions_hidden_for_vim_mode = hidden;
2121 if hidden {
2122 self.update_visible_inline_completion(window, cx);
2123 } else {
2124 self.refresh_inline_completion(true, false, window, cx);
2125 }
2126 }
2127 }
2128
2129 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2130 self.menu_inline_completions_policy = value;
2131 }
2132
2133 pub fn set_autoindent(&mut self, autoindent: bool) {
2134 if autoindent {
2135 self.autoindent_mode = Some(AutoindentMode::EachLine);
2136 } else {
2137 self.autoindent_mode = None;
2138 }
2139 }
2140
2141 pub fn read_only(&self, cx: &App) -> bool {
2142 self.read_only || self.buffer.read(cx).read_only()
2143 }
2144
2145 pub fn set_read_only(&mut self, read_only: bool) {
2146 self.read_only = read_only;
2147 }
2148
2149 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2150 self.use_autoclose = autoclose;
2151 }
2152
2153 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2154 self.use_auto_surround = auto_surround;
2155 }
2156
2157 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2158 self.auto_replace_emoji_shortcode = auto_replace;
2159 }
2160
2161 pub fn toggle_edit_predictions(
2162 &mut self,
2163 _: &ToggleEditPrediction,
2164 window: &mut Window,
2165 cx: &mut Context<Self>,
2166 ) {
2167 if self.show_inline_completions_override.is_some() {
2168 self.set_show_edit_predictions(None, window, cx);
2169 } else {
2170 let show_edit_predictions = !self.edit_predictions_enabled();
2171 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2172 }
2173 }
2174
2175 pub fn set_show_edit_predictions(
2176 &mut self,
2177 show_edit_predictions: Option<bool>,
2178 window: &mut Window,
2179 cx: &mut Context<Self>,
2180 ) {
2181 self.show_inline_completions_override = show_edit_predictions;
2182 self.update_edit_prediction_settings(cx);
2183
2184 if let Some(false) = show_edit_predictions {
2185 self.discard_inline_completion(false, cx);
2186 } else {
2187 self.refresh_inline_completion(false, true, window, cx);
2188 }
2189 }
2190
2191 fn inline_completions_disabled_in_scope(
2192 &self,
2193 buffer: &Entity<Buffer>,
2194 buffer_position: language::Anchor,
2195 cx: &App,
2196 ) -> bool {
2197 let snapshot = buffer.read(cx).snapshot();
2198 let settings = snapshot.settings_at(buffer_position, cx);
2199
2200 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2201 return false;
2202 };
2203
2204 scope.override_name().map_or(false, |scope_name| {
2205 settings
2206 .edit_predictions_disabled_in
2207 .iter()
2208 .any(|s| s == scope_name)
2209 })
2210 }
2211
2212 pub fn set_use_modal_editing(&mut self, to: bool) {
2213 self.use_modal_editing = to;
2214 }
2215
2216 pub fn use_modal_editing(&self) -> bool {
2217 self.use_modal_editing
2218 }
2219
2220 fn selections_did_change(
2221 &mut self,
2222 local: bool,
2223 old_cursor_position: &Anchor,
2224 show_completions: bool,
2225 window: &mut Window,
2226 cx: &mut Context<Self>,
2227 ) {
2228 window.invalidate_character_coordinates();
2229
2230 // Copy selections to primary selection buffer
2231 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2232 if local {
2233 let selections = self.selections.all::<usize>(cx);
2234 let buffer_handle = self.buffer.read(cx).read(cx);
2235
2236 let mut text = String::new();
2237 for (index, selection) in selections.iter().enumerate() {
2238 let text_for_selection = buffer_handle
2239 .text_for_range(selection.start..selection.end)
2240 .collect::<String>();
2241
2242 text.push_str(&text_for_selection);
2243 if index != selections.len() - 1 {
2244 text.push('\n');
2245 }
2246 }
2247
2248 if !text.is_empty() {
2249 cx.write_to_primary(ClipboardItem::new_string(text));
2250 }
2251 }
2252
2253 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2254 self.buffer.update(cx, |buffer, cx| {
2255 buffer.set_active_selections(
2256 &self.selections.disjoint_anchors(),
2257 self.selections.line_mode,
2258 self.cursor_shape,
2259 cx,
2260 )
2261 });
2262 }
2263 let display_map = self
2264 .display_map
2265 .update(cx, |display_map, cx| display_map.snapshot(cx));
2266 let buffer = &display_map.buffer_snapshot;
2267 self.add_selections_state = None;
2268 self.select_next_state = None;
2269 self.select_prev_state = None;
2270 self.select_syntax_node_history.try_clear();
2271 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2272 self.snippet_stack
2273 .invalidate(&self.selections.disjoint_anchors(), buffer);
2274 self.take_rename(false, window, cx);
2275
2276 let new_cursor_position = self.selections.newest_anchor().head();
2277
2278 self.push_to_nav_history(
2279 *old_cursor_position,
2280 Some(new_cursor_position.to_point(buffer)),
2281 false,
2282 cx,
2283 );
2284
2285 if local {
2286 let new_cursor_position = self.selections.newest_anchor().head();
2287 let mut context_menu = self.context_menu.borrow_mut();
2288 let completion_menu = match context_menu.as_ref() {
2289 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2290 _ => {
2291 *context_menu = None;
2292 None
2293 }
2294 };
2295 if let Some(buffer_id) = new_cursor_position.buffer_id {
2296 if !self.registered_buffers.contains_key(&buffer_id) {
2297 if let Some(project) = self.project.as_ref() {
2298 project.update(cx, |project, cx| {
2299 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2300 return;
2301 };
2302 self.registered_buffers.insert(
2303 buffer_id,
2304 project.register_buffer_with_language_servers(&buffer, cx),
2305 );
2306 })
2307 }
2308 }
2309 }
2310
2311 if let Some(completion_menu) = completion_menu {
2312 let cursor_position = new_cursor_position.to_offset(buffer);
2313 let (word_range, kind) =
2314 buffer.surrounding_word(completion_menu.initial_position, true);
2315 if kind == Some(CharKind::Word)
2316 && word_range.to_inclusive().contains(&cursor_position)
2317 {
2318 let mut completion_menu = completion_menu.clone();
2319 drop(context_menu);
2320
2321 let query = Self::completion_query(buffer, cursor_position);
2322 cx.spawn(async move |this, cx| {
2323 completion_menu
2324 .filter(query.as_deref(), cx.background_executor().clone())
2325 .await;
2326
2327 this.update(cx, |this, cx| {
2328 let mut context_menu = this.context_menu.borrow_mut();
2329 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2330 else {
2331 return;
2332 };
2333
2334 if menu.id > completion_menu.id {
2335 return;
2336 }
2337
2338 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2339 drop(context_menu);
2340 cx.notify();
2341 })
2342 })
2343 .detach();
2344
2345 if show_completions {
2346 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2347 }
2348 } else {
2349 drop(context_menu);
2350 self.hide_context_menu(window, cx);
2351 }
2352 } else {
2353 drop(context_menu);
2354 }
2355
2356 hide_hover(self, cx);
2357
2358 if old_cursor_position.to_display_point(&display_map).row()
2359 != new_cursor_position.to_display_point(&display_map).row()
2360 {
2361 self.available_code_actions.take();
2362 }
2363 self.refresh_code_actions(window, cx);
2364 self.refresh_document_highlights(cx);
2365 self.refresh_selected_text_highlights(window, cx);
2366 refresh_matching_bracket_highlights(self, window, cx);
2367 self.update_visible_inline_completion(window, cx);
2368 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2369 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2370 if self.git_blame_inline_enabled {
2371 self.start_inline_blame_timer(window, cx);
2372 }
2373 }
2374
2375 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2376 cx.emit(EditorEvent::SelectionsChanged { local });
2377
2378 let selections = &self.selections.disjoint;
2379 if selections.len() == 1 {
2380 cx.emit(SearchEvent::ActiveMatchChanged)
2381 }
2382 if local {
2383 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2384 let inmemory_selections = selections
2385 .iter()
2386 .map(|s| {
2387 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2388 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2389 })
2390 .collect();
2391 self.update_restoration_data(cx, |data| {
2392 data.selections = inmemory_selections;
2393 });
2394
2395 if WorkspaceSettings::get(None, cx).restore_on_startup
2396 != RestoreOnStartupBehavior::None
2397 {
2398 if let Some(workspace_id) =
2399 self.workspace.as_ref().and_then(|workspace| workspace.1)
2400 {
2401 let snapshot = self.buffer().read(cx).snapshot(cx);
2402 let selections = selections.clone();
2403 let background_executor = cx.background_executor().clone();
2404 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2405 self.serialize_selections = cx.background_spawn(async move {
2406 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2407 let db_selections = selections
2408 .iter()
2409 .map(|selection| {
2410 (
2411 selection.start.to_offset(&snapshot),
2412 selection.end.to_offset(&snapshot),
2413 )
2414 })
2415 .collect();
2416
2417 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2418 .await
2419 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2420 .log_err();
2421 });
2422 }
2423 }
2424 }
2425 }
2426
2427 cx.notify();
2428 }
2429
2430 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2431 use text::ToOffset as _;
2432 use text::ToPoint as _;
2433
2434 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2435 return;
2436 }
2437
2438 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2439 return;
2440 };
2441
2442 let snapshot = singleton.read(cx).snapshot();
2443 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2444 let display_snapshot = display_map.snapshot(cx);
2445
2446 display_snapshot
2447 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2448 .map(|fold| {
2449 fold.range.start.text_anchor.to_point(&snapshot)
2450 ..fold.range.end.text_anchor.to_point(&snapshot)
2451 })
2452 .collect()
2453 });
2454 self.update_restoration_data(cx, |data| {
2455 data.folds = inmemory_folds;
2456 });
2457
2458 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2459 return;
2460 };
2461 let background_executor = cx.background_executor().clone();
2462 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2463 let db_folds = self.display_map.update(cx, |display_map, cx| {
2464 display_map
2465 .snapshot(cx)
2466 .folds_in_range(0..snapshot.len())
2467 .map(|fold| {
2468 (
2469 fold.range.start.text_anchor.to_offset(&snapshot),
2470 fold.range.end.text_anchor.to_offset(&snapshot),
2471 )
2472 })
2473 .collect()
2474 });
2475 self.serialize_folds = cx.background_spawn(async move {
2476 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2477 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2478 .await
2479 .with_context(|| {
2480 format!(
2481 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2482 )
2483 })
2484 .log_err();
2485 });
2486 }
2487
2488 pub fn sync_selections(
2489 &mut self,
2490 other: Entity<Editor>,
2491 cx: &mut Context<Self>,
2492 ) -> gpui::Subscription {
2493 let other_selections = other.read(cx).selections.disjoint.to_vec();
2494 self.selections.change_with(cx, |selections| {
2495 selections.select_anchors(other_selections);
2496 });
2497
2498 let other_subscription =
2499 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2500 EditorEvent::SelectionsChanged { local: true } => {
2501 let other_selections = other.read(cx).selections.disjoint.to_vec();
2502 if other_selections.is_empty() {
2503 return;
2504 }
2505 this.selections.change_with(cx, |selections| {
2506 selections.select_anchors(other_selections);
2507 });
2508 }
2509 _ => {}
2510 });
2511
2512 let this_subscription =
2513 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2514 EditorEvent::SelectionsChanged { local: true } => {
2515 let these_selections = this.selections.disjoint.to_vec();
2516 if these_selections.is_empty() {
2517 return;
2518 }
2519 other.update(cx, |other_editor, cx| {
2520 other_editor.selections.change_with(cx, |selections| {
2521 selections.select_anchors(these_selections);
2522 })
2523 });
2524 }
2525 _ => {}
2526 });
2527
2528 Subscription::join(other_subscription, this_subscription)
2529 }
2530
2531 pub fn change_selections<R>(
2532 &mut self,
2533 autoscroll: Option<Autoscroll>,
2534 window: &mut Window,
2535 cx: &mut Context<Self>,
2536 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2537 ) -> R {
2538 self.change_selections_inner(autoscroll, true, window, cx, change)
2539 }
2540
2541 fn change_selections_inner<R>(
2542 &mut self,
2543 autoscroll: Option<Autoscroll>,
2544 request_completions: bool,
2545 window: &mut Window,
2546 cx: &mut Context<Self>,
2547 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2548 ) -> R {
2549 let old_cursor_position = self.selections.newest_anchor().head();
2550 self.push_to_selection_history();
2551
2552 let (changed, result) = self.selections.change_with(cx, change);
2553
2554 if changed {
2555 if let Some(autoscroll) = autoscroll {
2556 self.request_autoscroll(autoscroll, cx);
2557 }
2558 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2559
2560 if self.should_open_signature_help_automatically(
2561 &old_cursor_position,
2562 self.signature_help_state.backspace_pressed(),
2563 cx,
2564 ) {
2565 self.show_signature_help(&ShowSignatureHelp, window, cx);
2566 }
2567 self.signature_help_state.set_backspace_pressed(false);
2568 }
2569
2570 result
2571 }
2572
2573 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2574 where
2575 I: IntoIterator<Item = (Range<S>, T)>,
2576 S: ToOffset,
2577 T: Into<Arc<str>>,
2578 {
2579 if self.read_only(cx) {
2580 return;
2581 }
2582
2583 self.buffer
2584 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2585 }
2586
2587 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2588 where
2589 I: IntoIterator<Item = (Range<S>, T)>,
2590 S: ToOffset,
2591 T: Into<Arc<str>>,
2592 {
2593 if self.read_only(cx) {
2594 return;
2595 }
2596
2597 self.buffer.update(cx, |buffer, cx| {
2598 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2599 });
2600 }
2601
2602 pub fn edit_with_block_indent<I, S, T>(
2603 &mut self,
2604 edits: I,
2605 original_indent_columns: Vec<Option<u32>>,
2606 cx: &mut Context<Self>,
2607 ) where
2608 I: IntoIterator<Item = (Range<S>, T)>,
2609 S: ToOffset,
2610 T: Into<Arc<str>>,
2611 {
2612 if self.read_only(cx) {
2613 return;
2614 }
2615
2616 self.buffer.update(cx, |buffer, cx| {
2617 buffer.edit(
2618 edits,
2619 Some(AutoindentMode::Block {
2620 original_indent_columns,
2621 }),
2622 cx,
2623 )
2624 });
2625 }
2626
2627 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2628 self.hide_context_menu(window, cx);
2629
2630 match phase {
2631 SelectPhase::Begin {
2632 position,
2633 add,
2634 click_count,
2635 } => self.begin_selection(position, add, click_count, window, cx),
2636 SelectPhase::BeginColumnar {
2637 position,
2638 goal_column,
2639 reset,
2640 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2641 SelectPhase::Extend {
2642 position,
2643 click_count,
2644 } => self.extend_selection(position, click_count, window, cx),
2645 SelectPhase::Update {
2646 position,
2647 goal_column,
2648 scroll_delta,
2649 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2650 SelectPhase::End => self.end_selection(window, cx),
2651 }
2652 }
2653
2654 fn extend_selection(
2655 &mut self,
2656 position: DisplayPoint,
2657 click_count: usize,
2658 window: &mut Window,
2659 cx: &mut Context<Self>,
2660 ) {
2661 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2662 let tail = self.selections.newest::<usize>(cx).tail();
2663 self.begin_selection(position, false, click_count, window, cx);
2664
2665 let position = position.to_offset(&display_map, Bias::Left);
2666 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2667
2668 let mut pending_selection = self
2669 .selections
2670 .pending_anchor()
2671 .expect("extend_selection not called with pending selection");
2672 if position >= tail {
2673 pending_selection.start = tail_anchor;
2674 } else {
2675 pending_selection.end = tail_anchor;
2676 pending_selection.reversed = true;
2677 }
2678
2679 let mut pending_mode = self.selections.pending_mode().unwrap();
2680 match &mut pending_mode {
2681 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2682 _ => {}
2683 }
2684
2685 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2686 s.set_pending(pending_selection, pending_mode)
2687 });
2688 }
2689
2690 fn begin_selection(
2691 &mut self,
2692 position: DisplayPoint,
2693 add: bool,
2694 click_count: usize,
2695 window: &mut Window,
2696 cx: &mut Context<Self>,
2697 ) {
2698 if !self.focus_handle.is_focused(window) {
2699 self.last_focused_descendant = None;
2700 window.focus(&self.focus_handle);
2701 }
2702
2703 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2704 let buffer = &display_map.buffer_snapshot;
2705 let newest_selection = self.selections.newest_anchor().clone();
2706 let position = display_map.clip_point(position, Bias::Left);
2707
2708 let start;
2709 let end;
2710 let mode;
2711 let mut auto_scroll;
2712 match click_count {
2713 1 => {
2714 start = buffer.anchor_before(position.to_point(&display_map));
2715 end = start;
2716 mode = SelectMode::Character;
2717 auto_scroll = true;
2718 }
2719 2 => {
2720 let range = movement::surrounding_word(&display_map, position);
2721 start = buffer.anchor_before(range.start.to_point(&display_map));
2722 end = buffer.anchor_before(range.end.to_point(&display_map));
2723 mode = SelectMode::Word(start..end);
2724 auto_scroll = true;
2725 }
2726 3 => {
2727 let position = display_map
2728 .clip_point(position, Bias::Left)
2729 .to_point(&display_map);
2730 let line_start = display_map.prev_line_boundary(position).0;
2731 let next_line_start = buffer.clip_point(
2732 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2733 Bias::Left,
2734 );
2735 start = buffer.anchor_before(line_start);
2736 end = buffer.anchor_before(next_line_start);
2737 mode = SelectMode::Line(start..end);
2738 auto_scroll = true;
2739 }
2740 _ => {
2741 start = buffer.anchor_before(0);
2742 end = buffer.anchor_before(buffer.len());
2743 mode = SelectMode::All;
2744 auto_scroll = false;
2745 }
2746 }
2747 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2748
2749 let point_to_delete: Option<usize> = {
2750 let selected_points: Vec<Selection<Point>> =
2751 self.selections.disjoint_in_range(start..end, cx);
2752
2753 if !add || click_count > 1 {
2754 None
2755 } else if !selected_points.is_empty() {
2756 Some(selected_points[0].id)
2757 } else {
2758 let clicked_point_already_selected =
2759 self.selections.disjoint.iter().find(|selection| {
2760 selection.start.to_point(buffer) == start.to_point(buffer)
2761 || selection.end.to_point(buffer) == end.to_point(buffer)
2762 });
2763
2764 clicked_point_already_selected.map(|selection| selection.id)
2765 }
2766 };
2767
2768 let selections_count = self.selections.count();
2769
2770 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2771 if let Some(point_to_delete) = point_to_delete {
2772 s.delete(point_to_delete);
2773
2774 if selections_count == 1 {
2775 s.set_pending_anchor_range(start..end, mode);
2776 }
2777 } else {
2778 if !add {
2779 s.clear_disjoint();
2780 } else if click_count > 1 {
2781 s.delete(newest_selection.id)
2782 }
2783
2784 s.set_pending_anchor_range(start..end, mode);
2785 }
2786 });
2787 }
2788
2789 fn begin_columnar_selection(
2790 &mut self,
2791 position: DisplayPoint,
2792 goal_column: u32,
2793 reset: bool,
2794 window: &mut Window,
2795 cx: &mut Context<Self>,
2796 ) {
2797 if !self.focus_handle.is_focused(window) {
2798 self.last_focused_descendant = None;
2799 window.focus(&self.focus_handle);
2800 }
2801
2802 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2803
2804 if reset {
2805 let pointer_position = display_map
2806 .buffer_snapshot
2807 .anchor_before(position.to_point(&display_map));
2808
2809 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2810 s.clear_disjoint();
2811 s.set_pending_anchor_range(
2812 pointer_position..pointer_position,
2813 SelectMode::Character,
2814 );
2815 });
2816 }
2817
2818 let tail = self.selections.newest::<Point>(cx).tail();
2819 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2820
2821 if !reset {
2822 self.select_columns(
2823 tail.to_display_point(&display_map),
2824 position,
2825 goal_column,
2826 &display_map,
2827 window,
2828 cx,
2829 );
2830 }
2831 }
2832
2833 fn update_selection(
2834 &mut self,
2835 position: DisplayPoint,
2836 goal_column: u32,
2837 scroll_delta: gpui::Point<f32>,
2838 window: &mut Window,
2839 cx: &mut Context<Self>,
2840 ) {
2841 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2842
2843 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2844 let tail = tail.to_display_point(&display_map);
2845 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2846 } else if let Some(mut pending) = self.selections.pending_anchor() {
2847 let buffer = self.buffer.read(cx).snapshot(cx);
2848 let head;
2849 let tail;
2850 let mode = self.selections.pending_mode().unwrap();
2851 match &mode {
2852 SelectMode::Character => {
2853 head = position.to_point(&display_map);
2854 tail = pending.tail().to_point(&buffer);
2855 }
2856 SelectMode::Word(original_range) => {
2857 let original_display_range = original_range.start.to_display_point(&display_map)
2858 ..original_range.end.to_display_point(&display_map);
2859 let original_buffer_range = original_display_range.start.to_point(&display_map)
2860 ..original_display_range.end.to_point(&display_map);
2861 if movement::is_inside_word(&display_map, position)
2862 || original_display_range.contains(&position)
2863 {
2864 let word_range = movement::surrounding_word(&display_map, position);
2865 if word_range.start < original_display_range.start {
2866 head = word_range.start.to_point(&display_map);
2867 } else {
2868 head = word_range.end.to_point(&display_map);
2869 }
2870 } else {
2871 head = position.to_point(&display_map);
2872 }
2873
2874 if head <= original_buffer_range.start {
2875 tail = original_buffer_range.end;
2876 } else {
2877 tail = original_buffer_range.start;
2878 }
2879 }
2880 SelectMode::Line(original_range) => {
2881 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2882
2883 let position = display_map
2884 .clip_point(position, Bias::Left)
2885 .to_point(&display_map);
2886 let line_start = display_map.prev_line_boundary(position).0;
2887 let next_line_start = buffer.clip_point(
2888 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2889 Bias::Left,
2890 );
2891
2892 if line_start < original_range.start {
2893 head = line_start
2894 } else {
2895 head = next_line_start
2896 }
2897
2898 if head <= original_range.start {
2899 tail = original_range.end;
2900 } else {
2901 tail = original_range.start;
2902 }
2903 }
2904 SelectMode::All => {
2905 return;
2906 }
2907 };
2908
2909 if head < tail {
2910 pending.start = buffer.anchor_before(head);
2911 pending.end = buffer.anchor_before(tail);
2912 pending.reversed = true;
2913 } else {
2914 pending.start = buffer.anchor_before(tail);
2915 pending.end = buffer.anchor_before(head);
2916 pending.reversed = false;
2917 }
2918
2919 self.change_selections(None, window, cx, |s| {
2920 s.set_pending(pending, mode);
2921 });
2922 } else {
2923 log::error!("update_selection dispatched with no pending selection");
2924 return;
2925 }
2926
2927 self.apply_scroll_delta(scroll_delta, window, cx);
2928 cx.notify();
2929 }
2930
2931 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2932 self.columnar_selection_tail.take();
2933 if self.selections.pending_anchor().is_some() {
2934 let selections = self.selections.all::<usize>(cx);
2935 self.change_selections(None, window, cx, |s| {
2936 s.select(selections);
2937 s.clear_pending();
2938 });
2939 }
2940 }
2941
2942 fn select_columns(
2943 &mut self,
2944 tail: DisplayPoint,
2945 head: DisplayPoint,
2946 goal_column: u32,
2947 display_map: &DisplaySnapshot,
2948 window: &mut Window,
2949 cx: &mut Context<Self>,
2950 ) {
2951 let start_row = cmp::min(tail.row(), head.row());
2952 let end_row = cmp::max(tail.row(), head.row());
2953 let start_column = cmp::min(tail.column(), goal_column);
2954 let end_column = cmp::max(tail.column(), goal_column);
2955 let reversed = start_column < tail.column();
2956
2957 let selection_ranges = (start_row.0..=end_row.0)
2958 .map(DisplayRow)
2959 .filter_map(|row| {
2960 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2961 let start = display_map
2962 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2963 .to_point(display_map);
2964 let end = display_map
2965 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2966 .to_point(display_map);
2967 if reversed {
2968 Some(end..start)
2969 } else {
2970 Some(start..end)
2971 }
2972 } else {
2973 None
2974 }
2975 })
2976 .collect::<Vec<_>>();
2977
2978 self.change_selections(None, window, cx, |s| {
2979 s.select_ranges(selection_ranges);
2980 });
2981 cx.notify();
2982 }
2983
2984 pub fn has_pending_nonempty_selection(&self) -> bool {
2985 let pending_nonempty_selection = match self.selections.pending_anchor() {
2986 Some(Selection { start, end, .. }) => start != end,
2987 None => false,
2988 };
2989
2990 pending_nonempty_selection
2991 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2992 }
2993
2994 pub fn has_pending_selection(&self) -> bool {
2995 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2996 }
2997
2998 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2999 self.selection_mark_mode = false;
3000
3001 if self.clear_expanded_diff_hunks(cx) {
3002 cx.notify();
3003 return;
3004 }
3005 if self.dismiss_menus_and_popups(true, window, cx) {
3006 return;
3007 }
3008
3009 if self.mode == EditorMode::Full
3010 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3011 {
3012 return;
3013 }
3014
3015 cx.propagate();
3016 }
3017
3018 pub fn dismiss_menus_and_popups(
3019 &mut self,
3020 is_user_requested: bool,
3021 window: &mut Window,
3022 cx: &mut Context<Self>,
3023 ) -> bool {
3024 if self.take_rename(false, window, cx).is_some() {
3025 return true;
3026 }
3027
3028 if hide_hover(self, cx) {
3029 return true;
3030 }
3031
3032 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3033 return true;
3034 }
3035
3036 if self.hide_context_menu(window, cx).is_some() {
3037 return true;
3038 }
3039
3040 if self.mouse_context_menu.take().is_some() {
3041 return true;
3042 }
3043
3044 if is_user_requested && self.discard_inline_completion(true, cx) {
3045 return true;
3046 }
3047
3048 if self.snippet_stack.pop().is_some() {
3049 return true;
3050 }
3051
3052 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
3053 self.dismiss_diagnostics(cx);
3054 return true;
3055 }
3056
3057 false
3058 }
3059
3060 fn linked_editing_ranges_for(
3061 &self,
3062 selection: Range<text::Anchor>,
3063 cx: &App,
3064 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3065 if self.linked_edit_ranges.is_empty() {
3066 return None;
3067 }
3068 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3069 selection.end.buffer_id.and_then(|end_buffer_id| {
3070 if selection.start.buffer_id != Some(end_buffer_id) {
3071 return None;
3072 }
3073 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3074 let snapshot = buffer.read(cx).snapshot();
3075 self.linked_edit_ranges
3076 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3077 .map(|ranges| (ranges, snapshot, buffer))
3078 })?;
3079 use text::ToOffset as TO;
3080 // find offset from the start of current range to current cursor position
3081 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3082
3083 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3084 let start_difference = start_offset - start_byte_offset;
3085 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3086 let end_difference = end_offset - start_byte_offset;
3087 // Current range has associated linked ranges.
3088 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3089 for range in linked_ranges.iter() {
3090 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3091 let end_offset = start_offset + end_difference;
3092 let start_offset = start_offset + start_difference;
3093 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3094 continue;
3095 }
3096 if self.selections.disjoint_anchor_ranges().any(|s| {
3097 if s.start.buffer_id != selection.start.buffer_id
3098 || s.end.buffer_id != selection.end.buffer_id
3099 {
3100 return false;
3101 }
3102 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3103 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3104 }) {
3105 continue;
3106 }
3107 let start = buffer_snapshot.anchor_after(start_offset);
3108 let end = buffer_snapshot.anchor_after(end_offset);
3109 linked_edits
3110 .entry(buffer.clone())
3111 .or_default()
3112 .push(start..end);
3113 }
3114 Some(linked_edits)
3115 }
3116
3117 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3118 let text: Arc<str> = text.into();
3119
3120 if self.read_only(cx) {
3121 return;
3122 }
3123
3124 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3125
3126 let selections = self.selections.all_adjusted(cx);
3127 let mut bracket_inserted = false;
3128 let mut edits = Vec::new();
3129 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3130 let mut new_selections = Vec::with_capacity(selections.len());
3131 let mut new_autoclose_regions = Vec::new();
3132 let snapshot = self.buffer.read(cx).read(cx);
3133
3134 for (selection, autoclose_region) in
3135 self.selections_with_autoclose_regions(selections, &snapshot)
3136 {
3137 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3138 // Determine if the inserted text matches the opening or closing
3139 // bracket of any of this language's bracket pairs.
3140 let mut bracket_pair = None;
3141 let mut is_bracket_pair_start = false;
3142 let mut is_bracket_pair_end = false;
3143 if !text.is_empty() {
3144 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3145 // and they are removing the character that triggered IME popup.
3146 for (pair, enabled) in scope.brackets() {
3147 if !pair.close && !pair.surround {
3148 continue;
3149 }
3150
3151 if enabled && pair.start.ends_with(text.as_ref()) {
3152 let prefix_len = pair.start.len() - text.len();
3153 let preceding_text_matches_prefix = prefix_len == 0
3154 || (selection.start.column >= (prefix_len as u32)
3155 && snapshot.contains_str_at(
3156 Point::new(
3157 selection.start.row,
3158 selection.start.column - (prefix_len as u32),
3159 ),
3160 &pair.start[..prefix_len],
3161 ));
3162 if preceding_text_matches_prefix {
3163 bracket_pair = Some(pair.clone());
3164 is_bracket_pair_start = true;
3165 break;
3166 }
3167 }
3168 if pair.end.as_str() == text.as_ref() {
3169 bracket_pair = Some(pair.clone());
3170 is_bracket_pair_end = true;
3171 break;
3172 }
3173 }
3174 }
3175
3176 if let Some(bracket_pair) = bracket_pair {
3177 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3178 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3179 let auto_surround =
3180 self.use_auto_surround && snapshot_settings.use_auto_surround;
3181 if selection.is_empty() {
3182 if is_bracket_pair_start {
3183 // If the inserted text is a suffix of an opening bracket and the
3184 // selection is preceded by the rest of the opening bracket, then
3185 // insert the closing bracket.
3186 let following_text_allows_autoclose = snapshot
3187 .chars_at(selection.start)
3188 .next()
3189 .map_or(true, |c| scope.should_autoclose_before(c));
3190
3191 let preceding_text_allows_autoclose = selection.start.column == 0
3192 || snapshot.reversed_chars_at(selection.start).next().map_or(
3193 true,
3194 |c| {
3195 bracket_pair.start != bracket_pair.end
3196 || !snapshot
3197 .char_classifier_at(selection.start)
3198 .is_word(c)
3199 },
3200 );
3201
3202 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3203 && bracket_pair.start.len() == 1
3204 {
3205 let target = bracket_pair.start.chars().next().unwrap();
3206 let current_line_count = snapshot
3207 .reversed_chars_at(selection.start)
3208 .take_while(|&c| c != '\n')
3209 .filter(|&c| c == target)
3210 .count();
3211 current_line_count % 2 == 1
3212 } else {
3213 false
3214 };
3215
3216 if autoclose
3217 && bracket_pair.close
3218 && following_text_allows_autoclose
3219 && preceding_text_allows_autoclose
3220 && !is_closing_quote
3221 {
3222 let anchor = snapshot.anchor_before(selection.end);
3223 new_selections.push((selection.map(|_| anchor), text.len()));
3224 new_autoclose_regions.push((
3225 anchor,
3226 text.len(),
3227 selection.id,
3228 bracket_pair.clone(),
3229 ));
3230 edits.push((
3231 selection.range(),
3232 format!("{}{}", text, bracket_pair.end).into(),
3233 ));
3234 bracket_inserted = true;
3235 continue;
3236 }
3237 }
3238
3239 if let Some(region) = autoclose_region {
3240 // If the selection is followed by an auto-inserted closing bracket,
3241 // then don't insert that closing bracket again; just move the selection
3242 // past the closing bracket.
3243 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3244 && text.as_ref() == region.pair.end.as_str();
3245 if should_skip {
3246 let anchor = snapshot.anchor_after(selection.end);
3247 new_selections
3248 .push((selection.map(|_| anchor), region.pair.end.len()));
3249 continue;
3250 }
3251 }
3252
3253 let always_treat_brackets_as_autoclosed = snapshot
3254 .language_settings_at(selection.start, cx)
3255 .always_treat_brackets_as_autoclosed;
3256 if always_treat_brackets_as_autoclosed
3257 && is_bracket_pair_end
3258 && snapshot.contains_str_at(selection.end, text.as_ref())
3259 {
3260 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3261 // and the inserted text is a closing bracket and the selection is followed
3262 // by the closing bracket then move the selection past the closing bracket.
3263 let anchor = snapshot.anchor_after(selection.end);
3264 new_selections.push((selection.map(|_| anchor), text.len()));
3265 continue;
3266 }
3267 }
3268 // If an opening bracket is 1 character long and is typed while
3269 // text is selected, then surround that text with the bracket pair.
3270 else if auto_surround
3271 && bracket_pair.surround
3272 && is_bracket_pair_start
3273 && bracket_pair.start.chars().count() == 1
3274 {
3275 edits.push((selection.start..selection.start, text.clone()));
3276 edits.push((
3277 selection.end..selection.end,
3278 bracket_pair.end.as_str().into(),
3279 ));
3280 bracket_inserted = true;
3281 new_selections.push((
3282 Selection {
3283 id: selection.id,
3284 start: snapshot.anchor_after(selection.start),
3285 end: snapshot.anchor_before(selection.end),
3286 reversed: selection.reversed,
3287 goal: selection.goal,
3288 },
3289 0,
3290 ));
3291 continue;
3292 }
3293 }
3294 }
3295
3296 if self.auto_replace_emoji_shortcode
3297 && selection.is_empty()
3298 && text.as_ref().ends_with(':')
3299 {
3300 if let Some(possible_emoji_short_code) =
3301 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3302 {
3303 if !possible_emoji_short_code.is_empty() {
3304 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3305 let emoji_shortcode_start = Point::new(
3306 selection.start.row,
3307 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3308 );
3309
3310 // Remove shortcode from buffer
3311 edits.push((
3312 emoji_shortcode_start..selection.start,
3313 "".to_string().into(),
3314 ));
3315 new_selections.push((
3316 Selection {
3317 id: selection.id,
3318 start: snapshot.anchor_after(emoji_shortcode_start),
3319 end: snapshot.anchor_before(selection.start),
3320 reversed: selection.reversed,
3321 goal: selection.goal,
3322 },
3323 0,
3324 ));
3325
3326 // Insert emoji
3327 let selection_start_anchor = snapshot.anchor_after(selection.start);
3328 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3329 edits.push((selection.start..selection.end, emoji.to_string().into()));
3330
3331 continue;
3332 }
3333 }
3334 }
3335 }
3336
3337 // If not handling any auto-close operation, then just replace the selected
3338 // text with the given input and move the selection to the end of the
3339 // newly inserted text.
3340 let anchor = snapshot.anchor_after(selection.end);
3341 if !self.linked_edit_ranges.is_empty() {
3342 let start_anchor = snapshot.anchor_before(selection.start);
3343
3344 let is_word_char = text.chars().next().map_or(true, |char| {
3345 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3346 classifier.is_word(char)
3347 });
3348
3349 if is_word_char {
3350 if let Some(ranges) = self
3351 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3352 {
3353 for (buffer, edits) in ranges {
3354 linked_edits
3355 .entry(buffer.clone())
3356 .or_default()
3357 .extend(edits.into_iter().map(|range| (range, text.clone())));
3358 }
3359 }
3360 }
3361 }
3362
3363 new_selections.push((selection.map(|_| anchor), 0));
3364 edits.push((selection.start..selection.end, text.clone()));
3365 }
3366
3367 drop(snapshot);
3368
3369 self.transact(window, cx, |this, window, cx| {
3370 let initial_buffer_versions =
3371 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3372
3373 this.buffer.update(cx, |buffer, cx| {
3374 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3375 });
3376 for (buffer, edits) in linked_edits {
3377 buffer.update(cx, |buffer, cx| {
3378 let snapshot = buffer.snapshot();
3379 let edits = edits
3380 .into_iter()
3381 .map(|(range, text)| {
3382 use text::ToPoint as TP;
3383 let end_point = TP::to_point(&range.end, &snapshot);
3384 let start_point = TP::to_point(&range.start, &snapshot);
3385 (start_point..end_point, text)
3386 })
3387 .sorted_by_key(|(range, _)| range.start);
3388 buffer.edit(edits, None, cx);
3389 })
3390 }
3391 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3392 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3393 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3394 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3395 .zip(new_selection_deltas)
3396 .map(|(selection, delta)| Selection {
3397 id: selection.id,
3398 start: selection.start + delta,
3399 end: selection.end + delta,
3400 reversed: selection.reversed,
3401 goal: SelectionGoal::None,
3402 })
3403 .collect::<Vec<_>>();
3404
3405 let mut i = 0;
3406 for (position, delta, selection_id, pair) in new_autoclose_regions {
3407 let position = position.to_offset(&map.buffer_snapshot) + delta;
3408 let start = map.buffer_snapshot.anchor_before(position);
3409 let end = map.buffer_snapshot.anchor_after(position);
3410 while let Some(existing_state) = this.autoclose_regions.get(i) {
3411 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3412 Ordering::Less => i += 1,
3413 Ordering::Greater => break,
3414 Ordering::Equal => {
3415 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3416 Ordering::Less => i += 1,
3417 Ordering::Equal => break,
3418 Ordering::Greater => break,
3419 }
3420 }
3421 }
3422 }
3423 this.autoclose_regions.insert(
3424 i,
3425 AutocloseRegion {
3426 selection_id,
3427 range: start..end,
3428 pair,
3429 },
3430 );
3431 }
3432
3433 let had_active_inline_completion = this.has_active_inline_completion();
3434 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3435 s.select(new_selections)
3436 });
3437
3438 if !bracket_inserted {
3439 if let Some(on_type_format_task) =
3440 this.trigger_on_type_formatting(text.to_string(), window, cx)
3441 {
3442 on_type_format_task.detach_and_log_err(cx);
3443 }
3444 }
3445
3446 let editor_settings = EditorSettings::get_global(cx);
3447 if bracket_inserted
3448 && (editor_settings.auto_signature_help
3449 || editor_settings.show_signature_help_after_edits)
3450 {
3451 this.show_signature_help(&ShowSignatureHelp, window, cx);
3452 }
3453
3454 let trigger_in_words =
3455 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3456 if this.hard_wrap.is_some() {
3457 let latest: Range<Point> = this.selections.newest(cx).range();
3458 if latest.is_empty()
3459 && this
3460 .buffer()
3461 .read(cx)
3462 .snapshot(cx)
3463 .line_len(MultiBufferRow(latest.start.row))
3464 == latest.start.column
3465 {
3466 this.rewrap_impl(
3467 RewrapOptions {
3468 override_language_settings: true,
3469 preserve_existing_whitespace: true,
3470 },
3471 cx,
3472 )
3473 }
3474 }
3475 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3476 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3477 this.refresh_inline_completion(true, false, window, cx);
3478 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3479 });
3480 }
3481
3482 fn find_possible_emoji_shortcode_at_position(
3483 snapshot: &MultiBufferSnapshot,
3484 position: Point,
3485 ) -> Option<String> {
3486 let mut chars = Vec::new();
3487 let mut found_colon = false;
3488 for char in snapshot.reversed_chars_at(position).take(100) {
3489 // Found a possible emoji shortcode in the middle of the buffer
3490 if found_colon {
3491 if char.is_whitespace() {
3492 chars.reverse();
3493 return Some(chars.iter().collect());
3494 }
3495 // If the previous character is not a whitespace, we are in the middle of a word
3496 // and we only want to complete the shortcode if the word is made up of other emojis
3497 let mut containing_word = String::new();
3498 for ch in snapshot
3499 .reversed_chars_at(position)
3500 .skip(chars.len() + 1)
3501 .take(100)
3502 {
3503 if ch.is_whitespace() {
3504 break;
3505 }
3506 containing_word.push(ch);
3507 }
3508 let containing_word = containing_word.chars().rev().collect::<String>();
3509 if util::word_consists_of_emojis(containing_word.as_str()) {
3510 chars.reverse();
3511 return Some(chars.iter().collect());
3512 }
3513 }
3514
3515 if char.is_whitespace() || !char.is_ascii() {
3516 return None;
3517 }
3518 if char == ':' {
3519 found_colon = true;
3520 } else {
3521 chars.push(char);
3522 }
3523 }
3524 // Found a possible emoji shortcode at the beginning of the buffer
3525 chars.reverse();
3526 Some(chars.iter().collect())
3527 }
3528
3529 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3530 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3531 self.transact(window, cx, |this, window, cx| {
3532 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3533 let selections = this.selections.all::<usize>(cx);
3534 let multi_buffer = this.buffer.read(cx);
3535 let buffer = multi_buffer.snapshot(cx);
3536 selections
3537 .iter()
3538 .map(|selection| {
3539 let start_point = selection.start.to_point(&buffer);
3540 let mut indent =
3541 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3542 indent.len = cmp::min(indent.len, start_point.column);
3543 let start = selection.start;
3544 let end = selection.end;
3545 let selection_is_empty = start == end;
3546 let language_scope = buffer.language_scope_at(start);
3547 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3548 &language_scope
3549 {
3550 let insert_extra_newline =
3551 insert_extra_newline_brackets(&buffer, start..end, language)
3552 || insert_extra_newline_tree_sitter(&buffer, start..end);
3553
3554 // Comment extension on newline is allowed only for cursor selections
3555 let comment_delimiter = maybe!({
3556 if !selection_is_empty {
3557 return None;
3558 }
3559
3560 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3561 return None;
3562 }
3563
3564 let delimiters = language.line_comment_prefixes();
3565 let max_len_of_delimiter =
3566 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3567 let (snapshot, range) =
3568 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3569
3570 let mut index_of_first_non_whitespace = 0;
3571 let comment_candidate = snapshot
3572 .chars_for_range(range)
3573 .skip_while(|c| {
3574 let should_skip = c.is_whitespace();
3575 if should_skip {
3576 index_of_first_non_whitespace += 1;
3577 }
3578 should_skip
3579 })
3580 .take(max_len_of_delimiter)
3581 .collect::<String>();
3582 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3583 comment_candidate.starts_with(comment_prefix.as_ref())
3584 })?;
3585 let cursor_is_placed_after_comment_marker =
3586 index_of_first_non_whitespace + comment_prefix.len()
3587 <= start_point.column as usize;
3588 if cursor_is_placed_after_comment_marker {
3589 Some(comment_prefix.clone())
3590 } else {
3591 None
3592 }
3593 });
3594 (comment_delimiter, insert_extra_newline)
3595 } else {
3596 (None, false)
3597 };
3598
3599 let capacity_for_delimiter = comment_delimiter
3600 .as_deref()
3601 .map(str::len)
3602 .unwrap_or_default();
3603 let mut new_text =
3604 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3605 new_text.push('\n');
3606 new_text.extend(indent.chars());
3607 if let Some(delimiter) = &comment_delimiter {
3608 new_text.push_str(delimiter);
3609 }
3610 if insert_extra_newline {
3611 new_text = new_text.repeat(2);
3612 }
3613
3614 let anchor = buffer.anchor_after(end);
3615 let new_selection = selection.map(|_| anchor);
3616 (
3617 (start..end, new_text),
3618 (insert_extra_newline, new_selection),
3619 )
3620 })
3621 .unzip()
3622 };
3623
3624 this.edit_with_autoindent(edits, cx);
3625 let buffer = this.buffer.read(cx).snapshot(cx);
3626 let new_selections = selection_fixup_info
3627 .into_iter()
3628 .map(|(extra_newline_inserted, new_selection)| {
3629 let mut cursor = new_selection.end.to_point(&buffer);
3630 if extra_newline_inserted {
3631 cursor.row -= 1;
3632 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3633 }
3634 new_selection.map(|_| cursor)
3635 })
3636 .collect();
3637
3638 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3639 s.select(new_selections)
3640 });
3641 this.refresh_inline_completion(true, false, window, cx);
3642 });
3643 }
3644
3645 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3646 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3647
3648 let buffer = self.buffer.read(cx);
3649 let snapshot = buffer.snapshot(cx);
3650
3651 let mut edits = Vec::new();
3652 let mut rows = Vec::new();
3653
3654 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3655 let cursor = selection.head();
3656 let row = cursor.row;
3657
3658 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3659
3660 let newline = "\n".to_string();
3661 edits.push((start_of_line..start_of_line, newline));
3662
3663 rows.push(row + rows_inserted as u32);
3664 }
3665
3666 self.transact(window, cx, |editor, window, cx| {
3667 editor.edit(edits, cx);
3668
3669 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3670 let mut index = 0;
3671 s.move_cursors_with(|map, _, _| {
3672 let row = rows[index];
3673 index += 1;
3674
3675 let point = Point::new(row, 0);
3676 let boundary = map.next_line_boundary(point).1;
3677 let clipped = map.clip_point(boundary, Bias::Left);
3678
3679 (clipped, SelectionGoal::None)
3680 });
3681 });
3682
3683 let mut indent_edits = Vec::new();
3684 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3685 for row in rows {
3686 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3687 for (row, indent) in indents {
3688 if indent.len == 0 {
3689 continue;
3690 }
3691
3692 let text = match indent.kind {
3693 IndentKind::Space => " ".repeat(indent.len as usize),
3694 IndentKind::Tab => "\t".repeat(indent.len as usize),
3695 };
3696 let point = Point::new(row.0, 0);
3697 indent_edits.push((point..point, text));
3698 }
3699 }
3700 editor.edit(indent_edits, cx);
3701 });
3702 }
3703
3704 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3705 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3706
3707 let buffer = self.buffer.read(cx);
3708 let snapshot = buffer.snapshot(cx);
3709
3710 let mut edits = Vec::new();
3711 let mut rows = Vec::new();
3712 let mut rows_inserted = 0;
3713
3714 for selection in self.selections.all_adjusted(cx) {
3715 let cursor = selection.head();
3716 let row = cursor.row;
3717
3718 let point = Point::new(row + 1, 0);
3719 let start_of_line = snapshot.clip_point(point, Bias::Left);
3720
3721 let newline = "\n".to_string();
3722 edits.push((start_of_line..start_of_line, newline));
3723
3724 rows_inserted += 1;
3725 rows.push(row + rows_inserted);
3726 }
3727
3728 self.transact(window, cx, |editor, window, cx| {
3729 editor.edit(edits, cx);
3730
3731 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3732 let mut index = 0;
3733 s.move_cursors_with(|map, _, _| {
3734 let row = rows[index];
3735 index += 1;
3736
3737 let point = Point::new(row, 0);
3738 let boundary = map.next_line_boundary(point).1;
3739 let clipped = map.clip_point(boundary, Bias::Left);
3740
3741 (clipped, SelectionGoal::None)
3742 });
3743 });
3744
3745 let mut indent_edits = Vec::new();
3746 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3747 for row in rows {
3748 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3749 for (row, indent) in indents {
3750 if indent.len == 0 {
3751 continue;
3752 }
3753
3754 let text = match indent.kind {
3755 IndentKind::Space => " ".repeat(indent.len as usize),
3756 IndentKind::Tab => "\t".repeat(indent.len as usize),
3757 };
3758 let point = Point::new(row.0, 0);
3759 indent_edits.push((point..point, text));
3760 }
3761 }
3762 editor.edit(indent_edits, cx);
3763 });
3764 }
3765
3766 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3767 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3768 original_indent_columns: Vec::new(),
3769 });
3770 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3771 }
3772
3773 fn insert_with_autoindent_mode(
3774 &mut self,
3775 text: &str,
3776 autoindent_mode: Option<AutoindentMode>,
3777 window: &mut Window,
3778 cx: &mut Context<Self>,
3779 ) {
3780 if self.read_only(cx) {
3781 return;
3782 }
3783
3784 let text: Arc<str> = text.into();
3785 self.transact(window, cx, |this, window, cx| {
3786 let old_selections = this.selections.all_adjusted(cx);
3787 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3788 let anchors = {
3789 let snapshot = buffer.read(cx);
3790 old_selections
3791 .iter()
3792 .map(|s| {
3793 let anchor = snapshot.anchor_after(s.head());
3794 s.map(|_| anchor)
3795 })
3796 .collect::<Vec<_>>()
3797 };
3798 buffer.edit(
3799 old_selections
3800 .iter()
3801 .map(|s| (s.start..s.end, text.clone())),
3802 autoindent_mode,
3803 cx,
3804 );
3805 anchors
3806 });
3807
3808 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3809 s.select_anchors(selection_anchors);
3810 });
3811
3812 cx.notify();
3813 });
3814 }
3815
3816 fn trigger_completion_on_input(
3817 &mut self,
3818 text: &str,
3819 trigger_in_words: bool,
3820 window: &mut Window,
3821 cx: &mut Context<Self>,
3822 ) {
3823 let ignore_completion_provider = self
3824 .context_menu
3825 .borrow()
3826 .as_ref()
3827 .map(|menu| match menu {
3828 CodeContextMenu::Completions(completions_menu) => {
3829 completions_menu.ignore_completion_provider
3830 }
3831 CodeContextMenu::CodeActions(_) => false,
3832 })
3833 .unwrap_or(false);
3834
3835 if ignore_completion_provider {
3836 self.show_word_completions(&ShowWordCompletions, window, cx);
3837 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3838 self.show_completions(
3839 &ShowCompletions {
3840 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3841 },
3842 window,
3843 cx,
3844 );
3845 } else {
3846 self.hide_context_menu(window, cx);
3847 }
3848 }
3849
3850 fn is_completion_trigger(
3851 &self,
3852 text: &str,
3853 trigger_in_words: bool,
3854 cx: &mut Context<Self>,
3855 ) -> bool {
3856 let position = self.selections.newest_anchor().head();
3857 let multibuffer = self.buffer.read(cx);
3858 let Some(buffer) = position
3859 .buffer_id
3860 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3861 else {
3862 return false;
3863 };
3864
3865 if let Some(completion_provider) = &self.completion_provider {
3866 completion_provider.is_completion_trigger(
3867 &buffer,
3868 position.text_anchor,
3869 text,
3870 trigger_in_words,
3871 cx,
3872 )
3873 } else {
3874 false
3875 }
3876 }
3877
3878 /// If any empty selections is touching the start of its innermost containing autoclose
3879 /// region, expand it to select the brackets.
3880 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3881 let selections = self.selections.all::<usize>(cx);
3882 let buffer = self.buffer.read(cx).read(cx);
3883 let new_selections = self
3884 .selections_with_autoclose_regions(selections, &buffer)
3885 .map(|(mut selection, region)| {
3886 if !selection.is_empty() {
3887 return selection;
3888 }
3889
3890 if let Some(region) = region {
3891 let mut range = region.range.to_offset(&buffer);
3892 if selection.start == range.start && range.start >= region.pair.start.len() {
3893 range.start -= region.pair.start.len();
3894 if buffer.contains_str_at(range.start, ®ion.pair.start)
3895 && buffer.contains_str_at(range.end, ®ion.pair.end)
3896 {
3897 range.end += region.pair.end.len();
3898 selection.start = range.start;
3899 selection.end = range.end;
3900
3901 return selection;
3902 }
3903 }
3904 }
3905
3906 let always_treat_brackets_as_autoclosed = buffer
3907 .language_settings_at(selection.start, cx)
3908 .always_treat_brackets_as_autoclosed;
3909
3910 if !always_treat_brackets_as_autoclosed {
3911 return selection;
3912 }
3913
3914 if let Some(scope) = buffer.language_scope_at(selection.start) {
3915 for (pair, enabled) in scope.brackets() {
3916 if !enabled || !pair.close {
3917 continue;
3918 }
3919
3920 if buffer.contains_str_at(selection.start, &pair.end) {
3921 let pair_start_len = pair.start.len();
3922 if buffer.contains_str_at(
3923 selection.start.saturating_sub(pair_start_len),
3924 &pair.start,
3925 ) {
3926 selection.start -= pair_start_len;
3927 selection.end += pair.end.len();
3928
3929 return selection;
3930 }
3931 }
3932 }
3933 }
3934
3935 selection
3936 })
3937 .collect();
3938
3939 drop(buffer);
3940 self.change_selections(None, window, cx, |selections| {
3941 selections.select(new_selections)
3942 });
3943 }
3944
3945 /// Iterate the given selections, and for each one, find the smallest surrounding
3946 /// autoclose region. This uses the ordering of the selections and the autoclose
3947 /// regions to avoid repeated comparisons.
3948 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3949 &'a self,
3950 selections: impl IntoIterator<Item = Selection<D>>,
3951 buffer: &'a MultiBufferSnapshot,
3952 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3953 let mut i = 0;
3954 let mut regions = self.autoclose_regions.as_slice();
3955 selections.into_iter().map(move |selection| {
3956 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3957
3958 let mut enclosing = None;
3959 while let Some(pair_state) = regions.get(i) {
3960 if pair_state.range.end.to_offset(buffer) < range.start {
3961 regions = ®ions[i + 1..];
3962 i = 0;
3963 } else if pair_state.range.start.to_offset(buffer) > range.end {
3964 break;
3965 } else {
3966 if pair_state.selection_id == selection.id {
3967 enclosing = Some(pair_state);
3968 }
3969 i += 1;
3970 }
3971 }
3972
3973 (selection, enclosing)
3974 })
3975 }
3976
3977 /// Remove any autoclose regions that no longer contain their selection.
3978 fn invalidate_autoclose_regions(
3979 &mut self,
3980 mut selections: &[Selection<Anchor>],
3981 buffer: &MultiBufferSnapshot,
3982 ) {
3983 self.autoclose_regions.retain(|state| {
3984 let mut i = 0;
3985 while let Some(selection) = selections.get(i) {
3986 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3987 selections = &selections[1..];
3988 continue;
3989 }
3990 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3991 break;
3992 }
3993 if selection.id == state.selection_id {
3994 return true;
3995 } else {
3996 i += 1;
3997 }
3998 }
3999 false
4000 });
4001 }
4002
4003 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4004 let offset = position.to_offset(buffer);
4005 let (word_range, kind) = buffer.surrounding_word(offset, true);
4006 if offset > word_range.start && kind == Some(CharKind::Word) {
4007 Some(
4008 buffer
4009 .text_for_range(word_range.start..offset)
4010 .collect::<String>(),
4011 )
4012 } else {
4013 None
4014 }
4015 }
4016
4017 pub fn toggle_inlay_hints(
4018 &mut self,
4019 _: &ToggleInlayHints,
4020 _: &mut Window,
4021 cx: &mut Context<Self>,
4022 ) {
4023 self.refresh_inlay_hints(
4024 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4025 cx,
4026 );
4027 }
4028
4029 pub fn inlay_hints_enabled(&self) -> bool {
4030 self.inlay_hint_cache.enabled
4031 }
4032
4033 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4034 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
4035 return;
4036 }
4037
4038 let reason_description = reason.description();
4039 let ignore_debounce = matches!(
4040 reason,
4041 InlayHintRefreshReason::SettingsChange(_)
4042 | InlayHintRefreshReason::Toggle(_)
4043 | InlayHintRefreshReason::ExcerptsRemoved(_)
4044 | InlayHintRefreshReason::ModifiersChanged(_)
4045 );
4046 let (invalidate_cache, required_languages) = match reason {
4047 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4048 match self.inlay_hint_cache.modifiers_override(enabled) {
4049 Some(enabled) => {
4050 if enabled {
4051 (InvalidationStrategy::RefreshRequested, None)
4052 } else {
4053 self.splice_inlays(
4054 &self
4055 .visible_inlay_hints(cx)
4056 .iter()
4057 .map(|inlay| inlay.id)
4058 .collect::<Vec<InlayId>>(),
4059 Vec::new(),
4060 cx,
4061 );
4062 return;
4063 }
4064 }
4065 None => return,
4066 }
4067 }
4068 InlayHintRefreshReason::Toggle(enabled) => {
4069 if self.inlay_hint_cache.toggle(enabled) {
4070 if enabled {
4071 (InvalidationStrategy::RefreshRequested, None)
4072 } else {
4073 self.splice_inlays(
4074 &self
4075 .visible_inlay_hints(cx)
4076 .iter()
4077 .map(|inlay| inlay.id)
4078 .collect::<Vec<InlayId>>(),
4079 Vec::new(),
4080 cx,
4081 );
4082 return;
4083 }
4084 } else {
4085 return;
4086 }
4087 }
4088 InlayHintRefreshReason::SettingsChange(new_settings) => {
4089 match self.inlay_hint_cache.update_settings(
4090 &self.buffer,
4091 new_settings,
4092 self.visible_inlay_hints(cx),
4093 cx,
4094 ) {
4095 ControlFlow::Break(Some(InlaySplice {
4096 to_remove,
4097 to_insert,
4098 })) => {
4099 self.splice_inlays(&to_remove, to_insert, cx);
4100 return;
4101 }
4102 ControlFlow::Break(None) => return,
4103 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4104 }
4105 }
4106 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4107 if let Some(InlaySplice {
4108 to_remove,
4109 to_insert,
4110 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
4111 {
4112 self.splice_inlays(&to_remove, to_insert, cx);
4113 }
4114 return;
4115 }
4116 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4117 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4118 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4119 }
4120 InlayHintRefreshReason::RefreshRequested => {
4121 (InvalidationStrategy::RefreshRequested, None)
4122 }
4123 };
4124
4125 if let Some(InlaySplice {
4126 to_remove,
4127 to_insert,
4128 }) = self.inlay_hint_cache.spawn_hint_refresh(
4129 reason_description,
4130 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4131 invalidate_cache,
4132 ignore_debounce,
4133 cx,
4134 ) {
4135 self.splice_inlays(&to_remove, to_insert, cx);
4136 }
4137 }
4138
4139 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4140 self.display_map
4141 .read(cx)
4142 .current_inlays()
4143 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4144 .cloned()
4145 .collect()
4146 }
4147
4148 pub fn excerpts_for_inlay_hints_query(
4149 &self,
4150 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4151 cx: &mut Context<Editor>,
4152 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4153 let Some(project) = self.project.as_ref() else {
4154 return HashMap::default();
4155 };
4156 let project = project.read(cx);
4157 let multi_buffer = self.buffer().read(cx);
4158 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4159 let multi_buffer_visible_start = self
4160 .scroll_manager
4161 .anchor()
4162 .anchor
4163 .to_point(&multi_buffer_snapshot);
4164 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4165 multi_buffer_visible_start
4166 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4167 Bias::Left,
4168 );
4169 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4170 multi_buffer_snapshot
4171 .range_to_buffer_ranges(multi_buffer_visible_range)
4172 .into_iter()
4173 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4174 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4175 let buffer_file = project::File::from_dyn(buffer.file())?;
4176 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4177 let worktree_entry = buffer_worktree
4178 .read(cx)
4179 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4180 if worktree_entry.is_ignored {
4181 return None;
4182 }
4183
4184 let language = buffer.language()?;
4185 if let Some(restrict_to_languages) = restrict_to_languages {
4186 if !restrict_to_languages.contains(language) {
4187 return None;
4188 }
4189 }
4190 Some((
4191 excerpt_id,
4192 (
4193 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4194 buffer.version().clone(),
4195 excerpt_visible_range,
4196 ),
4197 ))
4198 })
4199 .collect()
4200 }
4201
4202 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4203 TextLayoutDetails {
4204 text_system: window.text_system().clone(),
4205 editor_style: self.style.clone().unwrap(),
4206 rem_size: window.rem_size(),
4207 scroll_anchor: self.scroll_manager.anchor(),
4208 visible_rows: self.visible_line_count(),
4209 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4210 }
4211 }
4212
4213 pub fn splice_inlays(
4214 &self,
4215 to_remove: &[InlayId],
4216 to_insert: Vec<Inlay>,
4217 cx: &mut Context<Self>,
4218 ) {
4219 self.display_map.update(cx, |display_map, cx| {
4220 display_map.splice_inlays(to_remove, to_insert, cx)
4221 });
4222 cx.notify();
4223 }
4224
4225 fn trigger_on_type_formatting(
4226 &self,
4227 input: String,
4228 window: &mut Window,
4229 cx: &mut Context<Self>,
4230 ) -> Option<Task<Result<()>>> {
4231 if input.len() != 1 {
4232 return None;
4233 }
4234
4235 let project = self.project.as_ref()?;
4236 let position = self.selections.newest_anchor().head();
4237 let (buffer, buffer_position) = self
4238 .buffer
4239 .read(cx)
4240 .text_anchor_for_position(position, cx)?;
4241
4242 let settings = language_settings::language_settings(
4243 buffer
4244 .read(cx)
4245 .language_at(buffer_position)
4246 .map(|l| l.name()),
4247 buffer.read(cx).file(),
4248 cx,
4249 );
4250 if !settings.use_on_type_format {
4251 return None;
4252 }
4253
4254 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4255 // hence we do LSP request & edit on host side only — add formats to host's history.
4256 let push_to_lsp_host_history = true;
4257 // If this is not the host, append its history with new edits.
4258 let push_to_client_history = project.read(cx).is_via_collab();
4259
4260 let on_type_formatting = project.update(cx, |project, cx| {
4261 project.on_type_format(
4262 buffer.clone(),
4263 buffer_position,
4264 input,
4265 push_to_lsp_host_history,
4266 cx,
4267 )
4268 });
4269 Some(cx.spawn_in(window, async move |editor, cx| {
4270 if let Some(transaction) = on_type_formatting.await? {
4271 if push_to_client_history {
4272 buffer
4273 .update(cx, |buffer, _| {
4274 buffer.push_transaction(transaction, Instant::now());
4275 buffer.finalize_last_transaction();
4276 })
4277 .ok();
4278 }
4279 editor.update(cx, |editor, cx| {
4280 editor.refresh_document_highlights(cx);
4281 })?;
4282 }
4283 Ok(())
4284 }))
4285 }
4286
4287 pub fn show_word_completions(
4288 &mut self,
4289 _: &ShowWordCompletions,
4290 window: &mut Window,
4291 cx: &mut Context<Self>,
4292 ) {
4293 self.open_completions_menu(true, None, window, cx);
4294 }
4295
4296 pub fn show_completions(
4297 &mut self,
4298 options: &ShowCompletions,
4299 window: &mut Window,
4300 cx: &mut Context<Self>,
4301 ) {
4302 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4303 }
4304
4305 fn open_completions_menu(
4306 &mut self,
4307 ignore_completion_provider: bool,
4308 trigger: Option<&str>,
4309 window: &mut Window,
4310 cx: &mut Context<Self>,
4311 ) {
4312 if self.pending_rename.is_some() {
4313 return;
4314 }
4315 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4316 return;
4317 }
4318
4319 let position = self.selections.newest_anchor().head();
4320 if position.diff_base_anchor.is_some() {
4321 return;
4322 }
4323 let (buffer, buffer_position) =
4324 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4325 output
4326 } else {
4327 return;
4328 };
4329 let buffer_snapshot = buffer.read(cx).snapshot();
4330 let show_completion_documentation = buffer_snapshot
4331 .settings_at(buffer_position, cx)
4332 .show_completion_documentation;
4333
4334 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4335
4336 let trigger_kind = match trigger {
4337 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4338 CompletionTriggerKind::TRIGGER_CHARACTER
4339 }
4340 _ => CompletionTriggerKind::INVOKED,
4341 };
4342 let completion_context = CompletionContext {
4343 trigger_character: trigger.and_then(|trigger| {
4344 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4345 Some(String::from(trigger))
4346 } else {
4347 None
4348 }
4349 }),
4350 trigger_kind,
4351 };
4352
4353 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4354 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4355 let word_to_exclude = buffer_snapshot
4356 .text_for_range(old_range.clone())
4357 .collect::<String>();
4358 (
4359 buffer_snapshot.anchor_before(old_range.start)
4360 ..buffer_snapshot.anchor_after(old_range.end),
4361 Some(word_to_exclude),
4362 )
4363 } else {
4364 (buffer_position..buffer_position, None)
4365 };
4366
4367 let completion_settings = language_settings(
4368 buffer_snapshot
4369 .language_at(buffer_position)
4370 .map(|language| language.name()),
4371 buffer_snapshot.file(),
4372 cx,
4373 )
4374 .completions;
4375
4376 // The document can be large, so stay in reasonable bounds when searching for words,
4377 // otherwise completion pop-up might be slow to appear.
4378 const WORD_LOOKUP_ROWS: u32 = 5_000;
4379 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4380 let min_word_search = buffer_snapshot.clip_point(
4381 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4382 Bias::Left,
4383 );
4384 let max_word_search = buffer_snapshot.clip_point(
4385 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4386 Bias::Right,
4387 );
4388 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4389 ..buffer_snapshot.point_to_offset(max_word_search);
4390
4391 let provider = self
4392 .completion_provider
4393 .as_ref()
4394 .filter(|_| !ignore_completion_provider);
4395 let skip_digits = query
4396 .as_ref()
4397 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4398
4399 let (mut words, provided_completions) = match provider {
4400 Some(provider) => {
4401 let completions = provider.completions(
4402 position.excerpt_id,
4403 &buffer,
4404 buffer_position,
4405 completion_context,
4406 window,
4407 cx,
4408 );
4409
4410 let words = match completion_settings.words {
4411 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4412 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4413 .background_spawn(async move {
4414 buffer_snapshot.words_in_range(WordsQuery {
4415 fuzzy_contents: None,
4416 range: word_search_range,
4417 skip_digits,
4418 })
4419 }),
4420 };
4421
4422 (words, completions)
4423 }
4424 None => (
4425 cx.background_spawn(async move {
4426 buffer_snapshot.words_in_range(WordsQuery {
4427 fuzzy_contents: None,
4428 range: word_search_range,
4429 skip_digits,
4430 })
4431 }),
4432 Task::ready(Ok(None)),
4433 ),
4434 };
4435
4436 let sort_completions = provider
4437 .as_ref()
4438 .map_or(false, |provider| provider.sort_completions());
4439
4440 let filter_completions = provider
4441 .as_ref()
4442 .map_or(true, |provider| provider.filter_completions());
4443
4444 let id = post_inc(&mut self.next_completion_id);
4445 let task = cx.spawn_in(window, async move |editor, cx| {
4446 async move {
4447 editor.update(cx, |this, _| {
4448 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4449 })?;
4450
4451 let mut completions = Vec::new();
4452 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4453 completions.extend(provided_completions);
4454 if completion_settings.words == WordsCompletionMode::Fallback {
4455 words = Task::ready(BTreeMap::default());
4456 }
4457 }
4458
4459 let mut words = words.await;
4460 if let Some(word_to_exclude) = &word_to_exclude {
4461 words.remove(word_to_exclude);
4462 }
4463 for lsp_completion in &completions {
4464 words.remove(&lsp_completion.new_text);
4465 }
4466 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4467 replace_range: old_range.clone(),
4468 new_text: word.clone(),
4469 label: CodeLabel::plain(word, None),
4470 icon_path: None,
4471 documentation: None,
4472 source: CompletionSource::BufferWord {
4473 word_range,
4474 resolved: false,
4475 },
4476 insert_text_mode: Some(InsertTextMode::AS_IS),
4477 confirm: None,
4478 }));
4479
4480 let menu = if completions.is_empty() {
4481 None
4482 } else {
4483 let mut menu = CompletionsMenu::new(
4484 id,
4485 sort_completions,
4486 show_completion_documentation,
4487 ignore_completion_provider,
4488 position,
4489 buffer.clone(),
4490 completions.into(),
4491 );
4492
4493 menu.filter(
4494 if filter_completions {
4495 query.as_deref()
4496 } else {
4497 None
4498 },
4499 cx.background_executor().clone(),
4500 )
4501 .await;
4502
4503 menu.visible().then_some(menu)
4504 };
4505
4506 editor.update_in(cx, |editor, window, cx| {
4507 match editor.context_menu.borrow().as_ref() {
4508 None => {}
4509 Some(CodeContextMenu::Completions(prev_menu)) => {
4510 if prev_menu.id > id {
4511 return;
4512 }
4513 }
4514 _ => return,
4515 }
4516
4517 if editor.focus_handle.is_focused(window) && menu.is_some() {
4518 let mut menu = menu.unwrap();
4519 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4520
4521 *editor.context_menu.borrow_mut() =
4522 Some(CodeContextMenu::Completions(menu));
4523
4524 if editor.show_edit_predictions_in_menu() {
4525 editor.update_visible_inline_completion(window, cx);
4526 } else {
4527 editor.discard_inline_completion(false, cx);
4528 }
4529
4530 cx.notify();
4531 } else if editor.completion_tasks.len() <= 1 {
4532 // If there are no more completion tasks and the last menu was
4533 // empty, we should hide it.
4534 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4535 // If it was already hidden and we don't show inline
4536 // completions in the menu, we should also show the
4537 // inline-completion when available.
4538 if was_hidden && editor.show_edit_predictions_in_menu() {
4539 editor.update_visible_inline_completion(window, cx);
4540 }
4541 }
4542 })?;
4543
4544 anyhow::Ok(())
4545 }
4546 .log_err()
4547 .await
4548 });
4549
4550 self.completion_tasks.push((id, task));
4551 }
4552
4553 #[cfg(feature = "test-support")]
4554 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4555 let menu = self.context_menu.borrow();
4556 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4557 let completions = menu.completions.borrow();
4558 Some(completions.to_vec())
4559 } else {
4560 None
4561 }
4562 }
4563
4564 pub fn confirm_completion(
4565 &mut self,
4566 action: &ConfirmCompletion,
4567 window: &mut Window,
4568 cx: &mut Context<Self>,
4569 ) -> Option<Task<Result<()>>> {
4570 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4571 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4572 }
4573
4574 pub fn confirm_completion_insert(
4575 &mut self,
4576 _: &ConfirmCompletionInsert,
4577 window: &mut Window,
4578 cx: &mut Context<Self>,
4579 ) -> Option<Task<Result<()>>> {
4580 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4581 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4582 }
4583
4584 pub fn confirm_completion_replace(
4585 &mut self,
4586 _: &ConfirmCompletionReplace,
4587 window: &mut Window,
4588 cx: &mut Context<Self>,
4589 ) -> Option<Task<Result<()>>> {
4590 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4591 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4592 }
4593
4594 pub fn compose_completion(
4595 &mut self,
4596 action: &ComposeCompletion,
4597 window: &mut Window,
4598 cx: &mut Context<Self>,
4599 ) -> Option<Task<Result<()>>> {
4600 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4601 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4602 }
4603
4604 fn do_completion(
4605 &mut self,
4606 item_ix: Option<usize>,
4607 intent: CompletionIntent,
4608 window: &mut Window,
4609 cx: &mut Context<Editor>,
4610 ) -> Option<Task<Result<()>>> {
4611 use language::ToOffset as _;
4612
4613 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4614 else {
4615 return None;
4616 };
4617
4618 let candidate_id = {
4619 let entries = completions_menu.entries.borrow();
4620 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4621 if self.show_edit_predictions_in_menu() {
4622 self.discard_inline_completion(true, cx);
4623 }
4624 mat.candidate_id
4625 };
4626
4627 let buffer_handle = completions_menu.buffer;
4628 let completion = completions_menu
4629 .completions
4630 .borrow()
4631 .get(candidate_id)?
4632 .clone();
4633 cx.stop_propagation();
4634
4635 let snippet;
4636 let new_text;
4637 if completion.is_snippet() {
4638 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4639 new_text = snippet.as_ref().unwrap().text.clone();
4640 } else {
4641 snippet = None;
4642 new_text = completion.new_text.clone();
4643 };
4644 let selections = self.selections.all::<usize>(cx);
4645
4646 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4647 let buffer = buffer_handle.read(cx);
4648 let old_text = buffer
4649 .text_for_range(replace_range.clone())
4650 .collect::<String>();
4651
4652 let newest_selection = self.selections.newest_anchor();
4653 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4654 return None;
4655 }
4656
4657 let lookbehind = newest_selection
4658 .start
4659 .text_anchor
4660 .to_offset(buffer)
4661 .saturating_sub(replace_range.start);
4662 let lookahead = replace_range
4663 .end
4664 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4665 let mut common_prefix_len = 0;
4666 for (a, b) in old_text.chars().zip(new_text.chars()) {
4667 if a == b {
4668 common_prefix_len += a.len_utf8();
4669 } else {
4670 break;
4671 }
4672 }
4673
4674 let snapshot = self.buffer.read(cx).snapshot(cx);
4675 let mut range_to_replace: Option<Range<usize>> = None;
4676 let mut ranges = Vec::new();
4677 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4678 for selection in &selections {
4679 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4680 let start = selection.start.saturating_sub(lookbehind);
4681 let end = selection.end + lookahead;
4682 if selection.id == newest_selection.id {
4683 range_to_replace = Some(start + common_prefix_len..end);
4684 }
4685 ranges.push(start + common_prefix_len..end);
4686 } else {
4687 common_prefix_len = 0;
4688 ranges.clear();
4689 ranges.extend(selections.iter().map(|s| {
4690 if s.id == newest_selection.id {
4691 range_to_replace = Some(replace_range.clone());
4692 replace_range.clone()
4693 } else {
4694 s.start..s.end
4695 }
4696 }));
4697 break;
4698 }
4699 if !self.linked_edit_ranges.is_empty() {
4700 let start_anchor = snapshot.anchor_before(selection.head());
4701 let end_anchor = snapshot.anchor_after(selection.tail());
4702 if let Some(ranges) = self
4703 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4704 {
4705 for (buffer, edits) in ranges {
4706 linked_edits.entry(buffer.clone()).or_default().extend(
4707 edits
4708 .into_iter()
4709 .map(|range| (range, new_text[common_prefix_len..].to_owned())),
4710 );
4711 }
4712 }
4713 }
4714 }
4715 let text = &new_text[common_prefix_len..];
4716
4717 let utf16_range_to_replace = range_to_replace.map(|range| {
4718 let newest_selection = self.selections.newest::<OffsetUtf16>(cx).range();
4719 let selection_start_utf16 = newest_selection.start.0 as isize;
4720
4721 range.start.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16
4722 ..range.end.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16
4723 });
4724 cx.emit(EditorEvent::InputHandled {
4725 utf16_range_to_replace,
4726 text: text.into(),
4727 });
4728
4729 self.transact(window, cx, |this, window, cx| {
4730 if let Some(mut snippet) = snippet {
4731 snippet.text = text.to_string();
4732 for tabstop in snippet
4733 .tabstops
4734 .iter_mut()
4735 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4736 {
4737 tabstop.start -= common_prefix_len as isize;
4738 tabstop.end -= common_prefix_len as isize;
4739 }
4740
4741 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4742 } else {
4743 this.buffer.update(cx, |buffer, cx| {
4744 let edits = ranges.iter().map(|range| (range.clone(), text));
4745 let auto_indent = if completion.insert_text_mode == Some(InsertTextMode::AS_IS)
4746 {
4747 None
4748 } else {
4749 this.autoindent_mode.clone()
4750 };
4751 buffer.edit(edits, auto_indent, cx);
4752 });
4753 }
4754 for (buffer, edits) in linked_edits {
4755 buffer.update(cx, |buffer, cx| {
4756 let snapshot = buffer.snapshot();
4757 let edits = edits
4758 .into_iter()
4759 .map(|(range, text)| {
4760 use text::ToPoint as TP;
4761 let end_point = TP::to_point(&range.end, &snapshot);
4762 let start_point = TP::to_point(&range.start, &snapshot);
4763 (start_point..end_point, text)
4764 })
4765 .sorted_by_key(|(range, _)| range.start);
4766 buffer.edit(edits, None, cx);
4767 })
4768 }
4769
4770 this.refresh_inline_completion(true, false, window, cx);
4771 });
4772
4773 let show_new_completions_on_confirm = completion
4774 .confirm
4775 .as_ref()
4776 .map_or(false, |confirm| confirm(intent, window, cx));
4777 if show_new_completions_on_confirm {
4778 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4779 }
4780
4781 let provider = self.completion_provider.as_ref()?;
4782 drop(completion);
4783 let apply_edits = provider.apply_additional_edits_for_completion(
4784 buffer_handle,
4785 completions_menu.completions.clone(),
4786 candidate_id,
4787 true,
4788 cx,
4789 );
4790
4791 let editor_settings = EditorSettings::get_global(cx);
4792 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4793 // After the code completion is finished, users often want to know what signatures are needed.
4794 // so we should automatically call signature_help
4795 self.show_signature_help(&ShowSignatureHelp, window, cx);
4796 }
4797
4798 Some(cx.foreground_executor().spawn(async move {
4799 apply_edits.await?;
4800 Ok(())
4801 }))
4802 }
4803
4804 pub fn toggle_code_actions(
4805 &mut self,
4806 action: &ToggleCodeActions,
4807 window: &mut Window,
4808 cx: &mut Context<Self>,
4809 ) {
4810 let mut context_menu = self.context_menu.borrow_mut();
4811 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4812 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4813 // Toggle if we're selecting the same one
4814 *context_menu = None;
4815 cx.notify();
4816 return;
4817 } else {
4818 // Otherwise, clear it and start a new one
4819 *context_menu = None;
4820 cx.notify();
4821 }
4822 }
4823 drop(context_menu);
4824 let snapshot = self.snapshot(window, cx);
4825 let deployed_from_indicator = action.deployed_from_indicator;
4826 let mut task = self.code_actions_task.take();
4827 let action = action.clone();
4828 cx.spawn_in(window, async move |editor, cx| {
4829 while let Some(prev_task) = task {
4830 prev_task.await.log_err();
4831 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4832 }
4833
4834 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4835 if editor.focus_handle.is_focused(window) {
4836 let multibuffer_point = action
4837 .deployed_from_indicator
4838 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4839 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4840 let (buffer, buffer_row) = snapshot
4841 .buffer_snapshot
4842 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4843 .and_then(|(buffer_snapshot, range)| {
4844 editor
4845 .buffer
4846 .read(cx)
4847 .buffer(buffer_snapshot.remote_id())
4848 .map(|buffer| (buffer, range.start.row))
4849 })?;
4850 let (_, code_actions) = editor
4851 .available_code_actions
4852 .clone()
4853 .and_then(|(location, code_actions)| {
4854 let snapshot = location.buffer.read(cx).snapshot();
4855 let point_range = location.range.to_point(&snapshot);
4856 let point_range = point_range.start.row..=point_range.end.row;
4857 if point_range.contains(&buffer_row) {
4858 Some((location, code_actions))
4859 } else {
4860 None
4861 }
4862 })
4863 .unzip();
4864 let buffer_id = buffer.read(cx).remote_id();
4865 let tasks = editor
4866 .tasks
4867 .get(&(buffer_id, buffer_row))
4868 .map(|t| Arc::new(t.to_owned()));
4869 if tasks.is_none() && code_actions.is_none() {
4870 return None;
4871 }
4872
4873 editor.completion_tasks.clear();
4874 editor.discard_inline_completion(false, cx);
4875 let task_context =
4876 tasks
4877 .as_ref()
4878 .zip(editor.project.clone())
4879 .map(|(tasks, project)| {
4880 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4881 });
4882
4883 let debugger_flag = cx.has_flag::<Debugger>();
4884
4885 Some(cx.spawn_in(window, async move |editor, cx| {
4886 let task_context = match task_context {
4887 Some(task_context) => task_context.await,
4888 None => None,
4889 };
4890 let resolved_tasks =
4891 tasks.zip(task_context).map(|(tasks, task_context)| {
4892 Rc::new(ResolvedTasks {
4893 templates: tasks.resolve(&task_context).collect(),
4894 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4895 multibuffer_point.row,
4896 tasks.column,
4897 )),
4898 })
4899 });
4900 let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
4901 tasks
4902 .templates
4903 .iter()
4904 .filter(|task| {
4905 if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
4906 debugger_flag
4907 } else {
4908 true
4909 }
4910 })
4911 .count()
4912 == 1
4913 }) && code_actions
4914 .as_ref()
4915 .map_or(true, |actions| actions.is_empty());
4916 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4917 *editor.context_menu.borrow_mut() =
4918 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4919 buffer,
4920 actions: CodeActionContents {
4921 tasks: resolved_tasks,
4922 actions: code_actions,
4923 },
4924 selected_item: Default::default(),
4925 scroll_handle: UniformListScrollHandle::default(),
4926 deployed_from_indicator,
4927 }));
4928 if spawn_straight_away {
4929 if let Some(task) = editor.confirm_code_action(
4930 &ConfirmCodeAction { item_ix: Some(0) },
4931 window,
4932 cx,
4933 ) {
4934 cx.notify();
4935 return task;
4936 }
4937 }
4938 cx.notify();
4939 Task::ready(Ok(()))
4940 }) {
4941 task.await
4942 } else {
4943 Ok(())
4944 }
4945 }))
4946 } else {
4947 Some(Task::ready(Ok(())))
4948 }
4949 })?;
4950 if let Some(task) = spawned_test_task {
4951 task.await?;
4952 }
4953
4954 Ok::<_, anyhow::Error>(())
4955 })
4956 .detach_and_log_err(cx);
4957 }
4958
4959 pub fn confirm_code_action(
4960 &mut self,
4961 action: &ConfirmCodeAction,
4962 window: &mut Window,
4963 cx: &mut Context<Self>,
4964 ) -> Option<Task<Result<()>>> {
4965 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4966
4967 let actions_menu =
4968 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4969 menu
4970 } else {
4971 return None;
4972 };
4973
4974 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4975 let action = actions_menu.actions.get(action_ix)?;
4976 let title = action.label();
4977 let buffer = actions_menu.buffer;
4978 let workspace = self.workspace()?;
4979
4980 match action {
4981 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4982 match resolved_task.task_type() {
4983 task::TaskType::Script => workspace.update(cx, |workspace, cx| {
4984 workspace::tasks::schedule_resolved_task(
4985 workspace,
4986 task_source_kind,
4987 resolved_task,
4988 false,
4989 cx,
4990 );
4991
4992 Some(Task::ready(Ok(())))
4993 }),
4994 task::TaskType::Debug(debug_args) => {
4995 if debug_args.locator.is_some() {
4996 workspace.update(cx, |workspace, cx| {
4997 workspace::tasks::schedule_resolved_task(
4998 workspace,
4999 task_source_kind,
5000 resolved_task,
5001 false,
5002 cx,
5003 );
5004 });
5005
5006 return Some(Task::ready(Ok(())));
5007 }
5008
5009 if let Some(project) = self.project.as_ref() {
5010 project
5011 .update(cx, |project, cx| {
5012 project.start_debug_session(
5013 resolved_task.resolved_debug_adapter_config().unwrap(),
5014 cx,
5015 )
5016 })
5017 .detach_and_log_err(cx);
5018 Some(Task::ready(Ok(())))
5019 } else {
5020 Some(Task::ready(Ok(())))
5021 }
5022 }
5023 }
5024 }
5025 CodeActionsItem::CodeAction {
5026 excerpt_id,
5027 action,
5028 provider,
5029 } => {
5030 let apply_code_action =
5031 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5032 let workspace = workspace.downgrade();
5033 Some(cx.spawn_in(window, async move |editor, cx| {
5034 let project_transaction = apply_code_action.await?;
5035 Self::open_project_transaction(
5036 &editor,
5037 workspace,
5038 project_transaction,
5039 title,
5040 cx,
5041 )
5042 .await
5043 }))
5044 }
5045 }
5046 }
5047
5048 pub async fn open_project_transaction(
5049 this: &WeakEntity<Editor>,
5050 workspace: WeakEntity<Workspace>,
5051 transaction: ProjectTransaction,
5052 title: String,
5053 cx: &mut AsyncWindowContext,
5054 ) -> Result<()> {
5055 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5056 cx.update(|_, cx| {
5057 entries.sort_unstable_by_key(|(buffer, _)| {
5058 buffer.read(cx).file().map(|f| f.path().clone())
5059 });
5060 })?;
5061
5062 // If the project transaction's edits are all contained within this editor, then
5063 // avoid opening a new editor to display them.
5064
5065 if let Some((buffer, transaction)) = entries.first() {
5066 if entries.len() == 1 {
5067 let excerpt = this.update(cx, |editor, cx| {
5068 editor
5069 .buffer()
5070 .read(cx)
5071 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5072 })?;
5073 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5074 if excerpted_buffer == *buffer {
5075 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5076 let excerpt_range = excerpt_range.to_offset(buffer);
5077 buffer
5078 .edited_ranges_for_transaction::<usize>(transaction)
5079 .all(|range| {
5080 excerpt_range.start <= range.start
5081 && excerpt_range.end >= range.end
5082 })
5083 })?;
5084
5085 if all_edits_within_excerpt {
5086 return Ok(());
5087 }
5088 }
5089 }
5090 }
5091 } else {
5092 return Ok(());
5093 }
5094
5095 let mut ranges_to_highlight = Vec::new();
5096 let excerpt_buffer = cx.new(|cx| {
5097 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5098 for (buffer_handle, transaction) in &entries {
5099 let edited_ranges = buffer_handle
5100 .read(cx)
5101 .edited_ranges_for_transaction::<Point>(transaction)
5102 .collect::<Vec<_>>();
5103 let (ranges, _) = multibuffer.set_excerpts_for_path(
5104 PathKey::for_buffer(buffer_handle, cx),
5105 buffer_handle.clone(),
5106 edited_ranges,
5107 DEFAULT_MULTIBUFFER_CONTEXT,
5108 cx,
5109 );
5110
5111 ranges_to_highlight.extend(ranges);
5112 }
5113 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5114 multibuffer
5115 })?;
5116
5117 workspace.update_in(cx, |workspace, window, cx| {
5118 let project = workspace.project().clone();
5119 let editor =
5120 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5121 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5122 editor.update(cx, |editor, cx| {
5123 editor.highlight_background::<Self>(
5124 &ranges_to_highlight,
5125 |theme| theme.editor_highlighted_line_background,
5126 cx,
5127 );
5128 });
5129 })?;
5130
5131 Ok(())
5132 }
5133
5134 pub fn clear_code_action_providers(&mut self) {
5135 self.code_action_providers.clear();
5136 self.available_code_actions.take();
5137 }
5138
5139 pub fn add_code_action_provider(
5140 &mut self,
5141 provider: Rc<dyn CodeActionProvider>,
5142 window: &mut Window,
5143 cx: &mut Context<Self>,
5144 ) {
5145 if self
5146 .code_action_providers
5147 .iter()
5148 .any(|existing_provider| existing_provider.id() == provider.id())
5149 {
5150 return;
5151 }
5152
5153 self.code_action_providers.push(provider);
5154 self.refresh_code_actions(window, cx);
5155 }
5156
5157 pub fn remove_code_action_provider(
5158 &mut self,
5159 id: Arc<str>,
5160 window: &mut Window,
5161 cx: &mut Context<Self>,
5162 ) {
5163 self.code_action_providers
5164 .retain(|provider| provider.id() != id);
5165 self.refresh_code_actions(window, cx);
5166 }
5167
5168 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5169 let buffer = self.buffer.read(cx);
5170 let newest_selection = self.selections.newest_anchor().clone();
5171 if newest_selection.head().diff_base_anchor.is_some() {
5172 return None;
5173 }
5174 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
5175 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
5176 if start_buffer != end_buffer {
5177 return None;
5178 }
5179
5180 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5181 cx.background_executor()
5182 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5183 .await;
5184
5185 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5186 let providers = this.code_action_providers.clone();
5187 let tasks = this
5188 .code_action_providers
5189 .iter()
5190 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5191 .collect::<Vec<_>>();
5192 (providers, tasks)
5193 })?;
5194
5195 let mut actions = Vec::new();
5196 for (provider, provider_actions) in
5197 providers.into_iter().zip(future::join_all(tasks).await)
5198 {
5199 if let Some(provider_actions) = provider_actions.log_err() {
5200 actions.extend(provider_actions.into_iter().map(|action| {
5201 AvailableCodeAction {
5202 excerpt_id: newest_selection.start.excerpt_id,
5203 action,
5204 provider: provider.clone(),
5205 }
5206 }));
5207 }
5208 }
5209
5210 this.update(cx, |this, cx| {
5211 this.available_code_actions = if actions.is_empty() {
5212 None
5213 } else {
5214 Some((
5215 Location {
5216 buffer: start_buffer,
5217 range: start..end,
5218 },
5219 actions.into(),
5220 ))
5221 };
5222 cx.notify();
5223 })
5224 }));
5225 None
5226 }
5227
5228 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5229 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5230 self.show_git_blame_inline = false;
5231
5232 self.show_git_blame_inline_delay_task =
5233 Some(cx.spawn_in(window, async move |this, cx| {
5234 cx.background_executor().timer(delay).await;
5235
5236 this.update(cx, |this, cx| {
5237 this.show_git_blame_inline = true;
5238 cx.notify();
5239 })
5240 .log_err();
5241 }));
5242 }
5243 }
5244
5245 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5246 if self.pending_rename.is_some() {
5247 return None;
5248 }
5249
5250 let provider = self.semantics_provider.clone()?;
5251 let buffer = self.buffer.read(cx);
5252 let newest_selection = self.selections.newest_anchor().clone();
5253 let cursor_position = newest_selection.head();
5254 let (cursor_buffer, cursor_buffer_position) =
5255 buffer.text_anchor_for_position(cursor_position, cx)?;
5256 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5257 if cursor_buffer != tail_buffer {
5258 return None;
5259 }
5260 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5261 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5262 cx.background_executor()
5263 .timer(Duration::from_millis(debounce))
5264 .await;
5265
5266 let highlights = if let Some(highlights) = cx
5267 .update(|cx| {
5268 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5269 })
5270 .ok()
5271 .flatten()
5272 {
5273 highlights.await.log_err()
5274 } else {
5275 None
5276 };
5277
5278 if let Some(highlights) = highlights {
5279 this.update(cx, |this, cx| {
5280 if this.pending_rename.is_some() {
5281 return;
5282 }
5283
5284 let buffer_id = cursor_position.buffer_id;
5285 let buffer = this.buffer.read(cx);
5286 if !buffer
5287 .text_anchor_for_position(cursor_position, cx)
5288 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5289 {
5290 return;
5291 }
5292
5293 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5294 let mut write_ranges = Vec::new();
5295 let mut read_ranges = Vec::new();
5296 for highlight in highlights {
5297 for (excerpt_id, excerpt_range) in
5298 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5299 {
5300 let start = highlight
5301 .range
5302 .start
5303 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5304 let end = highlight
5305 .range
5306 .end
5307 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5308 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5309 continue;
5310 }
5311
5312 let range = Anchor {
5313 buffer_id,
5314 excerpt_id,
5315 text_anchor: start,
5316 diff_base_anchor: None,
5317 }..Anchor {
5318 buffer_id,
5319 excerpt_id,
5320 text_anchor: end,
5321 diff_base_anchor: None,
5322 };
5323 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5324 write_ranges.push(range);
5325 } else {
5326 read_ranges.push(range);
5327 }
5328 }
5329 }
5330
5331 this.highlight_background::<DocumentHighlightRead>(
5332 &read_ranges,
5333 |theme| theme.editor_document_highlight_read_background,
5334 cx,
5335 );
5336 this.highlight_background::<DocumentHighlightWrite>(
5337 &write_ranges,
5338 |theme| theme.editor_document_highlight_write_background,
5339 cx,
5340 );
5341 cx.notify();
5342 })
5343 .log_err();
5344 }
5345 }));
5346 None
5347 }
5348
5349 pub fn refresh_selected_text_highlights(
5350 &mut self,
5351 window: &mut Window,
5352 cx: &mut Context<Editor>,
5353 ) {
5354 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5355 return;
5356 }
5357 self.selection_highlight_task.take();
5358 if !EditorSettings::get_global(cx).selection_highlight {
5359 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5360 return;
5361 }
5362 if self.selections.count() != 1 || self.selections.line_mode {
5363 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5364 return;
5365 }
5366 let selection = self.selections.newest::<Point>(cx);
5367 if selection.is_empty() || selection.start.row != selection.end.row {
5368 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5369 return;
5370 }
5371 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5372 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5373 cx.background_executor()
5374 .timer(Duration::from_millis(debounce))
5375 .await;
5376 let Some(Some(matches_task)) = editor
5377 .update_in(cx, |editor, _, cx| {
5378 if editor.selections.count() != 1 || editor.selections.line_mode {
5379 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5380 return None;
5381 }
5382 let selection = editor.selections.newest::<Point>(cx);
5383 if selection.is_empty() || selection.start.row != selection.end.row {
5384 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5385 return None;
5386 }
5387 let buffer = editor.buffer().read(cx).snapshot(cx);
5388 let query = buffer.text_for_range(selection.range()).collect::<String>();
5389 if query.trim().is_empty() {
5390 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5391 return None;
5392 }
5393 Some(cx.background_spawn(async move {
5394 let mut ranges = Vec::new();
5395 let selection_anchors = selection.range().to_anchors(&buffer);
5396 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5397 for (search_buffer, search_range, excerpt_id) in
5398 buffer.range_to_buffer_ranges(range)
5399 {
5400 ranges.extend(
5401 project::search::SearchQuery::text(
5402 query.clone(),
5403 false,
5404 false,
5405 false,
5406 Default::default(),
5407 Default::default(),
5408 None,
5409 )
5410 .unwrap()
5411 .search(search_buffer, Some(search_range.clone()))
5412 .await
5413 .into_iter()
5414 .filter_map(
5415 |match_range| {
5416 let start = search_buffer.anchor_after(
5417 search_range.start + match_range.start,
5418 );
5419 let end = search_buffer.anchor_before(
5420 search_range.start + match_range.end,
5421 );
5422 let range = Anchor::range_in_buffer(
5423 excerpt_id,
5424 search_buffer.remote_id(),
5425 start..end,
5426 );
5427 (range != selection_anchors).then_some(range)
5428 },
5429 ),
5430 );
5431 }
5432 }
5433 ranges
5434 }))
5435 })
5436 .log_err()
5437 else {
5438 return;
5439 };
5440 let matches = matches_task.await;
5441 editor
5442 .update_in(cx, |editor, _, cx| {
5443 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5444 if !matches.is_empty() {
5445 editor.highlight_background::<SelectedTextHighlight>(
5446 &matches,
5447 |theme| theme.editor_document_highlight_bracket_background,
5448 cx,
5449 )
5450 }
5451 })
5452 .log_err();
5453 }));
5454 }
5455
5456 pub fn refresh_inline_completion(
5457 &mut self,
5458 debounce: bool,
5459 user_requested: bool,
5460 window: &mut Window,
5461 cx: &mut Context<Self>,
5462 ) -> Option<()> {
5463 let provider = self.edit_prediction_provider()?;
5464 let cursor = self.selections.newest_anchor().head();
5465 let (buffer, cursor_buffer_position) =
5466 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5467
5468 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5469 self.discard_inline_completion(false, cx);
5470 return None;
5471 }
5472
5473 if !user_requested
5474 && (!self.should_show_edit_predictions()
5475 || !self.is_focused(window)
5476 || buffer.read(cx).is_empty())
5477 {
5478 self.discard_inline_completion(false, cx);
5479 return None;
5480 }
5481
5482 self.update_visible_inline_completion(window, cx);
5483 provider.refresh(
5484 self.project.clone(),
5485 buffer,
5486 cursor_buffer_position,
5487 debounce,
5488 cx,
5489 );
5490 Some(())
5491 }
5492
5493 fn show_edit_predictions_in_menu(&self) -> bool {
5494 match self.edit_prediction_settings {
5495 EditPredictionSettings::Disabled => false,
5496 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5497 }
5498 }
5499
5500 pub fn edit_predictions_enabled(&self) -> bool {
5501 match self.edit_prediction_settings {
5502 EditPredictionSettings::Disabled => false,
5503 EditPredictionSettings::Enabled { .. } => true,
5504 }
5505 }
5506
5507 fn edit_prediction_requires_modifier(&self) -> bool {
5508 match self.edit_prediction_settings {
5509 EditPredictionSettings::Disabled => false,
5510 EditPredictionSettings::Enabled {
5511 preview_requires_modifier,
5512 ..
5513 } => preview_requires_modifier,
5514 }
5515 }
5516
5517 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5518 if self.edit_prediction_provider.is_none() {
5519 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5520 } else {
5521 let selection = self.selections.newest_anchor();
5522 let cursor = selection.head();
5523
5524 if let Some((buffer, cursor_buffer_position)) =
5525 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5526 {
5527 self.edit_prediction_settings =
5528 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5529 }
5530 }
5531 }
5532
5533 fn edit_prediction_settings_at_position(
5534 &self,
5535 buffer: &Entity<Buffer>,
5536 buffer_position: language::Anchor,
5537 cx: &App,
5538 ) -> EditPredictionSettings {
5539 if self.mode != EditorMode::Full
5540 || !self.show_inline_completions_override.unwrap_or(true)
5541 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5542 {
5543 return EditPredictionSettings::Disabled;
5544 }
5545
5546 let buffer = buffer.read(cx);
5547
5548 let file = buffer.file();
5549
5550 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5551 return EditPredictionSettings::Disabled;
5552 };
5553
5554 let by_provider = matches!(
5555 self.menu_inline_completions_policy,
5556 MenuInlineCompletionsPolicy::ByProvider
5557 );
5558
5559 let show_in_menu = by_provider
5560 && self
5561 .edit_prediction_provider
5562 .as_ref()
5563 .map_or(false, |provider| {
5564 provider.provider.show_completions_in_menu()
5565 });
5566
5567 let preview_requires_modifier =
5568 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5569
5570 EditPredictionSettings::Enabled {
5571 show_in_menu,
5572 preview_requires_modifier,
5573 }
5574 }
5575
5576 fn should_show_edit_predictions(&self) -> bool {
5577 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5578 }
5579
5580 pub fn edit_prediction_preview_is_active(&self) -> bool {
5581 matches!(
5582 self.edit_prediction_preview,
5583 EditPredictionPreview::Active { .. }
5584 )
5585 }
5586
5587 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5588 let cursor = self.selections.newest_anchor().head();
5589 if let Some((buffer, cursor_position)) =
5590 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5591 {
5592 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5593 } else {
5594 false
5595 }
5596 }
5597
5598 fn edit_predictions_enabled_in_buffer(
5599 &self,
5600 buffer: &Entity<Buffer>,
5601 buffer_position: language::Anchor,
5602 cx: &App,
5603 ) -> bool {
5604 maybe!({
5605 if self.read_only(cx) {
5606 return Some(false);
5607 }
5608 let provider = self.edit_prediction_provider()?;
5609 if !provider.is_enabled(&buffer, buffer_position, cx) {
5610 return Some(false);
5611 }
5612 let buffer = buffer.read(cx);
5613 let Some(file) = buffer.file() else {
5614 return Some(true);
5615 };
5616 let settings = all_language_settings(Some(file), cx);
5617 Some(settings.edit_predictions_enabled_for_file(file, cx))
5618 })
5619 .unwrap_or(false)
5620 }
5621
5622 fn cycle_inline_completion(
5623 &mut self,
5624 direction: Direction,
5625 window: &mut Window,
5626 cx: &mut Context<Self>,
5627 ) -> Option<()> {
5628 let provider = self.edit_prediction_provider()?;
5629 let cursor = self.selections.newest_anchor().head();
5630 let (buffer, cursor_buffer_position) =
5631 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5632 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5633 return None;
5634 }
5635
5636 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5637 self.update_visible_inline_completion(window, cx);
5638
5639 Some(())
5640 }
5641
5642 pub fn show_inline_completion(
5643 &mut self,
5644 _: &ShowEditPrediction,
5645 window: &mut Window,
5646 cx: &mut Context<Self>,
5647 ) {
5648 if !self.has_active_inline_completion() {
5649 self.refresh_inline_completion(false, true, window, cx);
5650 return;
5651 }
5652
5653 self.update_visible_inline_completion(window, cx);
5654 }
5655
5656 pub fn display_cursor_names(
5657 &mut self,
5658 _: &DisplayCursorNames,
5659 window: &mut Window,
5660 cx: &mut Context<Self>,
5661 ) {
5662 self.show_cursor_names(window, cx);
5663 }
5664
5665 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5666 self.show_cursor_names = true;
5667 cx.notify();
5668 cx.spawn_in(window, async move |this, cx| {
5669 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5670 this.update(cx, |this, cx| {
5671 this.show_cursor_names = false;
5672 cx.notify()
5673 })
5674 .ok()
5675 })
5676 .detach();
5677 }
5678
5679 pub fn next_edit_prediction(
5680 &mut self,
5681 _: &NextEditPrediction,
5682 window: &mut Window,
5683 cx: &mut Context<Self>,
5684 ) {
5685 if self.has_active_inline_completion() {
5686 self.cycle_inline_completion(Direction::Next, window, cx);
5687 } else {
5688 let is_copilot_disabled = self
5689 .refresh_inline_completion(false, true, window, cx)
5690 .is_none();
5691 if is_copilot_disabled {
5692 cx.propagate();
5693 }
5694 }
5695 }
5696
5697 pub fn previous_edit_prediction(
5698 &mut self,
5699 _: &PreviousEditPrediction,
5700 window: &mut Window,
5701 cx: &mut Context<Self>,
5702 ) {
5703 if self.has_active_inline_completion() {
5704 self.cycle_inline_completion(Direction::Prev, window, cx);
5705 } else {
5706 let is_copilot_disabled = self
5707 .refresh_inline_completion(false, true, window, cx)
5708 .is_none();
5709 if is_copilot_disabled {
5710 cx.propagate();
5711 }
5712 }
5713 }
5714
5715 pub fn accept_edit_prediction(
5716 &mut self,
5717 _: &AcceptEditPrediction,
5718 window: &mut Window,
5719 cx: &mut Context<Self>,
5720 ) {
5721 if self.show_edit_predictions_in_menu() {
5722 self.hide_context_menu(window, cx);
5723 }
5724
5725 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5726 return;
5727 };
5728
5729 self.report_inline_completion_event(
5730 active_inline_completion.completion_id.clone(),
5731 true,
5732 cx,
5733 );
5734
5735 match &active_inline_completion.completion {
5736 InlineCompletion::Move { target, .. } => {
5737 let target = *target;
5738
5739 if let Some(position_map) = &self.last_position_map {
5740 if position_map
5741 .visible_row_range
5742 .contains(&target.to_display_point(&position_map.snapshot).row())
5743 || !self.edit_prediction_requires_modifier()
5744 {
5745 self.unfold_ranges(&[target..target], true, false, cx);
5746 // Note that this is also done in vim's handler of the Tab action.
5747 self.change_selections(
5748 Some(Autoscroll::newest()),
5749 window,
5750 cx,
5751 |selections| {
5752 selections.select_anchor_ranges([target..target]);
5753 },
5754 );
5755 self.clear_row_highlights::<EditPredictionPreview>();
5756
5757 self.edit_prediction_preview
5758 .set_previous_scroll_position(None);
5759 } else {
5760 self.edit_prediction_preview
5761 .set_previous_scroll_position(Some(
5762 position_map.snapshot.scroll_anchor,
5763 ));
5764
5765 self.highlight_rows::<EditPredictionPreview>(
5766 target..target,
5767 cx.theme().colors().editor_highlighted_line_background,
5768 true,
5769 cx,
5770 );
5771 self.request_autoscroll(Autoscroll::fit(), cx);
5772 }
5773 }
5774 }
5775 InlineCompletion::Edit { edits, .. } => {
5776 if let Some(provider) = self.edit_prediction_provider() {
5777 provider.accept(cx);
5778 }
5779
5780 let snapshot = self.buffer.read(cx).snapshot(cx);
5781 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5782
5783 self.buffer.update(cx, |buffer, cx| {
5784 buffer.edit(edits.iter().cloned(), None, cx)
5785 });
5786
5787 self.change_selections(None, window, cx, |s| {
5788 s.select_anchor_ranges([last_edit_end..last_edit_end])
5789 });
5790
5791 self.update_visible_inline_completion(window, cx);
5792 if self.active_inline_completion.is_none() {
5793 self.refresh_inline_completion(true, true, window, cx);
5794 }
5795
5796 cx.notify();
5797 }
5798 }
5799
5800 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5801 }
5802
5803 pub fn accept_partial_inline_completion(
5804 &mut self,
5805 _: &AcceptPartialEditPrediction,
5806 window: &mut Window,
5807 cx: &mut Context<Self>,
5808 ) {
5809 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5810 return;
5811 };
5812 if self.selections.count() != 1 {
5813 return;
5814 }
5815
5816 self.report_inline_completion_event(
5817 active_inline_completion.completion_id.clone(),
5818 true,
5819 cx,
5820 );
5821
5822 match &active_inline_completion.completion {
5823 InlineCompletion::Move { target, .. } => {
5824 let target = *target;
5825 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5826 selections.select_anchor_ranges([target..target]);
5827 });
5828 }
5829 InlineCompletion::Edit { edits, .. } => {
5830 // Find an insertion that starts at the cursor position.
5831 let snapshot = self.buffer.read(cx).snapshot(cx);
5832 let cursor_offset = self.selections.newest::<usize>(cx).head();
5833 let insertion = edits.iter().find_map(|(range, text)| {
5834 let range = range.to_offset(&snapshot);
5835 if range.is_empty() && range.start == cursor_offset {
5836 Some(text)
5837 } else {
5838 None
5839 }
5840 });
5841
5842 if let Some(text) = insertion {
5843 let mut partial_completion = text
5844 .chars()
5845 .by_ref()
5846 .take_while(|c| c.is_alphabetic())
5847 .collect::<String>();
5848 if partial_completion.is_empty() {
5849 partial_completion = text
5850 .chars()
5851 .by_ref()
5852 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5853 .collect::<String>();
5854 }
5855
5856 cx.emit(EditorEvent::InputHandled {
5857 utf16_range_to_replace: None,
5858 text: partial_completion.clone().into(),
5859 });
5860
5861 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5862
5863 self.refresh_inline_completion(true, true, window, cx);
5864 cx.notify();
5865 } else {
5866 self.accept_edit_prediction(&Default::default(), window, cx);
5867 }
5868 }
5869 }
5870 }
5871
5872 fn discard_inline_completion(
5873 &mut self,
5874 should_report_inline_completion_event: bool,
5875 cx: &mut Context<Self>,
5876 ) -> bool {
5877 if should_report_inline_completion_event {
5878 let completion_id = self
5879 .active_inline_completion
5880 .as_ref()
5881 .and_then(|active_completion| active_completion.completion_id.clone());
5882
5883 self.report_inline_completion_event(completion_id, false, cx);
5884 }
5885
5886 if let Some(provider) = self.edit_prediction_provider() {
5887 provider.discard(cx);
5888 }
5889
5890 self.take_active_inline_completion(cx)
5891 }
5892
5893 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5894 let Some(provider) = self.edit_prediction_provider() else {
5895 return;
5896 };
5897
5898 let Some((_, buffer, _)) = self
5899 .buffer
5900 .read(cx)
5901 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5902 else {
5903 return;
5904 };
5905
5906 let extension = buffer
5907 .read(cx)
5908 .file()
5909 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5910
5911 let event_type = match accepted {
5912 true => "Edit Prediction Accepted",
5913 false => "Edit Prediction Discarded",
5914 };
5915 telemetry::event!(
5916 event_type,
5917 provider = provider.name(),
5918 prediction_id = id,
5919 suggestion_accepted = accepted,
5920 file_extension = extension,
5921 );
5922 }
5923
5924 pub fn has_active_inline_completion(&self) -> bool {
5925 self.active_inline_completion.is_some()
5926 }
5927
5928 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5929 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5930 return false;
5931 };
5932
5933 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5934 self.clear_highlights::<InlineCompletionHighlight>(cx);
5935 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5936 true
5937 }
5938
5939 /// Returns true when we're displaying the edit prediction popover below the cursor
5940 /// like we are not previewing and the LSP autocomplete menu is visible
5941 /// or we are in `when_holding_modifier` mode.
5942 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5943 if self.edit_prediction_preview_is_active()
5944 || !self.show_edit_predictions_in_menu()
5945 || !self.edit_predictions_enabled()
5946 {
5947 return false;
5948 }
5949
5950 if self.has_visible_completions_menu() {
5951 return true;
5952 }
5953
5954 has_completion && self.edit_prediction_requires_modifier()
5955 }
5956
5957 fn handle_modifiers_changed(
5958 &mut self,
5959 modifiers: Modifiers,
5960 position_map: &PositionMap,
5961 window: &mut Window,
5962 cx: &mut Context<Self>,
5963 ) {
5964 if self.show_edit_predictions_in_menu() {
5965 self.update_edit_prediction_preview(&modifiers, window, cx);
5966 }
5967
5968 self.update_selection_mode(&modifiers, position_map, window, cx);
5969
5970 let mouse_position = window.mouse_position();
5971 if !position_map.text_hitbox.is_hovered(window) {
5972 return;
5973 }
5974
5975 self.update_hovered_link(
5976 position_map.point_for_position(mouse_position),
5977 &position_map.snapshot,
5978 modifiers,
5979 window,
5980 cx,
5981 )
5982 }
5983
5984 fn update_selection_mode(
5985 &mut self,
5986 modifiers: &Modifiers,
5987 position_map: &PositionMap,
5988 window: &mut Window,
5989 cx: &mut Context<Self>,
5990 ) {
5991 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
5992 return;
5993 }
5994
5995 let mouse_position = window.mouse_position();
5996 let point_for_position = position_map.point_for_position(mouse_position);
5997 let position = point_for_position.previous_valid;
5998
5999 self.select(
6000 SelectPhase::BeginColumnar {
6001 position,
6002 reset: false,
6003 goal_column: point_for_position.exact_unclipped.column(),
6004 },
6005 window,
6006 cx,
6007 );
6008 }
6009
6010 fn update_edit_prediction_preview(
6011 &mut self,
6012 modifiers: &Modifiers,
6013 window: &mut Window,
6014 cx: &mut Context<Self>,
6015 ) {
6016 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6017 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6018 return;
6019 };
6020
6021 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6022 if matches!(
6023 self.edit_prediction_preview,
6024 EditPredictionPreview::Inactive { .. }
6025 ) {
6026 self.edit_prediction_preview = EditPredictionPreview::Active {
6027 previous_scroll_position: None,
6028 since: Instant::now(),
6029 };
6030
6031 self.update_visible_inline_completion(window, cx);
6032 cx.notify();
6033 }
6034 } else if let EditPredictionPreview::Active {
6035 previous_scroll_position,
6036 since,
6037 } = self.edit_prediction_preview
6038 {
6039 if let (Some(previous_scroll_position), Some(position_map)) =
6040 (previous_scroll_position, self.last_position_map.as_ref())
6041 {
6042 self.set_scroll_position(
6043 previous_scroll_position
6044 .scroll_position(&position_map.snapshot.display_snapshot),
6045 window,
6046 cx,
6047 );
6048 }
6049
6050 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6051 released_too_fast: since.elapsed() < Duration::from_millis(200),
6052 };
6053 self.clear_row_highlights::<EditPredictionPreview>();
6054 self.update_visible_inline_completion(window, cx);
6055 cx.notify();
6056 }
6057 }
6058
6059 fn update_visible_inline_completion(
6060 &mut self,
6061 _window: &mut Window,
6062 cx: &mut Context<Self>,
6063 ) -> Option<()> {
6064 let selection = self.selections.newest_anchor();
6065 let cursor = selection.head();
6066 let multibuffer = self.buffer.read(cx).snapshot(cx);
6067 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6068 let excerpt_id = cursor.excerpt_id;
6069
6070 let show_in_menu = self.show_edit_predictions_in_menu();
6071 let completions_menu_has_precedence = !show_in_menu
6072 && (self.context_menu.borrow().is_some()
6073 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6074
6075 if completions_menu_has_precedence
6076 || !offset_selection.is_empty()
6077 || self
6078 .active_inline_completion
6079 .as_ref()
6080 .map_or(false, |completion| {
6081 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6082 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6083 !invalidation_range.contains(&offset_selection.head())
6084 })
6085 {
6086 self.discard_inline_completion(false, cx);
6087 return None;
6088 }
6089
6090 self.take_active_inline_completion(cx);
6091 let Some(provider) = self.edit_prediction_provider() else {
6092 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6093 return None;
6094 };
6095
6096 let (buffer, cursor_buffer_position) =
6097 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6098
6099 self.edit_prediction_settings =
6100 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6101
6102 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6103
6104 if self.edit_prediction_indent_conflict {
6105 let cursor_point = cursor.to_point(&multibuffer);
6106
6107 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6108
6109 if let Some((_, indent)) = indents.iter().next() {
6110 if indent.len == cursor_point.column {
6111 self.edit_prediction_indent_conflict = false;
6112 }
6113 }
6114 }
6115
6116 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6117 let edits = inline_completion
6118 .edits
6119 .into_iter()
6120 .flat_map(|(range, new_text)| {
6121 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6122 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6123 Some((start..end, new_text))
6124 })
6125 .collect::<Vec<_>>();
6126 if edits.is_empty() {
6127 return None;
6128 }
6129
6130 let first_edit_start = edits.first().unwrap().0.start;
6131 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6132 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6133
6134 let last_edit_end = edits.last().unwrap().0.end;
6135 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6136 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6137
6138 let cursor_row = cursor.to_point(&multibuffer).row;
6139
6140 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6141
6142 let mut inlay_ids = Vec::new();
6143 let invalidation_row_range;
6144 let move_invalidation_row_range = if cursor_row < edit_start_row {
6145 Some(cursor_row..edit_end_row)
6146 } else if cursor_row > edit_end_row {
6147 Some(edit_start_row..cursor_row)
6148 } else {
6149 None
6150 };
6151 let is_move =
6152 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6153 let completion = if is_move {
6154 invalidation_row_range =
6155 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6156 let target = first_edit_start;
6157 InlineCompletion::Move { target, snapshot }
6158 } else {
6159 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6160 && !self.inline_completions_hidden_for_vim_mode;
6161
6162 if show_completions_in_buffer {
6163 if edits
6164 .iter()
6165 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6166 {
6167 let mut inlays = Vec::new();
6168 for (range, new_text) in &edits {
6169 let inlay = Inlay::inline_completion(
6170 post_inc(&mut self.next_inlay_id),
6171 range.start,
6172 new_text.as_str(),
6173 );
6174 inlay_ids.push(inlay.id);
6175 inlays.push(inlay);
6176 }
6177
6178 self.splice_inlays(&[], inlays, cx);
6179 } else {
6180 let background_color = cx.theme().status().deleted_background;
6181 self.highlight_text::<InlineCompletionHighlight>(
6182 edits.iter().map(|(range, _)| range.clone()).collect(),
6183 HighlightStyle {
6184 background_color: Some(background_color),
6185 ..Default::default()
6186 },
6187 cx,
6188 );
6189 }
6190 }
6191
6192 invalidation_row_range = edit_start_row..edit_end_row;
6193
6194 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6195 if provider.show_tab_accept_marker() {
6196 EditDisplayMode::TabAccept
6197 } else {
6198 EditDisplayMode::Inline
6199 }
6200 } else {
6201 EditDisplayMode::DiffPopover
6202 };
6203
6204 InlineCompletion::Edit {
6205 edits,
6206 edit_preview: inline_completion.edit_preview,
6207 display_mode,
6208 snapshot,
6209 }
6210 };
6211
6212 let invalidation_range = multibuffer
6213 .anchor_before(Point::new(invalidation_row_range.start, 0))
6214 ..multibuffer.anchor_after(Point::new(
6215 invalidation_row_range.end,
6216 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6217 ));
6218
6219 self.stale_inline_completion_in_menu = None;
6220 self.active_inline_completion = Some(InlineCompletionState {
6221 inlay_ids,
6222 completion,
6223 completion_id: inline_completion.id,
6224 invalidation_range,
6225 });
6226
6227 cx.notify();
6228
6229 Some(())
6230 }
6231
6232 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6233 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6234 }
6235
6236 fn render_code_actions_indicator(
6237 &self,
6238 _style: &EditorStyle,
6239 row: DisplayRow,
6240 is_active: bool,
6241 breakpoint: Option<&(Anchor, Breakpoint)>,
6242 cx: &mut Context<Self>,
6243 ) -> Option<IconButton> {
6244 let color = Color::Muted;
6245 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6246 let show_tooltip = !self.context_menu_visible();
6247
6248 if self.available_code_actions.is_some() {
6249 Some(
6250 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6251 .shape(ui::IconButtonShape::Square)
6252 .icon_size(IconSize::XSmall)
6253 .icon_color(color)
6254 .toggle_state(is_active)
6255 .when(show_tooltip, |this| {
6256 this.tooltip({
6257 let focus_handle = self.focus_handle.clone();
6258 move |window, cx| {
6259 Tooltip::for_action_in(
6260 "Toggle Code Actions",
6261 &ToggleCodeActions {
6262 deployed_from_indicator: None,
6263 },
6264 &focus_handle,
6265 window,
6266 cx,
6267 )
6268 }
6269 })
6270 })
6271 .on_click(cx.listener(move |editor, _e, window, cx| {
6272 window.focus(&editor.focus_handle(cx));
6273 editor.toggle_code_actions(
6274 &ToggleCodeActions {
6275 deployed_from_indicator: Some(row),
6276 },
6277 window,
6278 cx,
6279 );
6280 }))
6281 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6282 editor.set_breakpoint_context_menu(
6283 row,
6284 position,
6285 event.down.position,
6286 window,
6287 cx,
6288 );
6289 })),
6290 )
6291 } else {
6292 None
6293 }
6294 }
6295
6296 fn clear_tasks(&mut self) {
6297 self.tasks.clear()
6298 }
6299
6300 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6301 if self.tasks.insert(key, value).is_some() {
6302 // This case should hopefully be rare, but just in case...
6303 log::error!(
6304 "multiple different run targets found on a single line, only the last target will be rendered"
6305 )
6306 }
6307 }
6308
6309 /// Get all display points of breakpoints that will be rendered within editor
6310 ///
6311 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6312 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6313 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6314 fn active_breakpoints(
6315 &self,
6316 range: Range<DisplayRow>,
6317 window: &mut Window,
6318 cx: &mut Context<Self>,
6319 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6320 let mut breakpoint_display_points = HashMap::default();
6321
6322 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6323 return breakpoint_display_points;
6324 };
6325
6326 let snapshot = self.snapshot(window, cx);
6327
6328 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6329 let Some(project) = self.project.as_ref() else {
6330 return breakpoint_display_points;
6331 };
6332
6333 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6334 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6335
6336 for (buffer_snapshot, range, excerpt_id) in
6337 multi_buffer_snapshot.range_to_buffer_ranges(range)
6338 {
6339 let Some(buffer) = project.read_with(cx, |this, cx| {
6340 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6341 }) else {
6342 continue;
6343 };
6344 let breakpoints = breakpoint_store.read(cx).breakpoints(
6345 &buffer,
6346 Some(
6347 buffer_snapshot.anchor_before(range.start)
6348 ..buffer_snapshot.anchor_after(range.end),
6349 ),
6350 buffer_snapshot,
6351 cx,
6352 );
6353 for (anchor, breakpoint) in breakpoints {
6354 let multi_buffer_anchor =
6355 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6356 let position = multi_buffer_anchor
6357 .to_point(&multi_buffer_snapshot)
6358 .to_display_point(&snapshot);
6359
6360 breakpoint_display_points
6361 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6362 }
6363 }
6364
6365 breakpoint_display_points
6366 }
6367
6368 fn breakpoint_context_menu(
6369 &self,
6370 anchor: Anchor,
6371 window: &mut Window,
6372 cx: &mut Context<Self>,
6373 ) -> Entity<ui::ContextMenu> {
6374 let weak_editor = cx.weak_entity();
6375 let focus_handle = self.focus_handle(cx);
6376
6377 let row = self
6378 .buffer
6379 .read(cx)
6380 .snapshot(cx)
6381 .summary_for_anchor::<Point>(&anchor)
6382 .row;
6383
6384 let breakpoint = self
6385 .breakpoint_at_row(row, window, cx)
6386 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6387
6388 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6389 "Edit Log Breakpoint"
6390 } else {
6391 "Set Log Breakpoint"
6392 };
6393
6394 let condition_breakpoint_msg = if breakpoint
6395 .as_ref()
6396 .is_some_and(|bp| bp.1.condition.is_some())
6397 {
6398 "Edit Condition Breakpoint"
6399 } else {
6400 "Set Condition Breakpoint"
6401 };
6402
6403 let hit_condition_breakpoint_msg = if breakpoint
6404 .as_ref()
6405 .is_some_and(|bp| bp.1.hit_condition.is_some())
6406 {
6407 "Edit Hit Condition Breakpoint"
6408 } else {
6409 "Set Hit Condition Breakpoint"
6410 };
6411
6412 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6413 "Unset Breakpoint"
6414 } else {
6415 "Set Breakpoint"
6416 };
6417
6418 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6419 BreakpointState::Enabled => Some("Disable"),
6420 BreakpointState::Disabled => Some("Enable"),
6421 });
6422
6423 let (anchor, breakpoint) =
6424 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6425
6426 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6427 menu.on_blur_subscription(Subscription::new(|| {}))
6428 .context(focus_handle)
6429 .when_some(toggle_state_msg, |this, msg| {
6430 this.entry(msg, None, {
6431 let weak_editor = weak_editor.clone();
6432 let breakpoint = breakpoint.clone();
6433 move |_window, cx| {
6434 weak_editor
6435 .update(cx, |this, cx| {
6436 this.edit_breakpoint_at_anchor(
6437 anchor,
6438 breakpoint.as_ref().clone(),
6439 BreakpointEditAction::InvertState,
6440 cx,
6441 );
6442 })
6443 .log_err();
6444 }
6445 })
6446 })
6447 .entry(set_breakpoint_msg, None, {
6448 let weak_editor = weak_editor.clone();
6449 let breakpoint = breakpoint.clone();
6450 move |_window, cx| {
6451 weak_editor
6452 .update(cx, |this, cx| {
6453 this.edit_breakpoint_at_anchor(
6454 anchor,
6455 breakpoint.as_ref().clone(),
6456 BreakpointEditAction::Toggle,
6457 cx,
6458 );
6459 })
6460 .log_err();
6461 }
6462 })
6463 .entry(log_breakpoint_msg, None, {
6464 let breakpoint = breakpoint.clone();
6465 let weak_editor = weak_editor.clone();
6466 move |window, cx| {
6467 weak_editor
6468 .update(cx, |this, cx| {
6469 this.add_edit_breakpoint_block(
6470 anchor,
6471 breakpoint.as_ref(),
6472 BreakpointPromptEditAction::Log,
6473 window,
6474 cx,
6475 );
6476 })
6477 .log_err();
6478 }
6479 })
6480 .entry(condition_breakpoint_msg, None, {
6481 let breakpoint = breakpoint.clone();
6482 let weak_editor = weak_editor.clone();
6483 move |window, cx| {
6484 weak_editor
6485 .update(cx, |this, cx| {
6486 this.add_edit_breakpoint_block(
6487 anchor,
6488 breakpoint.as_ref(),
6489 BreakpointPromptEditAction::Condition,
6490 window,
6491 cx,
6492 );
6493 })
6494 .log_err();
6495 }
6496 })
6497 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6498 weak_editor
6499 .update(cx, |this, cx| {
6500 this.add_edit_breakpoint_block(
6501 anchor,
6502 breakpoint.as_ref(),
6503 BreakpointPromptEditAction::HitCondition,
6504 window,
6505 cx,
6506 );
6507 })
6508 .log_err();
6509 })
6510 })
6511 }
6512
6513 fn render_breakpoint(
6514 &self,
6515 position: Anchor,
6516 row: DisplayRow,
6517 breakpoint: &Breakpoint,
6518 cx: &mut Context<Self>,
6519 ) -> IconButton {
6520 let (color, icon) = {
6521 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6522 (false, false) => ui::IconName::DebugBreakpoint,
6523 (true, false) => ui::IconName::DebugLogBreakpoint,
6524 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6525 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6526 };
6527
6528 let color = if self
6529 .gutter_breakpoint_indicator
6530 .0
6531 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6532 {
6533 Color::Hint
6534 } else {
6535 Color::Debugger
6536 };
6537
6538 (color, icon)
6539 };
6540
6541 let breakpoint = Arc::from(breakpoint.clone());
6542
6543 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6544 .icon_size(IconSize::XSmall)
6545 .size(ui::ButtonSize::None)
6546 .icon_color(color)
6547 .style(ButtonStyle::Transparent)
6548 .on_click(cx.listener({
6549 let breakpoint = breakpoint.clone();
6550
6551 move |editor, event: &ClickEvent, window, cx| {
6552 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6553 BreakpointEditAction::InvertState
6554 } else {
6555 BreakpointEditAction::Toggle
6556 };
6557
6558 window.focus(&editor.focus_handle(cx));
6559 editor.edit_breakpoint_at_anchor(
6560 position,
6561 breakpoint.as_ref().clone(),
6562 edit_action,
6563 cx,
6564 );
6565 }
6566 }))
6567 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6568 editor.set_breakpoint_context_menu(
6569 row,
6570 Some(position),
6571 event.down.position,
6572 window,
6573 cx,
6574 );
6575 }))
6576 }
6577
6578 fn build_tasks_context(
6579 project: &Entity<Project>,
6580 buffer: &Entity<Buffer>,
6581 buffer_row: u32,
6582 tasks: &Arc<RunnableTasks>,
6583 cx: &mut Context<Self>,
6584 ) -> Task<Option<task::TaskContext>> {
6585 let position = Point::new(buffer_row, tasks.column);
6586 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6587 let location = Location {
6588 buffer: buffer.clone(),
6589 range: range_start..range_start,
6590 };
6591 // Fill in the environmental variables from the tree-sitter captures
6592 let mut captured_task_variables = TaskVariables::default();
6593 for (capture_name, value) in tasks.extra_variables.clone() {
6594 captured_task_variables.insert(
6595 task::VariableName::Custom(capture_name.into()),
6596 value.clone(),
6597 );
6598 }
6599 project.update(cx, |project, cx| {
6600 project.task_store().update(cx, |task_store, cx| {
6601 task_store.task_context_for_location(captured_task_variables, location, cx)
6602 })
6603 })
6604 }
6605
6606 pub fn spawn_nearest_task(
6607 &mut self,
6608 action: &SpawnNearestTask,
6609 window: &mut Window,
6610 cx: &mut Context<Self>,
6611 ) {
6612 let Some((workspace, _)) = self.workspace.clone() else {
6613 return;
6614 };
6615 let Some(project) = self.project.clone() else {
6616 return;
6617 };
6618
6619 // Try to find a closest, enclosing node using tree-sitter that has a
6620 // task
6621 let Some((buffer, buffer_row, tasks)) = self
6622 .find_enclosing_node_task(cx)
6623 // Or find the task that's closest in row-distance.
6624 .or_else(|| self.find_closest_task(cx))
6625 else {
6626 return;
6627 };
6628
6629 let reveal_strategy = action.reveal;
6630 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6631 cx.spawn_in(window, async move |_, cx| {
6632 let context = task_context.await?;
6633 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6634
6635 let resolved = resolved_task.resolved.as_mut()?;
6636 resolved.reveal = reveal_strategy;
6637
6638 workspace
6639 .update(cx, |workspace, cx| {
6640 workspace::tasks::schedule_resolved_task(
6641 workspace,
6642 task_source_kind,
6643 resolved_task,
6644 false,
6645 cx,
6646 );
6647 })
6648 .ok()
6649 })
6650 .detach();
6651 }
6652
6653 fn find_closest_task(
6654 &mut self,
6655 cx: &mut Context<Self>,
6656 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6657 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6658
6659 let ((buffer_id, row), tasks) = self
6660 .tasks
6661 .iter()
6662 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6663
6664 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6665 let tasks = Arc::new(tasks.to_owned());
6666 Some((buffer, *row, tasks))
6667 }
6668
6669 fn find_enclosing_node_task(
6670 &mut self,
6671 cx: &mut Context<Self>,
6672 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6673 let snapshot = self.buffer.read(cx).snapshot(cx);
6674 let offset = self.selections.newest::<usize>(cx).head();
6675 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6676 let buffer_id = excerpt.buffer().remote_id();
6677
6678 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6679 let mut cursor = layer.node().walk();
6680
6681 while cursor.goto_first_child_for_byte(offset).is_some() {
6682 if cursor.node().end_byte() == offset {
6683 cursor.goto_next_sibling();
6684 }
6685 }
6686
6687 // Ascend to the smallest ancestor that contains the range and has a task.
6688 loop {
6689 let node = cursor.node();
6690 let node_range = node.byte_range();
6691 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6692
6693 // Check if this node contains our offset
6694 if node_range.start <= offset && node_range.end >= offset {
6695 // If it contains offset, check for task
6696 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6697 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6698 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6699 }
6700 }
6701
6702 if !cursor.goto_parent() {
6703 break;
6704 }
6705 }
6706 None
6707 }
6708
6709 fn render_run_indicator(
6710 &self,
6711 _style: &EditorStyle,
6712 is_active: bool,
6713 row: DisplayRow,
6714 breakpoint: Option<(Anchor, Breakpoint)>,
6715 cx: &mut Context<Self>,
6716 ) -> IconButton {
6717 let color = Color::Muted;
6718 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6719
6720 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6721 .shape(ui::IconButtonShape::Square)
6722 .icon_size(IconSize::XSmall)
6723 .icon_color(color)
6724 .toggle_state(is_active)
6725 .on_click(cx.listener(move |editor, _e, window, cx| {
6726 window.focus(&editor.focus_handle(cx));
6727 editor.toggle_code_actions(
6728 &ToggleCodeActions {
6729 deployed_from_indicator: Some(row),
6730 },
6731 window,
6732 cx,
6733 );
6734 }))
6735 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6736 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6737 }))
6738 }
6739
6740 pub fn context_menu_visible(&self) -> bool {
6741 !self.edit_prediction_preview_is_active()
6742 && self
6743 .context_menu
6744 .borrow()
6745 .as_ref()
6746 .map_or(false, |menu| menu.visible())
6747 }
6748
6749 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6750 self.context_menu
6751 .borrow()
6752 .as_ref()
6753 .map(|menu| menu.origin())
6754 }
6755
6756 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6757 self.context_menu_options = Some(options);
6758 }
6759
6760 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6761 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6762
6763 fn render_edit_prediction_popover(
6764 &mut self,
6765 text_bounds: &Bounds<Pixels>,
6766 content_origin: gpui::Point<Pixels>,
6767 editor_snapshot: &EditorSnapshot,
6768 visible_row_range: Range<DisplayRow>,
6769 scroll_top: f32,
6770 scroll_bottom: f32,
6771 line_layouts: &[LineWithInvisibles],
6772 line_height: Pixels,
6773 scroll_pixel_position: gpui::Point<Pixels>,
6774 newest_selection_head: Option<DisplayPoint>,
6775 editor_width: Pixels,
6776 style: &EditorStyle,
6777 window: &mut Window,
6778 cx: &mut App,
6779 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6780 let active_inline_completion = self.active_inline_completion.as_ref()?;
6781
6782 if self.edit_prediction_visible_in_cursor_popover(true) {
6783 return None;
6784 }
6785
6786 match &active_inline_completion.completion {
6787 InlineCompletion::Move { target, .. } => {
6788 let target_display_point = target.to_display_point(editor_snapshot);
6789
6790 if self.edit_prediction_requires_modifier() {
6791 if !self.edit_prediction_preview_is_active() {
6792 return None;
6793 }
6794
6795 self.render_edit_prediction_modifier_jump_popover(
6796 text_bounds,
6797 content_origin,
6798 visible_row_range,
6799 line_layouts,
6800 line_height,
6801 scroll_pixel_position,
6802 newest_selection_head,
6803 target_display_point,
6804 window,
6805 cx,
6806 )
6807 } else {
6808 self.render_edit_prediction_eager_jump_popover(
6809 text_bounds,
6810 content_origin,
6811 editor_snapshot,
6812 visible_row_range,
6813 scroll_top,
6814 scroll_bottom,
6815 line_height,
6816 scroll_pixel_position,
6817 target_display_point,
6818 editor_width,
6819 window,
6820 cx,
6821 )
6822 }
6823 }
6824 InlineCompletion::Edit {
6825 display_mode: EditDisplayMode::Inline,
6826 ..
6827 } => None,
6828 InlineCompletion::Edit {
6829 display_mode: EditDisplayMode::TabAccept,
6830 edits,
6831 ..
6832 } => {
6833 let range = &edits.first()?.0;
6834 let target_display_point = range.end.to_display_point(editor_snapshot);
6835
6836 self.render_edit_prediction_end_of_line_popover(
6837 "Accept",
6838 editor_snapshot,
6839 visible_row_range,
6840 target_display_point,
6841 line_height,
6842 scroll_pixel_position,
6843 content_origin,
6844 editor_width,
6845 window,
6846 cx,
6847 )
6848 }
6849 InlineCompletion::Edit {
6850 edits,
6851 edit_preview,
6852 display_mode: EditDisplayMode::DiffPopover,
6853 snapshot,
6854 } => self.render_edit_prediction_diff_popover(
6855 text_bounds,
6856 content_origin,
6857 editor_snapshot,
6858 visible_row_range,
6859 line_layouts,
6860 line_height,
6861 scroll_pixel_position,
6862 newest_selection_head,
6863 editor_width,
6864 style,
6865 edits,
6866 edit_preview,
6867 snapshot,
6868 window,
6869 cx,
6870 ),
6871 }
6872 }
6873
6874 fn render_edit_prediction_modifier_jump_popover(
6875 &mut self,
6876 text_bounds: &Bounds<Pixels>,
6877 content_origin: gpui::Point<Pixels>,
6878 visible_row_range: Range<DisplayRow>,
6879 line_layouts: &[LineWithInvisibles],
6880 line_height: Pixels,
6881 scroll_pixel_position: gpui::Point<Pixels>,
6882 newest_selection_head: Option<DisplayPoint>,
6883 target_display_point: DisplayPoint,
6884 window: &mut Window,
6885 cx: &mut App,
6886 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6887 let scrolled_content_origin =
6888 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6889
6890 const SCROLL_PADDING_Y: Pixels = px(12.);
6891
6892 if target_display_point.row() < visible_row_range.start {
6893 return self.render_edit_prediction_scroll_popover(
6894 |_| SCROLL_PADDING_Y,
6895 IconName::ArrowUp,
6896 visible_row_range,
6897 line_layouts,
6898 newest_selection_head,
6899 scrolled_content_origin,
6900 window,
6901 cx,
6902 );
6903 } else if target_display_point.row() >= visible_row_range.end {
6904 return self.render_edit_prediction_scroll_popover(
6905 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6906 IconName::ArrowDown,
6907 visible_row_range,
6908 line_layouts,
6909 newest_selection_head,
6910 scrolled_content_origin,
6911 window,
6912 cx,
6913 );
6914 }
6915
6916 const POLE_WIDTH: Pixels = px(2.);
6917
6918 let line_layout =
6919 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6920 let target_column = target_display_point.column() as usize;
6921
6922 let target_x = line_layout.x_for_index(target_column);
6923 let target_y =
6924 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6925
6926 let flag_on_right = target_x < text_bounds.size.width / 2.;
6927
6928 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6929 border_color.l += 0.001;
6930
6931 let mut element = v_flex()
6932 .items_end()
6933 .when(flag_on_right, |el| el.items_start())
6934 .child(if flag_on_right {
6935 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6936 .rounded_bl(px(0.))
6937 .rounded_tl(px(0.))
6938 .border_l_2()
6939 .border_color(border_color)
6940 } else {
6941 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6942 .rounded_br(px(0.))
6943 .rounded_tr(px(0.))
6944 .border_r_2()
6945 .border_color(border_color)
6946 })
6947 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6948 .into_any();
6949
6950 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6951
6952 let mut origin = scrolled_content_origin + point(target_x, target_y)
6953 - point(
6954 if flag_on_right {
6955 POLE_WIDTH
6956 } else {
6957 size.width - POLE_WIDTH
6958 },
6959 size.height - line_height,
6960 );
6961
6962 origin.x = origin.x.max(content_origin.x);
6963
6964 element.prepaint_at(origin, window, cx);
6965
6966 Some((element, origin))
6967 }
6968
6969 fn render_edit_prediction_scroll_popover(
6970 &mut self,
6971 to_y: impl Fn(Size<Pixels>) -> Pixels,
6972 scroll_icon: IconName,
6973 visible_row_range: Range<DisplayRow>,
6974 line_layouts: &[LineWithInvisibles],
6975 newest_selection_head: Option<DisplayPoint>,
6976 scrolled_content_origin: gpui::Point<Pixels>,
6977 window: &mut Window,
6978 cx: &mut App,
6979 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6980 let mut element = self
6981 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
6982 .into_any();
6983
6984 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6985
6986 let cursor = newest_selection_head?;
6987 let cursor_row_layout =
6988 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
6989 let cursor_column = cursor.column() as usize;
6990
6991 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
6992
6993 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
6994
6995 element.prepaint_at(origin, window, cx);
6996 Some((element, origin))
6997 }
6998
6999 fn render_edit_prediction_eager_jump_popover(
7000 &mut self,
7001 text_bounds: &Bounds<Pixels>,
7002 content_origin: gpui::Point<Pixels>,
7003 editor_snapshot: &EditorSnapshot,
7004 visible_row_range: Range<DisplayRow>,
7005 scroll_top: f32,
7006 scroll_bottom: f32,
7007 line_height: Pixels,
7008 scroll_pixel_position: gpui::Point<Pixels>,
7009 target_display_point: DisplayPoint,
7010 editor_width: Pixels,
7011 window: &mut Window,
7012 cx: &mut App,
7013 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7014 if target_display_point.row().as_f32() < scroll_top {
7015 let mut element = self
7016 .render_edit_prediction_line_popover(
7017 "Jump to Edit",
7018 Some(IconName::ArrowUp),
7019 window,
7020 cx,
7021 )?
7022 .into_any();
7023
7024 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7025 let offset = point(
7026 (text_bounds.size.width - size.width) / 2.,
7027 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7028 );
7029
7030 let origin = text_bounds.origin + offset;
7031 element.prepaint_at(origin, window, cx);
7032 Some((element, origin))
7033 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7034 let mut element = self
7035 .render_edit_prediction_line_popover(
7036 "Jump to Edit",
7037 Some(IconName::ArrowDown),
7038 window,
7039 cx,
7040 )?
7041 .into_any();
7042
7043 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7044 let offset = point(
7045 (text_bounds.size.width - size.width) / 2.,
7046 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7047 );
7048
7049 let origin = text_bounds.origin + offset;
7050 element.prepaint_at(origin, window, cx);
7051 Some((element, origin))
7052 } else {
7053 self.render_edit_prediction_end_of_line_popover(
7054 "Jump to Edit",
7055 editor_snapshot,
7056 visible_row_range,
7057 target_display_point,
7058 line_height,
7059 scroll_pixel_position,
7060 content_origin,
7061 editor_width,
7062 window,
7063 cx,
7064 )
7065 }
7066 }
7067
7068 fn render_edit_prediction_end_of_line_popover(
7069 self: &mut Editor,
7070 label: &'static str,
7071 editor_snapshot: &EditorSnapshot,
7072 visible_row_range: Range<DisplayRow>,
7073 target_display_point: DisplayPoint,
7074 line_height: Pixels,
7075 scroll_pixel_position: gpui::Point<Pixels>,
7076 content_origin: gpui::Point<Pixels>,
7077 editor_width: Pixels,
7078 window: &mut Window,
7079 cx: &mut App,
7080 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7081 let target_line_end = DisplayPoint::new(
7082 target_display_point.row(),
7083 editor_snapshot.line_len(target_display_point.row()),
7084 );
7085
7086 let mut element = self
7087 .render_edit_prediction_line_popover(label, None, window, cx)?
7088 .into_any();
7089
7090 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7091
7092 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7093
7094 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7095 let mut origin = start_point
7096 + line_origin
7097 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7098 origin.x = origin.x.max(content_origin.x);
7099
7100 let max_x = content_origin.x + editor_width - size.width;
7101
7102 if origin.x > max_x {
7103 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7104
7105 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7106 origin.y += offset;
7107 IconName::ArrowUp
7108 } else {
7109 origin.y -= offset;
7110 IconName::ArrowDown
7111 };
7112
7113 element = self
7114 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7115 .into_any();
7116
7117 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7118
7119 origin.x = content_origin.x + editor_width - size.width - px(2.);
7120 }
7121
7122 element.prepaint_at(origin, window, cx);
7123 Some((element, origin))
7124 }
7125
7126 fn render_edit_prediction_diff_popover(
7127 self: &Editor,
7128 text_bounds: &Bounds<Pixels>,
7129 content_origin: gpui::Point<Pixels>,
7130 editor_snapshot: &EditorSnapshot,
7131 visible_row_range: Range<DisplayRow>,
7132 line_layouts: &[LineWithInvisibles],
7133 line_height: Pixels,
7134 scroll_pixel_position: gpui::Point<Pixels>,
7135 newest_selection_head: Option<DisplayPoint>,
7136 editor_width: Pixels,
7137 style: &EditorStyle,
7138 edits: &Vec<(Range<Anchor>, String)>,
7139 edit_preview: &Option<language::EditPreview>,
7140 snapshot: &language::BufferSnapshot,
7141 window: &mut Window,
7142 cx: &mut App,
7143 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7144 let edit_start = edits
7145 .first()
7146 .unwrap()
7147 .0
7148 .start
7149 .to_display_point(editor_snapshot);
7150 let edit_end = edits
7151 .last()
7152 .unwrap()
7153 .0
7154 .end
7155 .to_display_point(editor_snapshot);
7156
7157 let is_visible = visible_row_range.contains(&edit_start.row())
7158 || visible_row_range.contains(&edit_end.row());
7159 if !is_visible {
7160 return None;
7161 }
7162
7163 let highlighted_edits =
7164 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7165
7166 let styled_text = highlighted_edits.to_styled_text(&style.text);
7167 let line_count = highlighted_edits.text.lines().count();
7168
7169 const BORDER_WIDTH: Pixels = px(1.);
7170
7171 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7172 let has_keybind = keybind.is_some();
7173
7174 let mut element = h_flex()
7175 .items_start()
7176 .child(
7177 h_flex()
7178 .bg(cx.theme().colors().editor_background)
7179 .border(BORDER_WIDTH)
7180 .shadow_sm()
7181 .border_color(cx.theme().colors().border)
7182 .rounded_l_lg()
7183 .when(line_count > 1, |el| el.rounded_br_lg())
7184 .pr_1()
7185 .child(styled_text),
7186 )
7187 .child(
7188 h_flex()
7189 .h(line_height + BORDER_WIDTH * 2.)
7190 .px_1p5()
7191 .gap_1()
7192 // Workaround: For some reason, there's a gap if we don't do this
7193 .ml(-BORDER_WIDTH)
7194 .shadow(smallvec![gpui::BoxShadow {
7195 color: gpui::black().opacity(0.05),
7196 offset: point(px(1.), px(1.)),
7197 blur_radius: px(2.),
7198 spread_radius: px(0.),
7199 }])
7200 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7201 .border(BORDER_WIDTH)
7202 .border_color(cx.theme().colors().border)
7203 .rounded_r_lg()
7204 .id("edit_prediction_diff_popover_keybind")
7205 .when(!has_keybind, |el| {
7206 let status_colors = cx.theme().status();
7207
7208 el.bg(status_colors.error_background)
7209 .border_color(status_colors.error.opacity(0.6))
7210 .child(Icon::new(IconName::Info).color(Color::Error))
7211 .cursor_default()
7212 .hoverable_tooltip(move |_window, cx| {
7213 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7214 })
7215 })
7216 .children(keybind),
7217 )
7218 .into_any();
7219
7220 let longest_row =
7221 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7222 let longest_line_width = if visible_row_range.contains(&longest_row) {
7223 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7224 } else {
7225 layout_line(
7226 longest_row,
7227 editor_snapshot,
7228 style,
7229 editor_width,
7230 |_| false,
7231 window,
7232 cx,
7233 )
7234 .width
7235 };
7236
7237 let viewport_bounds =
7238 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7239 right: -EditorElement::SCROLLBAR_WIDTH,
7240 ..Default::default()
7241 });
7242
7243 let x_after_longest =
7244 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7245 - scroll_pixel_position.x;
7246
7247 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7248
7249 // Fully visible if it can be displayed within the window (allow overlapping other
7250 // panes). However, this is only allowed if the popover starts within text_bounds.
7251 let can_position_to_the_right = x_after_longest < text_bounds.right()
7252 && x_after_longest + element_bounds.width < viewport_bounds.right();
7253
7254 let mut origin = if can_position_to_the_right {
7255 point(
7256 x_after_longest,
7257 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7258 - scroll_pixel_position.y,
7259 )
7260 } else {
7261 let cursor_row = newest_selection_head.map(|head| head.row());
7262 let above_edit = edit_start
7263 .row()
7264 .0
7265 .checked_sub(line_count as u32)
7266 .map(DisplayRow);
7267 let below_edit = Some(edit_end.row() + 1);
7268 let above_cursor =
7269 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7270 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7271
7272 // Place the edit popover adjacent to the edit if there is a location
7273 // available that is onscreen and does not obscure the cursor. Otherwise,
7274 // place it adjacent to the cursor.
7275 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7276 .into_iter()
7277 .flatten()
7278 .find(|&start_row| {
7279 let end_row = start_row + line_count as u32;
7280 visible_row_range.contains(&start_row)
7281 && visible_row_range.contains(&end_row)
7282 && cursor_row.map_or(true, |cursor_row| {
7283 !((start_row..end_row).contains(&cursor_row))
7284 })
7285 })?;
7286
7287 content_origin
7288 + point(
7289 -scroll_pixel_position.x,
7290 row_target.as_f32() * line_height - scroll_pixel_position.y,
7291 )
7292 };
7293
7294 origin.x -= BORDER_WIDTH;
7295
7296 window.defer_draw(element, origin, 1);
7297
7298 // Do not return an element, since it will already be drawn due to defer_draw.
7299 None
7300 }
7301
7302 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7303 px(30.)
7304 }
7305
7306 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7307 if self.read_only(cx) {
7308 cx.theme().players().read_only()
7309 } else {
7310 self.style.as_ref().unwrap().local_player
7311 }
7312 }
7313
7314 fn render_edit_prediction_accept_keybind(
7315 &self,
7316 window: &mut Window,
7317 cx: &App,
7318 ) -> Option<AnyElement> {
7319 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7320 let accept_keystroke = accept_binding.keystroke()?;
7321
7322 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7323
7324 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7325 Color::Accent
7326 } else {
7327 Color::Muted
7328 };
7329
7330 h_flex()
7331 .px_0p5()
7332 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7333 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7334 .text_size(TextSize::XSmall.rems(cx))
7335 .child(h_flex().children(ui::render_modifiers(
7336 &accept_keystroke.modifiers,
7337 PlatformStyle::platform(),
7338 Some(modifiers_color),
7339 Some(IconSize::XSmall.rems().into()),
7340 true,
7341 )))
7342 .when(is_platform_style_mac, |parent| {
7343 parent.child(accept_keystroke.key.clone())
7344 })
7345 .when(!is_platform_style_mac, |parent| {
7346 parent.child(
7347 Key::new(
7348 util::capitalize(&accept_keystroke.key),
7349 Some(Color::Default),
7350 )
7351 .size(Some(IconSize::XSmall.rems().into())),
7352 )
7353 })
7354 .into_any()
7355 .into()
7356 }
7357
7358 fn render_edit_prediction_line_popover(
7359 &self,
7360 label: impl Into<SharedString>,
7361 icon: Option<IconName>,
7362 window: &mut Window,
7363 cx: &App,
7364 ) -> Option<Stateful<Div>> {
7365 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7366
7367 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7368 let has_keybind = keybind.is_some();
7369
7370 let result = h_flex()
7371 .id("ep-line-popover")
7372 .py_0p5()
7373 .pl_1()
7374 .pr(padding_right)
7375 .gap_1()
7376 .rounded_md()
7377 .border_1()
7378 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7379 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7380 .shadow_sm()
7381 .when(!has_keybind, |el| {
7382 let status_colors = cx.theme().status();
7383
7384 el.bg(status_colors.error_background)
7385 .border_color(status_colors.error.opacity(0.6))
7386 .pl_2()
7387 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7388 .cursor_default()
7389 .hoverable_tooltip(move |_window, cx| {
7390 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7391 })
7392 })
7393 .children(keybind)
7394 .child(
7395 Label::new(label)
7396 .size(LabelSize::Small)
7397 .when(!has_keybind, |el| {
7398 el.color(cx.theme().status().error.into()).strikethrough()
7399 }),
7400 )
7401 .when(!has_keybind, |el| {
7402 el.child(
7403 h_flex().ml_1().child(
7404 Icon::new(IconName::Info)
7405 .size(IconSize::Small)
7406 .color(cx.theme().status().error.into()),
7407 ),
7408 )
7409 })
7410 .when_some(icon, |element, icon| {
7411 element.child(
7412 div()
7413 .mt(px(1.5))
7414 .child(Icon::new(icon).size(IconSize::Small)),
7415 )
7416 });
7417
7418 Some(result)
7419 }
7420
7421 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7422 let accent_color = cx.theme().colors().text_accent;
7423 let editor_bg_color = cx.theme().colors().editor_background;
7424 editor_bg_color.blend(accent_color.opacity(0.1))
7425 }
7426
7427 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7428 let accent_color = cx.theme().colors().text_accent;
7429 let editor_bg_color = cx.theme().colors().editor_background;
7430 editor_bg_color.blend(accent_color.opacity(0.6))
7431 }
7432
7433 fn render_edit_prediction_cursor_popover(
7434 &self,
7435 min_width: Pixels,
7436 max_width: Pixels,
7437 cursor_point: Point,
7438 style: &EditorStyle,
7439 accept_keystroke: Option<&gpui::Keystroke>,
7440 _window: &Window,
7441 cx: &mut Context<Editor>,
7442 ) -> Option<AnyElement> {
7443 let provider = self.edit_prediction_provider.as_ref()?;
7444
7445 if provider.provider.needs_terms_acceptance(cx) {
7446 return Some(
7447 h_flex()
7448 .min_w(min_width)
7449 .flex_1()
7450 .px_2()
7451 .py_1()
7452 .gap_3()
7453 .elevation_2(cx)
7454 .hover(|style| style.bg(cx.theme().colors().element_hover))
7455 .id("accept-terms")
7456 .cursor_pointer()
7457 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7458 .on_click(cx.listener(|this, _event, window, cx| {
7459 cx.stop_propagation();
7460 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7461 window.dispatch_action(
7462 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7463 cx,
7464 );
7465 }))
7466 .child(
7467 h_flex()
7468 .flex_1()
7469 .gap_2()
7470 .child(Icon::new(IconName::ZedPredict))
7471 .child(Label::new("Accept Terms of Service"))
7472 .child(div().w_full())
7473 .child(
7474 Icon::new(IconName::ArrowUpRight)
7475 .color(Color::Muted)
7476 .size(IconSize::Small),
7477 )
7478 .into_any_element(),
7479 )
7480 .into_any(),
7481 );
7482 }
7483
7484 let is_refreshing = provider.provider.is_refreshing(cx);
7485
7486 fn pending_completion_container() -> Div {
7487 h_flex()
7488 .h_full()
7489 .flex_1()
7490 .gap_2()
7491 .child(Icon::new(IconName::ZedPredict))
7492 }
7493
7494 let completion = match &self.active_inline_completion {
7495 Some(prediction) => {
7496 if !self.has_visible_completions_menu() {
7497 const RADIUS: Pixels = px(6.);
7498 const BORDER_WIDTH: Pixels = px(1.);
7499
7500 return Some(
7501 h_flex()
7502 .elevation_2(cx)
7503 .border(BORDER_WIDTH)
7504 .border_color(cx.theme().colors().border)
7505 .when(accept_keystroke.is_none(), |el| {
7506 el.border_color(cx.theme().status().error)
7507 })
7508 .rounded(RADIUS)
7509 .rounded_tl(px(0.))
7510 .overflow_hidden()
7511 .child(div().px_1p5().child(match &prediction.completion {
7512 InlineCompletion::Move { target, snapshot } => {
7513 use text::ToPoint as _;
7514 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7515 {
7516 Icon::new(IconName::ZedPredictDown)
7517 } else {
7518 Icon::new(IconName::ZedPredictUp)
7519 }
7520 }
7521 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7522 }))
7523 .child(
7524 h_flex()
7525 .gap_1()
7526 .py_1()
7527 .px_2()
7528 .rounded_r(RADIUS - BORDER_WIDTH)
7529 .border_l_1()
7530 .border_color(cx.theme().colors().border)
7531 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7532 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7533 el.child(
7534 Label::new("Hold")
7535 .size(LabelSize::Small)
7536 .when(accept_keystroke.is_none(), |el| {
7537 el.strikethrough()
7538 })
7539 .line_height_style(LineHeightStyle::UiLabel),
7540 )
7541 })
7542 .id("edit_prediction_cursor_popover_keybind")
7543 .when(accept_keystroke.is_none(), |el| {
7544 let status_colors = cx.theme().status();
7545
7546 el.bg(status_colors.error_background)
7547 .border_color(status_colors.error.opacity(0.6))
7548 .child(Icon::new(IconName::Info).color(Color::Error))
7549 .cursor_default()
7550 .hoverable_tooltip(move |_window, cx| {
7551 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7552 .into()
7553 })
7554 })
7555 .when_some(
7556 accept_keystroke.as_ref(),
7557 |el, accept_keystroke| {
7558 el.child(h_flex().children(ui::render_modifiers(
7559 &accept_keystroke.modifiers,
7560 PlatformStyle::platform(),
7561 Some(Color::Default),
7562 Some(IconSize::XSmall.rems().into()),
7563 false,
7564 )))
7565 },
7566 ),
7567 )
7568 .into_any(),
7569 );
7570 }
7571
7572 self.render_edit_prediction_cursor_popover_preview(
7573 prediction,
7574 cursor_point,
7575 style,
7576 cx,
7577 )?
7578 }
7579
7580 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7581 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7582 stale_completion,
7583 cursor_point,
7584 style,
7585 cx,
7586 )?,
7587
7588 None => {
7589 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7590 }
7591 },
7592
7593 None => pending_completion_container().child(Label::new("No Prediction")),
7594 };
7595
7596 let completion = if is_refreshing {
7597 completion
7598 .with_animation(
7599 "loading-completion",
7600 Animation::new(Duration::from_secs(2))
7601 .repeat()
7602 .with_easing(pulsating_between(0.4, 0.8)),
7603 |label, delta| label.opacity(delta),
7604 )
7605 .into_any_element()
7606 } else {
7607 completion.into_any_element()
7608 };
7609
7610 let has_completion = self.active_inline_completion.is_some();
7611
7612 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7613 Some(
7614 h_flex()
7615 .min_w(min_width)
7616 .max_w(max_width)
7617 .flex_1()
7618 .elevation_2(cx)
7619 .border_color(cx.theme().colors().border)
7620 .child(
7621 div()
7622 .flex_1()
7623 .py_1()
7624 .px_2()
7625 .overflow_hidden()
7626 .child(completion),
7627 )
7628 .when_some(accept_keystroke, |el, accept_keystroke| {
7629 if !accept_keystroke.modifiers.modified() {
7630 return el;
7631 }
7632
7633 el.child(
7634 h_flex()
7635 .h_full()
7636 .border_l_1()
7637 .rounded_r_lg()
7638 .border_color(cx.theme().colors().border)
7639 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7640 .gap_1()
7641 .py_1()
7642 .px_2()
7643 .child(
7644 h_flex()
7645 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7646 .when(is_platform_style_mac, |parent| parent.gap_1())
7647 .child(h_flex().children(ui::render_modifiers(
7648 &accept_keystroke.modifiers,
7649 PlatformStyle::platform(),
7650 Some(if !has_completion {
7651 Color::Muted
7652 } else {
7653 Color::Default
7654 }),
7655 None,
7656 false,
7657 ))),
7658 )
7659 .child(Label::new("Preview").into_any_element())
7660 .opacity(if has_completion { 1.0 } else { 0.4 }),
7661 )
7662 })
7663 .into_any(),
7664 )
7665 }
7666
7667 fn render_edit_prediction_cursor_popover_preview(
7668 &self,
7669 completion: &InlineCompletionState,
7670 cursor_point: Point,
7671 style: &EditorStyle,
7672 cx: &mut Context<Editor>,
7673 ) -> Option<Div> {
7674 use text::ToPoint as _;
7675
7676 fn render_relative_row_jump(
7677 prefix: impl Into<String>,
7678 current_row: u32,
7679 target_row: u32,
7680 ) -> Div {
7681 let (row_diff, arrow) = if target_row < current_row {
7682 (current_row - target_row, IconName::ArrowUp)
7683 } else {
7684 (target_row - current_row, IconName::ArrowDown)
7685 };
7686
7687 h_flex()
7688 .child(
7689 Label::new(format!("{}{}", prefix.into(), row_diff))
7690 .color(Color::Muted)
7691 .size(LabelSize::Small),
7692 )
7693 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7694 }
7695
7696 match &completion.completion {
7697 InlineCompletion::Move {
7698 target, snapshot, ..
7699 } => Some(
7700 h_flex()
7701 .px_2()
7702 .gap_2()
7703 .flex_1()
7704 .child(
7705 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7706 Icon::new(IconName::ZedPredictDown)
7707 } else {
7708 Icon::new(IconName::ZedPredictUp)
7709 },
7710 )
7711 .child(Label::new("Jump to Edit")),
7712 ),
7713
7714 InlineCompletion::Edit {
7715 edits,
7716 edit_preview,
7717 snapshot,
7718 display_mode: _,
7719 } => {
7720 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7721
7722 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7723 &snapshot,
7724 &edits,
7725 edit_preview.as_ref()?,
7726 true,
7727 cx,
7728 )
7729 .first_line_preview();
7730
7731 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7732 .with_default_highlights(&style.text, highlighted_edits.highlights);
7733
7734 let preview = h_flex()
7735 .gap_1()
7736 .min_w_16()
7737 .child(styled_text)
7738 .when(has_more_lines, |parent| parent.child("…"));
7739
7740 let left = if first_edit_row != cursor_point.row {
7741 render_relative_row_jump("", cursor_point.row, first_edit_row)
7742 .into_any_element()
7743 } else {
7744 Icon::new(IconName::ZedPredict).into_any_element()
7745 };
7746
7747 Some(
7748 h_flex()
7749 .h_full()
7750 .flex_1()
7751 .gap_2()
7752 .pr_1()
7753 .overflow_x_hidden()
7754 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7755 .child(left)
7756 .child(preview),
7757 )
7758 }
7759 }
7760 }
7761
7762 fn render_context_menu(
7763 &self,
7764 style: &EditorStyle,
7765 max_height_in_lines: u32,
7766 window: &mut Window,
7767 cx: &mut Context<Editor>,
7768 ) -> Option<AnyElement> {
7769 let menu = self.context_menu.borrow();
7770 let menu = menu.as_ref()?;
7771 if !menu.visible() {
7772 return None;
7773 };
7774 Some(menu.render(style, max_height_in_lines, window, cx))
7775 }
7776
7777 fn render_context_menu_aside(
7778 &mut self,
7779 max_size: Size<Pixels>,
7780 window: &mut Window,
7781 cx: &mut Context<Editor>,
7782 ) -> Option<AnyElement> {
7783 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7784 if menu.visible() {
7785 menu.render_aside(self, max_size, window, cx)
7786 } else {
7787 None
7788 }
7789 })
7790 }
7791
7792 fn hide_context_menu(
7793 &mut self,
7794 window: &mut Window,
7795 cx: &mut Context<Self>,
7796 ) -> Option<CodeContextMenu> {
7797 cx.notify();
7798 self.completion_tasks.clear();
7799 let context_menu = self.context_menu.borrow_mut().take();
7800 self.stale_inline_completion_in_menu.take();
7801 self.update_visible_inline_completion(window, cx);
7802 context_menu
7803 }
7804
7805 fn show_snippet_choices(
7806 &mut self,
7807 choices: &Vec<String>,
7808 selection: Range<Anchor>,
7809 cx: &mut Context<Self>,
7810 ) {
7811 if selection.start.buffer_id.is_none() {
7812 return;
7813 }
7814 let buffer_id = selection.start.buffer_id.unwrap();
7815 let buffer = self.buffer().read(cx).buffer(buffer_id);
7816 let id = post_inc(&mut self.next_completion_id);
7817
7818 if let Some(buffer) = buffer {
7819 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7820 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7821 ));
7822 }
7823 }
7824
7825 pub fn insert_snippet(
7826 &mut self,
7827 insertion_ranges: &[Range<usize>],
7828 snippet: Snippet,
7829 window: &mut Window,
7830 cx: &mut Context<Self>,
7831 ) -> Result<()> {
7832 struct Tabstop<T> {
7833 is_end_tabstop: bool,
7834 ranges: Vec<Range<T>>,
7835 choices: Option<Vec<String>>,
7836 }
7837
7838 let tabstops = self.buffer.update(cx, |buffer, cx| {
7839 let snippet_text: Arc<str> = snippet.text.clone().into();
7840 let edits = insertion_ranges
7841 .iter()
7842 .cloned()
7843 .map(|range| (range, snippet_text.clone()));
7844 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
7845
7846 let snapshot = &*buffer.read(cx);
7847 let snippet = &snippet;
7848 snippet
7849 .tabstops
7850 .iter()
7851 .map(|tabstop| {
7852 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7853 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7854 });
7855 let mut tabstop_ranges = tabstop
7856 .ranges
7857 .iter()
7858 .flat_map(|tabstop_range| {
7859 let mut delta = 0_isize;
7860 insertion_ranges.iter().map(move |insertion_range| {
7861 let insertion_start = insertion_range.start as isize + delta;
7862 delta +=
7863 snippet.text.len() as isize - insertion_range.len() as isize;
7864
7865 let start = ((insertion_start + tabstop_range.start) as usize)
7866 .min(snapshot.len());
7867 let end = ((insertion_start + tabstop_range.end) as usize)
7868 .min(snapshot.len());
7869 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7870 })
7871 })
7872 .collect::<Vec<_>>();
7873 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7874
7875 Tabstop {
7876 is_end_tabstop,
7877 ranges: tabstop_ranges,
7878 choices: tabstop.choices.clone(),
7879 }
7880 })
7881 .collect::<Vec<_>>()
7882 });
7883 if let Some(tabstop) = tabstops.first() {
7884 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7885 s.select_ranges(tabstop.ranges.iter().cloned());
7886 });
7887
7888 if let Some(choices) = &tabstop.choices {
7889 if let Some(selection) = tabstop.ranges.first() {
7890 self.show_snippet_choices(choices, selection.clone(), cx)
7891 }
7892 }
7893
7894 // If we're already at the last tabstop and it's at the end of the snippet,
7895 // we're done, we don't need to keep the state around.
7896 if !tabstop.is_end_tabstop {
7897 let choices = tabstops
7898 .iter()
7899 .map(|tabstop| tabstop.choices.clone())
7900 .collect();
7901
7902 let ranges = tabstops
7903 .into_iter()
7904 .map(|tabstop| tabstop.ranges)
7905 .collect::<Vec<_>>();
7906
7907 self.snippet_stack.push(SnippetState {
7908 active_index: 0,
7909 ranges,
7910 choices,
7911 });
7912 }
7913
7914 // Check whether the just-entered snippet ends with an auto-closable bracket.
7915 if self.autoclose_regions.is_empty() {
7916 let snapshot = self.buffer.read(cx).snapshot(cx);
7917 for selection in &mut self.selections.all::<Point>(cx) {
7918 let selection_head = selection.head();
7919 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7920 continue;
7921 };
7922
7923 let mut bracket_pair = None;
7924 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7925 let prev_chars = snapshot
7926 .reversed_chars_at(selection_head)
7927 .collect::<String>();
7928 for (pair, enabled) in scope.brackets() {
7929 if enabled
7930 && pair.close
7931 && prev_chars.starts_with(pair.start.as_str())
7932 && next_chars.starts_with(pair.end.as_str())
7933 {
7934 bracket_pair = Some(pair.clone());
7935 break;
7936 }
7937 }
7938 if let Some(pair) = bracket_pair {
7939 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
7940 let autoclose_enabled =
7941 self.use_autoclose && snapshot_settings.use_autoclose;
7942 if autoclose_enabled {
7943 let start = snapshot.anchor_after(selection_head);
7944 let end = snapshot.anchor_after(selection_head);
7945 self.autoclose_regions.push(AutocloseRegion {
7946 selection_id: selection.id,
7947 range: start..end,
7948 pair,
7949 });
7950 }
7951 }
7952 }
7953 }
7954 }
7955 Ok(())
7956 }
7957
7958 pub fn move_to_next_snippet_tabstop(
7959 &mut self,
7960 window: &mut Window,
7961 cx: &mut Context<Self>,
7962 ) -> bool {
7963 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7964 }
7965
7966 pub fn move_to_prev_snippet_tabstop(
7967 &mut self,
7968 window: &mut Window,
7969 cx: &mut Context<Self>,
7970 ) -> bool {
7971 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7972 }
7973
7974 pub fn move_to_snippet_tabstop(
7975 &mut self,
7976 bias: Bias,
7977 window: &mut Window,
7978 cx: &mut Context<Self>,
7979 ) -> bool {
7980 if let Some(mut snippet) = self.snippet_stack.pop() {
7981 match bias {
7982 Bias::Left => {
7983 if snippet.active_index > 0 {
7984 snippet.active_index -= 1;
7985 } else {
7986 self.snippet_stack.push(snippet);
7987 return false;
7988 }
7989 }
7990 Bias::Right => {
7991 if snippet.active_index + 1 < snippet.ranges.len() {
7992 snippet.active_index += 1;
7993 } else {
7994 self.snippet_stack.push(snippet);
7995 return false;
7996 }
7997 }
7998 }
7999 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8000 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8001 s.select_anchor_ranges(current_ranges.iter().cloned())
8002 });
8003
8004 if let Some(choices) = &snippet.choices[snippet.active_index] {
8005 if let Some(selection) = current_ranges.first() {
8006 self.show_snippet_choices(&choices, selection.clone(), cx);
8007 }
8008 }
8009
8010 // If snippet state is not at the last tabstop, push it back on the stack
8011 if snippet.active_index + 1 < snippet.ranges.len() {
8012 self.snippet_stack.push(snippet);
8013 }
8014 return true;
8015 }
8016 }
8017
8018 false
8019 }
8020
8021 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8022 self.transact(window, cx, |this, window, cx| {
8023 this.select_all(&SelectAll, window, cx);
8024 this.insert("", window, cx);
8025 });
8026 }
8027
8028 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8029 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8030 self.transact(window, cx, |this, window, cx| {
8031 this.select_autoclose_pair(window, cx);
8032 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8033 if !this.linked_edit_ranges.is_empty() {
8034 let selections = this.selections.all::<MultiBufferPoint>(cx);
8035 let snapshot = this.buffer.read(cx).snapshot(cx);
8036
8037 for selection in selections.iter() {
8038 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8039 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8040 if selection_start.buffer_id != selection_end.buffer_id {
8041 continue;
8042 }
8043 if let Some(ranges) =
8044 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8045 {
8046 for (buffer, entries) in ranges {
8047 linked_ranges.entry(buffer).or_default().extend(entries);
8048 }
8049 }
8050 }
8051 }
8052
8053 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8054 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8055 for selection in &mut selections {
8056 if selection.is_empty() {
8057 let old_head = selection.head();
8058 let mut new_head =
8059 movement::left(&display_map, old_head.to_display_point(&display_map))
8060 .to_point(&display_map);
8061 if let Some((buffer, line_buffer_range)) = display_map
8062 .buffer_snapshot
8063 .buffer_line_for_row(MultiBufferRow(old_head.row))
8064 {
8065 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8066 let indent_len = match indent_size.kind {
8067 IndentKind::Space => {
8068 buffer.settings_at(line_buffer_range.start, cx).tab_size
8069 }
8070 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8071 };
8072 if old_head.column <= indent_size.len && old_head.column > 0 {
8073 let indent_len = indent_len.get();
8074 new_head = cmp::min(
8075 new_head,
8076 MultiBufferPoint::new(
8077 old_head.row,
8078 ((old_head.column - 1) / indent_len) * indent_len,
8079 ),
8080 );
8081 }
8082 }
8083
8084 selection.set_head(new_head, SelectionGoal::None);
8085 }
8086 }
8087
8088 this.signature_help_state.set_backspace_pressed(true);
8089 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8090 s.select(selections)
8091 });
8092 this.insert("", window, cx);
8093 let empty_str: Arc<str> = Arc::from("");
8094 for (buffer, edits) in linked_ranges {
8095 let snapshot = buffer.read(cx).snapshot();
8096 use text::ToPoint as TP;
8097
8098 let edits = edits
8099 .into_iter()
8100 .map(|range| {
8101 let end_point = TP::to_point(&range.end, &snapshot);
8102 let mut start_point = TP::to_point(&range.start, &snapshot);
8103
8104 if end_point == start_point {
8105 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8106 .saturating_sub(1);
8107 start_point =
8108 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8109 };
8110
8111 (start_point..end_point, empty_str.clone())
8112 })
8113 .sorted_by_key(|(range, _)| range.start)
8114 .collect::<Vec<_>>();
8115 buffer.update(cx, |this, cx| {
8116 this.edit(edits, None, cx);
8117 })
8118 }
8119 this.refresh_inline_completion(true, false, window, cx);
8120 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8121 });
8122 }
8123
8124 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8125 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8126 self.transact(window, cx, |this, window, cx| {
8127 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8128 s.move_with(|map, selection| {
8129 if selection.is_empty() {
8130 let cursor = movement::right(map, selection.head());
8131 selection.end = cursor;
8132 selection.reversed = true;
8133 selection.goal = SelectionGoal::None;
8134 }
8135 })
8136 });
8137 this.insert("", window, cx);
8138 this.refresh_inline_completion(true, false, window, cx);
8139 });
8140 }
8141
8142 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8143 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8144 if self.move_to_prev_snippet_tabstop(window, cx) {
8145 return;
8146 }
8147 self.outdent(&Outdent, window, cx);
8148 }
8149
8150 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8151 if self.move_to_next_snippet_tabstop(window, cx) {
8152 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8153 return;
8154 }
8155 if self.read_only(cx) {
8156 return;
8157 }
8158 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8159 let mut selections = self.selections.all_adjusted(cx);
8160 let buffer = self.buffer.read(cx);
8161 let snapshot = buffer.snapshot(cx);
8162 let rows_iter = selections.iter().map(|s| s.head().row);
8163 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8164
8165 let mut edits = Vec::new();
8166 let mut prev_edited_row = 0;
8167 let mut row_delta = 0;
8168 for selection in &mut selections {
8169 if selection.start.row != prev_edited_row {
8170 row_delta = 0;
8171 }
8172 prev_edited_row = selection.end.row;
8173
8174 // If the selection is non-empty, then increase the indentation of the selected lines.
8175 if !selection.is_empty() {
8176 row_delta =
8177 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8178 continue;
8179 }
8180
8181 // If the selection is empty and the cursor is in the leading whitespace before the
8182 // suggested indentation, then auto-indent the line.
8183 let cursor = selection.head();
8184 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8185 if let Some(suggested_indent) =
8186 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8187 {
8188 if cursor.column < suggested_indent.len
8189 && cursor.column <= current_indent.len
8190 && current_indent.len <= suggested_indent.len
8191 {
8192 selection.start = Point::new(cursor.row, suggested_indent.len);
8193 selection.end = selection.start;
8194 if row_delta == 0 {
8195 edits.extend(Buffer::edit_for_indent_size_adjustment(
8196 cursor.row,
8197 current_indent,
8198 suggested_indent,
8199 ));
8200 row_delta = suggested_indent.len - current_indent.len;
8201 }
8202 continue;
8203 }
8204 }
8205
8206 // Otherwise, insert a hard or soft tab.
8207 let settings = buffer.language_settings_at(cursor, cx);
8208 let tab_size = if settings.hard_tabs {
8209 IndentSize::tab()
8210 } else {
8211 let tab_size = settings.tab_size.get();
8212 let char_column = snapshot
8213 .text_for_range(Point::new(cursor.row, 0)..cursor)
8214 .flat_map(str::chars)
8215 .count()
8216 + row_delta as usize;
8217 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
8218 IndentSize::spaces(chars_to_next_tab_stop)
8219 };
8220 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8221 selection.end = selection.start;
8222 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8223 row_delta += tab_size.len;
8224 }
8225
8226 self.transact(window, cx, |this, window, cx| {
8227 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8228 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8229 s.select(selections)
8230 });
8231 this.refresh_inline_completion(true, false, window, cx);
8232 });
8233 }
8234
8235 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8236 if self.read_only(cx) {
8237 return;
8238 }
8239 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8240 let mut selections = self.selections.all::<Point>(cx);
8241 let mut prev_edited_row = 0;
8242 let mut row_delta = 0;
8243 let mut edits = Vec::new();
8244 let buffer = self.buffer.read(cx);
8245 let snapshot = buffer.snapshot(cx);
8246 for selection in &mut selections {
8247 if selection.start.row != prev_edited_row {
8248 row_delta = 0;
8249 }
8250 prev_edited_row = selection.end.row;
8251
8252 row_delta =
8253 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8254 }
8255
8256 self.transact(window, cx, |this, window, cx| {
8257 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8258 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8259 s.select(selections)
8260 });
8261 });
8262 }
8263
8264 fn indent_selection(
8265 buffer: &MultiBuffer,
8266 snapshot: &MultiBufferSnapshot,
8267 selection: &mut Selection<Point>,
8268 edits: &mut Vec<(Range<Point>, String)>,
8269 delta_for_start_row: u32,
8270 cx: &App,
8271 ) -> u32 {
8272 let settings = buffer.language_settings_at(selection.start, cx);
8273 let tab_size = settings.tab_size.get();
8274 let indent_kind = if settings.hard_tabs {
8275 IndentKind::Tab
8276 } else {
8277 IndentKind::Space
8278 };
8279 let mut start_row = selection.start.row;
8280 let mut end_row = selection.end.row + 1;
8281
8282 // If a selection ends at the beginning of a line, don't indent
8283 // that last line.
8284 if selection.end.column == 0 && selection.end.row > selection.start.row {
8285 end_row -= 1;
8286 }
8287
8288 // Avoid re-indenting a row that has already been indented by a
8289 // previous selection, but still update this selection's column
8290 // to reflect that indentation.
8291 if delta_for_start_row > 0 {
8292 start_row += 1;
8293 selection.start.column += delta_for_start_row;
8294 if selection.end.row == selection.start.row {
8295 selection.end.column += delta_for_start_row;
8296 }
8297 }
8298
8299 let mut delta_for_end_row = 0;
8300 let has_multiple_rows = start_row + 1 != end_row;
8301 for row in start_row..end_row {
8302 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8303 let indent_delta = match (current_indent.kind, indent_kind) {
8304 (IndentKind::Space, IndentKind::Space) => {
8305 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8306 IndentSize::spaces(columns_to_next_tab_stop)
8307 }
8308 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8309 (_, IndentKind::Tab) => IndentSize::tab(),
8310 };
8311
8312 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8313 0
8314 } else {
8315 selection.start.column
8316 };
8317 let row_start = Point::new(row, start);
8318 edits.push((
8319 row_start..row_start,
8320 indent_delta.chars().collect::<String>(),
8321 ));
8322
8323 // Update this selection's endpoints to reflect the indentation.
8324 if row == selection.start.row {
8325 selection.start.column += indent_delta.len;
8326 }
8327 if row == selection.end.row {
8328 selection.end.column += indent_delta.len;
8329 delta_for_end_row = indent_delta.len;
8330 }
8331 }
8332
8333 if selection.start.row == selection.end.row {
8334 delta_for_start_row + delta_for_end_row
8335 } else {
8336 delta_for_end_row
8337 }
8338 }
8339
8340 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8341 if self.read_only(cx) {
8342 return;
8343 }
8344 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8346 let selections = self.selections.all::<Point>(cx);
8347 let mut deletion_ranges = Vec::new();
8348 let mut last_outdent = None;
8349 {
8350 let buffer = self.buffer.read(cx);
8351 let snapshot = buffer.snapshot(cx);
8352 for selection in &selections {
8353 let settings = buffer.language_settings_at(selection.start, cx);
8354 let tab_size = settings.tab_size.get();
8355 let mut rows = selection.spanned_rows(false, &display_map);
8356
8357 // Avoid re-outdenting a row that has already been outdented by a
8358 // previous selection.
8359 if let Some(last_row) = last_outdent {
8360 if last_row == rows.start {
8361 rows.start = rows.start.next_row();
8362 }
8363 }
8364 let has_multiple_rows = rows.len() > 1;
8365 for row in rows.iter_rows() {
8366 let indent_size = snapshot.indent_size_for_line(row);
8367 if indent_size.len > 0 {
8368 let deletion_len = match indent_size.kind {
8369 IndentKind::Space => {
8370 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8371 if columns_to_prev_tab_stop == 0 {
8372 tab_size
8373 } else {
8374 columns_to_prev_tab_stop
8375 }
8376 }
8377 IndentKind::Tab => 1,
8378 };
8379 let start = if has_multiple_rows
8380 || deletion_len > selection.start.column
8381 || indent_size.len < selection.start.column
8382 {
8383 0
8384 } else {
8385 selection.start.column - deletion_len
8386 };
8387 deletion_ranges.push(
8388 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8389 );
8390 last_outdent = Some(row);
8391 }
8392 }
8393 }
8394 }
8395
8396 self.transact(window, cx, |this, window, cx| {
8397 this.buffer.update(cx, |buffer, cx| {
8398 let empty_str: Arc<str> = Arc::default();
8399 buffer.edit(
8400 deletion_ranges
8401 .into_iter()
8402 .map(|range| (range, empty_str.clone())),
8403 None,
8404 cx,
8405 );
8406 });
8407 let selections = this.selections.all::<usize>(cx);
8408 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8409 s.select(selections)
8410 });
8411 });
8412 }
8413
8414 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8415 if self.read_only(cx) {
8416 return;
8417 }
8418 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8419 let selections = self
8420 .selections
8421 .all::<usize>(cx)
8422 .into_iter()
8423 .map(|s| s.range());
8424
8425 self.transact(window, cx, |this, window, cx| {
8426 this.buffer.update(cx, |buffer, cx| {
8427 buffer.autoindent_ranges(selections, cx);
8428 });
8429 let selections = this.selections.all::<usize>(cx);
8430 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8431 s.select(selections)
8432 });
8433 });
8434 }
8435
8436 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8437 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8438 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8439 let selections = self.selections.all::<Point>(cx);
8440
8441 let mut new_cursors = Vec::new();
8442 let mut edit_ranges = Vec::new();
8443 let mut selections = selections.iter().peekable();
8444 while let Some(selection) = selections.next() {
8445 let mut rows = selection.spanned_rows(false, &display_map);
8446 let goal_display_column = selection.head().to_display_point(&display_map).column();
8447
8448 // Accumulate contiguous regions of rows that we want to delete.
8449 while let Some(next_selection) = selections.peek() {
8450 let next_rows = next_selection.spanned_rows(false, &display_map);
8451 if next_rows.start <= rows.end {
8452 rows.end = next_rows.end;
8453 selections.next().unwrap();
8454 } else {
8455 break;
8456 }
8457 }
8458
8459 let buffer = &display_map.buffer_snapshot;
8460 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8461 let edit_end;
8462 let cursor_buffer_row;
8463 if buffer.max_point().row >= rows.end.0 {
8464 // If there's a line after the range, delete the \n from the end of the row range
8465 // and position the cursor on the next line.
8466 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8467 cursor_buffer_row = rows.end;
8468 } else {
8469 // If there isn't a line after the range, delete the \n from the line before the
8470 // start of the row range and position the cursor there.
8471 edit_start = edit_start.saturating_sub(1);
8472 edit_end = buffer.len();
8473 cursor_buffer_row = rows.start.previous_row();
8474 }
8475
8476 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8477 *cursor.column_mut() =
8478 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8479
8480 new_cursors.push((
8481 selection.id,
8482 buffer.anchor_after(cursor.to_point(&display_map)),
8483 ));
8484 edit_ranges.push(edit_start..edit_end);
8485 }
8486
8487 self.transact(window, cx, |this, window, cx| {
8488 let buffer = this.buffer.update(cx, |buffer, cx| {
8489 let empty_str: Arc<str> = Arc::default();
8490 buffer.edit(
8491 edit_ranges
8492 .into_iter()
8493 .map(|range| (range, empty_str.clone())),
8494 None,
8495 cx,
8496 );
8497 buffer.snapshot(cx)
8498 });
8499 let new_selections = new_cursors
8500 .into_iter()
8501 .map(|(id, cursor)| {
8502 let cursor = cursor.to_point(&buffer);
8503 Selection {
8504 id,
8505 start: cursor,
8506 end: cursor,
8507 reversed: false,
8508 goal: SelectionGoal::None,
8509 }
8510 })
8511 .collect();
8512
8513 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8514 s.select(new_selections);
8515 });
8516 });
8517 }
8518
8519 pub fn join_lines_impl(
8520 &mut self,
8521 insert_whitespace: bool,
8522 window: &mut Window,
8523 cx: &mut Context<Self>,
8524 ) {
8525 if self.read_only(cx) {
8526 return;
8527 }
8528 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8529 for selection in self.selections.all::<Point>(cx) {
8530 let start = MultiBufferRow(selection.start.row);
8531 // Treat single line selections as if they include the next line. Otherwise this action
8532 // would do nothing for single line selections individual cursors.
8533 let end = if selection.start.row == selection.end.row {
8534 MultiBufferRow(selection.start.row + 1)
8535 } else {
8536 MultiBufferRow(selection.end.row)
8537 };
8538
8539 if let Some(last_row_range) = row_ranges.last_mut() {
8540 if start <= last_row_range.end {
8541 last_row_range.end = end;
8542 continue;
8543 }
8544 }
8545 row_ranges.push(start..end);
8546 }
8547
8548 let snapshot = self.buffer.read(cx).snapshot(cx);
8549 let mut cursor_positions = Vec::new();
8550 for row_range in &row_ranges {
8551 let anchor = snapshot.anchor_before(Point::new(
8552 row_range.end.previous_row().0,
8553 snapshot.line_len(row_range.end.previous_row()),
8554 ));
8555 cursor_positions.push(anchor..anchor);
8556 }
8557
8558 self.transact(window, cx, |this, window, cx| {
8559 for row_range in row_ranges.into_iter().rev() {
8560 for row in row_range.iter_rows().rev() {
8561 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8562 let next_line_row = row.next_row();
8563 let indent = snapshot.indent_size_for_line(next_line_row);
8564 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8565
8566 let replace =
8567 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8568 " "
8569 } else {
8570 ""
8571 };
8572
8573 this.buffer.update(cx, |buffer, cx| {
8574 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8575 });
8576 }
8577 }
8578
8579 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8580 s.select_anchor_ranges(cursor_positions)
8581 });
8582 });
8583 }
8584
8585 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8586 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8587 self.join_lines_impl(true, window, cx);
8588 }
8589
8590 pub fn sort_lines_case_sensitive(
8591 &mut self,
8592 _: &SortLinesCaseSensitive,
8593 window: &mut Window,
8594 cx: &mut Context<Self>,
8595 ) {
8596 self.manipulate_lines(window, cx, |lines| lines.sort())
8597 }
8598
8599 pub fn sort_lines_case_insensitive(
8600 &mut self,
8601 _: &SortLinesCaseInsensitive,
8602 window: &mut Window,
8603 cx: &mut Context<Self>,
8604 ) {
8605 self.manipulate_lines(window, cx, |lines| {
8606 lines.sort_by_key(|line| line.to_lowercase())
8607 })
8608 }
8609
8610 pub fn unique_lines_case_insensitive(
8611 &mut self,
8612 _: &UniqueLinesCaseInsensitive,
8613 window: &mut Window,
8614 cx: &mut Context<Self>,
8615 ) {
8616 self.manipulate_lines(window, cx, |lines| {
8617 let mut seen = HashSet::default();
8618 lines.retain(|line| seen.insert(line.to_lowercase()));
8619 })
8620 }
8621
8622 pub fn unique_lines_case_sensitive(
8623 &mut self,
8624 _: &UniqueLinesCaseSensitive,
8625 window: &mut Window,
8626 cx: &mut Context<Self>,
8627 ) {
8628 self.manipulate_lines(window, cx, |lines| {
8629 let mut seen = HashSet::default();
8630 lines.retain(|line| seen.insert(*line));
8631 })
8632 }
8633
8634 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8635 let Some(project) = self.project.clone() else {
8636 return;
8637 };
8638 self.reload(project, window, cx)
8639 .detach_and_notify_err(window, cx);
8640 }
8641
8642 pub fn restore_file(
8643 &mut self,
8644 _: &::git::RestoreFile,
8645 window: &mut Window,
8646 cx: &mut Context<Self>,
8647 ) {
8648 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8649 let mut buffer_ids = HashSet::default();
8650 let snapshot = self.buffer().read(cx).snapshot(cx);
8651 for selection in self.selections.all::<usize>(cx) {
8652 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8653 }
8654
8655 let buffer = self.buffer().read(cx);
8656 let ranges = buffer_ids
8657 .into_iter()
8658 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8659 .collect::<Vec<_>>();
8660
8661 self.restore_hunks_in_ranges(ranges, window, cx);
8662 }
8663
8664 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8665 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8666 let selections = self
8667 .selections
8668 .all(cx)
8669 .into_iter()
8670 .map(|s| s.range())
8671 .collect();
8672 self.restore_hunks_in_ranges(selections, window, cx);
8673 }
8674
8675 pub fn restore_hunks_in_ranges(
8676 &mut self,
8677 ranges: Vec<Range<Point>>,
8678 window: &mut Window,
8679 cx: &mut Context<Editor>,
8680 ) {
8681 let mut revert_changes = HashMap::default();
8682 let chunk_by = self
8683 .snapshot(window, cx)
8684 .hunks_for_ranges(ranges)
8685 .into_iter()
8686 .chunk_by(|hunk| hunk.buffer_id);
8687 for (buffer_id, hunks) in &chunk_by {
8688 let hunks = hunks.collect::<Vec<_>>();
8689 for hunk in &hunks {
8690 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8691 }
8692 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8693 }
8694 drop(chunk_by);
8695 if !revert_changes.is_empty() {
8696 self.transact(window, cx, |editor, window, cx| {
8697 editor.restore(revert_changes, window, cx);
8698 });
8699 }
8700 }
8701
8702 pub fn open_active_item_in_terminal(
8703 &mut self,
8704 _: &OpenInTerminal,
8705 window: &mut Window,
8706 cx: &mut Context<Self>,
8707 ) {
8708 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8709 let project_path = buffer.read(cx).project_path(cx)?;
8710 let project = self.project.as_ref()?.read(cx);
8711 let entry = project.entry_for_path(&project_path, cx)?;
8712 let parent = match &entry.canonical_path {
8713 Some(canonical_path) => canonical_path.to_path_buf(),
8714 None => project.absolute_path(&project_path, cx)?,
8715 }
8716 .parent()?
8717 .to_path_buf();
8718 Some(parent)
8719 }) {
8720 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8721 }
8722 }
8723
8724 fn set_breakpoint_context_menu(
8725 &mut self,
8726 display_row: DisplayRow,
8727 position: Option<Anchor>,
8728 clicked_point: gpui::Point<Pixels>,
8729 window: &mut Window,
8730 cx: &mut Context<Self>,
8731 ) {
8732 if !cx.has_flag::<Debugger>() {
8733 return;
8734 }
8735 let source = self
8736 .buffer
8737 .read(cx)
8738 .snapshot(cx)
8739 .anchor_before(Point::new(display_row.0, 0u32));
8740
8741 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8742
8743 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8744 self,
8745 source,
8746 clicked_point,
8747 context_menu,
8748 window,
8749 cx,
8750 );
8751 }
8752
8753 fn add_edit_breakpoint_block(
8754 &mut self,
8755 anchor: Anchor,
8756 breakpoint: &Breakpoint,
8757 edit_action: BreakpointPromptEditAction,
8758 window: &mut Window,
8759 cx: &mut Context<Self>,
8760 ) {
8761 let weak_editor = cx.weak_entity();
8762 let bp_prompt = cx.new(|cx| {
8763 BreakpointPromptEditor::new(
8764 weak_editor,
8765 anchor,
8766 breakpoint.clone(),
8767 edit_action,
8768 window,
8769 cx,
8770 )
8771 });
8772
8773 let height = bp_prompt.update(cx, |this, cx| {
8774 this.prompt
8775 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8776 });
8777 let cloned_prompt = bp_prompt.clone();
8778 let blocks = vec![BlockProperties {
8779 style: BlockStyle::Sticky,
8780 placement: BlockPlacement::Above(anchor),
8781 height: Some(height),
8782 render: Arc::new(move |cx| {
8783 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8784 cloned_prompt.clone().into_any_element()
8785 }),
8786 priority: 0,
8787 }];
8788
8789 let focus_handle = bp_prompt.focus_handle(cx);
8790 window.focus(&focus_handle);
8791
8792 let block_ids = self.insert_blocks(blocks, None, cx);
8793 bp_prompt.update(cx, |prompt, _| {
8794 prompt.add_block_ids(block_ids);
8795 });
8796 }
8797
8798 fn breakpoint_at_cursor_head(
8799 &self,
8800 window: &mut Window,
8801 cx: &mut Context<Self>,
8802 ) -> Option<(Anchor, Breakpoint)> {
8803 let cursor_position: Point = self.selections.newest(cx).head();
8804 self.breakpoint_at_row(cursor_position.row, window, cx)
8805 }
8806
8807 pub(crate) fn breakpoint_at_row(
8808 &self,
8809 row: u32,
8810 window: &mut Window,
8811 cx: &mut Context<Self>,
8812 ) -> Option<(Anchor, Breakpoint)> {
8813 let snapshot = self.snapshot(window, cx);
8814 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
8815
8816 let project = self.project.clone()?;
8817
8818 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
8819 snapshot
8820 .buffer_snapshot
8821 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
8822 })?;
8823
8824 let enclosing_excerpt = breakpoint_position.excerpt_id;
8825 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8826 let buffer_snapshot = buffer.read(cx).snapshot();
8827
8828 let row = buffer_snapshot
8829 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
8830 .row;
8831
8832 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
8833 let anchor_end = snapshot
8834 .buffer_snapshot
8835 .anchor_after(Point::new(row, line_len));
8836
8837 let bp = self
8838 .breakpoint_store
8839 .as_ref()?
8840 .read_with(cx, |breakpoint_store, cx| {
8841 breakpoint_store
8842 .breakpoints(
8843 &buffer,
8844 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
8845 &buffer_snapshot,
8846 cx,
8847 )
8848 .next()
8849 .and_then(|(anchor, bp)| {
8850 let breakpoint_row = buffer_snapshot
8851 .summary_for_anchor::<text::PointUtf16>(anchor)
8852 .row;
8853
8854 if breakpoint_row == row {
8855 snapshot
8856 .buffer_snapshot
8857 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8858 .map(|anchor| (anchor, bp.clone()))
8859 } else {
8860 None
8861 }
8862 })
8863 });
8864 bp
8865 }
8866
8867 pub fn edit_log_breakpoint(
8868 &mut self,
8869 _: &EditLogBreakpoint,
8870 window: &mut Window,
8871 cx: &mut Context<Self>,
8872 ) {
8873 let (anchor, bp) = self
8874 .breakpoint_at_cursor_head(window, cx)
8875 .unwrap_or_else(|| {
8876 let cursor_position: Point = self.selections.newest(cx).head();
8877
8878 let breakpoint_position = self
8879 .snapshot(window, cx)
8880 .display_snapshot
8881 .buffer_snapshot
8882 .anchor_after(Point::new(cursor_position.row, 0));
8883
8884 (
8885 breakpoint_position,
8886 Breakpoint {
8887 message: None,
8888 state: BreakpointState::Enabled,
8889 condition: None,
8890 hit_condition: None,
8891 },
8892 )
8893 });
8894
8895 self.add_edit_breakpoint_block(anchor, &bp, BreakpointPromptEditAction::Log, window, cx);
8896 }
8897
8898 pub fn enable_breakpoint(
8899 &mut self,
8900 _: &crate::actions::EnableBreakpoint,
8901 window: &mut Window,
8902 cx: &mut Context<Self>,
8903 ) {
8904 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8905 if breakpoint.is_disabled() {
8906 self.edit_breakpoint_at_anchor(
8907 anchor,
8908 breakpoint,
8909 BreakpointEditAction::InvertState,
8910 cx,
8911 );
8912 }
8913 }
8914 }
8915
8916 pub fn disable_breakpoint(
8917 &mut self,
8918 _: &crate::actions::DisableBreakpoint,
8919 window: &mut Window,
8920 cx: &mut Context<Self>,
8921 ) {
8922 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8923 if breakpoint.is_enabled() {
8924 self.edit_breakpoint_at_anchor(
8925 anchor,
8926 breakpoint,
8927 BreakpointEditAction::InvertState,
8928 cx,
8929 );
8930 }
8931 }
8932 }
8933
8934 pub fn toggle_breakpoint(
8935 &mut self,
8936 _: &crate::actions::ToggleBreakpoint,
8937 window: &mut Window,
8938 cx: &mut Context<Self>,
8939 ) {
8940 let edit_action = BreakpointEditAction::Toggle;
8941
8942 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8943 self.edit_breakpoint_at_anchor(anchor, breakpoint, edit_action, cx);
8944 } else {
8945 let cursor_position: Point = self.selections.newest(cx).head();
8946
8947 let breakpoint_position = self
8948 .snapshot(window, cx)
8949 .display_snapshot
8950 .buffer_snapshot
8951 .anchor_after(Point::new(cursor_position.row, 0));
8952
8953 self.edit_breakpoint_at_anchor(
8954 breakpoint_position,
8955 Breakpoint::new_standard(),
8956 edit_action,
8957 cx,
8958 );
8959 }
8960 }
8961
8962 pub fn edit_breakpoint_at_anchor(
8963 &mut self,
8964 breakpoint_position: Anchor,
8965 breakpoint: Breakpoint,
8966 edit_action: BreakpointEditAction,
8967 cx: &mut Context<Self>,
8968 ) {
8969 let Some(breakpoint_store) = &self.breakpoint_store else {
8970 return;
8971 };
8972
8973 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8974 if breakpoint_position == Anchor::min() {
8975 self.buffer()
8976 .read(cx)
8977 .excerpt_buffer_ids()
8978 .into_iter()
8979 .next()
8980 } else {
8981 None
8982 }
8983 }) else {
8984 return;
8985 };
8986
8987 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8988 return;
8989 };
8990
8991 breakpoint_store.update(cx, |breakpoint_store, cx| {
8992 breakpoint_store.toggle_breakpoint(
8993 buffer,
8994 (breakpoint_position.text_anchor, breakpoint),
8995 edit_action,
8996 cx,
8997 );
8998 });
8999
9000 cx.notify();
9001 }
9002
9003 #[cfg(any(test, feature = "test-support"))]
9004 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9005 self.breakpoint_store.clone()
9006 }
9007
9008 pub fn prepare_restore_change(
9009 &self,
9010 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9011 hunk: &MultiBufferDiffHunk,
9012 cx: &mut App,
9013 ) -> Option<()> {
9014 if hunk.is_created_file() {
9015 return None;
9016 }
9017 let buffer = self.buffer.read(cx);
9018 let diff = buffer.diff_for(hunk.buffer_id)?;
9019 let buffer = buffer.buffer(hunk.buffer_id)?;
9020 let buffer = buffer.read(cx);
9021 let original_text = diff
9022 .read(cx)
9023 .base_text()
9024 .as_rope()
9025 .slice(hunk.diff_base_byte_range.clone());
9026 let buffer_snapshot = buffer.snapshot();
9027 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9028 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9029 probe
9030 .0
9031 .start
9032 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9033 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9034 }) {
9035 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9036 Some(())
9037 } else {
9038 None
9039 }
9040 }
9041
9042 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9043 self.manipulate_lines(window, cx, |lines| lines.reverse())
9044 }
9045
9046 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9047 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9048 }
9049
9050 fn manipulate_lines<Fn>(
9051 &mut self,
9052 window: &mut Window,
9053 cx: &mut Context<Self>,
9054 mut callback: Fn,
9055 ) where
9056 Fn: FnMut(&mut Vec<&str>),
9057 {
9058 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9059
9060 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9061 let buffer = self.buffer.read(cx).snapshot(cx);
9062
9063 let mut edits = Vec::new();
9064
9065 let selections = self.selections.all::<Point>(cx);
9066 let mut selections = selections.iter().peekable();
9067 let mut contiguous_row_selections = Vec::new();
9068 let mut new_selections = Vec::new();
9069 let mut added_lines = 0;
9070 let mut removed_lines = 0;
9071
9072 while let Some(selection) = selections.next() {
9073 let (start_row, end_row) = consume_contiguous_rows(
9074 &mut contiguous_row_selections,
9075 selection,
9076 &display_map,
9077 &mut selections,
9078 );
9079
9080 let start_point = Point::new(start_row.0, 0);
9081 let end_point = Point::new(
9082 end_row.previous_row().0,
9083 buffer.line_len(end_row.previous_row()),
9084 );
9085 let text = buffer
9086 .text_for_range(start_point..end_point)
9087 .collect::<String>();
9088
9089 let mut lines = text.split('\n').collect_vec();
9090
9091 let lines_before = lines.len();
9092 callback(&mut lines);
9093 let lines_after = lines.len();
9094
9095 edits.push((start_point..end_point, lines.join("\n")));
9096
9097 // Selections must change based on added and removed line count
9098 let start_row =
9099 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9100 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9101 new_selections.push(Selection {
9102 id: selection.id,
9103 start: start_row,
9104 end: end_row,
9105 goal: SelectionGoal::None,
9106 reversed: selection.reversed,
9107 });
9108
9109 if lines_after > lines_before {
9110 added_lines += lines_after - lines_before;
9111 } else if lines_before > lines_after {
9112 removed_lines += lines_before - lines_after;
9113 }
9114 }
9115
9116 self.transact(window, cx, |this, window, cx| {
9117 let buffer = this.buffer.update(cx, |buffer, cx| {
9118 buffer.edit(edits, None, cx);
9119 buffer.snapshot(cx)
9120 });
9121
9122 // Recalculate offsets on newly edited buffer
9123 let new_selections = new_selections
9124 .iter()
9125 .map(|s| {
9126 let start_point = Point::new(s.start.0, 0);
9127 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9128 Selection {
9129 id: s.id,
9130 start: buffer.point_to_offset(start_point),
9131 end: buffer.point_to_offset(end_point),
9132 goal: s.goal,
9133 reversed: s.reversed,
9134 }
9135 })
9136 .collect();
9137
9138 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9139 s.select(new_selections);
9140 });
9141
9142 this.request_autoscroll(Autoscroll::fit(), cx);
9143 });
9144 }
9145
9146 pub fn convert_to_upper_case(
9147 &mut self,
9148 _: &ConvertToUpperCase,
9149 window: &mut Window,
9150 cx: &mut Context<Self>,
9151 ) {
9152 self.manipulate_text(window, cx, |text| text.to_uppercase())
9153 }
9154
9155 pub fn convert_to_lower_case(
9156 &mut self,
9157 _: &ConvertToLowerCase,
9158 window: &mut Window,
9159 cx: &mut Context<Self>,
9160 ) {
9161 self.manipulate_text(window, cx, |text| text.to_lowercase())
9162 }
9163
9164 pub fn convert_to_title_case(
9165 &mut self,
9166 _: &ConvertToTitleCase,
9167 window: &mut Window,
9168 cx: &mut Context<Self>,
9169 ) {
9170 self.manipulate_text(window, cx, |text| {
9171 text.split('\n')
9172 .map(|line| line.to_case(Case::Title))
9173 .join("\n")
9174 })
9175 }
9176
9177 pub fn convert_to_snake_case(
9178 &mut self,
9179 _: &ConvertToSnakeCase,
9180 window: &mut Window,
9181 cx: &mut Context<Self>,
9182 ) {
9183 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9184 }
9185
9186 pub fn convert_to_kebab_case(
9187 &mut self,
9188 _: &ConvertToKebabCase,
9189 window: &mut Window,
9190 cx: &mut Context<Self>,
9191 ) {
9192 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9193 }
9194
9195 pub fn convert_to_upper_camel_case(
9196 &mut self,
9197 _: &ConvertToUpperCamelCase,
9198 window: &mut Window,
9199 cx: &mut Context<Self>,
9200 ) {
9201 self.manipulate_text(window, cx, |text| {
9202 text.split('\n')
9203 .map(|line| line.to_case(Case::UpperCamel))
9204 .join("\n")
9205 })
9206 }
9207
9208 pub fn convert_to_lower_camel_case(
9209 &mut self,
9210 _: &ConvertToLowerCamelCase,
9211 window: &mut Window,
9212 cx: &mut Context<Self>,
9213 ) {
9214 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9215 }
9216
9217 pub fn convert_to_opposite_case(
9218 &mut self,
9219 _: &ConvertToOppositeCase,
9220 window: &mut Window,
9221 cx: &mut Context<Self>,
9222 ) {
9223 self.manipulate_text(window, cx, |text| {
9224 text.chars()
9225 .fold(String::with_capacity(text.len()), |mut t, c| {
9226 if c.is_uppercase() {
9227 t.extend(c.to_lowercase());
9228 } else {
9229 t.extend(c.to_uppercase());
9230 }
9231 t
9232 })
9233 })
9234 }
9235
9236 pub fn convert_to_rot13(
9237 &mut self,
9238 _: &ConvertToRot13,
9239 window: &mut Window,
9240 cx: &mut Context<Self>,
9241 ) {
9242 self.manipulate_text(window, cx, |text| {
9243 text.chars()
9244 .map(|c| match c {
9245 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9246 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9247 _ => c,
9248 })
9249 .collect()
9250 })
9251 }
9252
9253 pub fn convert_to_rot47(
9254 &mut self,
9255 _: &ConvertToRot47,
9256 window: &mut Window,
9257 cx: &mut Context<Self>,
9258 ) {
9259 self.manipulate_text(window, cx, |text| {
9260 text.chars()
9261 .map(|c| {
9262 let code_point = c as u32;
9263 if code_point >= 33 && code_point <= 126 {
9264 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9265 }
9266 c
9267 })
9268 .collect()
9269 })
9270 }
9271
9272 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9273 where
9274 Fn: FnMut(&str) -> String,
9275 {
9276 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9277 let buffer = self.buffer.read(cx).snapshot(cx);
9278
9279 let mut new_selections = Vec::new();
9280 let mut edits = Vec::new();
9281 let mut selection_adjustment = 0i32;
9282
9283 for selection in self.selections.all::<usize>(cx) {
9284 let selection_is_empty = selection.is_empty();
9285
9286 let (start, end) = if selection_is_empty {
9287 let word_range = movement::surrounding_word(
9288 &display_map,
9289 selection.start.to_display_point(&display_map),
9290 );
9291 let start = word_range.start.to_offset(&display_map, Bias::Left);
9292 let end = word_range.end.to_offset(&display_map, Bias::Left);
9293 (start, end)
9294 } else {
9295 (selection.start, selection.end)
9296 };
9297
9298 let text = buffer.text_for_range(start..end).collect::<String>();
9299 let old_length = text.len() as i32;
9300 let text = callback(&text);
9301
9302 new_selections.push(Selection {
9303 start: (start as i32 - selection_adjustment) as usize,
9304 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9305 goal: SelectionGoal::None,
9306 ..selection
9307 });
9308
9309 selection_adjustment += old_length - text.len() as i32;
9310
9311 edits.push((start..end, text));
9312 }
9313
9314 self.transact(window, cx, |this, window, cx| {
9315 this.buffer.update(cx, |buffer, cx| {
9316 buffer.edit(edits, None, cx);
9317 });
9318
9319 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9320 s.select(new_selections);
9321 });
9322
9323 this.request_autoscroll(Autoscroll::fit(), cx);
9324 });
9325 }
9326
9327 pub fn duplicate(
9328 &mut self,
9329 upwards: bool,
9330 whole_lines: bool,
9331 window: &mut Window,
9332 cx: &mut Context<Self>,
9333 ) {
9334 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9335
9336 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9337 let buffer = &display_map.buffer_snapshot;
9338 let selections = self.selections.all::<Point>(cx);
9339
9340 let mut edits = Vec::new();
9341 let mut selections_iter = selections.iter().peekable();
9342 while let Some(selection) = selections_iter.next() {
9343 let mut rows = selection.spanned_rows(false, &display_map);
9344 // duplicate line-wise
9345 if whole_lines || selection.start == selection.end {
9346 // Avoid duplicating the same lines twice.
9347 while let Some(next_selection) = selections_iter.peek() {
9348 let next_rows = next_selection.spanned_rows(false, &display_map);
9349 if next_rows.start < rows.end {
9350 rows.end = next_rows.end;
9351 selections_iter.next().unwrap();
9352 } else {
9353 break;
9354 }
9355 }
9356
9357 // Copy the text from the selected row region and splice it either at the start
9358 // or end of the region.
9359 let start = Point::new(rows.start.0, 0);
9360 let end = Point::new(
9361 rows.end.previous_row().0,
9362 buffer.line_len(rows.end.previous_row()),
9363 );
9364 let text = buffer
9365 .text_for_range(start..end)
9366 .chain(Some("\n"))
9367 .collect::<String>();
9368 let insert_location = if upwards {
9369 Point::new(rows.end.0, 0)
9370 } else {
9371 start
9372 };
9373 edits.push((insert_location..insert_location, text));
9374 } else {
9375 // duplicate character-wise
9376 let start = selection.start;
9377 let end = selection.end;
9378 let text = buffer.text_for_range(start..end).collect::<String>();
9379 edits.push((selection.end..selection.end, text));
9380 }
9381 }
9382
9383 self.transact(window, cx, |this, _, cx| {
9384 this.buffer.update(cx, |buffer, cx| {
9385 buffer.edit(edits, None, cx);
9386 });
9387
9388 this.request_autoscroll(Autoscroll::fit(), cx);
9389 });
9390 }
9391
9392 pub fn duplicate_line_up(
9393 &mut self,
9394 _: &DuplicateLineUp,
9395 window: &mut Window,
9396 cx: &mut Context<Self>,
9397 ) {
9398 self.duplicate(true, true, window, cx);
9399 }
9400
9401 pub fn duplicate_line_down(
9402 &mut self,
9403 _: &DuplicateLineDown,
9404 window: &mut Window,
9405 cx: &mut Context<Self>,
9406 ) {
9407 self.duplicate(false, true, window, cx);
9408 }
9409
9410 pub fn duplicate_selection(
9411 &mut self,
9412 _: &DuplicateSelection,
9413 window: &mut Window,
9414 cx: &mut Context<Self>,
9415 ) {
9416 self.duplicate(false, false, window, cx);
9417 }
9418
9419 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9420 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9421
9422 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9423 let buffer = self.buffer.read(cx).snapshot(cx);
9424
9425 let mut edits = Vec::new();
9426 let mut unfold_ranges = Vec::new();
9427 let mut refold_creases = Vec::new();
9428
9429 let selections = self.selections.all::<Point>(cx);
9430 let mut selections = selections.iter().peekable();
9431 let mut contiguous_row_selections = Vec::new();
9432 let mut new_selections = Vec::new();
9433
9434 while let Some(selection) = selections.next() {
9435 // Find all the selections that span a contiguous row range
9436 let (start_row, end_row) = consume_contiguous_rows(
9437 &mut contiguous_row_selections,
9438 selection,
9439 &display_map,
9440 &mut selections,
9441 );
9442
9443 // Move the text spanned by the row range to be before the line preceding the row range
9444 if start_row.0 > 0 {
9445 let range_to_move = Point::new(
9446 start_row.previous_row().0,
9447 buffer.line_len(start_row.previous_row()),
9448 )
9449 ..Point::new(
9450 end_row.previous_row().0,
9451 buffer.line_len(end_row.previous_row()),
9452 );
9453 let insertion_point = display_map
9454 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9455 .0;
9456
9457 // Don't move lines across excerpts
9458 if buffer
9459 .excerpt_containing(insertion_point..range_to_move.end)
9460 .is_some()
9461 {
9462 let text = buffer
9463 .text_for_range(range_to_move.clone())
9464 .flat_map(|s| s.chars())
9465 .skip(1)
9466 .chain(['\n'])
9467 .collect::<String>();
9468
9469 edits.push((
9470 buffer.anchor_after(range_to_move.start)
9471 ..buffer.anchor_before(range_to_move.end),
9472 String::new(),
9473 ));
9474 let insertion_anchor = buffer.anchor_after(insertion_point);
9475 edits.push((insertion_anchor..insertion_anchor, text));
9476
9477 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9478
9479 // Move selections up
9480 new_selections.extend(contiguous_row_selections.drain(..).map(
9481 |mut selection| {
9482 selection.start.row -= row_delta;
9483 selection.end.row -= row_delta;
9484 selection
9485 },
9486 ));
9487
9488 // Move folds up
9489 unfold_ranges.push(range_to_move.clone());
9490 for fold in display_map.folds_in_range(
9491 buffer.anchor_before(range_to_move.start)
9492 ..buffer.anchor_after(range_to_move.end),
9493 ) {
9494 let mut start = fold.range.start.to_point(&buffer);
9495 let mut end = fold.range.end.to_point(&buffer);
9496 start.row -= row_delta;
9497 end.row -= row_delta;
9498 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9499 }
9500 }
9501 }
9502
9503 // If we didn't move line(s), preserve the existing selections
9504 new_selections.append(&mut contiguous_row_selections);
9505 }
9506
9507 self.transact(window, cx, |this, window, cx| {
9508 this.unfold_ranges(&unfold_ranges, true, true, cx);
9509 this.buffer.update(cx, |buffer, cx| {
9510 for (range, text) in edits {
9511 buffer.edit([(range, text)], None, cx);
9512 }
9513 });
9514 this.fold_creases(refold_creases, true, window, cx);
9515 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9516 s.select(new_selections);
9517 })
9518 });
9519 }
9520
9521 pub fn move_line_down(
9522 &mut self,
9523 _: &MoveLineDown,
9524 window: &mut Window,
9525 cx: &mut Context<Self>,
9526 ) {
9527 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9528
9529 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9530 let buffer = self.buffer.read(cx).snapshot(cx);
9531
9532 let mut edits = Vec::new();
9533 let mut unfold_ranges = Vec::new();
9534 let mut refold_creases = Vec::new();
9535
9536 let selections = self.selections.all::<Point>(cx);
9537 let mut selections = selections.iter().peekable();
9538 let mut contiguous_row_selections = Vec::new();
9539 let mut new_selections = Vec::new();
9540
9541 while let Some(selection) = selections.next() {
9542 // Find all the selections that span a contiguous row range
9543 let (start_row, end_row) = consume_contiguous_rows(
9544 &mut contiguous_row_selections,
9545 selection,
9546 &display_map,
9547 &mut selections,
9548 );
9549
9550 // Move the text spanned by the row range to be after the last line of the row range
9551 if end_row.0 <= buffer.max_point().row {
9552 let range_to_move =
9553 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9554 let insertion_point = display_map
9555 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9556 .0;
9557
9558 // Don't move lines across excerpt boundaries
9559 if buffer
9560 .excerpt_containing(range_to_move.start..insertion_point)
9561 .is_some()
9562 {
9563 let mut text = String::from("\n");
9564 text.extend(buffer.text_for_range(range_to_move.clone()));
9565 text.pop(); // Drop trailing newline
9566 edits.push((
9567 buffer.anchor_after(range_to_move.start)
9568 ..buffer.anchor_before(range_to_move.end),
9569 String::new(),
9570 ));
9571 let insertion_anchor = buffer.anchor_after(insertion_point);
9572 edits.push((insertion_anchor..insertion_anchor, text));
9573
9574 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9575
9576 // Move selections down
9577 new_selections.extend(contiguous_row_selections.drain(..).map(
9578 |mut selection| {
9579 selection.start.row += row_delta;
9580 selection.end.row += row_delta;
9581 selection
9582 },
9583 ));
9584
9585 // Move folds down
9586 unfold_ranges.push(range_to_move.clone());
9587 for fold in display_map.folds_in_range(
9588 buffer.anchor_before(range_to_move.start)
9589 ..buffer.anchor_after(range_to_move.end),
9590 ) {
9591 let mut start = fold.range.start.to_point(&buffer);
9592 let mut end = fold.range.end.to_point(&buffer);
9593 start.row += row_delta;
9594 end.row += row_delta;
9595 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9596 }
9597 }
9598 }
9599
9600 // If we didn't move line(s), preserve the existing selections
9601 new_selections.append(&mut contiguous_row_selections);
9602 }
9603
9604 self.transact(window, cx, |this, window, cx| {
9605 this.unfold_ranges(&unfold_ranges, true, true, cx);
9606 this.buffer.update(cx, |buffer, cx| {
9607 for (range, text) in edits {
9608 buffer.edit([(range, text)], None, cx);
9609 }
9610 });
9611 this.fold_creases(refold_creases, true, window, cx);
9612 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9613 s.select(new_selections)
9614 });
9615 });
9616 }
9617
9618 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9619 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9620 let text_layout_details = &self.text_layout_details(window);
9621 self.transact(window, cx, |this, window, cx| {
9622 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9623 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9624 s.move_with(|display_map, selection| {
9625 if !selection.is_empty() {
9626 return;
9627 }
9628
9629 let mut head = selection.head();
9630 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9631 if head.column() == display_map.line_len(head.row()) {
9632 transpose_offset = display_map
9633 .buffer_snapshot
9634 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9635 }
9636
9637 if transpose_offset == 0 {
9638 return;
9639 }
9640
9641 *head.column_mut() += 1;
9642 head = display_map.clip_point(head, Bias::Right);
9643 let goal = SelectionGoal::HorizontalPosition(
9644 display_map
9645 .x_for_display_point(head, text_layout_details)
9646 .into(),
9647 );
9648 selection.collapse_to(head, goal);
9649
9650 let transpose_start = display_map
9651 .buffer_snapshot
9652 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9653 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9654 let transpose_end = display_map
9655 .buffer_snapshot
9656 .clip_offset(transpose_offset + 1, Bias::Right);
9657 if let Some(ch) =
9658 display_map.buffer_snapshot.chars_at(transpose_start).next()
9659 {
9660 edits.push((transpose_start..transpose_offset, String::new()));
9661 edits.push((transpose_end..transpose_end, ch.to_string()));
9662 }
9663 }
9664 });
9665 edits
9666 });
9667 this.buffer
9668 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9669 let selections = this.selections.all::<usize>(cx);
9670 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9671 s.select(selections);
9672 });
9673 });
9674 }
9675
9676 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9677 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9678 self.rewrap_impl(RewrapOptions::default(), cx)
9679 }
9680
9681 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9682 let buffer = self.buffer.read(cx).snapshot(cx);
9683 let selections = self.selections.all::<Point>(cx);
9684 let mut selections = selections.iter().peekable();
9685
9686 let mut edits = Vec::new();
9687 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9688
9689 while let Some(selection) = selections.next() {
9690 let mut start_row = selection.start.row;
9691 let mut end_row = selection.end.row;
9692
9693 // Skip selections that overlap with a range that has already been rewrapped.
9694 let selection_range = start_row..end_row;
9695 if rewrapped_row_ranges
9696 .iter()
9697 .any(|range| range.overlaps(&selection_range))
9698 {
9699 continue;
9700 }
9701
9702 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9703
9704 // Since not all lines in the selection may be at the same indent
9705 // level, choose the indent size that is the most common between all
9706 // of the lines.
9707 //
9708 // If there is a tie, we use the deepest indent.
9709 let (indent_size, indent_end) = {
9710 let mut indent_size_occurrences = HashMap::default();
9711 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9712
9713 for row in start_row..=end_row {
9714 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9715 rows_by_indent_size.entry(indent).or_default().push(row);
9716 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9717 }
9718
9719 let indent_size = indent_size_occurrences
9720 .into_iter()
9721 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9722 .map(|(indent, _)| indent)
9723 .unwrap_or_default();
9724 let row = rows_by_indent_size[&indent_size][0];
9725 let indent_end = Point::new(row, indent_size.len);
9726
9727 (indent_size, indent_end)
9728 };
9729
9730 let mut line_prefix = indent_size.chars().collect::<String>();
9731
9732 let mut inside_comment = false;
9733 if let Some(comment_prefix) =
9734 buffer
9735 .language_scope_at(selection.head())
9736 .and_then(|language| {
9737 language
9738 .line_comment_prefixes()
9739 .iter()
9740 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9741 .cloned()
9742 })
9743 {
9744 line_prefix.push_str(&comment_prefix);
9745 inside_comment = true;
9746 }
9747
9748 let language_settings = buffer.language_settings_at(selection.head(), cx);
9749 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9750 RewrapBehavior::InComments => inside_comment,
9751 RewrapBehavior::InSelections => !selection.is_empty(),
9752 RewrapBehavior::Anywhere => true,
9753 };
9754
9755 let should_rewrap = options.override_language_settings
9756 || allow_rewrap_based_on_language
9757 || self.hard_wrap.is_some();
9758 if !should_rewrap {
9759 continue;
9760 }
9761
9762 if selection.is_empty() {
9763 'expand_upwards: while start_row > 0 {
9764 let prev_row = start_row - 1;
9765 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9766 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9767 {
9768 start_row = prev_row;
9769 } else {
9770 break 'expand_upwards;
9771 }
9772 }
9773
9774 'expand_downwards: while end_row < buffer.max_point().row {
9775 let next_row = end_row + 1;
9776 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9777 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9778 {
9779 end_row = next_row;
9780 } else {
9781 break 'expand_downwards;
9782 }
9783 }
9784 }
9785
9786 let start = Point::new(start_row, 0);
9787 let start_offset = start.to_offset(&buffer);
9788 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9789 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9790 let Some(lines_without_prefixes) = selection_text
9791 .lines()
9792 .map(|line| {
9793 line.strip_prefix(&line_prefix)
9794 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9795 .ok_or_else(|| {
9796 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9797 })
9798 })
9799 .collect::<Result<Vec<_>, _>>()
9800 .log_err()
9801 else {
9802 continue;
9803 };
9804
9805 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9806 buffer
9807 .language_settings_at(Point::new(start_row, 0), cx)
9808 .preferred_line_length as usize
9809 });
9810 let wrapped_text = wrap_with_prefix(
9811 line_prefix,
9812 lines_without_prefixes.join("\n"),
9813 wrap_column,
9814 tab_size,
9815 options.preserve_existing_whitespace,
9816 );
9817
9818 // TODO: should always use char-based diff while still supporting cursor behavior that
9819 // matches vim.
9820 let mut diff_options = DiffOptions::default();
9821 if options.override_language_settings {
9822 diff_options.max_word_diff_len = 0;
9823 diff_options.max_word_diff_line_count = 0;
9824 } else {
9825 diff_options.max_word_diff_len = usize::MAX;
9826 diff_options.max_word_diff_line_count = usize::MAX;
9827 }
9828
9829 for (old_range, new_text) in
9830 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9831 {
9832 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9833 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9834 edits.push((edit_start..edit_end, new_text));
9835 }
9836
9837 rewrapped_row_ranges.push(start_row..=end_row);
9838 }
9839
9840 self.buffer
9841 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9842 }
9843
9844 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9845 let mut text = String::new();
9846 let buffer = self.buffer.read(cx).snapshot(cx);
9847 let mut selections = self.selections.all::<Point>(cx);
9848 let mut clipboard_selections = Vec::with_capacity(selections.len());
9849 {
9850 let max_point = buffer.max_point();
9851 let mut is_first = true;
9852 for selection in &mut selections {
9853 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9854 if is_entire_line {
9855 selection.start = Point::new(selection.start.row, 0);
9856 if !selection.is_empty() && selection.end.column == 0 {
9857 selection.end = cmp::min(max_point, selection.end);
9858 } else {
9859 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9860 }
9861 selection.goal = SelectionGoal::None;
9862 }
9863 if is_first {
9864 is_first = false;
9865 } else {
9866 text += "\n";
9867 }
9868 let mut len = 0;
9869 for chunk in buffer.text_for_range(selection.start..selection.end) {
9870 text.push_str(chunk);
9871 len += chunk.len();
9872 }
9873 clipboard_selections.push(ClipboardSelection {
9874 len,
9875 is_entire_line,
9876 first_line_indent: buffer
9877 .indent_size_for_line(MultiBufferRow(selection.start.row))
9878 .len,
9879 });
9880 }
9881 }
9882
9883 self.transact(window, cx, |this, window, cx| {
9884 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9885 s.select(selections);
9886 });
9887 this.insert("", window, cx);
9888 });
9889 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9890 }
9891
9892 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9893 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9894 let item = self.cut_common(window, cx);
9895 cx.write_to_clipboard(item);
9896 }
9897
9898 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9899 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9900 self.change_selections(None, window, cx, |s| {
9901 s.move_with(|snapshot, sel| {
9902 if sel.is_empty() {
9903 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9904 }
9905 });
9906 });
9907 let item = self.cut_common(window, cx);
9908 cx.set_global(KillRing(item))
9909 }
9910
9911 pub fn kill_ring_yank(
9912 &mut self,
9913 _: &KillRingYank,
9914 window: &mut Window,
9915 cx: &mut Context<Self>,
9916 ) {
9917 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9918 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9919 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9920 (kill_ring.text().to_string(), kill_ring.metadata_json())
9921 } else {
9922 return;
9923 }
9924 } else {
9925 return;
9926 };
9927 self.do_paste(&text, metadata, false, window, cx);
9928 }
9929
9930 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9931 self.do_copy(true, cx);
9932 }
9933
9934 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9935 self.do_copy(false, cx);
9936 }
9937
9938 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9939 let selections = self.selections.all::<Point>(cx);
9940 let buffer = self.buffer.read(cx).read(cx);
9941 let mut text = String::new();
9942
9943 let mut clipboard_selections = Vec::with_capacity(selections.len());
9944 {
9945 let max_point = buffer.max_point();
9946 let mut is_first = true;
9947 for selection in &selections {
9948 let mut start = selection.start;
9949 let mut end = selection.end;
9950 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9951 if is_entire_line {
9952 start = Point::new(start.row, 0);
9953 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9954 }
9955
9956 let mut trimmed_selections = Vec::new();
9957 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
9958 let row = MultiBufferRow(start.row);
9959 let first_indent = buffer.indent_size_for_line(row);
9960 if first_indent.len == 0 || start.column > first_indent.len {
9961 trimmed_selections.push(start..end);
9962 } else {
9963 trimmed_selections.push(
9964 Point::new(row.0, first_indent.len)
9965 ..Point::new(row.0, buffer.line_len(row)),
9966 );
9967 for row in start.row + 1..=end.row {
9968 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
9969 if row_indent_size.len >= first_indent.len {
9970 trimmed_selections.push(
9971 Point::new(row, first_indent.len)
9972 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
9973 );
9974 } else {
9975 trimmed_selections.clear();
9976 trimmed_selections.push(start..end);
9977 break;
9978 }
9979 }
9980 }
9981 } else {
9982 trimmed_selections.push(start..end);
9983 }
9984
9985 for trimmed_range in trimmed_selections {
9986 if is_first {
9987 is_first = false;
9988 } else {
9989 text += "\n";
9990 }
9991 let mut len = 0;
9992 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
9993 text.push_str(chunk);
9994 len += chunk.len();
9995 }
9996 clipboard_selections.push(ClipboardSelection {
9997 len,
9998 is_entire_line,
9999 first_line_indent: buffer
10000 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10001 .len,
10002 });
10003 }
10004 }
10005 }
10006
10007 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10008 text,
10009 clipboard_selections,
10010 ));
10011 }
10012
10013 pub fn do_paste(
10014 &mut self,
10015 text: &String,
10016 clipboard_selections: Option<Vec<ClipboardSelection>>,
10017 handle_entire_lines: bool,
10018 window: &mut Window,
10019 cx: &mut Context<Self>,
10020 ) {
10021 if self.read_only(cx) {
10022 return;
10023 }
10024
10025 let clipboard_text = Cow::Borrowed(text);
10026
10027 self.transact(window, cx, |this, window, cx| {
10028 if let Some(mut clipboard_selections) = clipboard_selections {
10029 let old_selections = this.selections.all::<usize>(cx);
10030 let all_selections_were_entire_line =
10031 clipboard_selections.iter().all(|s| s.is_entire_line);
10032 let first_selection_indent_column =
10033 clipboard_selections.first().map(|s| s.first_line_indent);
10034 if clipboard_selections.len() != old_selections.len() {
10035 clipboard_selections.drain(..);
10036 }
10037 let cursor_offset = this.selections.last::<usize>(cx).head();
10038 let mut auto_indent_on_paste = true;
10039
10040 this.buffer.update(cx, |buffer, cx| {
10041 let snapshot = buffer.read(cx);
10042 auto_indent_on_paste = snapshot
10043 .language_settings_at(cursor_offset, cx)
10044 .auto_indent_on_paste;
10045
10046 let mut start_offset = 0;
10047 let mut edits = Vec::new();
10048 let mut original_indent_columns = Vec::new();
10049 for (ix, selection) in old_selections.iter().enumerate() {
10050 let to_insert;
10051 let entire_line;
10052 let original_indent_column;
10053 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10054 let end_offset = start_offset + clipboard_selection.len;
10055 to_insert = &clipboard_text[start_offset..end_offset];
10056 entire_line = clipboard_selection.is_entire_line;
10057 start_offset = end_offset + 1;
10058 original_indent_column = Some(clipboard_selection.first_line_indent);
10059 } else {
10060 to_insert = clipboard_text.as_str();
10061 entire_line = all_selections_were_entire_line;
10062 original_indent_column = first_selection_indent_column
10063 }
10064
10065 // If the corresponding selection was empty when this slice of the
10066 // clipboard text was written, then the entire line containing the
10067 // selection was copied. If this selection is also currently empty,
10068 // then paste the line before the current line of the buffer.
10069 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10070 let column = selection.start.to_point(&snapshot).column as usize;
10071 let line_start = selection.start - column;
10072 line_start..line_start
10073 } else {
10074 selection.range()
10075 };
10076
10077 edits.push((range, to_insert));
10078 original_indent_columns.push(original_indent_column);
10079 }
10080 drop(snapshot);
10081
10082 buffer.edit(
10083 edits,
10084 if auto_indent_on_paste {
10085 Some(AutoindentMode::Block {
10086 original_indent_columns,
10087 })
10088 } else {
10089 None
10090 },
10091 cx,
10092 );
10093 });
10094
10095 let selections = this.selections.all::<usize>(cx);
10096 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10097 s.select(selections)
10098 });
10099 } else {
10100 this.insert(&clipboard_text, window, cx);
10101 }
10102 });
10103 }
10104
10105 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10106 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10107 if let Some(item) = cx.read_from_clipboard() {
10108 let entries = item.entries();
10109
10110 match entries.first() {
10111 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10112 // of all the pasted entries.
10113 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10114 .do_paste(
10115 clipboard_string.text(),
10116 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10117 true,
10118 window,
10119 cx,
10120 ),
10121 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10122 }
10123 }
10124 }
10125
10126 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10127 if self.read_only(cx) {
10128 return;
10129 }
10130
10131 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10132
10133 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10134 if let Some((selections, _)) =
10135 self.selection_history.transaction(transaction_id).cloned()
10136 {
10137 self.change_selections(None, window, cx, |s| {
10138 s.select_anchors(selections.to_vec());
10139 });
10140 } else {
10141 log::error!(
10142 "No entry in selection_history found for undo. \
10143 This may correspond to a bug where undo does not update the selection. \
10144 If this is occurring, please add details to \
10145 https://github.com/zed-industries/zed/issues/22692"
10146 );
10147 }
10148 self.request_autoscroll(Autoscroll::fit(), cx);
10149 self.unmark_text(window, cx);
10150 self.refresh_inline_completion(true, false, window, cx);
10151 cx.emit(EditorEvent::Edited { transaction_id });
10152 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10153 }
10154 }
10155
10156 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10157 if self.read_only(cx) {
10158 return;
10159 }
10160
10161 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10162
10163 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10164 if let Some((_, Some(selections))) =
10165 self.selection_history.transaction(transaction_id).cloned()
10166 {
10167 self.change_selections(None, window, cx, |s| {
10168 s.select_anchors(selections.to_vec());
10169 });
10170 } else {
10171 log::error!(
10172 "No entry in selection_history found for redo. \
10173 This may correspond to a bug where undo does not update the selection. \
10174 If this is occurring, please add details to \
10175 https://github.com/zed-industries/zed/issues/22692"
10176 );
10177 }
10178 self.request_autoscroll(Autoscroll::fit(), cx);
10179 self.unmark_text(window, cx);
10180 self.refresh_inline_completion(true, false, window, cx);
10181 cx.emit(EditorEvent::Edited { transaction_id });
10182 }
10183 }
10184
10185 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10186 self.buffer
10187 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10188 }
10189
10190 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10191 self.buffer
10192 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10193 }
10194
10195 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10196 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10197 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10198 s.move_with(|map, selection| {
10199 let cursor = if selection.is_empty() {
10200 movement::left(map, selection.start)
10201 } else {
10202 selection.start
10203 };
10204 selection.collapse_to(cursor, SelectionGoal::None);
10205 });
10206 })
10207 }
10208
10209 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10210 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10211 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10212 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10213 })
10214 }
10215
10216 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10217 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10218 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10219 s.move_with(|map, selection| {
10220 let cursor = if selection.is_empty() {
10221 movement::right(map, selection.end)
10222 } else {
10223 selection.end
10224 };
10225 selection.collapse_to(cursor, SelectionGoal::None)
10226 });
10227 })
10228 }
10229
10230 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10231 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10232 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10233 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10234 })
10235 }
10236
10237 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10238 if self.take_rename(true, window, cx).is_some() {
10239 return;
10240 }
10241
10242 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10243 cx.propagate();
10244 return;
10245 }
10246
10247 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10248
10249 let text_layout_details = &self.text_layout_details(window);
10250 let selection_count = self.selections.count();
10251 let first_selection = self.selections.first_anchor();
10252
10253 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10254 s.move_with(|map, selection| {
10255 if !selection.is_empty() {
10256 selection.goal = SelectionGoal::None;
10257 }
10258 let (cursor, goal) = movement::up(
10259 map,
10260 selection.start,
10261 selection.goal,
10262 false,
10263 text_layout_details,
10264 );
10265 selection.collapse_to(cursor, goal);
10266 });
10267 });
10268
10269 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10270 {
10271 cx.propagate();
10272 }
10273 }
10274
10275 pub fn move_up_by_lines(
10276 &mut self,
10277 action: &MoveUpByLines,
10278 window: &mut Window,
10279 cx: &mut Context<Self>,
10280 ) {
10281 if self.take_rename(true, window, cx).is_some() {
10282 return;
10283 }
10284
10285 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10286 cx.propagate();
10287 return;
10288 }
10289
10290 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10291
10292 let text_layout_details = &self.text_layout_details(window);
10293
10294 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10295 s.move_with(|map, selection| {
10296 if !selection.is_empty() {
10297 selection.goal = SelectionGoal::None;
10298 }
10299 let (cursor, goal) = movement::up_by_rows(
10300 map,
10301 selection.start,
10302 action.lines,
10303 selection.goal,
10304 false,
10305 text_layout_details,
10306 );
10307 selection.collapse_to(cursor, goal);
10308 });
10309 })
10310 }
10311
10312 pub fn move_down_by_lines(
10313 &mut self,
10314 action: &MoveDownByLines,
10315 window: &mut Window,
10316 cx: &mut Context<Self>,
10317 ) {
10318 if self.take_rename(true, window, cx).is_some() {
10319 return;
10320 }
10321
10322 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10323 cx.propagate();
10324 return;
10325 }
10326
10327 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10328
10329 let text_layout_details = &self.text_layout_details(window);
10330
10331 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10332 s.move_with(|map, selection| {
10333 if !selection.is_empty() {
10334 selection.goal = SelectionGoal::None;
10335 }
10336 let (cursor, goal) = movement::down_by_rows(
10337 map,
10338 selection.start,
10339 action.lines,
10340 selection.goal,
10341 false,
10342 text_layout_details,
10343 );
10344 selection.collapse_to(cursor, goal);
10345 });
10346 })
10347 }
10348
10349 pub fn select_down_by_lines(
10350 &mut self,
10351 action: &SelectDownByLines,
10352 window: &mut Window,
10353 cx: &mut Context<Self>,
10354 ) {
10355 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10356 let text_layout_details = &self.text_layout_details(window);
10357 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10358 s.move_heads_with(|map, head, goal| {
10359 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10360 })
10361 })
10362 }
10363
10364 pub fn select_up_by_lines(
10365 &mut self,
10366 action: &SelectUpByLines,
10367 window: &mut Window,
10368 cx: &mut Context<Self>,
10369 ) {
10370 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10371 let text_layout_details = &self.text_layout_details(window);
10372 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10373 s.move_heads_with(|map, head, goal| {
10374 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10375 })
10376 })
10377 }
10378
10379 pub fn select_page_up(
10380 &mut self,
10381 _: &SelectPageUp,
10382 window: &mut Window,
10383 cx: &mut Context<Self>,
10384 ) {
10385 let Some(row_count) = self.visible_row_count() else {
10386 return;
10387 };
10388
10389 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10390
10391 let text_layout_details = &self.text_layout_details(window);
10392
10393 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10394 s.move_heads_with(|map, head, goal| {
10395 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10396 })
10397 })
10398 }
10399
10400 pub fn move_page_up(
10401 &mut self,
10402 action: &MovePageUp,
10403 window: &mut Window,
10404 cx: &mut Context<Self>,
10405 ) {
10406 if self.take_rename(true, window, cx).is_some() {
10407 return;
10408 }
10409
10410 if self
10411 .context_menu
10412 .borrow_mut()
10413 .as_mut()
10414 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10415 .unwrap_or(false)
10416 {
10417 return;
10418 }
10419
10420 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10421 cx.propagate();
10422 return;
10423 }
10424
10425 let Some(row_count) = self.visible_row_count() else {
10426 return;
10427 };
10428
10429 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10430
10431 let autoscroll = if action.center_cursor {
10432 Autoscroll::center()
10433 } else {
10434 Autoscroll::fit()
10435 };
10436
10437 let text_layout_details = &self.text_layout_details(window);
10438
10439 self.change_selections(Some(autoscroll), window, cx, |s| {
10440 s.move_with(|map, selection| {
10441 if !selection.is_empty() {
10442 selection.goal = SelectionGoal::None;
10443 }
10444 let (cursor, goal) = movement::up_by_rows(
10445 map,
10446 selection.end,
10447 row_count,
10448 selection.goal,
10449 false,
10450 text_layout_details,
10451 );
10452 selection.collapse_to(cursor, goal);
10453 });
10454 });
10455 }
10456
10457 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10458 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10459 let text_layout_details = &self.text_layout_details(window);
10460 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10461 s.move_heads_with(|map, head, goal| {
10462 movement::up(map, head, goal, false, text_layout_details)
10463 })
10464 })
10465 }
10466
10467 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10468 self.take_rename(true, window, cx);
10469
10470 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10471 cx.propagate();
10472 return;
10473 }
10474
10475 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10476
10477 let text_layout_details = &self.text_layout_details(window);
10478 let selection_count = self.selections.count();
10479 let first_selection = self.selections.first_anchor();
10480
10481 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10482 s.move_with(|map, selection| {
10483 if !selection.is_empty() {
10484 selection.goal = SelectionGoal::None;
10485 }
10486 let (cursor, goal) = movement::down(
10487 map,
10488 selection.end,
10489 selection.goal,
10490 false,
10491 text_layout_details,
10492 );
10493 selection.collapse_to(cursor, goal);
10494 });
10495 });
10496
10497 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10498 {
10499 cx.propagate();
10500 }
10501 }
10502
10503 pub fn select_page_down(
10504 &mut self,
10505 _: &SelectPageDown,
10506 window: &mut Window,
10507 cx: &mut Context<Self>,
10508 ) {
10509 let Some(row_count) = self.visible_row_count() else {
10510 return;
10511 };
10512
10513 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10514
10515 let text_layout_details = &self.text_layout_details(window);
10516
10517 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10518 s.move_heads_with(|map, head, goal| {
10519 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10520 })
10521 })
10522 }
10523
10524 pub fn move_page_down(
10525 &mut self,
10526 action: &MovePageDown,
10527 window: &mut Window,
10528 cx: &mut Context<Self>,
10529 ) {
10530 if self.take_rename(true, window, cx).is_some() {
10531 return;
10532 }
10533
10534 if self
10535 .context_menu
10536 .borrow_mut()
10537 .as_mut()
10538 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10539 .unwrap_or(false)
10540 {
10541 return;
10542 }
10543
10544 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10545 cx.propagate();
10546 return;
10547 }
10548
10549 let Some(row_count) = self.visible_row_count() else {
10550 return;
10551 };
10552
10553 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10554
10555 let autoscroll = if action.center_cursor {
10556 Autoscroll::center()
10557 } else {
10558 Autoscroll::fit()
10559 };
10560
10561 let text_layout_details = &self.text_layout_details(window);
10562 self.change_selections(Some(autoscroll), window, cx, |s| {
10563 s.move_with(|map, selection| {
10564 if !selection.is_empty() {
10565 selection.goal = SelectionGoal::None;
10566 }
10567 let (cursor, goal) = movement::down_by_rows(
10568 map,
10569 selection.end,
10570 row_count,
10571 selection.goal,
10572 false,
10573 text_layout_details,
10574 );
10575 selection.collapse_to(cursor, goal);
10576 });
10577 });
10578 }
10579
10580 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10581 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10582 let text_layout_details = &self.text_layout_details(window);
10583 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10584 s.move_heads_with(|map, head, goal| {
10585 movement::down(map, head, goal, false, text_layout_details)
10586 })
10587 });
10588 }
10589
10590 pub fn context_menu_first(
10591 &mut self,
10592 _: &ContextMenuFirst,
10593 _window: &mut Window,
10594 cx: &mut Context<Self>,
10595 ) {
10596 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10597 context_menu.select_first(self.completion_provider.as_deref(), cx);
10598 }
10599 }
10600
10601 pub fn context_menu_prev(
10602 &mut self,
10603 _: &ContextMenuPrevious,
10604 _window: &mut Window,
10605 cx: &mut Context<Self>,
10606 ) {
10607 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10608 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10609 }
10610 }
10611
10612 pub fn context_menu_next(
10613 &mut self,
10614 _: &ContextMenuNext,
10615 _window: &mut Window,
10616 cx: &mut Context<Self>,
10617 ) {
10618 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10619 context_menu.select_next(self.completion_provider.as_deref(), cx);
10620 }
10621 }
10622
10623 pub fn context_menu_last(
10624 &mut self,
10625 _: &ContextMenuLast,
10626 _window: &mut Window,
10627 cx: &mut Context<Self>,
10628 ) {
10629 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10630 context_menu.select_last(self.completion_provider.as_deref(), cx);
10631 }
10632 }
10633
10634 pub fn move_to_previous_word_start(
10635 &mut self,
10636 _: &MoveToPreviousWordStart,
10637 window: &mut Window,
10638 cx: &mut Context<Self>,
10639 ) {
10640 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10641 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10642 s.move_cursors_with(|map, head, _| {
10643 (
10644 movement::previous_word_start(map, head),
10645 SelectionGoal::None,
10646 )
10647 });
10648 })
10649 }
10650
10651 pub fn move_to_previous_subword_start(
10652 &mut self,
10653 _: &MoveToPreviousSubwordStart,
10654 window: &mut Window,
10655 cx: &mut Context<Self>,
10656 ) {
10657 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10658 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10659 s.move_cursors_with(|map, head, _| {
10660 (
10661 movement::previous_subword_start(map, head),
10662 SelectionGoal::None,
10663 )
10664 });
10665 })
10666 }
10667
10668 pub fn select_to_previous_word_start(
10669 &mut self,
10670 _: &SelectToPreviousWordStart,
10671 window: &mut Window,
10672 cx: &mut Context<Self>,
10673 ) {
10674 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10675 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10676 s.move_heads_with(|map, head, _| {
10677 (
10678 movement::previous_word_start(map, head),
10679 SelectionGoal::None,
10680 )
10681 });
10682 })
10683 }
10684
10685 pub fn select_to_previous_subword_start(
10686 &mut self,
10687 _: &SelectToPreviousSubwordStart,
10688 window: &mut Window,
10689 cx: &mut Context<Self>,
10690 ) {
10691 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10692 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10693 s.move_heads_with(|map, head, _| {
10694 (
10695 movement::previous_subword_start(map, head),
10696 SelectionGoal::None,
10697 )
10698 });
10699 })
10700 }
10701
10702 pub fn delete_to_previous_word_start(
10703 &mut self,
10704 action: &DeleteToPreviousWordStart,
10705 window: &mut Window,
10706 cx: &mut Context<Self>,
10707 ) {
10708 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10709 self.transact(window, cx, |this, window, cx| {
10710 this.select_autoclose_pair(window, cx);
10711 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10712 s.move_with(|map, selection| {
10713 if selection.is_empty() {
10714 let cursor = if action.ignore_newlines {
10715 movement::previous_word_start(map, selection.head())
10716 } else {
10717 movement::previous_word_start_or_newline(map, selection.head())
10718 };
10719 selection.set_head(cursor, SelectionGoal::None);
10720 }
10721 });
10722 });
10723 this.insert("", window, cx);
10724 });
10725 }
10726
10727 pub fn delete_to_previous_subword_start(
10728 &mut self,
10729 _: &DeleteToPreviousSubwordStart,
10730 window: &mut Window,
10731 cx: &mut Context<Self>,
10732 ) {
10733 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10734 self.transact(window, cx, |this, window, cx| {
10735 this.select_autoclose_pair(window, cx);
10736 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10737 s.move_with(|map, selection| {
10738 if selection.is_empty() {
10739 let cursor = movement::previous_subword_start(map, selection.head());
10740 selection.set_head(cursor, SelectionGoal::None);
10741 }
10742 });
10743 });
10744 this.insert("", window, cx);
10745 });
10746 }
10747
10748 pub fn move_to_next_word_end(
10749 &mut self,
10750 _: &MoveToNextWordEnd,
10751 window: &mut Window,
10752 cx: &mut Context<Self>,
10753 ) {
10754 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10755 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10756 s.move_cursors_with(|map, head, _| {
10757 (movement::next_word_end(map, head), SelectionGoal::None)
10758 });
10759 })
10760 }
10761
10762 pub fn move_to_next_subword_end(
10763 &mut self,
10764 _: &MoveToNextSubwordEnd,
10765 window: &mut Window,
10766 cx: &mut Context<Self>,
10767 ) {
10768 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10769 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10770 s.move_cursors_with(|map, head, _| {
10771 (movement::next_subword_end(map, head), SelectionGoal::None)
10772 });
10773 })
10774 }
10775
10776 pub fn select_to_next_word_end(
10777 &mut self,
10778 _: &SelectToNextWordEnd,
10779 window: &mut Window,
10780 cx: &mut Context<Self>,
10781 ) {
10782 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10783 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10784 s.move_heads_with(|map, head, _| {
10785 (movement::next_word_end(map, head), SelectionGoal::None)
10786 });
10787 })
10788 }
10789
10790 pub fn select_to_next_subword_end(
10791 &mut self,
10792 _: &SelectToNextSubwordEnd,
10793 window: &mut Window,
10794 cx: &mut Context<Self>,
10795 ) {
10796 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10797 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10798 s.move_heads_with(|map, head, _| {
10799 (movement::next_subword_end(map, head), SelectionGoal::None)
10800 });
10801 })
10802 }
10803
10804 pub fn delete_to_next_word_end(
10805 &mut self,
10806 action: &DeleteToNextWordEnd,
10807 window: &mut Window,
10808 cx: &mut Context<Self>,
10809 ) {
10810 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10811 self.transact(window, cx, |this, window, cx| {
10812 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10813 s.move_with(|map, selection| {
10814 if selection.is_empty() {
10815 let cursor = if action.ignore_newlines {
10816 movement::next_word_end(map, selection.head())
10817 } else {
10818 movement::next_word_end_or_newline(map, selection.head())
10819 };
10820 selection.set_head(cursor, SelectionGoal::None);
10821 }
10822 });
10823 });
10824 this.insert("", window, cx);
10825 });
10826 }
10827
10828 pub fn delete_to_next_subword_end(
10829 &mut self,
10830 _: &DeleteToNextSubwordEnd,
10831 window: &mut Window,
10832 cx: &mut Context<Self>,
10833 ) {
10834 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10835 self.transact(window, cx, |this, window, cx| {
10836 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10837 s.move_with(|map, selection| {
10838 if selection.is_empty() {
10839 let cursor = movement::next_subword_end(map, selection.head());
10840 selection.set_head(cursor, SelectionGoal::None);
10841 }
10842 });
10843 });
10844 this.insert("", window, cx);
10845 });
10846 }
10847
10848 pub fn move_to_beginning_of_line(
10849 &mut self,
10850 action: &MoveToBeginningOfLine,
10851 window: &mut Window,
10852 cx: &mut Context<Self>,
10853 ) {
10854 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10855 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10856 s.move_cursors_with(|map, head, _| {
10857 (
10858 movement::indented_line_beginning(
10859 map,
10860 head,
10861 action.stop_at_soft_wraps,
10862 action.stop_at_indent,
10863 ),
10864 SelectionGoal::None,
10865 )
10866 });
10867 })
10868 }
10869
10870 pub fn select_to_beginning_of_line(
10871 &mut self,
10872 action: &SelectToBeginningOfLine,
10873 window: &mut Window,
10874 cx: &mut Context<Self>,
10875 ) {
10876 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10877 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10878 s.move_heads_with(|map, head, _| {
10879 (
10880 movement::indented_line_beginning(
10881 map,
10882 head,
10883 action.stop_at_soft_wraps,
10884 action.stop_at_indent,
10885 ),
10886 SelectionGoal::None,
10887 )
10888 });
10889 });
10890 }
10891
10892 pub fn delete_to_beginning_of_line(
10893 &mut self,
10894 action: &DeleteToBeginningOfLine,
10895 window: &mut Window,
10896 cx: &mut Context<Self>,
10897 ) {
10898 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10899 self.transact(window, cx, |this, window, cx| {
10900 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10901 s.move_with(|_, selection| {
10902 selection.reversed = true;
10903 });
10904 });
10905
10906 this.select_to_beginning_of_line(
10907 &SelectToBeginningOfLine {
10908 stop_at_soft_wraps: false,
10909 stop_at_indent: action.stop_at_indent,
10910 },
10911 window,
10912 cx,
10913 );
10914 this.backspace(&Backspace, window, cx);
10915 });
10916 }
10917
10918 pub fn move_to_end_of_line(
10919 &mut self,
10920 action: &MoveToEndOfLine,
10921 window: &mut Window,
10922 cx: &mut Context<Self>,
10923 ) {
10924 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10925 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10926 s.move_cursors_with(|map, head, _| {
10927 (
10928 movement::line_end(map, head, action.stop_at_soft_wraps),
10929 SelectionGoal::None,
10930 )
10931 });
10932 })
10933 }
10934
10935 pub fn select_to_end_of_line(
10936 &mut self,
10937 action: &SelectToEndOfLine,
10938 window: &mut Window,
10939 cx: &mut Context<Self>,
10940 ) {
10941 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10942 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10943 s.move_heads_with(|map, head, _| {
10944 (
10945 movement::line_end(map, head, action.stop_at_soft_wraps),
10946 SelectionGoal::None,
10947 )
10948 });
10949 })
10950 }
10951
10952 pub fn delete_to_end_of_line(
10953 &mut self,
10954 _: &DeleteToEndOfLine,
10955 window: &mut Window,
10956 cx: &mut Context<Self>,
10957 ) {
10958 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10959 self.transact(window, cx, |this, window, cx| {
10960 this.select_to_end_of_line(
10961 &SelectToEndOfLine {
10962 stop_at_soft_wraps: false,
10963 },
10964 window,
10965 cx,
10966 );
10967 this.delete(&Delete, window, cx);
10968 });
10969 }
10970
10971 pub fn cut_to_end_of_line(
10972 &mut self,
10973 _: &CutToEndOfLine,
10974 window: &mut Window,
10975 cx: &mut Context<Self>,
10976 ) {
10977 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10978 self.transact(window, cx, |this, window, cx| {
10979 this.select_to_end_of_line(
10980 &SelectToEndOfLine {
10981 stop_at_soft_wraps: false,
10982 },
10983 window,
10984 cx,
10985 );
10986 this.cut(&Cut, window, cx);
10987 });
10988 }
10989
10990 pub fn move_to_start_of_paragraph(
10991 &mut self,
10992 _: &MoveToStartOfParagraph,
10993 window: &mut Window,
10994 cx: &mut Context<Self>,
10995 ) {
10996 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10997 cx.propagate();
10998 return;
10999 }
11000 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11001 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11002 s.move_with(|map, selection| {
11003 selection.collapse_to(
11004 movement::start_of_paragraph(map, selection.head(), 1),
11005 SelectionGoal::None,
11006 )
11007 });
11008 })
11009 }
11010
11011 pub fn move_to_end_of_paragraph(
11012 &mut self,
11013 _: &MoveToEndOfParagraph,
11014 window: &mut Window,
11015 cx: &mut Context<Self>,
11016 ) {
11017 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11018 cx.propagate();
11019 return;
11020 }
11021 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11022 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11023 s.move_with(|map, selection| {
11024 selection.collapse_to(
11025 movement::end_of_paragraph(map, selection.head(), 1),
11026 SelectionGoal::None,
11027 )
11028 });
11029 })
11030 }
11031
11032 pub fn select_to_start_of_paragraph(
11033 &mut self,
11034 _: &SelectToStartOfParagraph,
11035 window: &mut Window,
11036 cx: &mut Context<Self>,
11037 ) {
11038 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11039 cx.propagate();
11040 return;
11041 }
11042 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11043 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11044 s.move_heads_with(|map, head, _| {
11045 (
11046 movement::start_of_paragraph(map, head, 1),
11047 SelectionGoal::None,
11048 )
11049 });
11050 })
11051 }
11052
11053 pub fn select_to_end_of_paragraph(
11054 &mut self,
11055 _: &SelectToEndOfParagraph,
11056 window: &mut Window,
11057 cx: &mut Context<Self>,
11058 ) {
11059 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11060 cx.propagate();
11061 return;
11062 }
11063 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11064 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11065 s.move_heads_with(|map, head, _| {
11066 (
11067 movement::end_of_paragraph(map, head, 1),
11068 SelectionGoal::None,
11069 )
11070 });
11071 })
11072 }
11073
11074 pub fn move_to_start_of_excerpt(
11075 &mut self,
11076 _: &MoveToStartOfExcerpt,
11077 window: &mut Window,
11078 cx: &mut Context<Self>,
11079 ) {
11080 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11081 cx.propagate();
11082 return;
11083 }
11084 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11085 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11086 s.move_with(|map, selection| {
11087 selection.collapse_to(
11088 movement::start_of_excerpt(
11089 map,
11090 selection.head(),
11091 workspace::searchable::Direction::Prev,
11092 ),
11093 SelectionGoal::None,
11094 )
11095 });
11096 })
11097 }
11098
11099 pub fn move_to_start_of_next_excerpt(
11100 &mut self,
11101 _: &MoveToStartOfNextExcerpt,
11102 window: &mut Window,
11103 cx: &mut Context<Self>,
11104 ) {
11105 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11106 cx.propagate();
11107 return;
11108 }
11109
11110 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11111 s.move_with(|map, selection| {
11112 selection.collapse_to(
11113 movement::start_of_excerpt(
11114 map,
11115 selection.head(),
11116 workspace::searchable::Direction::Next,
11117 ),
11118 SelectionGoal::None,
11119 )
11120 });
11121 })
11122 }
11123
11124 pub fn move_to_end_of_excerpt(
11125 &mut self,
11126 _: &MoveToEndOfExcerpt,
11127 window: &mut Window,
11128 cx: &mut Context<Self>,
11129 ) {
11130 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11131 cx.propagate();
11132 return;
11133 }
11134 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11135 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11136 s.move_with(|map, selection| {
11137 selection.collapse_to(
11138 movement::end_of_excerpt(
11139 map,
11140 selection.head(),
11141 workspace::searchable::Direction::Next,
11142 ),
11143 SelectionGoal::None,
11144 )
11145 });
11146 })
11147 }
11148
11149 pub fn move_to_end_of_previous_excerpt(
11150 &mut self,
11151 _: &MoveToEndOfPreviousExcerpt,
11152 window: &mut Window,
11153 cx: &mut Context<Self>,
11154 ) {
11155 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11156 cx.propagate();
11157 return;
11158 }
11159 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11160 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11161 s.move_with(|map, selection| {
11162 selection.collapse_to(
11163 movement::end_of_excerpt(
11164 map,
11165 selection.head(),
11166 workspace::searchable::Direction::Prev,
11167 ),
11168 SelectionGoal::None,
11169 )
11170 });
11171 })
11172 }
11173
11174 pub fn select_to_start_of_excerpt(
11175 &mut self,
11176 _: &SelectToStartOfExcerpt,
11177 window: &mut Window,
11178 cx: &mut Context<Self>,
11179 ) {
11180 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11181 cx.propagate();
11182 return;
11183 }
11184 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11185 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11186 s.move_heads_with(|map, head, _| {
11187 (
11188 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11189 SelectionGoal::None,
11190 )
11191 });
11192 })
11193 }
11194
11195 pub fn select_to_start_of_next_excerpt(
11196 &mut self,
11197 _: &SelectToStartOfNextExcerpt,
11198 window: &mut Window,
11199 cx: &mut Context<Self>,
11200 ) {
11201 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11202 cx.propagate();
11203 return;
11204 }
11205 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11206 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11207 s.move_heads_with(|map, head, _| {
11208 (
11209 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11210 SelectionGoal::None,
11211 )
11212 });
11213 })
11214 }
11215
11216 pub fn select_to_end_of_excerpt(
11217 &mut self,
11218 _: &SelectToEndOfExcerpt,
11219 window: &mut Window,
11220 cx: &mut Context<Self>,
11221 ) {
11222 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11223 cx.propagate();
11224 return;
11225 }
11226 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11227 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11228 s.move_heads_with(|map, head, _| {
11229 (
11230 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11231 SelectionGoal::None,
11232 )
11233 });
11234 })
11235 }
11236
11237 pub fn select_to_end_of_previous_excerpt(
11238 &mut self,
11239 _: &SelectToEndOfPreviousExcerpt,
11240 window: &mut Window,
11241 cx: &mut Context<Self>,
11242 ) {
11243 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11244 cx.propagate();
11245 return;
11246 }
11247 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11248 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11249 s.move_heads_with(|map, head, _| {
11250 (
11251 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11252 SelectionGoal::None,
11253 )
11254 });
11255 })
11256 }
11257
11258 pub fn move_to_beginning(
11259 &mut self,
11260 _: &MoveToBeginning,
11261 window: &mut Window,
11262 cx: &mut Context<Self>,
11263 ) {
11264 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11265 cx.propagate();
11266 return;
11267 }
11268 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11269 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11270 s.select_ranges(vec![0..0]);
11271 });
11272 }
11273
11274 pub fn select_to_beginning(
11275 &mut self,
11276 _: &SelectToBeginning,
11277 window: &mut Window,
11278 cx: &mut Context<Self>,
11279 ) {
11280 let mut selection = self.selections.last::<Point>(cx);
11281 selection.set_head(Point::zero(), SelectionGoal::None);
11282 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11283 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11284 s.select(vec![selection]);
11285 });
11286 }
11287
11288 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11289 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11290 cx.propagate();
11291 return;
11292 }
11293 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11294 let cursor = self.buffer.read(cx).read(cx).len();
11295 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11296 s.select_ranges(vec![cursor..cursor])
11297 });
11298 }
11299
11300 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11301 self.nav_history = nav_history;
11302 }
11303
11304 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11305 self.nav_history.as_ref()
11306 }
11307
11308 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11309 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11310 }
11311
11312 fn push_to_nav_history(
11313 &mut self,
11314 cursor_anchor: Anchor,
11315 new_position: Option<Point>,
11316 is_deactivate: bool,
11317 cx: &mut Context<Self>,
11318 ) {
11319 if let Some(nav_history) = self.nav_history.as_mut() {
11320 let buffer = self.buffer.read(cx).read(cx);
11321 let cursor_position = cursor_anchor.to_point(&buffer);
11322 let scroll_state = self.scroll_manager.anchor();
11323 let scroll_top_row = scroll_state.top_row(&buffer);
11324 drop(buffer);
11325
11326 if let Some(new_position) = new_position {
11327 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11328 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11329 return;
11330 }
11331 }
11332
11333 nav_history.push(
11334 Some(NavigationData {
11335 cursor_anchor,
11336 cursor_position,
11337 scroll_anchor: scroll_state,
11338 scroll_top_row,
11339 }),
11340 cx,
11341 );
11342 cx.emit(EditorEvent::PushedToNavHistory {
11343 anchor: cursor_anchor,
11344 is_deactivate,
11345 })
11346 }
11347 }
11348
11349 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11350 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11351 let buffer = self.buffer.read(cx).snapshot(cx);
11352 let mut selection = self.selections.first::<usize>(cx);
11353 selection.set_head(buffer.len(), SelectionGoal::None);
11354 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11355 s.select(vec![selection]);
11356 });
11357 }
11358
11359 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11360 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11361 let end = self.buffer.read(cx).read(cx).len();
11362 self.change_selections(None, window, cx, |s| {
11363 s.select_ranges(vec![0..end]);
11364 });
11365 }
11366
11367 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11368 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11369 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11370 let mut selections = self.selections.all::<Point>(cx);
11371 let max_point = display_map.buffer_snapshot.max_point();
11372 for selection in &mut selections {
11373 let rows = selection.spanned_rows(true, &display_map);
11374 selection.start = Point::new(rows.start.0, 0);
11375 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11376 selection.reversed = false;
11377 }
11378 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11379 s.select(selections);
11380 });
11381 }
11382
11383 pub fn split_selection_into_lines(
11384 &mut self,
11385 _: &SplitSelectionIntoLines,
11386 window: &mut Window,
11387 cx: &mut Context<Self>,
11388 ) {
11389 let selections = self
11390 .selections
11391 .all::<Point>(cx)
11392 .into_iter()
11393 .map(|selection| selection.start..selection.end)
11394 .collect::<Vec<_>>();
11395 self.unfold_ranges(&selections, true, true, cx);
11396
11397 let mut new_selection_ranges = Vec::new();
11398 {
11399 let buffer = self.buffer.read(cx).read(cx);
11400 for selection in selections {
11401 for row in selection.start.row..selection.end.row {
11402 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11403 new_selection_ranges.push(cursor..cursor);
11404 }
11405
11406 let is_multiline_selection = selection.start.row != selection.end.row;
11407 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11408 // so this action feels more ergonomic when paired with other selection operations
11409 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11410 if !should_skip_last {
11411 new_selection_ranges.push(selection.end..selection.end);
11412 }
11413 }
11414 }
11415 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11416 s.select_ranges(new_selection_ranges);
11417 });
11418 }
11419
11420 pub fn add_selection_above(
11421 &mut self,
11422 _: &AddSelectionAbove,
11423 window: &mut Window,
11424 cx: &mut Context<Self>,
11425 ) {
11426 self.add_selection(true, window, cx);
11427 }
11428
11429 pub fn add_selection_below(
11430 &mut self,
11431 _: &AddSelectionBelow,
11432 window: &mut Window,
11433 cx: &mut Context<Self>,
11434 ) {
11435 self.add_selection(false, window, cx);
11436 }
11437
11438 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11439 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11440
11441 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11442 let mut selections = self.selections.all::<Point>(cx);
11443 let text_layout_details = self.text_layout_details(window);
11444 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11445 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11446 let range = oldest_selection.display_range(&display_map).sorted();
11447
11448 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11449 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11450 let positions = start_x.min(end_x)..start_x.max(end_x);
11451
11452 selections.clear();
11453 let mut stack = Vec::new();
11454 for row in range.start.row().0..=range.end.row().0 {
11455 if let Some(selection) = self.selections.build_columnar_selection(
11456 &display_map,
11457 DisplayRow(row),
11458 &positions,
11459 oldest_selection.reversed,
11460 &text_layout_details,
11461 ) {
11462 stack.push(selection.id);
11463 selections.push(selection);
11464 }
11465 }
11466
11467 if above {
11468 stack.reverse();
11469 }
11470
11471 AddSelectionsState { above, stack }
11472 });
11473
11474 let last_added_selection = *state.stack.last().unwrap();
11475 let mut new_selections = Vec::new();
11476 if above == state.above {
11477 let end_row = if above {
11478 DisplayRow(0)
11479 } else {
11480 display_map.max_point().row()
11481 };
11482
11483 'outer: for selection in selections {
11484 if selection.id == last_added_selection {
11485 let range = selection.display_range(&display_map).sorted();
11486 debug_assert_eq!(range.start.row(), range.end.row());
11487 let mut row = range.start.row();
11488 let positions =
11489 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11490 px(start)..px(end)
11491 } else {
11492 let start_x =
11493 display_map.x_for_display_point(range.start, &text_layout_details);
11494 let end_x =
11495 display_map.x_for_display_point(range.end, &text_layout_details);
11496 start_x.min(end_x)..start_x.max(end_x)
11497 };
11498
11499 while row != end_row {
11500 if above {
11501 row.0 -= 1;
11502 } else {
11503 row.0 += 1;
11504 }
11505
11506 if let Some(new_selection) = self.selections.build_columnar_selection(
11507 &display_map,
11508 row,
11509 &positions,
11510 selection.reversed,
11511 &text_layout_details,
11512 ) {
11513 state.stack.push(new_selection.id);
11514 if above {
11515 new_selections.push(new_selection);
11516 new_selections.push(selection);
11517 } else {
11518 new_selections.push(selection);
11519 new_selections.push(new_selection);
11520 }
11521
11522 continue 'outer;
11523 }
11524 }
11525 }
11526
11527 new_selections.push(selection);
11528 }
11529 } else {
11530 new_selections = selections;
11531 new_selections.retain(|s| s.id != last_added_selection);
11532 state.stack.pop();
11533 }
11534
11535 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11536 s.select(new_selections);
11537 });
11538 if state.stack.len() > 1 {
11539 self.add_selections_state = Some(state);
11540 }
11541 }
11542
11543 pub fn select_next_match_internal(
11544 &mut self,
11545 display_map: &DisplaySnapshot,
11546 replace_newest: bool,
11547 autoscroll: Option<Autoscroll>,
11548 window: &mut Window,
11549 cx: &mut Context<Self>,
11550 ) -> Result<()> {
11551 fn select_next_match_ranges(
11552 this: &mut Editor,
11553 range: Range<usize>,
11554 replace_newest: bool,
11555 auto_scroll: Option<Autoscroll>,
11556 window: &mut Window,
11557 cx: &mut Context<Editor>,
11558 ) {
11559 this.unfold_ranges(&[range.clone()], false, true, cx);
11560 this.change_selections(auto_scroll, window, cx, |s| {
11561 if replace_newest {
11562 s.delete(s.newest_anchor().id);
11563 }
11564 s.insert_range(range.clone());
11565 });
11566 }
11567
11568 let buffer = &display_map.buffer_snapshot;
11569 let mut selections = self.selections.all::<usize>(cx);
11570 if let Some(mut select_next_state) = self.select_next_state.take() {
11571 let query = &select_next_state.query;
11572 if !select_next_state.done {
11573 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11574 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11575 let mut next_selected_range = None;
11576
11577 let bytes_after_last_selection =
11578 buffer.bytes_in_range(last_selection.end..buffer.len());
11579 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11580 let query_matches = query
11581 .stream_find_iter(bytes_after_last_selection)
11582 .map(|result| (last_selection.end, result))
11583 .chain(
11584 query
11585 .stream_find_iter(bytes_before_first_selection)
11586 .map(|result| (0, result)),
11587 );
11588
11589 for (start_offset, query_match) in query_matches {
11590 let query_match = query_match.unwrap(); // can only fail due to I/O
11591 let offset_range =
11592 start_offset + query_match.start()..start_offset + query_match.end();
11593 let display_range = offset_range.start.to_display_point(display_map)
11594 ..offset_range.end.to_display_point(display_map);
11595
11596 if !select_next_state.wordwise
11597 || (!movement::is_inside_word(display_map, display_range.start)
11598 && !movement::is_inside_word(display_map, display_range.end))
11599 {
11600 // TODO: This is n^2, because we might check all the selections
11601 if !selections
11602 .iter()
11603 .any(|selection| selection.range().overlaps(&offset_range))
11604 {
11605 next_selected_range = Some(offset_range);
11606 break;
11607 }
11608 }
11609 }
11610
11611 if let Some(next_selected_range) = next_selected_range {
11612 select_next_match_ranges(
11613 self,
11614 next_selected_range,
11615 replace_newest,
11616 autoscroll,
11617 window,
11618 cx,
11619 );
11620 } else {
11621 select_next_state.done = true;
11622 }
11623 }
11624
11625 self.select_next_state = Some(select_next_state);
11626 } else {
11627 let mut only_carets = true;
11628 let mut same_text_selected = true;
11629 let mut selected_text = None;
11630
11631 let mut selections_iter = selections.iter().peekable();
11632 while let Some(selection) = selections_iter.next() {
11633 if selection.start != selection.end {
11634 only_carets = false;
11635 }
11636
11637 if same_text_selected {
11638 if selected_text.is_none() {
11639 selected_text =
11640 Some(buffer.text_for_range(selection.range()).collect::<String>());
11641 }
11642
11643 if let Some(next_selection) = selections_iter.peek() {
11644 if next_selection.range().len() == selection.range().len() {
11645 let next_selected_text = buffer
11646 .text_for_range(next_selection.range())
11647 .collect::<String>();
11648 if Some(next_selected_text) != selected_text {
11649 same_text_selected = false;
11650 selected_text = None;
11651 }
11652 } else {
11653 same_text_selected = false;
11654 selected_text = None;
11655 }
11656 }
11657 }
11658 }
11659
11660 if only_carets {
11661 for selection in &mut selections {
11662 let word_range = movement::surrounding_word(
11663 display_map,
11664 selection.start.to_display_point(display_map),
11665 );
11666 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11667 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11668 selection.goal = SelectionGoal::None;
11669 selection.reversed = false;
11670 select_next_match_ranges(
11671 self,
11672 selection.start..selection.end,
11673 replace_newest,
11674 autoscroll,
11675 window,
11676 cx,
11677 );
11678 }
11679
11680 if selections.len() == 1 {
11681 let selection = selections
11682 .last()
11683 .expect("ensured that there's only one selection");
11684 let query = buffer
11685 .text_for_range(selection.start..selection.end)
11686 .collect::<String>();
11687 let is_empty = query.is_empty();
11688 let select_state = SelectNextState {
11689 query: AhoCorasick::new(&[query])?,
11690 wordwise: true,
11691 done: is_empty,
11692 };
11693 self.select_next_state = Some(select_state);
11694 } else {
11695 self.select_next_state = None;
11696 }
11697 } else if let Some(selected_text) = selected_text {
11698 self.select_next_state = Some(SelectNextState {
11699 query: AhoCorasick::new(&[selected_text])?,
11700 wordwise: false,
11701 done: false,
11702 });
11703 self.select_next_match_internal(
11704 display_map,
11705 replace_newest,
11706 autoscroll,
11707 window,
11708 cx,
11709 )?;
11710 }
11711 }
11712 Ok(())
11713 }
11714
11715 pub fn select_all_matches(
11716 &mut self,
11717 _action: &SelectAllMatches,
11718 window: &mut Window,
11719 cx: &mut Context<Self>,
11720 ) -> Result<()> {
11721 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11722
11723 self.push_to_selection_history();
11724 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11725
11726 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11727 let Some(select_next_state) = self.select_next_state.as_mut() else {
11728 return Ok(());
11729 };
11730 if select_next_state.done {
11731 return Ok(());
11732 }
11733
11734 let mut new_selections = self.selections.all::<usize>(cx);
11735
11736 let buffer = &display_map.buffer_snapshot;
11737 let query_matches = select_next_state
11738 .query
11739 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11740
11741 for query_match in query_matches {
11742 let query_match = query_match.unwrap(); // can only fail due to I/O
11743 let offset_range = query_match.start()..query_match.end();
11744 let display_range = offset_range.start.to_display_point(&display_map)
11745 ..offset_range.end.to_display_point(&display_map);
11746
11747 if !select_next_state.wordwise
11748 || (!movement::is_inside_word(&display_map, display_range.start)
11749 && !movement::is_inside_word(&display_map, display_range.end))
11750 {
11751 self.selections.change_with(cx, |selections| {
11752 new_selections.push(Selection {
11753 id: selections.new_selection_id(),
11754 start: offset_range.start,
11755 end: offset_range.end,
11756 reversed: false,
11757 goal: SelectionGoal::None,
11758 });
11759 });
11760 }
11761 }
11762
11763 new_selections.sort_by_key(|selection| selection.start);
11764 let mut ix = 0;
11765 while ix + 1 < new_selections.len() {
11766 let current_selection = &new_selections[ix];
11767 let next_selection = &new_selections[ix + 1];
11768 if current_selection.range().overlaps(&next_selection.range()) {
11769 if current_selection.id < next_selection.id {
11770 new_selections.remove(ix + 1);
11771 } else {
11772 new_selections.remove(ix);
11773 }
11774 } else {
11775 ix += 1;
11776 }
11777 }
11778
11779 let reversed = self.selections.oldest::<usize>(cx).reversed;
11780
11781 for selection in new_selections.iter_mut() {
11782 selection.reversed = reversed;
11783 }
11784
11785 select_next_state.done = true;
11786 self.unfold_ranges(
11787 &new_selections
11788 .iter()
11789 .map(|selection| selection.range())
11790 .collect::<Vec<_>>(),
11791 false,
11792 false,
11793 cx,
11794 );
11795 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11796 selections.select(new_selections)
11797 });
11798
11799 Ok(())
11800 }
11801
11802 pub fn select_next(
11803 &mut self,
11804 action: &SelectNext,
11805 window: &mut Window,
11806 cx: &mut Context<Self>,
11807 ) -> Result<()> {
11808 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11809 self.push_to_selection_history();
11810 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11811 self.select_next_match_internal(
11812 &display_map,
11813 action.replace_newest,
11814 Some(Autoscroll::newest()),
11815 window,
11816 cx,
11817 )?;
11818 Ok(())
11819 }
11820
11821 pub fn select_previous(
11822 &mut self,
11823 action: &SelectPrevious,
11824 window: &mut Window,
11825 cx: &mut Context<Self>,
11826 ) -> Result<()> {
11827 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11828 self.push_to_selection_history();
11829 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11830 let buffer = &display_map.buffer_snapshot;
11831 let mut selections = self.selections.all::<usize>(cx);
11832 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11833 let query = &select_prev_state.query;
11834 if !select_prev_state.done {
11835 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11836 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11837 let mut next_selected_range = None;
11838 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11839 let bytes_before_last_selection =
11840 buffer.reversed_bytes_in_range(0..last_selection.start);
11841 let bytes_after_first_selection =
11842 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11843 let query_matches = query
11844 .stream_find_iter(bytes_before_last_selection)
11845 .map(|result| (last_selection.start, result))
11846 .chain(
11847 query
11848 .stream_find_iter(bytes_after_first_selection)
11849 .map(|result| (buffer.len(), result)),
11850 );
11851 for (end_offset, query_match) in query_matches {
11852 let query_match = query_match.unwrap(); // can only fail due to I/O
11853 let offset_range =
11854 end_offset - query_match.end()..end_offset - query_match.start();
11855 let display_range = offset_range.start.to_display_point(&display_map)
11856 ..offset_range.end.to_display_point(&display_map);
11857
11858 if !select_prev_state.wordwise
11859 || (!movement::is_inside_word(&display_map, display_range.start)
11860 && !movement::is_inside_word(&display_map, display_range.end))
11861 {
11862 next_selected_range = Some(offset_range);
11863 break;
11864 }
11865 }
11866
11867 if let Some(next_selected_range) = next_selected_range {
11868 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11869 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11870 if action.replace_newest {
11871 s.delete(s.newest_anchor().id);
11872 }
11873 s.insert_range(next_selected_range);
11874 });
11875 } else {
11876 select_prev_state.done = true;
11877 }
11878 }
11879
11880 self.select_prev_state = Some(select_prev_state);
11881 } else {
11882 let mut only_carets = true;
11883 let mut same_text_selected = true;
11884 let mut selected_text = None;
11885
11886 let mut selections_iter = selections.iter().peekable();
11887 while let Some(selection) = selections_iter.next() {
11888 if selection.start != selection.end {
11889 only_carets = false;
11890 }
11891
11892 if same_text_selected {
11893 if selected_text.is_none() {
11894 selected_text =
11895 Some(buffer.text_for_range(selection.range()).collect::<String>());
11896 }
11897
11898 if let Some(next_selection) = selections_iter.peek() {
11899 if next_selection.range().len() == selection.range().len() {
11900 let next_selected_text = buffer
11901 .text_for_range(next_selection.range())
11902 .collect::<String>();
11903 if Some(next_selected_text) != selected_text {
11904 same_text_selected = false;
11905 selected_text = None;
11906 }
11907 } else {
11908 same_text_selected = false;
11909 selected_text = None;
11910 }
11911 }
11912 }
11913 }
11914
11915 if only_carets {
11916 for selection in &mut selections {
11917 let word_range = movement::surrounding_word(
11918 &display_map,
11919 selection.start.to_display_point(&display_map),
11920 );
11921 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11922 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11923 selection.goal = SelectionGoal::None;
11924 selection.reversed = false;
11925 }
11926 if selections.len() == 1 {
11927 let selection = selections
11928 .last()
11929 .expect("ensured that there's only one selection");
11930 let query = buffer
11931 .text_for_range(selection.start..selection.end)
11932 .collect::<String>();
11933 let is_empty = query.is_empty();
11934 let select_state = SelectNextState {
11935 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11936 wordwise: true,
11937 done: is_empty,
11938 };
11939 self.select_prev_state = Some(select_state);
11940 } else {
11941 self.select_prev_state = None;
11942 }
11943
11944 self.unfold_ranges(
11945 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11946 false,
11947 true,
11948 cx,
11949 );
11950 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11951 s.select(selections);
11952 });
11953 } else if let Some(selected_text) = selected_text {
11954 self.select_prev_state = Some(SelectNextState {
11955 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11956 wordwise: false,
11957 done: false,
11958 });
11959 self.select_previous(action, window, cx)?;
11960 }
11961 }
11962 Ok(())
11963 }
11964
11965 pub fn toggle_comments(
11966 &mut self,
11967 action: &ToggleComments,
11968 window: &mut Window,
11969 cx: &mut Context<Self>,
11970 ) {
11971 if self.read_only(cx) {
11972 return;
11973 }
11974 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11975 let text_layout_details = &self.text_layout_details(window);
11976 self.transact(window, cx, |this, window, cx| {
11977 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11978 let mut edits = Vec::new();
11979 let mut selection_edit_ranges = Vec::new();
11980 let mut last_toggled_row = None;
11981 let snapshot = this.buffer.read(cx).read(cx);
11982 let empty_str: Arc<str> = Arc::default();
11983 let mut suffixes_inserted = Vec::new();
11984 let ignore_indent = action.ignore_indent;
11985
11986 fn comment_prefix_range(
11987 snapshot: &MultiBufferSnapshot,
11988 row: MultiBufferRow,
11989 comment_prefix: &str,
11990 comment_prefix_whitespace: &str,
11991 ignore_indent: bool,
11992 ) -> Range<Point> {
11993 let indent_size = if ignore_indent {
11994 0
11995 } else {
11996 snapshot.indent_size_for_line(row).len
11997 };
11998
11999 let start = Point::new(row.0, indent_size);
12000
12001 let mut line_bytes = snapshot
12002 .bytes_in_range(start..snapshot.max_point())
12003 .flatten()
12004 .copied();
12005
12006 // If this line currently begins with the line comment prefix, then record
12007 // the range containing the prefix.
12008 if line_bytes
12009 .by_ref()
12010 .take(comment_prefix.len())
12011 .eq(comment_prefix.bytes())
12012 {
12013 // Include any whitespace that matches the comment prefix.
12014 let matching_whitespace_len = line_bytes
12015 .zip(comment_prefix_whitespace.bytes())
12016 .take_while(|(a, b)| a == b)
12017 .count() as u32;
12018 let end = Point::new(
12019 start.row,
12020 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12021 );
12022 start..end
12023 } else {
12024 start..start
12025 }
12026 }
12027
12028 fn comment_suffix_range(
12029 snapshot: &MultiBufferSnapshot,
12030 row: MultiBufferRow,
12031 comment_suffix: &str,
12032 comment_suffix_has_leading_space: bool,
12033 ) -> Range<Point> {
12034 let end = Point::new(row.0, snapshot.line_len(row));
12035 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12036
12037 let mut line_end_bytes = snapshot
12038 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12039 .flatten()
12040 .copied();
12041
12042 let leading_space_len = if suffix_start_column > 0
12043 && line_end_bytes.next() == Some(b' ')
12044 && comment_suffix_has_leading_space
12045 {
12046 1
12047 } else {
12048 0
12049 };
12050
12051 // If this line currently begins with the line comment prefix, then record
12052 // the range containing the prefix.
12053 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12054 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12055 start..end
12056 } else {
12057 end..end
12058 }
12059 }
12060
12061 // TODO: Handle selections that cross excerpts
12062 for selection in &mut selections {
12063 let start_column = snapshot
12064 .indent_size_for_line(MultiBufferRow(selection.start.row))
12065 .len;
12066 let language = if let Some(language) =
12067 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12068 {
12069 language
12070 } else {
12071 continue;
12072 };
12073
12074 selection_edit_ranges.clear();
12075
12076 // If multiple selections contain a given row, avoid processing that
12077 // row more than once.
12078 let mut start_row = MultiBufferRow(selection.start.row);
12079 if last_toggled_row == Some(start_row) {
12080 start_row = start_row.next_row();
12081 }
12082 let end_row =
12083 if selection.end.row > selection.start.row && selection.end.column == 0 {
12084 MultiBufferRow(selection.end.row - 1)
12085 } else {
12086 MultiBufferRow(selection.end.row)
12087 };
12088 last_toggled_row = Some(end_row);
12089
12090 if start_row > end_row {
12091 continue;
12092 }
12093
12094 // If the language has line comments, toggle those.
12095 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12096
12097 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12098 if ignore_indent {
12099 full_comment_prefixes = full_comment_prefixes
12100 .into_iter()
12101 .map(|s| Arc::from(s.trim_end()))
12102 .collect();
12103 }
12104
12105 if !full_comment_prefixes.is_empty() {
12106 let first_prefix = full_comment_prefixes
12107 .first()
12108 .expect("prefixes is non-empty");
12109 let prefix_trimmed_lengths = full_comment_prefixes
12110 .iter()
12111 .map(|p| p.trim_end_matches(' ').len())
12112 .collect::<SmallVec<[usize; 4]>>();
12113
12114 let mut all_selection_lines_are_comments = true;
12115
12116 for row in start_row.0..=end_row.0 {
12117 let row = MultiBufferRow(row);
12118 if start_row < end_row && snapshot.is_line_blank(row) {
12119 continue;
12120 }
12121
12122 let prefix_range = full_comment_prefixes
12123 .iter()
12124 .zip(prefix_trimmed_lengths.iter().copied())
12125 .map(|(prefix, trimmed_prefix_len)| {
12126 comment_prefix_range(
12127 snapshot.deref(),
12128 row,
12129 &prefix[..trimmed_prefix_len],
12130 &prefix[trimmed_prefix_len..],
12131 ignore_indent,
12132 )
12133 })
12134 .max_by_key(|range| range.end.column - range.start.column)
12135 .expect("prefixes is non-empty");
12136
12137 if prefix_range.is_empty() {
12138 all_selection_lines_are_comments = false;
12139 }
12140
12141 selection_edit_ranges.push(prefix_range);
12142 }
12143
12144 if all_selection_lines_are_comments {
12145 edits.extend(
12146 selection_edit_ranges
12147 .iter()
12148 .cloned()
12149 .map(|range| (range, empty_str.clone())),
12150 );
12151 } else {
12152 let min_column = selection_edit_ranges
12153 .iter()
12154 .map(|range| range.start.column)
12155 .min()
12156 .unwrap_or(0);
12157 edits.extend(selection_edit_ranges.iter().map(|range| {
12158 let position = Point::new(range.start.row, min_column);
12159 (position..position, first_prefix.clone())
12160 }));
12161 }
12162 } else if let Some((full_comment_prefix, comment_suffix)) =
12163 language.block_comment_delimiters()
12164 {
12165 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12166 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12167 let prefix_range = comment_prefix_range(
12168 snapshot.deref(),
12169 start_row,
12170 comment_prefix,
12171 comment_prefix_whitespace,
12172 ignore_indent,
12173 );
12174 let suffix_range = comment_suffix_range(
12175 snapshot.deref(),
12176 end_row,
12177 comment_suffix.trim_start_matches(' '),
12178 comment_suffix.starts_with(' '),
12179 );
12180
12181 if prefix_range.is_empty() || suffix_range.is_empty() {
12182 edits.push((
12183 prefix_range.start..prefix_range.start,
12184 full_comment_prefix.clone(),
12185 ));
12186 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12187 suffixes_inserted.push((end_row, comment_suffix.len()));
12188 } else {
12189 edits.push((prefix_range, empty_str.clone()));
12190 edits.push((suffix_range, empty_str.clone()));
12191 }
12192 } else {
12193 continue;
12194 }
12195 }
12196
12197 drop(snapshot);
12198 this.buffer.update(cx, |buffer, cx| {
12199 buffer.edit(edits, None, cx);
12200 });
12201
12202 // Adjust selections so that they end before any comment suffixes that
12203 // were inserted.
12204 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12205 let mut selections = this.selections.all::<Point>(cx);
12206 let snapshot = this.buffer.read(cx).read(cx);
12207 for selection in &mut selections {
12208 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12209 match row.cmp(&MultiBufferRow(selection.end.row)) {
12210 Ordering::Less => {
12211 suffixes_inserted.next();
12212 continue;
12213 }
12214 Ordering::Greater => break,
12215 Ordering::Equal => {
12216 if selection.end.column == snapshot.line_len(row) {
12217 if selection.is_empty() {
12218 selection.start.column -= suffix_len as u32;
12219 }
12220 selection.end.column -= suffix_len as u32;
12221 }
12222 break;
12223 }
12224 }
12225 }
12226 }
12227
12228 drop(snapshot);
12229 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12230 s.select(selections)
12231 });
12232
12233 let selections = this.selections.all::<Point>(cx);
12234 let selections_on_single_row = selections.windows(2).all(|selections| {
12235 selections[0].start.row == selections[1].start.row
12236 && selections[0].end.row == selections[1].end.row
12237 && selections[0].start.row == selections[0].end.row
12238 });
12239 let selections_selecting = selections
12240 .iter()
12241 .any(|selection| selection.start != selection.end);
12242 let advance_downwards = action.advance_downwards
12243 && selections_on_single_row
12244 && !selections_selecting
12245 && !matches!(this.mode, EditorMode::SingleLine { .. });
12246
12247 if advance_downwards {
12248 let snapshot = this.buffer.read(cx).snapshot(cx);
12249
12250 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12251 s.move_cursors_with(|display_snapshot, display_point, _| {
12252 let mut point = display_point.to_point(display_snapshot);
12253 point.row += 1;
12254 point = snapshot.clip_point(point, Bias::Left);
12255 let display_point = point.to_display_point(display_snapshot);
12256 let goal = SelectionGoal::HorizontalPosition(
12257 display_snapshot
12258 .x_for_display_point(display_point, text_layout_details)
12259 .into(),
12260 );
12261 (display_point, goal)
12262 })
12263 });
12264 }
12265 });
12266 }
12267
12268 pub fn select_enclosing_symbol(
12269 &mut self,
12270 _: &SelectEnclosingSymbol,
12271 window: &mut Window,
12272 cx: &mut Context<Self>,
12273 ) {
12274 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12275
12276 let buffer = self.buffer.read(cx).snapshot(cx);
12277 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12278
12279 fn update_selection(
12280 selection: &Selection<usize>,
12281 buffer_snap: &MultiBufferSnapshot,
12282 ) -> Option<Selection<usize>> {
12283 let cursor = selection.head();
12284 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12285 for symbol in symbols.iter().rev() {
12286 let start = symbol.range.start.to_offset(buffer_snap);
12287 let end = symbol.range.end.to_offset(buffer_snap);
12288 let new_range = start..end;
12289 if start < selection.start || end > selection.end {
12290 return Some(Selection {
12291 id: selection.id,
12292 start: new_range.start,
12293 end: new_range.end,
12294 goal: SelectionGoal::None,
12295 reversed: selection.reversed,
12296 });
12297 }
12298 }
12299 None
12300 }
12301
12302 let mut selected_larger_symbol = false;
12303 let new_selections = old_selections
12304 .iter()
12305 .map(|selection| match update_selection(selection, &buffer) {
12306 Some(new_selection) => {
12307 if new_selection.range() != selection.range() {
12308 selected_larger_symbol = true;
12309 }
12310 new_selection
12311 }
12312 None => selection.clone(),
12313 })
12314 .collect::<Vec<_>>();
12315
12316 if selected_larger_symbol {
12317 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12318 s.select(new_selections);
12319 });
12320 }
12321 }
12322
12323 pub fn select_larger_syntax_node(
12324 &mut self,
12325 _: &SelectLargerSyntaxNode,
12326 window: &mut Window,
12327 cx: &mut Context<Self>,
12328 ) {
12329 let Some(visible_row_count) = self.visible_row_count() else {
12330 return;
12331 };
12332 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12333 if old_selections.is_empty() {
12334 return;
12335 }
12336
12337 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12338
12339 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12340 let buffer = self.buffer.read(cx).snapshot(cx);
12341
12342 let mut selected_larger_node = false;
12343 let mut new_selections = old_selections
12344 .iter()
12345 .map(|selection| {
12346 let old_range = selection.start..selection.end;
12347 let mut new_range = old_range.clone();
12348 let mut new_node = None;
12349 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12350 {
12351 new_node = Some(node);
12352 new_range = match containing_range {
12353 MultiOrSingleBufferOffsetRange::Single(_) => break,
12354 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12355 };
12356 if !display_map.intersects_fold(new_range.start)
12357 && !display_map.intersects_fold(new_range.end)
12358 {
12359 break;
12360 }
12361 }
12362
12363 if let Some(node) = new_node {
12364 // Log the ancestor, to support using this action as a way to explore TreeSitter
12365 // nodes. Parent and grandparent are also logged because this operation will not
12366 // visit nodes that have the same range as their parent.
12367 log::info!("Node: {node:?}");
12368 let parent = node.parent();
12369 log::info!("Parent: {parent:?}");
12370 let grandparent = parent.and_then(|x| x.parent());
12371 log::info!("Grandparent: {grandparent:?}");
12372 }
12373
12374 selected_larger_node |= new_range != old_range;
12375 Selection {
12376 id: selection.id,
12377 start: new_range.start,
12378 end: new_range.end,
12379 goal: SelectionGoal::None,
12380 reversed: selection.reversed,
12381 }
12382 })
12383 .collect::<Vec<_>>();
12384
12385 if !selected_larger_node {
12386 return; // don't put this call in the history
12387 }
12388
12389 // scroll based on transformation done to the last selection created by the user
12390 let (last_old, last_new) = old_selections
12391 .last()
12392 .zip(new_selections.last().cloned())
12393 .expect("old_selections isn't empty");
12394
12395 // revert selection
12396 let is_selection_reversed = {
12397 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12398 new_selections.last_mut().expect("checked above").reversed =
12399 should_newest_selection_be_reversed;
12400 should_newest_selection_be_reversed
12401 };
12402
12403 if selected_larger_node {
12404 self.select_syntax_node_history.disable_clearing = true;
12405 self.change_selections(None, window, cx, |s| {
12406 s.select(new_selections.clone());
12407 });
12408 self.select_syntax_node_history.disable_clearing = false;
12409 }
12410
12411 let start_row = last_new.start.to_display_point(&display_map).row().0;
12412 let end_row = last_new.end.to_display_point(&display_map).row().0;
12413 let selection_height = end_row - start_row + 1;
12414 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12415
12416 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
12417 let scroll_behavior = if fits_on_the_screen {
12418 self.request_autoscroll(Autoscroll::fit(), cx);
12419 SelectSyntaxNodeScrollBehavior::FitSelection
12420 } else if is_selection_reversed {
12421 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12422 SelectSyntaxNodeScrollBehavior::CursorTop
12423 } else {
12424 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12425 SelectSyntaxNodeScrollBehavior::CursorBottom
12426 };
12427
12428 self.select_syntax_node_history.push((
12429 old_selections,
12430 scroll_behavior,
12431 is_selection_reversed,
12432 ));
12433 }
12434
12435 pub fn select_smaller_syntax_node(
12436 &mut self,
12437 _: &SelectSmallerSyntaxNode,
12438 window: &mut Window,
12439 cx: &mut Context<Self>,
12440 ) {
12441 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12442
12443 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12444 self.select_syntax_node_history.pop()
12445 {
12446 if let Some(selection) = selections.last_mut() {
12447 selection.reversed = is_selection_reversed;
12448 }
12449
12450 self.select_syntax_node_history.disable_clearing = true;
12451 self.change_selections(None, window, cx, |s| {
12452 s.select(selections.to_vec());
12453 });
12454 self.select_syntax_node_history.disable_clearing = false;
12455
12456 match scroll_behavior {
12457 SelectSyntaxNodeScrollBehavior::CursorTop => {
12458 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12459 }
12460 SelectSyntaxNodeScrollBehavior::FitSelection => {
12461 self.request_autoscroll(Autoscroll::fit(), cx);
12462 }
12463 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12464 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12465 }
12466 }
12467 }
12468 }
12469
12470 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12471 if !EditorSettings::get_global(cx).gutter.runnables {
12472 self.clear_tasks();
12473 return Task::ready(());
12474 }
12475 let project = self.project.as_ref().map(Entity::downgrade);
12476 let task_sources = self.lsp_task_sources(cx);
12477 cx.spawn_in(window, async move |editor, cx| {
12478 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12479 let Some(project) = project.and_then(|p| p.upgrade()) else {
12480 return;
12481 };
12482 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
12483 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12484 }) else {
12485 return;
12486 };
12487
12488 let hide_runnables = project
12489 .update(cx, |project, cx| {
12490 // Do not display any test indicators in non-dev server remote projects.
12491 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12492 })
12493 .unwrap_or(true);
12494 if hide_runnables {
12495 return;
12496 }
12497 let new_rows =
12498 cx.background_spawn({
12499 let snapshot = display_snapshot.clone();
12500 async move {
12501 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12502 }
12503 })
12504 .await;
12505 let Ok(lsp_tasks) =
12506 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
12507 else {
12508 return;
12509 };
12510 let lsp_tasks = lsp_tasks.await;
12511
12512 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
12513 lsp_tasks
12514 .into_iter()
12515 .flat_map(|(kind, tasks)| {
12516 tasks.into_iter().filter_map(move |(location, task)| {
12517 Some((kind.clone(), location?, task))
12518 })
12519 })
12520 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
12521 let buffer = location.target.buffer;
12522 let buffer_snapshot = buffer.read(cx).snapshot();
12523 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
12524 |(excerpt_id, snapshot, _)| {
12525 if snapshot.remote_id() == buffer_snapshot.remote_id() {
12526 display_snapshot
12527 .buffer_snapshot
12528 .anchor_in_excerpt(excerpt_id, location.target.range.start)
12529 } else {
12530 None
12531 }
12532 },
12533 );
12534 if let Some(offset) = offset {
12535 let task_buffer_range =
12536 location.target.range.to_point(&buffer_snapshot);
12537 let context_buffer_range =
12538 task_buffer_range.to_offset(&buffer_snapshot);
12539 let context_range = BufferOffset(context_buffer_range.start)
12540 ..BufferOffset(context_buffer_range.end);
12541
12542 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
12543 .or_insert_with(|| RunnableTasks {
12544 templates: Vec::new(),
12545 offset,
12546 column: task_buffer_range.start.column,
12547 extra_variables: HashMap::default(),
12548 context_range,
12549 })
12550 .templates
12551 .push((kind, task.original_task().clone()));
12552 }
12553
12554 acc
12555 })
12556 }) else {
12557 return;
12558 };
12559
12560 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12561 editor
12562 .update(cx, |editor, _| {
12563 editor.clear_tasks();
12564 for (key, mut value) in rows {
12565 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
12566 value.templates.extend(lsp_tasks.templates);
12567 }
12568
12569 editor.insert_tasks(key, value);
12570 }
12571 for (key, value) in lsp_tasks_by_rows {
12572 editor.insert_tasks(key, value);
12573 }
12574 })
12575 .ok();
12576 })
12577 }
12578 fn fetch_runnable_ranges(
12579 snapshot: &DisplaySnapshot,
12580 range: Range<Anchor>,
12581 ) -> Vec<language::RunnableRange> {
12582 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12583 }
12584
12585 fn runnable_rows(
12586 project: Entity<Project>,
12587 snapshot: DisplaySnapshot,
12588 runnable_ranges: Vec<RunnableRange>,
12589 mut cx: AsyncWindowContext,
12590 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
12591 runnable_ranges
12592 .into_iter()
12593 .filter_map(|mut runnable| {
12594 let tasks = cx
12595 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12596 .ok()?;
12597 if tasks.is_empty() {
12598 return None;
12599 }
12600
12601 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12602
12603 let row = snapshot
12604 .buffer_snapshot
12605 .buffer_line_for_row(MultiBufferRow(point.row))?
12606 .1
12607 .start
12608 .row;
12609
12610 let context_range =
12611 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12612 Some((
12613 (runnable.buffer_id, row),
12614 RunnableTasks {
12615 templates: tasks,
12616 offset: snapshot
12617 .buffer_snapshot
12618 .anchor_before(runnable.run_range.start),
12619 context_range,
12620 column: point.column,
12621 extra_variables: runnable.extra_captures,
12622 },
12623 ))
12624 })
12625 .collect()
12626 }
12627
12628 fn templates_with_tags(
12629 project: &Entity<Project>,
12630 runnable: &mut Runnable,
12631 cx: &mut App,
12632 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12633 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12634 let (worktree_id, file) = project
12635 .buffer_for_id(runnable.buffer, cx)
12636 .and_then(|buffer| buffer.read(cx).file())
12637 .map(|file| (file.worktree_id(cx), file.clone()))
12638 .unzip();
12639
12640 (
12641 project.task_store().read(cx).task_inventory().cloned(),
12642 worktree_id,
12643 file,
12644 )
12645 });
12646
12647 let mut templates_with_tags = mem::take(&mut runnable.tags)
12648 .into_iter()
12649 .flat_map(|RunnableTag(tag)| {
12650 inventory
12651 .as_ref()
12652 .into_iter()
12653 .flat_map(|inventory| {
12654 inventory.read(cx).list_tasks(
12655 file.clone(),
12656 Some(runnable.language.clone()),
12657 worktree_id,
12658 cx,
12659 )
12660 })
12661 .filter(move |(_, template)| {
12662 template.tags.iter().any(|source_tag| source_tag == &tag)
12663 })
12664 })
12665 .sorted_by_key(|(kind, _)| kind.to_owned())
12666 .collect::<Vec<_>>();
12667 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
12668 // Strongest source wins; if we have worktree tag binding, prefer that to
12669 // global and language bindings;
12670 // if we have a global binding, prefer that to language binding.
12671 let first_mismatch = templates_with_tags
12672 .iter()
12673 .position(|(tag_source, _)| tag_source != leading_tag_source);
12674 if let Some(index) = first_mismatch {
12675 templates_with_tags.truncate(index);
12676 }
12677 }
12678
12679 templates_with_tags
12680 }
12681
12682 pub fn move_to_enclosing_bracket(
12683 &mut self,
12684 _: &MoveToEnclosingBracket,
12685 window: &mut Window,
12686 cx: &mut Context<Self>,
12687 ) {
12688 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12689 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12690 s.move_offsets_with(|snapshot, selection| {
12691 let Some(enclosing_bracket_ranges) =
12692 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12693 else {
12694 return;
12695 };
12696
12697 let mut best_length = usize::MAX;
12698 let mut best_inside = false;
12699 let mut best_in_bracket_range = false;
12700 let mut best_destination = None;
12701 for (open, close) in enclosing_bracket_ranges {
12702 let close = close.to_inclusive();
12703 let length = close.end() - open.start;
12704 let inside = selection.start >= open.end && selection.end <= *close.start();
12705 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12706 || close.contains(&selection.head());
12707
12708 // If best is next to a bracket and current isn't, skip
12709 if !in_bracket_range && best_in_bracket_range {
12710 continue;
12711 }
12712
12713 // Prefer smaller lengths unless best is inside and current isn't
12714 if length > best_length && (best_inside || !inside) {
12715 continue;
12716 }
12717
12718 best_length = length;
12719 best_inside = inside;
12720 best_in_bracket_range = in_bracket_range;
12721 best_destination = Some(
12722 if close.contains(&selection.start) && close.contains(&selection.end) {
12723 if inside { open.end } else { open.start }
12724 } else if inside {
12725 *close.start()
12726 } else {
12727 *close.end()
12728 },
12729 );
12730 }
12731
12732 if let Some(destination) = best_destination {
12733 selection.collapse_to(destination, SelectionGoal::None);
12734 }
12735 })
12736 });
12737 }
12738
12739 pub fn undo_selection(
12740 &mut self,
12741 _: &UndoSelection,
12742 window: &mut Window,
12743 cx: &mut Context<Self>,
12744 ) {
12745 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12746 self.end_selection(window, cx);
12747 self.selection_history.mode = SelectionHistoryMode::Undoing;
12748 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12749 self.change_selections(None, window, cx, |s| {
12750 s.select_anchors(entry.selections.to_vec())
12751 });
12752 self.select_next_state = entry.select_next_state;
12753 self.select_prev_state = entry.select_prev_state;
12754 self.add_selections_state = entry.add_selections_state;
12755 self.request_autoscroll(Autoscroll::newest(), cx);
12756 }
12757 self.selection_history.mode = SelectionHistoryMode::Normal;
12758 }
12759
12760 pub fn redo_selection(
12761 &mut self,
12762 _: &RedoSelection,
12763 window: &mut Window,
12764 cx: &mut Context<Self>,
12765 ) {
12766 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12767 self.end_selection(window, cx);
12768 self.selection_history.mode = SelectionHistoryMode::Redoing;
12769 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12770 self.change_selections(None, window, cx, |s| {
12771 s.select_anchors(entry.selections.to_vec())
12772 });
12773 self.select_next_state = entry.select_next_state;
12774 self.select_prev_state = entry.select_prev_state;
12775 self.add_selections_state = entry.add_selections_state;
12776 self.request_autoscroll(Autoscroll::newest(), cx);
12777 }
12778 self.selection_history.mode = SelectionHistoryMode::Normal;
12779 }
12780
12781 pub fn expand_excerpts(
12782 &mut self,
12783 action: &ExpandExcerpts,
12784 _: &mut Window,
12785 cx: &mut Context<Self>,
12786 ) {
12787 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12788 }
12789
12790 pub fn expand_excerpts_down(
12791 &mut self,
12792 action: &ExpandExcerptsDown,
12793 _: &mut Window,
12794 cx: &mut Context<Self>,
12795 ) {
12796 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12797 }
12798
12799 pub fn expand_excerpts_up(
12800 &mut self,
12801 action: &ExpandExcerptsUp,
12802 _: &mut Window,
12803 cx: &mut Context<Self>,
12804 ) {
12805 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12806 }
12807
12808 pub fn expand_excerpts_for_direction(
12809 &mut self,
12810 lines: u32,
12811 direction: ExpandExcerptDirection,
12812
12813 cx: &mut Context<Self>,
12814 ) {
12815 let selections = self.selections.disjoint_anchors();
12816
12817 let lines = if lines == 0 {
12818 EditorSettings::get_global(cx).expand_excerpt_lines
12819 } else {
12820 lines
12821 };
12822
12823 self.buffer.update(cx, |buffer, cx| {
12824 let snapshot = buffer.snapshot(cx);
12825 let mut excerpt_ids = selections
12826 .iter()
12827 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12828 .collect::<Vec<_>>();
12829 excerpt_ids.sort();
12830 excerpt_ids.dedup();
12831 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12832 })
12833 }
12834
12835 pub fn expand_excerpt(
12836 &mut self,
12837 excerpt: ExcerptId,
12838 direction: ExpandExcerptDirection,
12839 window: &mut Window,
12840 cx: &mut Context<Self>,
12841 ) {
12842 let current_scroll_position = self.scroll_position(cx);
12843 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
12844 let mut should_scroll_up = false;
12845
12846 if direction == ExpandExcerptDirection::Down {
12847 let multi_buffer = self.buffer.read(cx);
12848 let snapshot = multi_buffer.snapshot(cx);
12849 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
12850 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
12851 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
12852 let buffer_snapshot = buffer.read(cx).snapshot();
12853 let excerpt_end_row =
12854 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
12855 let last_row = buffer_snapshot.max_point().row;
12856 let lines_below = last_row.saturating_sub(excerpt_end_row);
12857 should_scroll_up = lines_below >= lines_to_expand;
12858 }
12859 }
12860 }
12861 }
12862
12863 self.buffer.update(cx, |buffer, cx| {
12864 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
12865 });
12866
12867 if should_scroll_up {
12868 let new_scroll_position =
12869 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
12870 self.set_scroll_position(new_scroll_position, window, cx);
12871 }
12872 }
12873
12874 pub fn go_to_singleton_buffer_point(
12875 &mut self,
12876 point: Point,
12877 window: &mut Window,
12878 cx: &mut Context<Self>,
12879 ) {
12880 self.go_to_singleton_buffer_range(point..point, window, cx);
12881 }
12882
12883 pub fn go_to_singleton_buffer_range(
12884 &mut self,
12885 range: Range<Point>,
12886 window: &mut Window,
12887 cx: &mut Context<Self>,
12888 ) {
12889 let multibuffer = self.buffer().read(cx);
12890 let Some(buffer) = multibuffer.as_singleton() else {
12891 return;
12892 };
12893 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12894 return;
12895 };
12896 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12897 return;
12898 };
12899 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12900 s.select_anchor_ranges([start..end])
12901 });
12902 }
12903
12904 fn go_to_diagnostic(
12905 &mut self,
12906 _: &GoToDiagnostic,
12907 window: &mut Window,
12908 cx: &mut Context<Self>,
12909 ) {
12910 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12911 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12912 }
12913
12914 fn go_to_prev_diagnostic(
12915 &mut self,
12916 _: &GoToPreviousDiagnostic,
12917 window: &mut Window,
12918 cx: &mut Context<Self>,
12919 ) {
12920 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12921 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12922 }
12923
12924 pub fn go_to_diagnostic_impl(
12925 &mut self,
12926 direction: Direction,
12927 window: &mut Window,
12928 cx: &mut Context<Self>,
12929 ) {
12930 let buffer = self.buffer.read(cx).snapshot(cx);
12931 let selection = self.selections.newest::<usize>(cx);
12932 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12933 if direction == Direction::Next {
12934 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12935 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12936 return;
12937 };
12938 self.activate_diagnostics(
12939 buffer_id,
12940 popover.local_diagnostic.diagnostic.group_id,
12941 window,
12942 cx,
12943 );
12944 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12945 let primary_range_start = active_diagnostics.primary_range.start;
12946 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12947 let mut new_selection = s.newest_anchor().clone();
12948 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12949 s.select_anchors(vec![new_selection.clone()]);
12950 });
12951 self.refresh_inline_completion(false, true, window, cx);
12952 }
12953 return;
12954 }
12955 }
12956
12957 let active_group_id = self
12958 .active_diagnostics
12959 .as_ref()
12960 .map(|active_group| active_group.group_id);
12961 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12962 active_diagnostics
12963 .primary_range
12964 .to_offset(&buffer)
12965 .to_inclusive()
12966 });
12967 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12968 if active_primary_range.contains(&selection.head()) {
12969 *active_primary_range.start()
12970 } else {
12971 selection.head()
12972 }
12973 } else {
12974 selection.head()
12975 };
12976
12977 let snapshot = self.snapshot(window, cx);
12978 let primary_diagnostics_before = buffer
12979 .diagnostics_in_range::<usize>(0..search_start)
12980 .filter(|entry| entry.diagnostic.is_primary)
12981 .filter(|entry| entry.range.start != entry.range.end)
12982 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12983 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12984 .collect::<Vec<_>>();
12985 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12986 primary_diagnostics_before
12987 .iter()
12988 .position(|entry| entry.diagnostic.group_id == active_group_id)
12989 });
12990
12991 let primary_diagnostics_after = buffer
12992 .diagnostics_in_range::<usize>(search_start..buffer.len())
12993 .filter(|entry| entry.diagnostic.is_primary)
12994 .filter(|entry| entry.range.start != entry.range.end)
12995 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12996 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12997 .collect::<Vec<_>>();
12998 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12999 primary_diagnostics_after
13000 .iter()
13001 .enumerate()
13002 .rev()
13003 .find_map(|(i, entry)| {
13004 if entry.diagnostic.group_id == active_group_id {
13005 Some(i)
13006 } else {
13007 None
13008 }
13009 })
13010 });
13011
13012 let next_primary_diagnostic = match direction {
13013 Direction::Prev => primary_diagnostics_before
13014 .iter()
13015 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
13016 .rev()
13017 .next(),
13018 Direction::Next => primary_diagnostics_after
13019 .iter()
13020 .skip(
13021 last_same_group_diagnostic_after
13022 .map(|index| index + 1)
13023 .unwrap_or(0),
13024 )
13025 .next(),
13026 };
13027
13028 // Cycle around to the start of the buffer, potentially moving back to the start of
13029 // the currently active diagnostic.
13030 let cycle_around = || match direction {
13031 Direction::Prev => primary_diagnostics_after
13032 .iter()
13033 .rev()
13034 .chain(primary_diagnostics_before.iter().rev())
13035 .next(),
13036 Direction::Next => primary_diagnostics_before
13037 .iter()
13038 .chain(primary_diagnostics_after.iter())
13039 .next(),
13040 };
13041
13042 if let Some((primary_range, group_id)) = next_primary_diagnostic
13043 .or_else(cycle_around)
13044 .map(|entry| (&entry.range, entry.diagnostic.group_id))
13045 {
13046 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
13047 return;
13048 };
13049 self.activate_diagnostics(buffer_id, group_id, window, cx);
13050 if self.active_diagnostics.is_some() {
13051 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13052 s.select(vec![Selection {
13053 id: selection.id,
13054 start: primary_range.start,
13055 end: primary_range.start,
13056 reversed: false,
13057 goal: SelectionGoal::None,
13058 }]);
13059 });
13060 self.refresh_inline_completion(false, true, window, cx);
13061 }
13062 }
13063 }
13064
13065 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13066 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13067 let snapshot = self.snapshot(window, cx);
13068 let selection = self.selections.newest::<Point>(cx);
13069 self.go_to_hunk_before_or_after_position(
13070 &snapshot,
13071 selection.head(),
13072 Direction::Next,
13073 window,
13074 cx,
13075 );
13076 }
13077
13078 pub fn go_to_hunk_before_or_after_position(
13079 &mut self,
13080 snapshot: &EditorSnapshot,
13081 position: Point,
13082 direction: Direction,
13083 window: &mut Window,
13084 cx: &mut Context<Editor>,
13085 ) {
13086 let row = if direction == Direction::Next {
13087 self.hunk_after_position(snapshot, position)
13088 .map(|hunk| hunk.row_range.start)
13089 } else {
13090 self.hunk_before_position(snapshot, position)
13091 };
13092
13093 if let Some(row) = row {
13094 let destination = Point::new(row.0, 0);
13095 let autoscroll = Autoscroll::center();
13096
13097 self.unfold_ranges(&[destination..destination], false, false, cx);
13098 self.change_selections(Some(autoscroll), window, cx, |s| {
13099 s.select_ranges([destination..destination]);
13100 });
13101 }
13102 }
13103
13104 fn hunk_after_position(
13105 &mut self,
13106 snapshot: &EditorSnapshot,
13107 position: Point,
13108 ) -> Option<MultiBufferDiffHunk> {
13109 snapshot
13110 .buffer_snapshot
13111 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13112 .find(|hunk| hunk.row_range.start.0 > position.row)
13113 .or_else(|| {
13114 snapshot
13115 .buffer_snapshot
13116 .diff_hunks_in_range(Point::zero()..position)
13117 .find(|hunk| hunk.row_range.end.0 < position.row)
13118 })
13119 }
13120
13121 fn go_to_prev_hunk(
13122 &mut self,
13123 _: &GoToPreviousHunk,
13124 window: &mut Window,
13125 cx: &mut Context<Self>,
13126 ) {
13127 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13128 let snapshot = self.snapshot(window, cx);
13129 let selection = self.selections.newest::<Point>(cx);
13130 self.go_to_hunk_before_or_after_position(
13131 &snapshot,
13132 selection.head(),
13133 Direction::Prev,
13134 window,
13135 cx,
13136 );
13137 }
13138
13139 fn hunk_before_position(
13140 &mut self,
13141 snapshot: &EditorSnapshot,
13142 position: Point,
13143 ) -> Option<MultiBufferRow> {
13144 snapshot
13145 .buffer_snapshot
13146 .diff_hunk_before(position)
13147 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13148 }
13149
13150 fn go_to_line<T: 'static>(
13151 &mut self,
13152 position: Anchor,
13153 highlight_color: Option<Hsla>,
13154 window: &mut Window,
13155 cx: &mut Context<Self>,
13156 ) {
13157 let snapshot = self.snapshot(window, cx).display_snapshot;
13158 let position = position.to_point(&snapshot.buffer_snapshot);
13159 let start = snapshot
13160 .buffer_snapshot
13161 .clip_point(Point::new(position.row, 0), Bias::Left);
13162 let end = start + Point::new(1, 0);
13163 let start = snapshot.buffer_snapshot.anchor_before(start);
13164 let end = snapshot.buffer_snapshot.anchor_before(end);
13165
13166 self.highlight_rows::<T>(
13167 start..end,
13168 highlight_color
13169 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13170 false,
13171 cx,
13172 );
13173 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13174 }
13175
13176 pub fn go_to_definition(
13177 &mut self,
13178 _: &GoToDefinition,
13179 window: &mut Window,
13180 cx: &mut Context<Self>,
13181 ) -> Task<Result<Navigated>> {
13182 let definition =
13183 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13184 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13185 cx.spawn_in(window, async move |editor, cx| {
13186 if definition.await? == Navigated::Yes {
13187 return Ok(Navigated::Yes);
13188 }
13189 match fallback_strategy {
13190 GoToDefinitionFallback::None => Ok(Navigated::No),
13191 GoToDefinitionFallback::FindAllReferences => {
13192 match editor.update_in(cx, |editor, window, cx| {
13193 editor.find_all_references(&FindAllReferences, window, cx)
13194 })? {
13195 Some(references) => references.await,
13196 None => Ok(Navigated::No),
13197 }
13198 }
13199 }
13200 })
13201 }
13202
13203 pub fn go_to_declaration(
13204 &mut self,
13205 _: &GoToDeclaration,
13206 window: &mut Window,
13207 cx: &mut Context<Self>,
13208 ) -> Task<Result<Navigated>> {
13209 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13210 }
13211
13212 pub fn go_to_declaration_split(
13213 &mut self,
13214 _: &GoToDeclaration,
13215 window: &mut Window,
13216 cx: &mut Context<Self>,
13217 ) -> Task<Result<Navigated>> {
13218 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13219 }
13220
13221 pub fn go_to_implementation(
13222 &mut self,
13223 _: &GoToImplementation,
13224 window: &mut Window,
13225 cx: &mut Context<Self>,
13226 ) -> Task<Result<Navigated>> {
13227 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13228 }
13229
13230 pub fn go_to_implementation_split(
13231 &mut self,
13232 _: &GoToImplementationSplit,
13233 window: &mut Window,
13234 cx: &mut Context<Self>,
13235 ) -> Task<Result<Navigated>> {
13236 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13237 }
13238
13239 pub fn go_to_type_definition(
13240 &mut self,
13241 _: &GoToTypeDefinition,
13242 window: &mut Window,
13243 cx: &mut Context<Self>,
13244 ) -> Task<Result<Navigated>> {
13245 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13246 }
13247
13248 pub fn go_to_definition_split(
13249 &mut self,
13250 _: &GoToDefinitionSplit,
13251 window: &mut Window,
13252 cx: &mut Context<Self>,
13253 ) -> Task<Result<Navigated>> {
13254 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13255 }
13256
13257 pub fn go_to_type_definition_split(
13258 &mut self,
13259 _: &GoToTypeDefinitionSplit,
13260 window: &mut Window,
13261 cx: &mut Context<Self>,
13262 ) -> Task<Result<Navigated>> {
13263 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13264 }
13265
13266 fn go_to_definition_of_kind(
13267 &mut self,
13268 kind: GotoDefinitionKind,
13269 split: bool,
13270 window: &mut Window,
13271 cx: &mut Context<Self>,
13272 ) -> Task<Result<Navigated>> {
13273 let Some(provider) = self.semantics_provider.clone() else {
13274 return Task::ready(Ok(Navigated::No));
13275 };
13276 let head = self.selections.newest::<usize>(cx).head();
13277 let buffer = self.buffer.read(cx);
13278 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13279 text_anchor
13280 } else {
13281 return Task::ready(Ok(Navigated::No));
13282 };
13283
13284 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13285 return Task::ready(Ok(Navigated::No));
13286 };
13287
13288 cx.spawn_in(window, async move |editor, cx| {
13289 let definitions = definitions.await?;
13290 let navigated = editor
13291 .update_in(cx, |editor, window, cx| {
13292 editor.navigate_to_hover_links(
13293 Some(kind),
13294 definitions
13295 .into_iter()
13296 .filter(|location| {
13297 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13298 })
13299 .map(HoverLink::Text)
13300 .collect::<Vec<_>>(),
13301 split,
13302 window,
13303 cx,
13304 )
13305 })?
13306 .await?;
13307 anyhow::Ok(navigated)
13308 })
13309 }
13310
13311 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13312 let selection = self.selections.newest_anchor();
13313 let head = selection.head();
13314 let tail = selection.tail();
13315
13316 let Some((buffer, start_position)) =
13317 self.buffer.read(cx).text_anchor_for_position(head, cx)
13318 else {
13319 return;
13320 };
13321
13322 let end_position = if head != tail {
13323 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13324 return;
13325 };
13326 Some(pos)
13327 } else {
13328 None
13329 };
13330
13331 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13332 let url = if let Some(end_pos) = end_position {
13333 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13334 } else {
13335 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13336 };
13337
13338 if let Some(url) = url {
13339 editor.update(cx, |_, cx| {
13340 cx.open_url(&url);
13341 })
13342 } else {
13343 Ok(())
13344 }
13345 });
13346
13347 url_finder.detach();
13348 }
13349
13350 pub fn open_selected_filename(
13351 &mut self,
13352 _: &OpenSelectedFilename,
13353 window: &mut Window,
13354 cx: &mut Context<Self>,
13355 ) {
13356 let Some(workspace) = self.workspace() else {
13357 return;
13358 };
13359
13360 let position = self.selections.newest_anchor().head();
13361
13362 let Some((buffer, buffer_position)) =
13363 self.buffer.read(cx).text_anchor_for_position(position, cx)
13364 else {
13365 return;
13366 };
13367
13368 let project = self.project.clone();
13369
13370 cx.spawn_in(window, async move |_, cx| {
13371 let result = find_file(&buffer, project, buffer_position, cx).await;
13372
13373 if let Some((_, path)) = result {
13374 workspace
13375 .update_in(cx, |workspace, window, cx| {
13376 workspace.open_resolved_path(path, window, cx)
13377 })?
13378 .await?;
13379 }
13380 anyhow::Ok(())
13381 })
13382 .detach();
13383 }
13384
13385 pub(crate) fn navigate_to_hover_links(
13386 &mut self,
13387 kind: Option<GotoDefinitionKind>,
13388 mut definitions: Vec<HoverLink>,
13389 split: bool,
13390 window: &mut Window,
13391 cx: &mut Context<Editor>,
13392 ) -> Task<Result<Navigated>> {
13393 // If there is one definition, just open it directly
13394 if definitions.len() == 1 {
13395 let definition = definitions.pop().unwrap();
13396
13397 enum TargetTaskResult {
13398 Location(Option<Location>),
13399 AlreadyNavigated,
13400 }
13401
13402 let target_task = match definition {
13403 HoverLink::Text(link) => {
13404 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13405 }
13406 HoverLink::InlayHint(lsp_location, server_id) => {
13407 let computation =
13408 self.compute_target_location(lsp_location, server_id, window, cx);
13409 cx.background_spawn(async move {
13410 let location = computation.await?;
13411 Ok(TargetTaskResult::Location(location))
13412 })
13413 }
13414 HoverLink::Url(url) => {
13415 cx.open_url(&url);
13416 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13417 }
13418 HoverLink::File(path) => {
13419 if let Some(workspace) = self.workspace() {
13420 cx.spawn_in(window, async move |_, cx| {
13421 workspace
13422 .update_in(cx, |workspace, window, cx| {
13423 workspace.open_resolved_path(path, window, cx)
13424 })?
13425 .await
13426 .map(|_| TargetTaskResult::AlreadyNavigated)
13427 })
13428 } else {
13429 Task::ready(Ok(TargetTaskResult::Location(None)))
13430 }
13431 }
13432 };
13433 cx.spawn_in(window, async move |editor, cx| {
13434 let target = match target_task.await.context("target resolution task")? {
13435 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13436 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13437 TargetTaskResult::Location(Some(target)) => target,
13438 };
13439
13440 editor.update_in(cx, |editor, window, cx| {
13441 let Some(workspace) = editor.workspace() else {
13442 return Navigated::No;
13443 };
13444 let pane = workspace.read(cx).active_pane().clone();
13445
13446 let range = target.range.to_point(target.buffer.read(cx));
13447 let range = editor.range_for_match(&range);
13448 let range = collapse_multiline_range(range);
13449
13450 if !split
13451 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13452 {
13453 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13454 } else {
13455 window.defer(cx, move |window, cx| {
13456 let target_editor: Entity<Self> =
13457 workspace.update(cx, |workspace, cx| {
13458 let pane = if split {
13459 workspace.adjacent_pane(window, cx)
13460 } else {
13461 workspace.active_pane().clone()
13462 };
13463
13464 workspace.open_project_item(
13465 pane,
13466 target.buffer.clone(),
13467 true,
13468 true,
13469 window,
13470 cx,
13471 )
13472 });
13473 target_editor.update(cx, |target_editor, cx| {
13474 // When selecting a definition in a different buffer, disable the nav history
13475 // to avoid creating a history entry at the previous cursor location.
13476 pane.update(cx, |pane, _| pane.disable_history());
13477 target_editor.go_to_singleton_buffer_range(range, window, cx);
13478 pane.update(cx, |pane, _| pane.enable_history());
13479 });
13480 });
13481 }
13482 Navigated::Yes
13483 })
13484 })
13485 } else if !definitions.is_empty() {
13486 cx.spawn_in(window, async move |editor, cx| {
13487 let (title, location_tasks, workspace) = editor
13488 .update_in(cx, |editor, window, cx| {
13489 let tab_kind = match kind {
13490 Some(GotoDefinitionKind::Implementation) => "Implementations",
13491 _ => "Definitions",
13492 };
13493 let title = definitions
13494 .iter()
13495 .find_map(|definition| match definition {
13496 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13497 let buffer = origin.buffer.read(cx);
13498 format!(
13499 "{} for {}",
13500 tab_kind,
13501 buffer
13502 .text_for_range(origin.range.clone())
13503 .collect::<String>()
13504 )
13505 }),
13506 HoverLink::InlayHint(_, _) => None,
13507 HoverLink::Url(_) => None,
13508 HoverLink::File(_) => None,
13509 })
13510 .unwrap_or(tab_kind.to_string());
13511 let location_tasks = definitions
13512 .into_iter()
13513 .map(|definition| match definition {
13514 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13515 HoverLink::InlayHint(lsp_location, server_id) => editor
13516 .compute_target_location(lsp_location, server_id, window, cx),
13517 HoverLink::Url(_) => Task::ready(Ok(None)),
13518 HoverLink::File(_) => Task::ready(Ok(None)),
13519 })
13520 .collect::<Vec<_>>();
13521 (title, location_tasks, editor.workspace().clone())
13522 })
13523 .context("location tasks preparation")?;
13524
13525 let locations = future::join_all(location_tasks)
13526 .await
13527 .into_iter()
13528 .filter_map(|location| location.transpose())
13529 .collect::<Result<_>>()
13530 .context("location tasks")?;
13531
13532 let Some(workspace) = workspace else {
13533 return Ok(Navigated::No);
13534 };
13535 let opened = workspace
13536 .update_in(cx, |workspace, window, cx| {
13537 Self::open_locations_in_multibuffer(
13538 workspace,
13539 locations,
13540 title,
13541 split,
13542 MultibufferSelectionMode::First,
13543 window,
13544 cx,
13545 )
13546 })
13547 .ok();
13548
13549 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13550 })
13551 } else {
13552 Task::ready(Ok(Navigated::No))
13553 }
13554 }
13555
13556 fn compute_target_location(
13557 &self,
13558 lsp_location: lsp::Location,
13559 server_id: LanguageServerId,
13560 window: &mut Window,
13561 cx: &mut Context<Self>,
13562 ) -> Task<anyhow::Result<Option<Location>>> {
13563 let Some(project) = self.project.clone() else {
13564 return Task::ready(Ok(None));
13565 };
13566
13567 cx.spawn_in(window, async move |editor, cx| {
13568 let location_task = editor.update(cx, |_, cx| {
13569 project.update(cx, |project, cx| {
13570 let language_server_name = project
13571 .language_server_statuses(cx)
13572 .find(|(id, _)| server_id == *id)
13573 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13574 language_server_name.map(|language_server_name| {
13575 project.open_local_buffer_via_lsp(
13576 lsp_location.uri.clone(),
13577 server_id,
13578 language_server_name,
13579 cx,
13580 )
13581 })
13582 })
13583 })?;
13584 let location = match location_task {
13585 Some(task) => Some({
13586 let target_buffer_handle = task.await.context("open local buffer")?;
13587 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13588 let target_start = target_buffer
13589 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13590 let target_end = target_buffer
13591 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13592 target_buffer.anchor_after(target_start)
13593 ..target_buffer.anchor_before(target_end)
13594 })?;
13595 Location {
13596 buffer: target_buffer_handle,
13597 range,
13598 }
13599 }),
13600 None => None,
13601 };
13602 Ok(location)
13603 })
13604 }
13605
13606 pub fn find_all_references(
13607 &mut self,
13608 _: &FindAllReferences,
13609 window: &mut Window,
13610 cx: &mut Context<Self>,
13611 ) -> Option<Task<Result<Navigated>>> {
13612 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13613
13614 let selection = self.selections.newest::<usize>(cx);
13615 let multi_buffer = self.buffer.read(cx);
13616 let head = selection.head();
13617
13618 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13619 let head_anchor = multi_buffer_snapshot.anchor_at(
13620 head,
13621 if head < selection.tail() {
13622 Bias::Right
13623 } else {
13624 Bias::Left
13625 },
13626 );
13627
13628 match self
13629 .find_all_references_task_sources
13630 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13631 {
13632 Ok(_) => {
13633 log::info!(
13634 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13635 );
13636 return None;
13637 }
13638 Err(i) => {
13639 self.find_all_references_task_sources.insert(i, head_anchor);
13640 }
13641 }
13642
13643 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13644 let workspace = self.workspace()?;
13645 let project = workspace.read(cx).project().clone();
13646 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13647 Some(cx.spawn_in(window, async move |editor, cx| {
13648 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13649 if let Ok(i) = editor
13650 .find_all_references_task_sources
13651 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13652 {
13653 editor.find_all_references_task_sources.remove(i);
13654 }
13655 });
13656
13657 let locations = references.await?;
13658 if locations.is_empty() {
13659 return anyhow::Ok(Navigated::No);
13660 }
13661
13662 workspace.update_in(cx, |workspace, window, cx| {
13663 let title = locations
13664 .first()
13665 .as_ref()
13666 .map(|location| {
13667 let buffer = location.buffer.read(cx);
13668 format!(
13669 "References to `{}`",
13670 buffer
13671 .text_for_range(location.range.clone())
13672 .collect::<String>()
13673 )
13674 })
13675 .unwrap();
13676 Self::open_locations_in_multibuffer(
13677 workspace,
13678 locations,
13679 title,
13680 false,
13681 MultibufferSelectionMode::First,
13682 window,
13683 cx,
13684 );
13685 Navigated::Yes
13686 })
13687 }))
13688 }
13689
13690 /// Opens a multibuffer with the given project locations in it
13691 pub fn open_locations_in_multibuffer(
13692 workspace: &mut Workspace,
13693 mut locations: Vec<Location>,
13694 title: String,
13695 split: bool,
13696 multibuffer_selection_mode: MultibufferSelectionMode,
13697 window: &mut Window,
13698 cx: &mut Context<Workspace>,
13699 ) {
13700 // If there are multiple definitions, open them in a multibuffer
13701 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13702 let mut locations = locations.into_iter().peekable();
13703 let mut ranges: Vec<Range<Anchor>> = Vec::new();
13704 let capability = workspace.project().read(cx).capability();
13705
13706 let excerpt_buffer = cx.new(|cx| {
13707 let mut multibuffer = MultiBuffer::new(capability);
13708 while let Some(location) = locations.next() {
13709 let buffer = location.buffer.read(cx);
13710 let mut ranges_for_buffer = Vec::new();
13711 let range = location.range.to_point(buffer);
13712 ranges_for_buffer.push(range.clone());
13713
13714 while let Some(next_location) = locations.peek() {
13715 if next_location.buffer == location.buffer {
13716 ranges_for_buffer.push(next_location.range.to_point(buffer));
13717 locations.next();
13718 } else {
13719 break;
13720 }
13721 }
13722
13723 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13724 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
13725 PathKey::for_buffer(&location.buffer, cx),
13726 location.buffer.clone(),
13727 ranges_for_buffer,
13728 DEFAULT_MULTIBUFFER_CONTEXT,
13729 cx,
13730 );
13731 ranges.extend(new_ranges)
13732 }
13733
13734 multibuffer.with_title(title)
13735 });
13736
13737 let editor = cx.new(|cx| {
13738 Editor::for_multibuffer(
13739 excerpt_buffer,
13740 Some(workspace.project().clone()),
13741 window,
13742 cx,
13743 )
13744 });
13745 editor.update(cx, |editor, cx| {
13746 match multibuffer_selection_mode {
13747 MultibufferSelectionMode::First => {
13748 if let Some(first_range) = ranges.first() {
13749 editor.change_selections(None, window, cx, |selections| {
13750 selections.clear_disjoint();
13751 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13752 });
13753 }
13754 editor.highlight_background::<Self>(
13755 &ranges,
13756 |theme| theme.editor_highlighted_line_background,
13757 cx,
13758 );
13759 }
13760 MultibufferSelectionMode::All => {
13761 editor.change_selections(None, window, cx, |selections| {
13762 selections.clear_disjoint();
13763 selections.select_anchor_ranges(ranges);
13764 });
13765 }
13766 }
13767 editor.register_buffers_with_language_servers(cx);
13768 });
13769
13770 let item = Box::new(editor);
13771 let item_id = item.item_id();
13772
13773 if split {
13774 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13775 } else {
13776 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13777 let (preview_item_id, preview_item_idx) =
13778 workspace.active_pane().update(cx, |pane, _| {
13779 (pane.preview_item_id(), pane.preview_item_idx())
13780 });
13781
13782 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13783
13784 if let Some(preview_item_id) = preview_item_id {
13785 workspace.active_pane().update(cx, |pane, cx| {
13786 pane.remove_item(preview_item_id, false, false, window, cx);
13787 });
13788 }
13789 } else {
13790 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13791 }
13792 }
13793 workspace.active_pane().update(cx, |pane, cx| {
13794 pane.set_preview_item_id(Some(item_id), cx);
13795 });
13796 }
13797
13798 pub fn rename(
13799 &mut self,
13800 _: &Rename,
13801 window: &mut Window,
13802 cx: &mut Context<Self>,
13803 ) -> Option<Task<Result<()>>> {
13804 use language::ToOffset as _;
13805
13806 let provider = self.semantics_provider.clone()?;
13807 let selection = self.selections.newest_anchor().clone();
13808 let (cursor_buffer, cursor_buffer_position) = self
13809 .buffer
13810 .read(cx)
13811 .text_anchor_for_position(selection.head(), cx)?;
13812 let (tail_buffer, cursor_buffer_position_end) = self
13813 .buffer
13814 .read(cx)
13815 .text_anchor_for_position(selection.tail(), cx)?;
13816 if tail_buffer != cursor_buffer {
13817 return None;
13818 }
13819
13820 let snapshot = cursor_buffer.read(cx).snapshot();
13821 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13822 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13823 let prepare_rename = provider
13824 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13825 .unwrap_or_else(|| Task::ready(Ok(None)));
13826 drop(snapshot);
13827
13828 Some(cx.spawn_in(window, async move |this, cx| {
13829 let rename_range = if let Some(range) = prepare_rename.await? {
13830 Some(range)
13831 } else {
13832 this.update(cx, |this, cx| {
13833 let buffer = this.buffer.read(cx).snapshot(cx);
13834 let mut buffer_highlights = this
13835 .document_highlights_for_position(selection.head(), &buffer)
13836 .filter(|highlight| {
13837 highlight.start.excerpt_id == selection.head().excerpt_id
13838 && highlight.end.excerpt_id == selection.head().excerpt_id
13839 });
13840 buffer_highlights
13841 .next()
13842 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13843 })?
13844 };
13845 if let Some(rename_range) = rename_range {
13846 this.update_in(cx, |this, window, cx| {
13847 let snapshot = cursor_buffer.read(cx).snapshot();
13848 let rename_buffer_range = rename_range.to_offset(&snapshot);
13849 let cursor_offset_in_rename_range =
13850 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13851 let cursor_offset_in_rename_range_end =
13852 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13853
13854 this.take_rename(false, window, cx);
13855 let buffer = this.buffer.read(cx).read(cx);
13856 let cursor_offset = selection.head().to_offset(&buffer);
13857 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13858 let rename_end = rename_start + rename_buffer_range.len();
13859 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13860 let mut old_highlight_id = None;
13861 let old_name: Arc<str> = buffer
13862 .chunks(rename_start..rename_end, true)
13863 .map(|chunk| {
13864 if old_highlight_id.is_none() {
13865 old_highlight_id = chunk.syntax_highlight_id;
13866 }
13867 chunk.text
13868 })
13869 .collect::<String>()
13870 .into();
13871
13872 drop(buffer);
13873
13874 // Position the selection in the rename editor so that it matches the current selection.
13875 this.show_local_selections = false;
13876 let rename_editor = cx.new(|cx| {
13877 let mut editor = Editor::single_line(window, cx);
13878 editor.buffer.update(cx, |buffer, cx| {
13879 buffer.edit([(0..0, old_name.clone())], None, cx)
13880 });
13881 let rename_selection_range = match cursor_offset_in_rename_range
13882 .cmp(&cursor_offset_in_rename_range_end)
13883 {
13884 Ordering::Equal => {
13885 editor.select_all(&SelectAll, window, cx);
13886 return editor;
13887 }
13888 Ordering::Less => {
13889 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13890 }
13891 Ordering::Greater => {
13892 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13893 }
13894 };
13895 if rename_selection_range.end > old_name.len() {
13896 editor.select_all(&SelectAll, window, cx);
13897 } else {
13898 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13899 s.select_ranges([rename_selection_range]);
13900 });
13901 }
13902 editor
13903 });
13904 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13905 if e == &EditorEvent::Focused {
13906 cx.emit(EditorEvent::FocusedIn)
13907 }
13908 })
13909 .detach();
13910
13911 let write_highlights =
13912 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13913 let read_highlights =
13914 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13915 let ranges = write_highlights
13916 .iter()
13917 .flat_map(|(_, ranges)| ranges.iter())
13918 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13919 .cloned()
13920 .collect();
13921
13922 this.highlight_text::<Rename>(
13923 ranges,
13924 HighlightStyle {
13925 fade_out: Some(0.6),
13926 ..Default::default()
13927 },
13928 cx,
13929 );
13930 let rename_focus_handle = rename_editor.focus_handle(cx);
13931 window.focus(&rename_focus_handle);
13932 let block_id = this.insert_blocks(
13933 [BlockProperties {
13934 style: BlockStyle::Flex,
13935 placement: BlockPlacement::Below(range.start),
13936 height: Some(1),
13937 render: Arc::new({
13938 let rename_editor = rename_editor.clone();
13939 move |cx: &mut BlockContext| {
13940 let mut text_style = cx.editor_style.text.clone();
13941 if let Some(highlight_style) = old_highlight_id
13942 .and_then(|h| h.style(&cx.editor_style.syntax))
13943 {
13944 text_style = text_style.highlight(highlight_style);
13945 }
13946 div()
13947 .block_mouse_down()
13948 .pl(cx.anchor_x)
13949 .child(EditorElement::new(
13950 &rename_editor,
13951 EditorStyle {
13952 background: cx.theme().system().transparent,
13953 local_player: cx.editor_style.local_player,
13954 text: text_style,
13955 scrollbar_width: cx.editor_style.scrollbar_width,
13956 syntax: cx.editor_style.syntax.clone(),
13957 status: cx.editor_style.status.clone(),
13958 inlay_hints_style: HighlightStyle {
13959 font_weight: Some(FontWeight::BOLD),
13960 ..make_inlay_hints_style(cx.app)
13961 },
13962 inline_completion_styles: make_suggestion_styles(
13963 cx.app,
13964 ),
13965 ..EditorStyle::default()
13966 },
13967 ))
13968 .into_any_element()
13969 }
13970 }),
13971 priority: 0,
13972 }],
13973 Some(Autoscroll::fit()),
13974 cx,
13975 )[0];
13976 this.pending_rename = Some(RenameState {
13977 range,
13978 old_name,
13979 editor: rename_editor,
13980 block_id,
13981 });
13982 })?;
13983 }
13984
13985 Ok(())
13986 }))
13987 }
13988
13989 pub fn confirm_rename(
13990 &mut self,
13991 _: &ConfirmRename,
13992 window: &mut Window,
13993 cx: &mut Context<Self>,
13994 ) -> Option<Task<Result<()>>> {
13995 let rename = self.take_rename(false, window, cx)?;
13996 let workspace = self.workspace()?.downgrade();
13997 let (buffer, start) = self
13998 .buffer
13999 .read(cx)
14000 .text_anchor_for_position(rename.range.start, cx)?;
14001 let (end_buffer, _) = self
14002 .buffer
14003 .read(cx)
14004 .text_anchor_for_position(rename.range.end, cx)?;
14005 if buffer != end_buffer {
14006 return None;
14007 }
14008
14009 let old_name = rename.old_name;
14010 let new_name = rename.editor.read(cx).text(cx);
14011
14012 let rename = self.semantics_provider.as_ref()?.perform_rename(
14013 &buffer,
14014 start,
14015 new_name.clone(),
14016 cx,
14017 )?;
14018
14019 Some(cx.spawn_in(window, async move |editor, cx| {
14020 let project_transaction = rename.await?;
14021 Self::open_project_transaction(
14022 &editor,
14023 workspace,
14024 project_transaction,
14025 format!("Rename: {} → {}", old_name, new_name),
14026 cx,
14027 )
14028 .await?;
14029
14030 editor.update(cx, |editor, cx| {
14031 editor.refresh_document_highlights(cx);
14032 })?;
14033 Ok(())
14034 }))
14035 }
14036
14037 fn take_rename(
14038 &mut self,
14039 moving_cursor: bool,
14040 window: &mut Window,
14041 cx: &mut Context<Self>,
14042 ) -> Option<RenameState> {
14043 let rename = self.pending_rename.take()?;
14044 if rename.editor.focus_handle(cx).is_focused(window) {
14045 window.focus(&self.focus_handle);
14046 }
14047
14048 self.remove_blocks(
14049 [rename.block_id].into_iter().collect(),
14050 Some(Autoscroll::fit()),
14051 cx,
14052 );
14053 self.clear_highlights::<Rename>(cx);
14054 self.show_local_selections = true;
14055
14056 if moving_cursor {
14057 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14058 editor.selections.newest::<usize>(cx).head()
14059 });
14060
14061 // Update the selection to match the position of the selection inside
14062 // the rename editor.
14063 let snapshot = self.buffer.read(cx).read(cx);
14064 let rename_range = rename.range.to_offset(&snapshot);
14065 let cursor_in_editor = snapshot
14066 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14067 .min(rename_range.end);
14068 drop(snapshot);
14069
14070 self.change_selections(None, window, cx, |s| {
14071 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14072 });
14073 } else {
14074 self.refresh_document_highlights(cx);
14075 }
14076
14077 Some(rename)
14078 }
14079
14080 pub fn pending_rename(&self) -> Option<&RenameState> {
14081 self.pending_rename.as_ref()
14082 }
14083
14084 fn format(
14085 &mut self,
14086 _: &Format,
14087 window: &mut Window,
14088 cx: &mut Context<Self>,
14089 ) -> Option<Task<Result<()>>> {
14090 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14091
14092 let project = match &self.project {
14093 Some(project) => project.clone(),
14094 None => return None,
14095 };
14096
14097 Some(self.perform_format(
14098 project,
14099 FormatTrigger::Manual,
14100 FormatTarget::Buffers,
14101 window,
14102 cx,
14103 ))
14104 }
14105
14106 fn format_selections(
14107 &mut self,
14108 _: &FormatSelections,
14109 window: &mut Window,
14110 cx: &mut Context<Self>,
14111 ) -> Option<Task<Result<()>>> {
14112 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14113
14114 let project = match &self.project {
14115 Some(project) => project.clone(),
14116 None => return None,
14117 };
14118
14119 let ranges = self
14120 .selections
14121 .all_adjusted(cx)
14122 .into_iter()
14123 .map(|selection| selection.range())
14124 .collect_vec();
14125
14126 Some(self.perform_format(
14127 project,
14128 FormatTrigger::Manual,
14129 FormatTarget::Ranges(ranges),
14130 window,
14131 cx,
14132 ))
14133 }
14134
14135 fn perform_format(
14136 &mut self,
14137 project: Entity<Project>,
14138 trigger: FormatTrigger,
14139 target: FormatTarget,
14140 window: &mut Window,
14141 cx: &mut Context<Self>,
14142 ) -> Task<Result<()>> {
14143 let buffer = self.buffer.clone();
14144 let (buffers, target) = match target {
14145 FormatTarget::Buffers => {
14146 let mut buffers = buffer.read(cx).all_buffers();
14147 if trigger == FormatTrigger::Save {
14148 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14149 }
14150 (buffers, LspFormatTarget::Buffers)
14151 }
14152 FormatTarget::Ranges(selection_ranges) => {
14153 let multi_buffer = buffer.read(cx);
14154 let snapshot = multi_buffer.read(cx);
14155 let mut buffers = HashSet::default();
14156 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14157 BTreeMap::new();
14158 for selection_range in selection_ranges {
14159 for (buffer, buffer_range, _) in
14160 snapshot.range_to_buffer_ranges(selection_range)
14161 {
14162 let buffer_id = buffer.remote_id();
14163 let start = buffer.anchor_before(buffer_range.start);
14164 let end = buffer.anchor_after(buffer_range.end);
14165 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14166 buffer_id_to_ranges
14167 .entry(buffer_id)
14168 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14169 .or_insert_with(|| vec![start..end]);
14170 }
14171 }
14172 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14173 }
14174 };
14175
14176 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14177 let format = project.update(cx, |project, cx| {
14178 project.format(buffers, target, true, trigger, cx)
14179 });
14180
14181 cx.spawn_in(window, async move |_, cx| {
14182 let transaction = futures::select_biased! {
14183 transaction = format.log_err().fuse() => transaction,
14184 () = timeout => {
14185 log::warn!("timed out waiting for formatting");
14186 None
14187 }
14188 };
14189
14190 buffer
14191 .update(cx, |buffer, cx| {
14192 if let Some(transaction) = transaction {
14193 if !buffer.is_singleton() {
14194 buffer.push_transaction(&transaction.0, cx);
14195 }
14196 }
14197 cx.notify();
14198 })
14199 .ok();
14200
14201 Ok(())
14202 })
14203 }
14204
14205 fn organize_imports(
14206 &mut self,
14207 _: &OrganizeImports,
14208 window: &mut Window,
14209 cx: &mut Context<Self>,
14210 ) -> Option<Task<Result<()>>> {
14211 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14212 let project = match &self.project {
14213 Some(project) => project.clone(),
14214 None => return None,
14215 };
14216 Some(self.perform_code_action_kind(
14217 project,
14218 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14219 window,
14220 cx,
14221 ))
14222 }
14223
14224 fn perform_code_action_kind(
14225 &mut self,
14226 project: Entity<Project>,
14227 kind: CodeActionKind,
14228 window: &mut Window,
14229 cx: &mut Context<Self>,
14230 ) -> Task<Result<()>> {
14231 let buffer = self.buffer.clone();
14232 let buffers = buffer.read(cx).all_buffers();
14233 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14234 let apply_action = project.update(cx, |project, cx| {
14235 project.apply_code_action_kind(buffers, kind, true, cx)
14236 });
14237 cx.spawn_in(window, async move |_, cx| {
14238 let transaction = futures::select_biased! {
14239 () = timeout => {
14240 log::warn!("timed out waiting for executing code action");
14241 None
14242 }
14243 transaction = apply_action.log_err().fuse() => transaction,
14244 };
14245 buffer
14246 .update(cx, |buffer, cx| {
14247 // check if we need this
14248 if let Some(transaction) = transaction {
14249 if !buffer.is_singleton() {
14250 buffer.push_transaction(&transaction.0, cx);
14251 }
14252 }
14253 cx.notify();
14254 })
14255 .ok();
14256 Ok(())
14257 })
14258 }
14259
14260 fn restart_language_server(
14261 &mut self,
14262 _: &RestartLanguageServer,
14263 _: &mut Window,
14264 cx: &mut Context<Self>,
14265 ) {
14266 if let Some(project) = self.project.clone() {
14267 self.buffer.update(cx, |multi_buffer, cx| {
14268 project.update(cx, |project, cx| {
14269 project.restart_language_servers_for_buffers(
14270 multi_buffer.all_buffers().into_iter().collect(),
14271 cx,
14272 );
14273 });
14274 })
14275 }
14276 }
14277
14278 fn stop_language_server(
14279 &mut self,
14280 _: &StopLanguageServer,
14281 _: &mut Window,
14282 cx: &mut Context<Self>,
14283 ) {
14284 if let Some(project) = self.project.clone() {
14285 self.buffer.update(cx, |multi_buffer, cx| {
14286 project.update(cx, |project, cx| {
14287 project.stop_language_servers_for_buffers(
14288 multi_buffer.all_buffers().into_iter().collect(),
14289 cx,
14290 );
14291 cx.emit(project::Event::RefreshInlayHints);
14292 });
14293 });
14294 }
14295 }
14296
14297 fn cancel_language_server_work(
14298 workspace: &mut Workspace,
14299 _: &actions::CancelLanguageServerWork,
14300 _: &mut Window,
14301 cx: &mut Context<Workspace>,
14302 ) {
14303 let project = workspace.project();
14304 let buffers = workspace
14305 .active_item(cx)
14306 .and_then(|item| item.act_as::<Editor>(cx))
14307 .map_or(HashSet::default(), |editor| {
14308 editor.read(cx).buffer.read(cx).all_buffers()
14309 });
14310 project.update(cx, |project, cx| {
14311 project.cancel_language_server_work_for_buffers(buffers, cx);
14312 });
14313 }
14314
14315 fn show_character_palette(
14316 &mut self,
14317 _: &ShowCharacterPalette,
14318 window: &mut Window,
14319 _: &mut Context<Self>,
14320 ) {
14321 window.show_character_palette();
14322 }
14323
14324 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14325 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
14326 let buffer = self.buffer.read(cx).snapshot(cx);
14327 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
14328 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
14329 let is_valid = buffer
14330 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14331 .any(|entry| {
14332 entry.diagnostic.is_primary
14333 && !entry.range.is_empty()
14334 && entry.range.start == primary_range_start
14335 && entry.diagnostic.message == active_diagnostics.primary_message
14336 });
14337
14338 if is_valid != active_diagnostics.is_valid {
14339 active_diagnostics.is_valid = is_valid;
14340 if is_valid {
14341 let mut new_styles = HashMap::default();
14342 for (block_id, diagnostic) in &active_diagnostics.blocks {
14343 new_styles.insert(
14344 *block_id,
14345 diagnostic_block_renderer(diagnostic.clone(), None, true),
14346 );
14347 }
14348 self.display_map.update(cx, |display_map, _cx| {
14349 display_map.replace_blocks(new_styles);
14350 });
14351 } else {
14352 self.dismiss_diagnostics(cx);
14353 }
14354 }
14355 }
14356 }
14357
14358 fn activate_diagnostics(
14359 &mut self,
14360 buffer_id: BufferId,
14361 group_id: usize,
14362 window: &mut Window,
14363 cx: &mut Context<Self>,
14364 ) {
14365 self.dismiss_diagnostics(cx);
14366 let snapshot = self.snapshot(window, cx);
14367 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
14368 let buffer = self.buffer.read(cx).snapshot(cx);
14369
14370 let mut primary_range = None;
14371 let mut primary_message = None;
14372 let diagnostic_group = buffer
14373 .diagnostic_group(buffer_id, group_id)
14374 .filter_map(|entry| {
14375 let start = entry.range.start;
14376 let end = entry.range.end;
14377 if snapshot.is_line_folded(MultiBufferRow(start.row))
14378 && (start.row == end.row
14379 || snapshot.is_line_folded(MultiBufferRow(end.row)))
14380 {
14381 return None;
14382 }
14383 if entry.diagnostic.is_primary {
14384 primary_range = Some(entry.range.clone());
14385 primary_message = Some(entry.diagnostic.message.clone());
14386 }
14387 Some(entry)
14388 })
14389 .collect::<Vec<_>>();
14390 let primary_range = primary_range?;
14391 let primary_message = primary_message?;
14392
14393 let blocks = display_map
14394 .insert_blocks(
14395 diagnostic_group.iter().map(|entry| {
14396 let diagnostic = entry.diagnostic.clone();
14397 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
14398 BlockProperties {
14399 style: BlockStyle::Fixed,
14400 placement: BlockPlacement::Below(
14401 buffer.anchor_after(entry.range.start),
14402 ),
14403 height: Some(message_height),
14404 render: diagnostic_block_renderer(diagnostic, None, true),
14405 priority: 0,
14406 }
14407 }),
14408 cx,
14409 )
14410 .into_iter()
14411 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
14412 .collect();
14413
14414 Some(ActiveDiagnosticGroup {
14415 primary_range: buffer.anchor_before(primary_range.start)
14416 ..buffer.anchor_after(primary_range.end),
14417 primary_message,
14418 group_id,
14419 blocks,
14420 is_valid: true,
14421 })
14422 });
14423 }
14424
14425 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14426 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
14427 self.display_map.update(cx, |display_map, cx| {
14428 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
14429 });
14430 cx.notify();
14431 }
14432 }
14433
14434 /// Disable inline diagnostics rendering for this editor.
14435 pub fn disable_inline_diagnostics(&mut self) {
14436 self.inline_diagnostics_enabled = false;
14437 self.inline_diagnostics_update = Task::ready(());
14438 self.inline_diagnostics.clear();
14439 }
14440
14441 pub fn inline_diagnostics_enabled(&self) -> bool {
14442 self.inline_diagnostics_enabled
14443 }
14444
14445 pub fn show_inline_diagnostics(&self) -> bool {
14446 self.show_inline_diagnostics
14447 }
14448
14449 pub fn toggle_inline_diagnostics(
14450 &mut self,
14451 _: &ToggleInlineDiagnostics,
14452 window: &mut Window,
14453 cx: &mut Context<Editor>,
14454 ) {
14455 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14456 self.refresh_inline_diagnostics(false, window, cx);
14457 }
14458
14459 fn refresh_inline_diagnostics(
14460 &mut self,
14461 debounce: bool,
14462 window: &mut Window,
14463 cx: &mut Context<Self>,
14464 ) {
14465 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14466 self.inline_diagnostics_update = Task::ready(());
14467 self.inline_diagnostics.clear();
14468 return;
14469 }
14470
14471 let debounce_ms = ProjectSettings::get_global(cx)
14472 .diagnostics
14473 .inline
14474 .update_debounce_ms;
14475 let debounce = if debounce && debounce_ms > 0 {
14476 Some(Duration::from_millis(debounce_ms))
14477 } else {
14478 None
14479 };
14480 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14481 if let Some(debounce) = debounce {
14482 cx.background_executor().timer(debounce).await;
14483 }
14484 let Some(snapshot) = editor
14485 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14486 .ok()
14487 else {
14488 return;
14489 };
14490
14491 let new_inline_diagnostics = cx
14492 .background_spawn(async move {
14493 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14494 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14495 let message = diagnostic_entry
14496 .diagnostic
14497 .message
14498 .split_once('\n')
14499 .map(|(line, _)| line)
14500 .map(SharedString::new)
14501 .unwrap_or_else(|| {
14502 SharedString::from(diagnostic_entry.diagnostic.message)
14503 });
14504 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14505 let (Ok(i) | Err(i)) = inline_diagnostics
14506 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14507 inline_diagnostics.insert(
14508 i,
14509 (
14510 start_anchor,
14511 InlineDiagnostic {
14512 message,
14513 group_id: diagnostic_entry.diagnostic.group_id,
14514 start: diagnostic_entry.range.start.to_point(&snapshot),
14515 is_primary: diagnostic_entry.diagnostic.is_primary,
14516 severity: diagnostic_entry.diagnostic.severity,
14517 },
14518 ),
14519 );
14520 }
14521 inline_diagnostics
14522 })
14523 .await;
14524
14525 editor
14526 .update(cx, |editor, cx| {
14527 editor.inline_diagnostics = new_inline_diagnostics;
14528 cx.notify();
14529 })
14530 .ok();
14531 });
14532 }
14533
14534 pub fn set_selections_from_remote(
14535 &mut self,
14536 selections: Vec<Selection<Anchor>>,
14537 pending_selection: Option<Selection<Anchor>>,
14538 window: &mut Window,
14539 cx: &mut Context<Self>,
14540 ) {
14541 let old_cursor_position = self.selections.newest_anchor().head();
14542 self.selections.change_with(cx, |s| {
14543 s.select_anchors(selections);
14544 if let Some(pending_selection) = pending_selection {
14545 s.set_pending(pending_selection, SelectMode::Character);
14546 } else {
14547 s.clear_pending();
14548 }
14549 });
14550 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14551 }
14552
14553 fn push_to_selection_history(&mut self) {
14554 self.selection_history.push(SelectionHistoryEntry {
14555 selections: self.selections.disjoint_anchors(),
14556 select_next_state: self.select_next_state.clone(),
14557 select_prev_state: self.select_prev_state.clone(),
14558 add_selections_state: self.add_selections_state.clone(),
14559 });
14560 }
14561
14562 pub fn transact(
14563 &mut self,
14564 window: &mut Window,
14565 cx: &mut Context<Self>,
14566 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14567 ) -> Option<TransactionId> {
14568 self.start_transaction_at(Instant::now(), window, cx);
14569 update(self, window, cx);
14570 self.end_transaction_at(Instant::now(), cx)
14571 }
14572
14573 pub fn start_transaction_at(
14574 &mut self,
14575 now: Instant,
14576 window: &mut Window,
14577 cx: &mut Context<Self>,
14578 ) {
14579 self.end_selection(window, cx);
14580 if let Some(tx_id) = self
14581 .buffer
14582 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14583 {
14584 self.selection_history
14585 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14586 cx.emit(EditorEvent::TransactionBegun {
14587 transaction_id: tx_id,
14588 })
14589 }
14590 }
14591
14592 pub fn end_transaction_at(
14593 &mut self,
14594 now: Instant,
14595 cx: &mut Context<Self>,
14596 ) -> Option<TransactionId> {
14597 if let Some(transaction_id) = self
14598 .buffer
14599 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14600 {
14601 if let Some((_, end_selections)) =
14602 self.selection_history.transaction_mut(transaction_id)
14603 {
14604 *end_selections = Some(self.selections.disjoint_anchors());
14605 } else {
14606 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14607 }
14608
14609 cx.emit(EditorEvent::Edited { transaction_id });
14610 Some(transaction_id)
14611 } else {
14612 None
14613 }
14614 }
14615
14616 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14617 if self.selection_mark_mode {
14618 self.change_selections(None, window, cx, |s| {
14619 s.move_with(|_, sel| {
14620 sel.collapse_to(sel.head(), SelectionGoal::None);
14621 });
14622 })
14623 }
14624 self.selection_mark_mode = true;
14625 cx.notify();
14626 }
14627
14628 pub fn swap_selection_ends(
14629 &mut self,
14630 _: &actions::SwapSelectionEnds,
14631 window: &mut Window,
14632 cx: &mut Context<Self>,
14633 ) {
14634 self.change_selections(None, window, cx, |s| {
14635 s.move_with(|_, sel| {
14636 if sel.start != sel.end {
14637 sel.reversed = !sel.reversed
14638 }
14639 });
14640 });
14641 self.request_autoscroll(Autoscroll::newest(), cx);
14642 cx.notify();
14643 }
14644
14645 pub fn toggle_fold(
14646 &mut self,
14647 _: &actions::ToggleFold,
14648 window: &mut Window,
14649 cx: &mut Context<Self>,
14650 ) {
14651 if self.is_singleton(cx) {
14652 let selection = self.selections.newest::<Point>(cx);
14653
14654 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14655 let range = if selection.is_empty() {
14656 let point = selection.head().to_display_point(&display_map);
14657 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14658 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14659 .to_point(&display_map);
14660 start..end
14661 } else {
14662 selection.range()
14663 };
14664 if display_map.folds_in_range(range).next().is_some() {
14665 self.unfold_lines(&Default::default(), window, cx)
14666 } else {
14667 self.fold(&Default::default(), window, cx)
14668 }
14669 } else {
14670 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14671 let buffer_ids: HashSet<_> = self
14672 .selections
14673 .disjoint_anchor_ranges()
14674 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14675 .collect();
14676
14677 let should_unfold = buffer_ids
14678 .iter()
14679 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14680
14681 for buffer_id in buffer_ids {
14682 if should_unfold {
14683 self.unfold_buffer(buffer_id, cx);
14684 } else {
14685 self.fold_buffer(buffer_id, cx);
14686 }
14687 }
14688 }
14689 }
14690
14691 pub fn toggle_fold_recursive(
14692 &mut self,
14693 _: &actions::ToggleFoldRecursive,
14694 window: &mut Window,
14695 cx: &mut Context<Self>,
14696 ) {
14697 let selection = self.selections.newest::<Point>(cx);
14698
14699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14700 let range = if selection.is_empty() {
14701 let point = selection.head().to_display_point(&display_map);
14702 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14703 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14704 .to_point(&display_map);
14705 start..end
14706 } else {
14707 selection.range()
14708 };
14709 if display_map.folds_in_range(range).next().is_some() {
14710 self.unfold_recursive(&Default::default(), window, cx)
14711 } else {
14712 self.fold_recursive(&Default::default(), window, cx)
14713 }
14714 }
14715
14716 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14717 if self.is_singleton(cx) {
14718 let mut to_fold = Vec::new();
14719 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14720 let selections = self.selections.all_adjusted(cx);
14721
14722 for selection in selections {
14723 let range = selection.range().sorted();
14724 let buffer_start_row = range.start.row;
14725
14726 if range.start.row != range.end.row {
14727 let mut found = false;
14728 let mut row = range.start.row;
14729 while row <= range.end.row {
14730 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14731 {
14732 found = true;
14733 row = crease.range().end.row + 1;
14734 to_fold.push(crease);
14735 } else {
14736 row += 1
14737 }
14738 }
14739 if found {
14740 continue;
14741 }
14742 }
14743
14744 for row in (0..=range.start.row).rev() {
14745 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14746 if crease.range().end.row >= buffer_start_row {
14747 to_fold.push(crease);
14748 if row <= range.start.row {
14749 break;
14750 }
14751 }
14752 }
14753 }
14754 }
14755
14756 self.fold_creases(to_fold, true, window, cx);
14757 } else {
14758 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14759 let buffer_ids = self
14760 .selections
14761 .disjoint_anchor_ranges()
14762 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14763 .collect::<HashSet<_>>();
14764 for buffer_id in buffer_ids {
14765 self.fold_buffer(buffer_id, cx);
14766 }
14767 }
14768 }
14769
14770 fn fold_at_level(
14771 &mut self,
14772 fold_at: &FoldAtLevel,
14773 window: &mut Window,
14774 cx: &mut Context<Self>,
14775 ) {
14776 if !self.buffer.read(cx).is_singleton() {
14777 return;
14778 }
14779
14780 let fold_at_level = fold_at.0;
14781 let snapshot = self.buffer.read(cx).snapshot(cx);
14782 let mut to_fold = Vec::new();
14783 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14784
14785 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14786 while start_row < end_row {
14787 match self
14788 .snapshot(window, cx)
14789 .crease_for_buffer_row(MultiBufferRow(start_row))
14790 {
14791 Some(crease) => {
14792 let nested_start_row = crease.range().start.row + 1;
14793 let nested_end_row = crease.range().end.row;
14794
14795 if current_level < fold_at_level {
14796 stack.push((nested_start_row, nested_end_row, current_level + 1));
14797 } else if current_level == fold_at_level {
14798 to_fold.push(crease);
14799 }
14800
14801 start_row = nested_end_row + 1;
14802 }
14803 None => start_row += 1,
14804 }
14805 }
14806 }
14807
14808 self.fold_creases(to_fold, true, window, cx);
14809 }
14810
14811 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14812 if self.buffer.read(cx).is_singleton() {
14813 let mut fold_ranges = Vec::new();
14814 let snapshot = self.buffer.read(cx).snapshot(cx);
14815
14816 for row in 0..snapshot.max_row().0 {
14817 if let Some(foldable_range) = self
14818 .snapshot(window, cx)
14819 .crease_for_buffer_row(MultiBufferRow(row))
14820 {
14821 fold_ranges.push(foldable_range);
14822 }
14823 }
14824
14825 self.fold_creases(fold_ranges, true, window, cx);
14826 } else {
14827 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14828 editor
14829 .update_in(cx, |editor, _, cx| {
14830 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14831 editor.fold_buffer(buffer_id, cx);
14832 }
14833 })
14834 .ok();
14835 });
14836 }
14837 }
14838
14839 pub fn fold_function_bodies(
14840 &mut self,
14841 _: &actions::FoldFunctionBodies,
14842 window: &mut Window,
14843 cx: &mut Context<Self>,
14844 ) {
14845 let snapshot = self.buffer.read(cx).snapshot(cx);
14846
14847 let ranges = snapshot
14848 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14849 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14850 .collect::<Vec<_>>();
14851
14852 let creases = ranges
14853 .into_iter()
14854 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14855 .collect();
14856
14857 self.fold_creases(creases, true, window, cx);
14858 }
14859
14860 pub fn fold_recursive(
14861 &mut self,
14862 _: &actions::FoldRecursive,
14863 window: &mut Window,
14864 cx: &mut Context<Self>,
14865 ) {
14866 let mut to_fold = Vec::new();
14867 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14868 let selections = self.selections.all_adjusted(cx);
14869
14870 for selection in selections {
14871 let range = selection.range().sorted();
14872 let buffer_start_row = range.start.row;
14873
14874 if range.start.row != range.end.row {
14875 let mut found = false;
14876 for row in range.start.row..=range.end.row {
14877 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14878 found = true;
14879 to_fold.push(crease);
14880 }
14881 }
14882 if found {
14883 continue;
14884 }
14885 }
14886
14887 for row in (0..=range.start.row).rev() {
14888 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14889 if crease.range().end.row >= buffer_start_row {
14890 to_fold.push(crease);
14891 } else {
14892 break;
14893 }
14894 }
14895 }
14896 }
14897
14898 self.fold_creases(to_fold, true, window, cx);
14899 }
14900
14901 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14902 let buffer_row = fold_at.buffer_row;
14903 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14904
14905 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14906 let autoscroll = self
14907 .selections
14908 .all::<Point>(cx)
14909 .iter()
14910 .any(|selection| crease.range().overlaps(&selection.range()));
14911
14912 self.fold_creases(vec![crease], autoscroll, window, cx);
14913 }
14914 }
14915
14916 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14917 if self.is_singleton(cx) {
14918 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14919 let buffer = &display_map.buffer_snapshot;
14920 let selections = self.selections.all::<Point>(cx);
14921 let ranges = selections
14922 .iter()
14923 .map(|s| {
14924 let range = s.display_range(&display_map).sorted();
14925 let mut start = range.start.to_point(&display_map);
14926 let mut end = range.end.to_point(&display_map);
14927 start.column = 0;
14928 end.column = buffer.line_len(MultiBufferRow(end.row));
14929 start..end
14930 })
14931 .collect::<Vec<_>>();
14932
14933 self.unfold_ranges(&ranges, true, true, cx);
14934 } else {
14935 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14936 let buffer_ids = self
14937 .selections
14938 .disjoint_anchor_ranges()
14939 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14940 .collect::<HashSet<_>>();
14941 for buffer_id in buffer_ids {
14942 self.unfold_buffer(buffer_id, cx);
14943 }
14944 }
14945 }
14946
14947 pub fn unfold_recursive(
14948 &mut self,
14949 _: &UnfoldRecursive,
14950 _window: &mut Window,
14951 cx: &mut Context<Self>,
14952 ) {
14953 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14954 let selections = self.selections.all::<Point>(cx);
14955 let ranges = selections
14956 .iter()
14957 .map(|s| {
14958 let mut range = s.display_range(&display_map).sorted();
14959 *range.start.column_mut() = 0;
14960 *range.end.column_mut() = display_map.line_len(range.end.row());
14961 let start = range.start.to_point(&display_map);
14962 let end = range.end.to_point(&display_map);
14963 start..end
14964 })
14965 .collect::<Vec<_>>();
14966
14967 self.unfold_ranges(&ranges, true, true, cx);
14968 }
14969
14970 pub fn unfold_at(
14971 &mut self,
14972 unfold_at: &UnfoldAt,
14973 _window: &mut Window,
14974 cx: &mut Context<Self>,
14975 ) {
14976 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14977
14978 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14979 ..Point::new(
14980 unfold_at.buffer_row.0,
14981 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14982 );
14983
14984 let autoscroll = self
14985 .selections
14986 .all::<Point>(cx)
14987 .iter()
14988 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14989
14990 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14991 }
14992
14993 pub fn unfold_all(
14994 &mut self,
14995 _: &actions::UnfoldAll,
14996 _window: &mut Window,
14997 cx: &mut Context<Self>,
14998 ) {
14999 if self.buffer.read(cx).is_singleton() {
15000 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15001 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15002 } else {
15003 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15004 editor
15005 .update(cx, |editor, cx| {
15006 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15007 editor.unfold_buffer(buffer_id, cx);
15008 }
15009 })
15010 .ok();
15011 });
15012 }
15013 }
15014
15015 pub fn fold_selected_ranges(
15016 &mut self,
15017 _: &FoldSelectedRanges,
15018 window: &mut Window,
15019 cx: &mut Context<Self>,
15020 ) {
15021 let selections = self.selections.all_adjusted(cx);
15022 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15023 let ranges = selections
15024 .into_iter()
15025 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15026 .collect::<Vec<_>>();
15027 self.fold_creases(ranges, true, window, cx);
15028 }
15029
15030 pub fn fold_ranges<T: ToOffset + Clone>(
15031 &mut self,
15032 ranges: Vec<Range<T>>,
15033 auto_scroll: bool,
15034 window: &mut Window,
15035 cx: &mut Context<Self>,
15036 ) {
15037 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15038 let ranges = ranges
15039 .into_iter()
15040 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15041 .collect::<Vec<_>>();
15042 self.fold_creases(ranges, auto_scroll, window, cx);
15043 }
15044
15045 pub fn fold_creases<T: ToOffset + Clone>(
15046 &mut self,
15047 creases: Vec<Crease<T>>,
15048 auto_scroll: bool,
15049 window: &mut Window,
15050 cx: &mut Context<Self>,
15051 ) {
15052 if creases.is_empty() {
15053 return;
15054 }
15055
15056 let mut buffers_affected = HashSet::default();
15057 let multi_buffer = self.buffer().read(cx);
15058 for crease in &creases {
15059 if let Some((_, buffer, _)) =
15060 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15061 {
15062 buffers_affected.insert(buffer.read(cx).remote_id());
15063 };
15064 }
15065
15066 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15067
15068 if auto_scroll {
15069 self.request_autoscroll(Autoscroll::fit(), cx);
15070 }
15071
15072 cx.notify();
15073
15074 if let Some(active_diagnostics) = self.active_diagnostics.take() {
15075 // Clear diagnostics block when folding a range that contains it.
15076 let snapshot = self.snapshot(window, cx);
15077 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
15078 drop(snapshot);
15079 self.active_diagnostics = Some(active_diagnostics);
15080 self.dismiss_diagnostics(cx);
15081 } else {
15082 self.active_diagnostics = Some(active_diagnostics);
15083 }
15084 }
15085
15086 self.scrollbar_marker_state.dirty = true;
15087 self.folds_did_change(cx);
15088 }
15089
15090 /// Removes any folds whose ranges intersect any of the given ranges.
15091 pub fn unfold_ranges<T: ToOffset + Clone>(
15092 &mut self,
15093 ranges: &[Range<T>],
15094 inclusive: bool,
15095 auto_scroll: bool,
15096 cx: &mut Context<Self>,
15097 ) {
15098 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15099 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15100 });
15101 self.folds_did_change(cx);
15102 }
15103
15104 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15105 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15106 return;
15107 }
15108 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15109 self.display_map.update(cx, |display_map, cx| {
15110 display_map.fold_buffers([buffer_id], cx)
15111 });
15112 cx.emit(EditorEvent::BufferFoldToggled {
15113 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15114 folded: true,
15115 });
15116 cx.notify();
15117 }
15118
15119 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15120 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15121 return;
15122 }
15123 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15124 self.display_map.update(cx, |display_map, cx| {
15125 display_map.unfold_buffers([buffer_id], cx);
15126 });
15127 cx.emit(EditorEvent::BufferFoldToggled {
15128 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15129 folded: false,
15130 });
15131 cx.notify();
15132 }
15133
15134 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15135 self.display_map.read(cx).is_buffer_folded(buffer)
15136 }
15137
15138 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15139 self.display_map.read(cx).folded_buffers()
15140 }
15141
15142 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15143 self.display_map.update(cx, |display_map, cx| {
15144 display_map.disable_header_for_buffer(buffer_id, cx);
15145 });
15146 cx.notify();
15147 }
15148
15149 /// Removes any folds with the given ranges.
15150 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15151 &mut self,
15152 ranges: &[Range<T>],
15153 type_id: TypeId,
15154 auto_scroll: bool,
15155 cx: &mut Context<Self>,
15156 ) {
15157 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15158 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15159 });
15160 self.folds_did_change(cx);
15161 }
15162
15163 fn remove_folds_with<T: ToOffset + Clone>(
15164 &mut self,
15165 ranges: &[Range<T>],
15166 auto_scroll: bool,
15167 cx: &mut Context<Self>,
15168 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15169 ) {
15170 if ranges.is_empty() {
15171 return;
15172 }
15173
15174 let mut buffers_affected = HashSet::default();
15175 let multi_buffer = self.buffer().read(cx);
15176 for range in ranges {
15177 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15178 buffers_affected.insert(buffer.read(cx).remote_id());
15179 };
15180 }
15181
15182 self.display_map.update(cx, update);
15183
15184 if auto_scroll {
15185 self.request_autoscroll(Autoscroll::fit(), cx);
15186 }
15187
15188 cx.notify();
15189 self.scrollbar_marker_state.dirty = true;
15190 self.active_indent_guides_state.dirty = true;
15191 }
15192
15193 pub fn update_fold_widths(
15194 &mut self,
15195 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15196 cx: &mut Context<Self>,
15197 ) -> bool {
15198 self.display_map
15199 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15200 }
15201
15202 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15203 self.display_map.read(cx).fold_placeholder.clone()
15204 }
15205
15206 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15207 self.buffer.update(cx, |buffer, cx| {
15208 buffer.set_all_diff_hunks_expanded(cx);
15209 });
15210 }
15211
15212 pub fn expand_all_diff_hunks(
15213 &mut self,
15214 _: &ExpandAllDiffHunks,
15215 _window: &mut Window,
15216 cx: &mut Context<Self>,
15217 ) {
15218 self.buffer.update(cx, |buffer, cx| {
15219 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15220 });
15221 }
15222
15223 pub fn toggle_selected_diff_hunks(
15224 &mut self,
15225 _: &ToggleSelectedDiffHunks,
15226 _window: &mut Window,
15227 cx: &mut Context<Self>,
15228 ) {
15229 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15230 self.toggle_diff_hunks_in_ranges(ranges, cx);
15231 }
15232
15233 pub fn diff_hunks_in_ranges<'a>(
15234 &'a self,
15235 ranges: &'a [Range<Anchor>],
15236 buffer: &'a MultiBufferSnapshot,
15237 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15238 ranges.iter().flat_map(move |range| {
15239 let end_excerpt_id = range.end.excerpt_id;
15240 let range = range.to_point(buffer);
15241 let mut peek_end = range.end;
15242 if range.end.row < buffer.max_row().0 {
15243 peek_end = Point::new(range.end.row + 1, 0);
15244 }
15245 buffer
15246 .diff_hunks_in_range(range.start..peek_end)
15247 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15248 })
15249 }
15250
15251 pub fn has_stageable_diff_hunks_in_ranges(
15252 &self,
15253 ranges: &[Range<Anchor>],
15254 snapshot: &MultiBufferSnapshot,
15255 ) -> bool {
15256 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15257 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15258 }
15259
15260 pub fn toggle_staged_selected_diff_hunks(
15261 &mut self,
15262 _: &::git::ToggleStaged,
15263 _: &mut Window,
15264 cx: &mut Context<Self>,
15265 ) {
15266 let snapshot = self.buffer.read(cx).snapshot(cx);
15267 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15268 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15269 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15270 }
15271
15272 pub fn set_render_diff_hunk_controls(
15273 &mut self,
15274 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15275 cx: &mut Context<Self>,
15276 ) {
15277 self.render_diff_hunk_controls = render_diff_hunk_controls;
15278 cx.notify();
15279 }
15280
15281 pub fn stage_and_next(
15282 &mut self,
15283 _: &::git::StageAndNext,
15284 window: &mut Window,
15285 cx: &mut Context<Self>,
15286 ) {
15287 self.do_stage_or_unstage_and_next(true, window, cx);
15288 }
15289
15290 pub fn unstage_and_next(
15291 &mut self,
15292 _: &::git::UnstageAndNext,
15293 window: &mut Window,
15294 cx: &mut Context<Self>,
15295 ) {
15296 self.do_stage_or_unstage_and_next(false, window, cx);
15297 }
15298
15299 pub fn stage_or_unstage_diff_hunks(
15300 &mut self,
15301 stage: bool,
15302 ranges: Vec<Range<Anchor>>,
15303 cx: &mut Context<Self>,
15304 ) {
15305 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15306 cx.spawn(async move |this, cx| {
15307 task.await?;
15308 this.update(cx, |this, cx| {
15309 let snapshot = this.buffer.read(cx).snapshot(cx);
15310 let chunk_by = this
15311 .diff_hunks_in_ranges(&ranges, &snapshot)
15312 .chunk_by(|hunk| hunk.buffer_id);
15313 for (buffer_id, hunks) in &chunk_by {
15314 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15315 }
15316 })
15317 })
15318 .detach_and_log_err(cx);
15319 }
15320
15321 fn save_buffers_for_ranges_if_needed(
15322 &mut self,
15323 ranges: &[Range<Anchor>],
15324 cx: &mut Context<Editor>,
15325 ) -> Task<Result<()>> {
15326 let multibuffer = self.buffer.read(cx);
15327 let snapshot = multibuffer.read(cx);
15328 let buffer_ids: HashSet<_> = ranges
15329 .iter()
15330 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15331 .collect();
15332 drop(snapshot);
15333
15334 let mut buffers = HashSet::default();
15335 for buffer_id in buffer_ids {
15336 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15337 let buffer = buffer_entity.read(cx);
15338 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15339 {
15340 buffers.insert(buffer_entity);
15341 }
15342 }
15343 }
15344
15345 if let Some(project) = &self.project {
15346 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15347 } else {
15348 Task::ready(Ok(()))
15349 }
15350 }
15351
15352 fn do_stage_or_unstage_and_next(
15353 &mut self,
15354 stage: bool,
15355 window: &mut Window,
15356 cx: &mut Context<Self>,
15357 ) {
15358 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15359
15360 if ranges.iter().any(|range| range.start != range.end) {
15361 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15362 return;
15363 }
15364
15365 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15366 let snapshot = self.snapshot(window, cx);
15367 let position = self.selections.newest::<Point>(cx).head();
15368 let mut row = snapshot
15369 .buffer_snapshot
15370 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15371 .find(|hunk| hunk.row_range.start.0 > position.row)
15372 .map(|hunk| hunk.row_range.start);
15373
15374 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15375 // Outside of the project diff editor, wrap around to the beginning.
15376 if !all_diff_hunks_expanded {
15377 row = row.or_else(|| {
15378 snapshot
15379 .buffer_snapshot
15380 .diff_hunks_in_range(Point::zero()..position)
15381 .find(|hunk| hunk.row_range.end.0 < position.row)
15382 .map(|hunk| hunk.row_range.start)
15383 });
15384 }
15385
15386 if let Some(row) = row {
15387 let destination = Point::new(row.0, 0);
15388 let autoscroll = Autoscroll::center();
15389
15390 self.unfold_ranges(&[destination..destination], false, false, cx);
15391 self.change_selections(Some(autoscroll), window, cx, |s| {
15392 s.select_ranges([destination..destination]);
15393 });
15394 }
15395 }
15396
15397 fn do_stage_or_unstage(
15398 &self,
15399 stage: bool,
15400 buffer_id: BufferId,
15401 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15402 cx: &mut App,
15403 ) -> Option<()> {
15404 let project = self.project.as_ref()?;
15405 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15406 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15407 let buffer_snapshot = buffer.read(cx).snapshot();
15408 let file_exists = buffer_snapshot
15409 .file()
15410 .is_some_and(|file| file.disk_state().exists());
15411 diff.update(cx, |diff, cx| {
15412 diff.stage_or_unstage_hunks(
15413 stage,
15414 &hunks
15415 .map(|hunk| buffer_diff::DiffHunk {
15416 buffer_range: hunk.buffer_range,
15417 diff_base_byte_range: hunk.diff_base_byte_range,
15418 secondary_status: hunk.secondary_status,
15419 range: Point::zero()..Point::zero(), // unused
15420 })
15421 .collect::<Vec<_>>(),
15422 &buffer_snapshot,
15423 file_exists,
15424 cx,
15425 )
15426 });
15427 None
15428 }
15429
15430 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15431 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15432 self.buffer
15433 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15434 }
15435
15436 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15437 self.buffer.update(cx, |buffer, cx| {
15438 let ranges = vec![Anchor::min()..Anchor::max()];
15439 if !buffer.all_diff_hunks_expanded()
15440 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15441 {
15442 buffer.collapse_diff_hunks(ranges, cx);
15443 true
15444 } else {
15445 false
15446 }
15447 })
15448 }
15449
15450 fn toggle_diff_hunks_in_ranges(
15451 &mut self,
15452 ranges: Vec<Range<Anchor>>,
15453 cx: &mut Context<Editor>,
15454 ) {
15455 self.buffer.update(cx, |buffer, cx| {
15456 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15457 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15458 })
15459 }
15460
15461 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15462 self.buffer.update(cx, |buffer, cx| {
15463 let snapshot = buffer.snapshot(cx);
15464 let excerpt_id = range.end.excerpt_id;
15465 let point_range = range.to_point(&snapshot);
15466 let expand = !buffer.single_hunk_is_expanded(range, cx);
15467 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15468 })
15469 }
15470
15471 pub(crate) fn apply_all_diff_hunks(
15472 &mut self,
15473 _: &ApplyAllDiffHunks,
15474 window: &mut Window,
15475 cx: &mut Context<Self>,
15476 ) {
15477 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15478
15479 let buffers = self.buffer.read(cx).all_buffers();
15480 for branch_buffer in buffers {
15481 branch_buffer.update(cx, |branch_buffer, cx| {
15482 branch_buffer.merge_into_base(Vec::new(), cx);
15483 });
15484 }
15485
15486 if let Some(project) = self.project.clone() {
15487 self.save(true, project, window, cx).detach_and_log_err(cx);
15488 }
15489 }
15490
15491 pub(crate) fn apply_selected_diff_hunks(
15492 &mut self,
15493 _: &ApplyDiffHunk,
15494 window: &mut Window,
15495 cx: &mut Context<Self>,
15496 ) {
15497 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15498 let snapshot = self.snapshot(window, cx);
15499 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15500 let mut ranges_by_buffer = HashMap::default();
15501 self.transact(window, cx, |editor, _window, cx| {
15502 for hunk in hunks {
15503 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15504 ranges_by_buffer
15505 .entry(buffer.clone())
15506 .or_insert_with(Vec::new)
15507 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15508 }
15509 }
15510
15511 for (buffer, ranges) in ranges_by_buffer {
15512 buffer.update(cx, |buffer, cx| {
15513 buffer.merge_into_base(ranges, cx);
15514 });
15515 }
15516 });
15517
15518 if let Some(project) = self.project.clone() {
15519 self.save(true, project, window, cx).detach_and_log_err(cx);
15520 }
15521 }
15522
15523 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15524 if hovered != self.gutter_hovered {
15525 self.gutter_hovered = hovered;
15526 cx.notify();
15527 }
15528 }
15529
15530 pub fn insert_blocks(
15531 &mut self,
15532 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15533 autoscroll: Option<Autoscroll>,
15534 cx: &mut Context<Self>,
15535 ) -> Vec<CustomBlockId> {
15536 let blocks = self
15537 .display_map
15538 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15539 if let Some(autoscroll) = autoscroll {
15540 self.request_autoscroll(autoscroll, cx);
15541 }
15542 cx.notify();
15543 blocks
15544 }
15545
15546 pub fn resize_blocks(
15547 &mut self,
15548 heights: HashMap<CustomBlockId, u32>,
15549 autoscroll: Option<Autoscroll>,
15550 cx: &mut Context<Self>,
15551 ) {
15552 self.display_map
15553 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15554 if let Some(autoscroll) = autoscroll {
15555 self.request_autoscroll(autoscroll, cx);
15556 }
15557 cx.notify();
15558 }
15559
15560 pub fn replace_blocks(
15561 &mut self,
15562 renderers: HashMap<CustomBlockId, RenderBlock>,
15563 autoscroll: Option<Autoscroll>,
15564 cx: &mut Context<Self>,
15565 ) {
15566 self.display_map
15567 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15568 if let Some(autoscroll) = autoscroll {
15569 self.request_autoscroll(autoscroll, cx);
15570 }
15571 cx.notify();
15572 }
15573
15574 pub fn remove_blocks(
15575 &mut self,
15576 block_ids: HashSet<CustomBlockId>,
15577 autoscroll: Option<Autoscroll>,
15578 cx: &mut Context<Self>,
15579 ) {
15580 self.display_map.update(cx, |display_map, cx| {
15581 display_map.remove_blocks(block_ids, cx)
15582 });
15583 if let Some(autoscroll) = autoscroll {
15584 self.request_autoscroll(autoscroll, cx);
15585 }
15586 cx.notify();
15587 }
15588
15589 pub fn row_for_block(
15590 &self,
15591 block_id: CustomBlockId,
15592 cx: &mut Context<Self>,
15593 ) -> Option<DisplayRow> {
15594 self.display_map
15595 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15596 }
15597
15598 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15599 self.focused_block = Some(focused_block);
15600 }
15601
15602 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15603 self.focused_block.take()
15604 }
15605
15606 pub fn insert_creases(
15607 &mut self,
15608 creases: impl IntoIterator<Item = Crease<Anchor>>,
15609 cx: &mut Context<Self>,
15610 ) -> Vec<CreaseId> {
15611 self.display_map
15612 .update(cx, |map, cx| map.insert_creases(creases, cx))
15613 }
15614
15615 pub fn remove_creases(
15616 &mut self,
15617 ids: impl IntoIterator<Item = CreaseId>,
15618 cx: &mut Context<Self>,
15619 ) {
15620 self.display_map
15621 .update(cx, |map, cx| map.remove_creases(ids, cx));
15622 }
15623
15624 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15625 self.display_map
15626 .update(cx, |map, cx| map.snapshot(cx))
15627 .longest_row()
15628 }
15629
15630 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15631 self.display_map
15632 .update(cx, |map, cx| map.snapshot(cx))
15633 .max_point()
15634 }
15635
15636 pub fn text(&self, cx: &App) -> String {
15637 self.buffer.read(cx).read(cx).text()
15638 }
15639
15640 pub fn is_empty(&self, cx: &App) -> bool {
15641 self.buffer.read(cx).read(cx).is_empty()
15642 }
15643
15644 pub fn text_option(&self, cx: &App) -> Option<String> {
15645 let text = self.text(cx);
15646 let text = text.trim();
15647
15648 if text.is_empty() {
15649 return None;
15650 }
15651
15652 Some(text.to_string())
15653 }
15654
15655 pub fn set_text(
15656 &mut self,
15657 text: impl Into<Arc<str>>,
15658 window: &mut Window,
15659 cx: &mut Context<Self>,
15660 ) {
15661 self.transact(window, cx, |this, _, cx| {
15662 this.buffer
15663 .read(cx)
15664 .as_singleton()
15665 .expect("you can only call set_text on editors for singleton buffers")
15666 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15667 });
15668 }
15669
15670 pub fn display_text(&self, cx: &mut App) -> String {
15671 self.display_map
15672 .update(cx, |map, cx| map.snapshot(cx))
15673 .text()
15674 }
15675
15676 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15677 let mut wrap_guides = smallvec::smallvec![];
15678
15679 if self.show_wrap_guides == Some(false) {
15680 return wrap_guides;
15681 }
15682
15683 let settings = self.buffer.read(cx).language_settings(cx);
15684 if settings.show_wrap_guides {
15685 match self.soft_wrap_mode(cx) {
15686 SoftWrap::Column(soft_wrap) => {
15687 wrap_guides.push((soft_wrap as usize, true));
15688 }
15689 SoftWrap::Bounded(soft_wrap) => {
15690 wrap_guides.push((soft_wrap as usize, true));
15691 }
15692 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15693 }
15694 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15695 }
15696
15697 wrap_guides
15698 }
15699
15700 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15701 let settings = self.buffer.read(cx).language_settings(cx);
15702 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15703 match mode {
15704 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15705 SoftWrap::None
15706 }
15707 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15708 language_settings::SoftWrap::PreferredLineLength => {
15709 SoftWrap::Column(settings.preferred_line_length)
15710 }
15711 language_settings::SoftWrap::Bounded => {
15712 SoftWrap::Bounded(settings.preferred_line_length)
15713 }
15714 }
15715 }
15716
15717 pub fn set_soft_wrap_mode(
15718 &mut self,
15719 mode: language_settings::SoftWrap,
15720
15721 cx: &mut Context<Self>,
15722 ) {
15723 self.soft_wrap_mode_override = Some(mode);
15724 cx.notify();
15725 }
15726
15727 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15728 self.hard_wrap = hard_wrap;
15729 cx.notify();
15730 }
15731
15732 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15733 self.text_style_refinement = Some(style);
15734 }
15735
15736 /// called by the Element so we know what style we were most recently rendered with.
15737 pub(crate) fn set_style(
15738 &mut self,
15739 style: EditorStyle,
15740 window: &mut Window,
15741 cx: &mut Context<Self>,
15742 ) {
15743 let rem_size = window.rem_size();
15744 self.display_map.update(cx, |map, cx| {
15745 map.set_font(
15746 style.text.font(),
15747 style.text.font_size.to_pixels(rem_size),
15748 cx,
15749 )
15750 });
15751 self.style = Some(style);
15752 }
15753
15754 pub fn style(&self) -> Option<&EditorStyle> {
15755 self.style.as_ref()
15756 }
15757
15758 // Called by the element. This method is not designed to be called outside of the editor
15759 // element's layout code because it does not notify when rewrapping is computed synchronously.
15760 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15761 self.display_map
15762 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15763 }
15764
15765 pub fn set_soft_wrap(&mut self) {
15766 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15767 }
15768
15769 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15770 if self.soft_wrap_mode_override.is_some() {
15771 self.soft_wrap_mode_override.take();
15772 } else {
15773 let soft_wrap = match self.soft_wrap_mode(cx) {
15774 SoftWrap::GitDiff => return,
15775 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15776 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15777 language_settings::SoftWrap::None
15778 }
15779 };
15780 self.soft_wrap_mode_override = Some(soft_wrap);
15781 }
15782 cx.notify();
15783 }
15784
15785 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15786 let Some(workspace) = self.workspace() else {
15787 return;
15788 };
15789 let fs = workspace.read(cx).app_state().fs.clone();
15790 let current_show = TabBarSettings::get_global(cx).show;
15791 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15792 setting.show = Some(!current_show);
15793 });
15794 }
15795
15796 pub fn toggle_indent_guides(
15797 &mut self,
15798 _: &ToggleIndentGuides,
15799 _: &mut Window,
15800 cx: &mut Context<Self>,
15801 ) {
15802 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15803 self.buffer
15804 .read(cx)
15805 .language_settings(cx)
15806 .indent_guides
15807 .enabled
15808 });
15809 self.show_indent_guides = Some(!currently_enabled);
15810 cx.notify();
15811 }
15812
15813 fn should_show_indent_guides(&self) -> Option<bool> {
15814 self.show_indent_guides
15815 }
15816
15817 pub fn toggle_line_numbers(
15818 &mut self,
15819 _: &ToggleLineNumbers,
15820 _: &mut Window,
15821 cx: &mut Context<Self>,
15822 ) {
15823 let mut editor_settings = EditorSettings::get_global(cx).clone();
15824 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15825 EditorSettings::override_global(editor_settings, cx);
15826 }
15827
15828 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15829 if let Some(show_line_numbers) = self.show_line_numbers {
15830 return show_line_numbers;
15831 }
15832 EditorSettings::get_global(cx).gutter.line_numbers
15833 }
15834
15835 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15836 self.use_relative_line_numbers
15837 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15838 }
15839
15840 pub fn toggle_relative_line_numbers(
15841 &mut self,
15842 _: &ToggleRelativeLineNumbers,
15843 _: &mut Window,
15844 cx: &mut Context<Self>,
15845 ) {
15846 let is_relative = self.should_use_relative_line_numbers(cx);
15847 self.set_relative_line_number(Some(!is_relative), cx)
15848 }
15849
15850 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15851 self.use_relative_line_numbers = is_relative;
15852 cx.notify();
15853 }
15854
15855 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15856 self.show_gutter = show_gutter;
15857 cx.notify();
15858 }
15859
15860 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15861 self.show_scrollbars = show_scrollbars;
15862 cx.notify();
15863 }
15864
15865 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15866 self.show_line_numbers = Some(show_line_numbers);
15867 cx.notify();
15868 }
15869
15870 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15871 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15872 cx.notify();
15873 }
15874
15875 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15876 self.show_code_actions = Some(show_code_actions);
15877 cx.notify();
15878 }
15879
15880 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15881 self.show_runnables = Some(show_runnables);
15882 cx.notify();
15883 }
15884
15885 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15886 self.show_breakpoints = Some(show_breakpoints);
15887 cx.notify();
15888 }
15889
15890 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15891 if self.display_map.read(cx).masked != masked {
15892 self.display_map.update(cx, |map, _| map.masked = masked);
15893 }
15894 cx.notify()
15895 }
15896
15897 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15898 self.show_wrap_guides = Some(show_wrap_guides);
15899 cx.notify();
15900 }
15901
15902 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15903 self.show_indent_guides = Some(show_indent_guides);
15904 cx.notify();
15905 }
15906
15907 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15908 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15909 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15910 if let Some(dir) = file.abs_path(cx).parent() {
15911 return Some(dir.to_owned());
15912 }
15913 }
15914
15915 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15916 return Some(project_path.path.to_path_buf());
15917 }
15918 }
15919
15920 None
15921 }
15922
15923 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15924 self.active_excerpt(cx)?
15925 .1
15926 .read(cx)
15927 .file()
15928 .and_then(|f| f.as_local())
15929 }
15930
15931 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15932 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15933 let buffer = buffer.read(cx);
15934 if let Some(project_path) = buffer.project_path(cx) {
15935 let project = self.project.as_ref()?.read(cx);
15936 project.absolute_path(&project_path, cx)
15937 } else {
15938 buffer
15939 .file()
15940 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15941 }
15942 })
15943 }
15944
15945 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15946 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15947 let project_path = buffer.read(cx).project_path(cx)?;
15948 let project = self.project.as_ref()?.read(cx);
15949 let entry = project.entry_for_path(&project_path, cx)?;
15950 let path = entry.path.to_path_buf();
15951 Some(path)
15952 })
15953 }
15954
15955 pub fn reveal_in_finder(
15956 &mut self,
15957 _: &RevealInFileManager,
15958 _window: &mut Window,
15959 cx: &mut Context<Self>,
15960 ) {
15961 if let Some(target) = self.target_file(cx) {
15962 cx.reveal_path(&target.abs_path(cx));
15963 }
15964 }
15965
15966 pub fn copy_path(
15967 &mut self,
15968 _: &zed_actions::workspace::CopyPath,
15969 _window: &mut Window,
15970 cx: &mut Context<Self>,
15971 ) {
15972 if let Some(path) = self.target_file_abs_path(cx) {
15973 if let Some(path) = path.to_str() {
15974 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15975 }
15976 }
15977 }
15978
15979 pub fn copy_relative_path(
15980 &mut self,
15981 _: &zed_actions::workspace::CopyRelativePath,
15982 _window: &mut Window,
15983 cx: &mut Context<Self>,
15984 ) {
15985 if let Some(path) = self.target_file_path(cx) {
15986 if let Some(path) = path.to_str() {
15987 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15988 }
15989 }
15990 }
15991
15992 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
15993 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15994 buffer.read(cx).project_path(cx)
15995 } else {
15996 None
15997 }
15998 }
15999
16000 // Returns true if the editor handled a go-to-line request
16001 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16002 maybe!({
16003 let breakpoint_store = self.breakpoint_store.as_ref()?;
16004
16005 let Some((_, _, active_position)) =
16006 breakpoint_store.read(cx).active_position().cloned()
16007 else {
16008 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16009 return None;
16010 };
16011
16012 let snapshot = self
16013 .project
16014 .as_ref()?
16015 .read(cx)
16016 .buffer_for_id(active_position.buffer_id?, cx)?
16017 .read(cx)
16018 .snapshot();
16019
16020 let mut handled = false;
16021 for (id, ExcerptRange { context, .. }) in self
16022 .buffer
16023 .read(cx)
16024 .excerpts_for_buffer(active_position.buffer_id?, cx)
16025 {
16026 if context.start.cmp(&active_position, &snapshot).is_ge()
16027 || context.end.cmp(&active_position, &snapshot).is_lt()
16028 {
16029 continue;
16030 }
16031 let snapshot = self.buffer.read(cx).snapshot(cx);
16032 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
16033
16034 handled = true;
16035 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16036 self.go_to_line::<DebugCurrentRowHighlight>(
16037 multibuffer_anchor,
16038 Some(cx.theme().colors().editor_debugger_active_line_background),
16039 window,
16040 cx,
16041 );
16042
16043 cx.notify();
16044 }
16045 handled.then_some(())
16046 })
16047 .is_some()
16048 }
16049
16050 pub fn copy_file_name_without_extension(
16051 &mut self,
16052 _: &CopyFileNameWithoutExtension,
16053 _: &mut Window,
16054 cx: &mut Context<Self>,
16055 ) {
16056 if let Some(file) = self.target_file(cx) {
16057 if let Some(file_stem) = file.path().file_stem() {
16058 if let Some(name) = file_stem.to_str() {
16059 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16060 }
16061 }
16062 }
16063 }
16064
16065 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16066 if let Some(file) = self.target_file(cx) {
16067 if let Some(file_name) = file.path().file_name() {
16068 if let Some(name) = file_name.to_str() {
16069 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16070 }
16071 }
16072 }
16073 }
16074
16075 pub fn toggle_git_blame(
16076 &mut self,
16077 _: &::git::Blame,
16078 window: &mut Window,
16079 cx: &mut Context<Self>,
16080 ) {
16081 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16082
16083 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16084 self.start_git_blame(true, window, cx);
16085 }
16086
16087 cx.notify();
16088 }
16089
16090 pub fn toggle_git_blame_inline(
16091 &mut self,
16092 _: &ToggleGitBlameInline,
16093 window: &mut Window,
16094 cx: &mut Context<Self>,
16095 ) {
16096 self.toggle_git_blame_inline_internal(true, window, cx);
16097 cx.notify();
16098 }
16099
16100 pub fn open_git_blame_commit(
16101 &mut self,
16102 _: &OpenGitBlameCommit,
16103 window: &mut Window,
16104 cx: &mut Context<Self>,
16105 ) {
16106 self.open_git_blame_commit_internal(window, cx);
16107 }
16108
16109 fn open_git_blame_commit_internal(
16110 &mut self,
16111 window: &mut Window,
16112 cx: &mut Context<Self>,
16113 ) -> Option<()> {
16114 let blame = self.blame.as_ref()?;
16115 let snapshot = self.snapshot(window, cx);
16116 let cursor = self.selections.newest::<Point>(cx).head();
16117 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16118 let blame_entry = blame
16119 .update(cx, |blame, cx| {
16120 blame
16121 .blame_for_rows(
16122 &[RowInfo {
16123 buffer_id: Some(buffer.remote_id()),
16124 buffer_row: Some(point.row),
16125 ..Default::default()
16126 }],
16127 cx,
16128 )
16129 .next()
16130 })
16131 .flatten()?;
16132 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16133 let repo = blame.read(cx).repository(cx)?;
16134 let workspace = self.workspace()?.downgrade();
16135 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16136 None
16137 }
16138
16139 pub fn git_blame_inline_enabled(&self) -> bool {
16140 self.git_blame_inline_enabled
16141 }
16142
16143 pub fn toggle_selection_menu(
16144 &mut self,
16145 _: &ToggleSelectionMenu,
16146 _: &mut Window,
16147 cx: &mut Context<Self>,
16148 ) {
16149 self.show_selection_menu = self
16150 .show_selection_menu
16151 .map(|show_selections_menu| !show_selections_menu)
16152 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16153
16154 cx.notify();
16155 }
16156
16157 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16158 self.show_selection_menu
16159 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16160 }
16161
16162 fn start_git_blame(
16163 &mut self,
16164 user_triggered: bool,
16165 window: &mut Window,
16166 cx: &mut Context<Self>,
16167 ) {
16168 if let Some(project) = self.project.as_ref() {
16169 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16170 return;
16171 };
16172
16173 if buffer.read(cx).file().is_none() {
16174 return;
16175 }
16176
16177 let focused = self.focus_handle(cx).contains_focused(window, cx);
16178
16179 let project = project.clone();
16180 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16181 self.blame_subscription =
16182 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16183 self.blame = Some(blame);
16184 }
16185 }
16186
16187 fn toggle_git_blame_inline_internal(
16188 &mut self,
16189 user_triggered: bool,
16190 window: &mut Window,
16191 cx: &mut Context<Self>,
16192 ) {
16193 if self.git_blame_inline_enabled {
16194 self.git_blame_inline_enabled = false;
16195 self.show_git_blame_inline = false;
16196 self.show_git_blame_inline_delay_task.take();
16197 } else {
16198 self.git_blame_inline_enabled = true;
16199 self.start_git_blame_inline(user_triggered, window, cx);
16200 }
16201
16202 cx.notify();
16203 }
16204
16205 fn start_git_blame_inline(
16206 &mut self,
16207 user_triggered: bool,
16208 window: &mut Window,
16209 cx: &mut Context<Self>,
16210 ) {
16211 self.start_git_blame(user_triggered, window, cx);
16212
16213 if ProjectSettings::get_global(cx)
16214 .git
16215 .inline_blame_delay()
16216 .is_some()
16217 {
16218 self.start_inline_blame_timer(window, cx);
16219 } else {
16220 self.show_git_blame_inline = true
16221 }
16222 }
16223
16224 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16225 self.blame.as_ref()
16226 }
16227
16228 pub fn show_git_blame_gutter(&self) -> bool {
16229 self.show_git_blame_gutter
16230 }
16231
16232 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16233 self.show_git_blame_gutter && self.has_blame_entries(cx)
16234 }
16235
16236 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16237 self.show_git_blame_inline
16238 && (self.focus_handle.is_focused(window)
16239 || self
16240 .git_blame_inline_tooltip
16241 .as_ref()
16242 .and_then(|t| t.upgrade())
16243 .is_some())
16244 && !self.newest_selection_head_on_empty_line(cx)
16245 && self.has_blame_entries(cx)
16246 }
16247
16248 fn has_blame_entries(&self, cx: &App) -> bool {
16249 self.blame()
16250 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16251 }
16252
16253 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16254 let cursor_anchor = self.selections.newest_anchor().head();
16255
16256 let snapshot = self.buffer.read(cx).snapshot(cx);
16257 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16258
16259 snapshot.line_len(buffer_row) == 0
16260 }
16261
16262 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16263 let buffer_and_selection = maybe!({
16264 let selection = self.selections.newest::<Point>(cx);
16265 let selection_range = selection.range();
16266
16267 let multi_buffer = self.buffer().read(cx);
16268 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16269 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16270
16271 let (buffer, range, _) = if selection.reversed {
16272 buffer_ranges.first()
16273 } else {
16274 buffer_ranges.last()
16275 }?;
16276
16277 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16278 ..text::ToPoint::to_point(&range.end, &buffer).row;
16279 Some((
16280 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16281 selection,
16282 ))
16283 });
16284
16285 let Some((buffer, selection)) = buffer_and_selection else {
16286 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16287 };
16288
16289 let Some(project) = self.project.as_ref() else {
16290 return Task::ready(Err(anyhow!("editor does not have project")));
16291 };
16292
16293 project.update(cx, |project, cx| {
16294 project.get_permalink_to_line(&buffer, selection, cx)
16295 })
16296 }
16297
16298 pub fn copy_permalink_to_line(
16299 &mut self,
16300 _: &CopyPermalinkToLine,
16301 window: &mut Window,
16302 cx: &mut Context<Self>,
16303 ) {
16304 let permalink_task = self.get_permalink_to_line(cx);
16305 let workspace = self.workspace();
16306
16307 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16308 Ok(permalink) => {
16309 cx.update(|_, cx| {
16310 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16311 })
16312 .ok();
16313 }
16314 Err(err) => {
16315 let message = format!("Failed to copy permalink: {err}");
16316
16317 Err::<(), anyhow::Error>(err).log_err();
16318
16319 if let Some(workspace) = workspace {
16320 workspace
16321 .update_in(cx, |workspace, _, cx| {
16322 struct CopyPermalinkToLine;
16323
16324 workspace.show_toast(
16325 Toast::new(
16326 NotificationId::unique::<CopyPermalinkToLine>(),
16327 message,
16328 ),
16329 cx,
16330 )
16331 })
16332 .ok();
16333 }
16334 }
16335 })
16336 .detach();
16337 }
16338
16339 pub fn copy_file_location(
16340 &mut self,
16341 _: &CopyFileLocation,
16342 _: &mut Window,
16343 cx: &mut Context<Self>,
16344 ) {
16345 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16346 if let Some(file) = self.target_file(cx) {
16347 if let Some(path) = file.path().to_str() {
16348 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16349 }
16350 }
16351 }
16352
16353 pub fn open_permalink_to_line(
16354 &mut self,
16355 _: &OpenPermalinkToLine,
16356 window: &mut Window,
16357 cx: &mut Context<Self>,
16358 ) {
16359 let permalink_task = self.get_permalink_to_line(cx);
16360 let workspace = self.workspace();
16361
16362 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16363 Ok(permalink) => {
16364 cx.update(|_, cx| {
16365 cx.open_url(permalink.as_ref());
16366 })
16367 .ok();
16368 }
16369 Err(err) => {
16370 let message = format!("Failed to open permalink: {err}");
16371
16372 Err::<(), anyhow::Error>(err).log_err();
16373
16374 if let Some(workspace) = workspace {
16375 workspace
16376 .update(cx, |workspace, cx| {
16377 struct OpenPermalinkToLine;
16378
16379 workspace.show_toast(
16380 Toast::new(
16381 NotificationId::unique::<OpenPermalinkToLine>(),
16382 message,
16383 ),
16384 cx,
16385 )
16386 })
16387 .ok();
16388 }
16389 }
16390 })
16391 .detach();
16392 }
16393
16394 pub fn insert_uuid_v4(
16395 &mut self,
16396 _: &InsertUuidV4,
16397 window: &mut Window,
16398 cx: &mut Context<Self>,
16399 ) {
16400 self.insert_uuid(UuidVersion::V4, window, cx);
16401 }
16402
16403 pub fn insert_uuid_v7(
16404 &mut self,
16405 _: &InsertUuidV7,
16406 window: &mut Window,
16407 cx: &mut Context<Self>,
16408 ) {
16409 self.insert_uuid(UuidVersion::V7, window, cx);
16410 }
16411
16412 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16413 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16414 self.transact(window, cx, |this, window, cx| {
16415 let edits = this
16416 .selections
16417 .all::<Point>(cx)
16418 .into_iter()
16419 .map(|selection| {
16420 let uuid = match version {
16421 UuidVersion::V4 => uuid::Uuid::new_v4(),
16422 UuidVersion::V7 => uuid::Uuid::now_v7(),
16423 };
16424
16425 (selection.range(), uuid.to_string())
16426 });
16427 this.edit(edits, cx);
16428 this.refresh_inline_completion(true, false, window, cx);
16429 });
16430 }
16431
16432 pub fn open_selections_in_multibuffer(
16433 &mut self,
16434 _: &OpenSelectionsInMultibuffer,
16435 window: &mut Window,
16436 cx: &mut Context<Self>,
16437 ) {
16438 let multibuffer = self.buffer.read(cx);
16439
16440 let Some(buffer) = multibuffer.as_singleton() else {
16441 return;
16442 };
16443
16444 let Some(workspace) = self.workspace() else {
16445 return;
16446 };
16447
16448 let locations = self
16449 .selections
16450 .disjoint_anchors()
16451 .iter()
16452 .map(|range| Location {
16453 buffer: buffer.clone(),
16454 range: range.start.text_anchor..range.end.text_anchor,
16455 })
16456 .collect::<Vec<_>>();
16457
16458 let title = multibuffer.title(cx).to_string();
16459
16460 cx.spawn_in(window, async move |_, cx| {
16461 workspace.update_in(cx, |workspace, window, cx| {
16462 Self::open_locations_in_multibuffer(
16463 workspace,
16464 locations,
16465 format!("Selections for '{title}'"),
16466 false,
16467 MultibufferSelectionMode::All,
16468 window,
16469 cx,
16470 );
16471 })
16472 })
16473 .detach();
16474 }
16475
16476 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16477 /// last highlight added will be used.
16478 ///
16479 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16480 pub fn highlight_rows<T: 'static>(
16481 &mut self,
16482 range: Range<Anchor>,
16483 color: Hsla,
16484 should_autoscroll: bool,
16485 cx: &mut Context<Self>,
16486 ) {
16487 let snapshot = self.buffer().read(cx).snapshot(cx);
16488 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16489 let ix = row_highlights.binary_search_by(|highlight| {
16490 Ordering::Equal
16491 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16492 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16493 });
16494
16495 if let Err(mut ix) = ix {
16496 let index = post_inc(&mut self.highlight_order);
16497
16498 // If this range intersects with the preceding highlight, then merge it with
16499 // the preceding highlight. Otherwise insert a new highlight.
16500 let mut merged = false;
16501 if ix > 0 {
16502 let prev_highlight = &mut row_highlights[ix - 1];
16503 if prev_highlight
16504 .range
16505 .end
16506 .cmp(&range.start, &snapshot)
16507 .is_ge()
16508 {
16509 ix -= 1;
16510 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16511 prev_highlight.range.end = range.end;
16512 }
16513 merged = true;
16514 prev_highlight.index = index;
16515 prev_highlight.color = color;
16516 prev_highlight.should_autoscroll = should_autoscroll;
16517 }
16518 }
16519
16520 if !merged {
16521 row_highlights.insert(
16522 ix,
16523 RowHighlight {
16524 range: range.clone(),
16525 index,
16526 color,
16527 should_autoscroll,
16528 },
16529 );
16530 }
16531
16532 // If any of the following highlights intersect with this one, merge them.
16533 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16534 let highlight = &row_highlights[ix];
16535 if next_highlight
16536 .range
16537 .start
16538 .cmp(&highlight.range.end, &snapshot)
16539 .is_le()
16540 {
16541 if next_highlight
16542 .range
16543 .end
16544 .cmp(&highlight.range.end, &snapshot)
16545 .is_gt()
16546 {
16547 row_highlights[ix].range.end = next_highlight.range.end;
16548 }
16549 row_highlights.remove(ix + 1);
16550 } else {
16551 break;
16552 }
16553 }
16554 }
16555 }
16556
16557 /// Remove any highlighted row ranges of the given type that intersect the
16558 /// given ranges.
16559 pub fn remove_highlighted_rows<T: 'static>(
16560 &mut self,
16561 ranges_to_remove: Vec<Range<Anchor>>,
16562 cx: &mut Context<Self>,
16563 ) {
16564 let snapshot = self.buffer().read(cx).snapshot(cx);
16565 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16566 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16567 row_highlights.retain(|highlight| {
16568 while let Some(range_to_remove) = ranges_to_remove.peek() {
16569 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16570 Ordering::Less | Ordering::Equal => {
16571 ranges_to_remove.next();
16572 }
16573 Ordering::Greater => {
16574 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16575 Ordering::Less | Ordering::Equal => {
16576 return false;
16577 }
16578 Ordering::Greater => break,
16579 }
16580 }
16581 }
16582 }
16583
16584 true
16585 })
16586 }
16587
16588 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16589 pub fn clear_row_highlights<T: 'static>(&mut self) {
16590 self.highlighted_rows.remove(&TypeId::of::<T>());
16591 }
16592
16593 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16594 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16595 self.highlighted_rows
16596 .get(&TypeId::of::<T>())
16597 .map_or(&[] as &[_], |vec| vec.as_slice())
16598 .iter()
16599 .map(|highlight| (highlight.range.clone(), highlight.color))
16600 }
16601
16602 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16603 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16604 /// Allows to ignore certain kinds of highlights.
16605 pub fn highlighted_display_rows(
16606 &self,
16607 window: &mut Window,
16608 cx: &mut App,
16609 ) -> BTreeMap<DisplayRow, LineHighlight> {
16610 let snapshot = self.snapshot(window, cx);
16611 let mut used_highlight_orders = HashMap::default();
16612 self.highlighted_rows
16613 .iter()
16614 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16615 .fold(
16616 BTreeMap::<DisplayRow, LineHighlight>::new(),
16617 |mut unique_rows, highlight| {
16618 let start = highlight.range.start.to_display_point(&snapshot);
16619 let end = highlight.range.end.to_display_point(&snapshot);
16620 let start_row = start.row().0;
16621 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16622 && end.column() == 0
16623 {
16624 end.row().0.saturating_sub(1)
16625 } else {
16626 end.row().0
16627 };
16628 for row in start_row..=end_row {
16629 let used_index =
16630 used_highlight_orders.entry(row).or_insert(highlight.index);
16631 if highlight.index >= *used_index {
16632 *used_index = highlight.index;
16633 unique_rows.insert(DisplayRow(row), highlight.color.into());
16634 }
16635 }
16636 unique_rows
16637 },
16638 )
16639 }
16640
16641 pub fn highlighted_display_row_for_autoscroll(
16642 &self,
16643 snapshot: &DisplaySnapshot,
16644 ) -> Option<DisplayRow> {
16645 self.highlighted_rows
16646 .values()
16647 .flat_map(|highlighted_rows| highlighted_rows.iter())
16648 .filter_map(|highlight| {
16649 if highlight.should_autoscroll {
16650 Some(highlight.range.start.to_display_point(snapshot).row())
16651 } else {
16652 None
16653 }
16654 })
16655 .min()
16656 }
16657
16658 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16659 self.highlight_background::<SearchWithinRange>(
16660 ranges,
16661 |colors| colors.editor_document_highlight_read_background,
16662 cx,
16663 )
16664 }
16665
16666 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16667 self.breadcrumb_header = Some(new_header);
16668 }
16669
16670 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16671 self.clear_background_highlights::<SearchWithinRange>(cx);
16672 }
16673
16674 pub fn highlight_background<T: 'static>(
16675 &mut self,
16676 ranges: &[Range<Anchor>],
16677 color_fetcher: fn(&ThemeColors) -> Hsla,
16678 cx: &mut Context<Self>,
16679 ) {
16680 self.background_highlights
16681 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16682 self.scrollbar_marker_state.dirty = true;
16683 cx.notify();
16684 }
16685
16686 pub fn clear_background_highlights<T: 'static>(
16687 &mut self,
16688 cx: &mut Context<Self>,
16689 ) -> Option<BackgroundHighlight> {
16690 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16691 if !text_highlights.1.is_empty() {
16692 self.scrollbar_marker_state.dirty = true;
16693 cx.notify();
16694 }
16695 Some(text_highlights)
16696 }
16697
16698 pub fn highlight_gutter<T: 'static>(
16699 &mut self,
16700 ranges: &[Range<Anchor>],
16701 color_fetcher: fn(&App) -> Hsla,
16702 cx: &mut Context<Self>,
16703 ) {
16704 self.gutter_highlights
16705 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16706 cx.notify();
16707 }
16708
16709 pub fn clear_gutter_highlights<T: 'static>(
16710 &mut self,
16711 cx: &mut Context<Self>,
16712 ) -> Option<GutterHighlight> {
16713 cx.notify();
16714 self.gutter_highlights.remove(&TypeId::of::<T>())
16715 }
16716
16717 #[cfg(feature = "test-support")]
16718 pub fn all_text_background_highlights(
16719 &self,
16720 window: &mut Window,
16721 cx: &mut Context<Self>,
16722 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16723 let snapshot = self.snapshot(window, cx);
16724 let buffer = &snapshot.buffer_snapshot;
16725 let start = buffer.anchor_before(0);
16726 let end = buffer.anchor_after(buffer.len());
16727 let theme = cx.theme().colors();
16728 self.background_highlights_in_range(start..end, &snapshot, theme)
16729 }
16730
16731 #[cfg(feature = "test-support")]
16732 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16733 let snapshot = self.buffer().read(cx).snapshot(cx);
16734
16735 let highlights = self
16736 .background_highlights
16737 .get(&TypeId::of::<items::BufferSearchHighlights>());
16738
16739 if let Some((_color, ranges)) = highlights {
16740 ranges
16741 .iter()
16742 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16743 .collect_vec()
16744 } else {
16745 vec![]
16746 }
16747 }
16748
16749 fn document_highlights_for_position<'a>(
16750 &'a self,
16751 position: Anchor,
16752 buffer: &'a MultiBufferSnapshot,
16753 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16754 let read_highlights = self
16755 .background_highlights
16756 .get(&TypeId::of::<DocumentHighlightRead>())
16757 .map(|h| &h.1);
16758 let write_highlights = self
16759 .background_highlights
16760 .get(&TypeId::of::<DocumentHighlightWrite>())
16761 .map(|h| &h.1);
16762 let left_position = position.bias_left(buffer);
16763 let right_position = position.bias_right(buffer);
16764 read_highlights
16765 .into_iter()
16766 .chain(write_highlights)
16767 .flat_map(move |ranges| {
16768 let start_ix = match ranges.binary_search_by(|probe| {
16769 let cmp = probe.end.cmp(&left_position, buffer);
16770 if cmp.is_ge() {
16771 Ordering::Greater
16772 } else {
16773 Ordering::Less
16774 }
16775 }) {
16776 Ok(i) | Err(i) => i,
16777 };
16778
16779 ranges[start_ix..]
16780 .iter()
16781 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16782 })
16783 }
16784
16785 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16786 self.background_highlights
16787 .get(&TypeId::of::<T>())
16788 .map_or(false, |(_, highlights)| !highlights.is_empty())
16789 }
16790
16791 pub fn background_highlights_in_range(
16792 &self,
16793 search_range: Range<Anchor>,
16794 display_snapshot: &DisplaySnapshot,
16795 theme: &ThemeColors,
16796 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16797 let mut results = Vec::new();
16798 for (color_fetcher, ranges) in self.background_highlights.values() {
16799 let color = color_fetcher(theme);
16800 let start_ix = match ranges.binary_search_by(|probe| {
16801 let cmp = probe
16802 .end
16803 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16804 if cmp.is_gt() {
16805 Ordering::Greater
16806 } else {
16807 Ordering::Less
16808 }
16809 }) {
16810 Ok(i) | Err(i) => i,
16811 };
16812 for range in &ranges[start_ix..] {
16813 if range
16814 .start
16815 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16816 .is_ge()
16817 {
16818 break;
16819 }
16820
16821 let start = range.start.to_display_point(display_snapshot);
16822 let end = range.end.to_display_point(display_snapshot);
16823 results.push((start..end, color))
16824 }
16825 }
16826 results
16827 }
16828
16829 pub fn background_highlight_row_ranges<T: 'static>(
16830 &self,
16831 search_range: Range<Anchor>,
16832 display_snapshot: &DisplaySnapshot,
16833 count: usize,
16834 ) -> Vec<RangeInclusive<DisplayPoint>> {
16835 let mut results = Vec::new();
16836 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16837 return vec![];
16838 };
16839
16840 let start_ix = match ranges.binary_search_by(|probe| {
16841 let cmp = probe
16842 .end
16843 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16844 if cmp.is_gt() {
16845 Ordering::Greater
16846 } else {
16847 Ordering::Less
16848 }
16849 }) {
16850 Ok(i) | Err(i) => i,
16851 };
16852 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16853 if let (Some(start_display), Some(end_display)) = (start, end) {
16854 results.push(
16855 start_display.to_display_point(display_snapshot)
16856 ..=end_display.to_display_point(display_snapshot),
16857 );
16858 }
16859 };
16860 let mut start_row: Option<Point> = None;
16861 let mut end_row: Option<Point> = None;
16862 if ranges.len() > count {
16863 return Vec::new();
16864 }
16865 for range in &ranges[start_ix..] {
16866 if range
16867 .start
16868 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16869 .is_ge()
16870 {
16871 break;
16872 }
16873 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16874 if let Some(current_row) = &end_row {
16875 if end.row == current_row.row {
16876 continue;
16877 }
16878 }
16879 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16880 if start_row.is_none() {
16881 assert_eq!(end_row, None);
16882 start_row = Some(start);
16883 end_row = Some(end);
16884 continue;
16885 }
16886 if let Some(current_end) = end_row.as_mut() {
16887 if start.row > current_end.row + 1 {
16888 push_region(start_row, end_row);
16889 start_row = Some(start);
16890 end_row = Some(end);
16891 } else {
16892 // Merge two hunks.
16893 *current_end = end;
16894 }
16895 } else {
16896 unreachable!();
16897 }
16898 }
16899 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16900 push_region(start_row, end_row);
16901 results
16902 }
16903
16904 pub fn gutter_highlights_in_range(
16905 &self,
16906 search_range: Range<Anchor>,
16907 display_snapshot: &DisplaySnapshot,
16908 cx: &App,
16909 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16910 let mut results = Vec::new();
16911 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16912 let color = color_fetcher(cx);
16913 let start_ix = match ranges.binary_search_by(|probe| {
16914 let cmp = probe
16915 .end
16916 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16917 if cmp.is_gt() {
16918 Ordering::Greater
16919 } else {
16920 Ordering::Less
16921 }
16922 }) {
16923 Ok(i) | Err(i) => i,
16924 };
16925 for range in &ranges[start_ix..] {
16926 if range
16927 .start
16928 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16929 .is_ge()
16930 {
16931 break;
16932 }
16933
16934 let start = range.start.to_display_point(display_snapshot);
16935 let end = range.end.to_display_point(display_snapshot);
16936 results.push((start..end, color))
16937 }
16938 }
16939 results
16940 }
16941
16942 /// Get the text ranges corresponding to the redaction query
16943 pub fn redacted_ranges(
16944 &self,
16945 search_range: Range<Anchor>,
16946 display_snapshot: &DisplaySnapshot,
16947 cx: &App,
16948 ) -> Vec<Range<DisplayPoint>> {
16949 display_snapshot
16950 .buffer_snapshot
16951 .redacted_ranges(search_range, |file| {
16952 if let Some(file) = file {
16953 file.is_private()
16954 && EditorSettings::get(
16955 Some(SettingsLocation {
16956 worktree_id: file.worktree_id(cx),
16957 path: file.path().as_ref(),
16958 }),
16959 cx,
16960 )
16961 .redact_private_values
16962 } else {
16963 false
16964 }
16965 })
16966 .map(|range| {
16967 range.start.to_display_point(display_snapshot)
16968 ..range.end.to_display_point(display_snapshot)
16969 })
16970 .collect()
16971 }
16972
16973 pub fn highlight_text<T: 'static>(
16974 &mut self,
16975 ranges: Vec<Range<Anchor>>,
16976 style: HighlightStyle,
16977 cx: &mut Context<Self>,
16978 ) {
16979 self.display_map.update(cx, |map, _| {
16980 map.highlight_text(TypeId::of::<T>(), ranges, style)
16981 });
16982 cx.notify();
16983 }
16984
16985 pub(crate) fn highlight_inlays<T: 'static>(
16986 &mut self,
16987 highlights: Vec<InlayHighlight>,
16988 style: HighlightStyle,
16989 cx: &mut Context<Self>,
16990 ) {
16991 self.display_map.update(cx, |map, _| {
16992 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16993 });
16994 cx.notify();
16995 }
16996
16997 pub fn text_highlights<'a, T: 'static>(
16998 &'a self,
16999 cx: &'a App,
17000 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17001 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17002 }
17003
17004 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17005 let cleared = self
17006 .display_map
17007 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17008 if cleared {
17009 cx.notify();
17010 }
17011 }
17012
17013 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17014 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17015 && self.focus_handle.is_focused(window)
17016 }
17017
17018 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17019 self.show_cursor_when_unfocused = is_enabled;
17020 cx.notify();
17021 }
17022
17023 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17024 cx.notify();
17025 }
17026
17027 fn on_buffer_event(
17028 &mut self,
17029 multibuffer: &Entity<MultiBuffer>,
17030 event: &multi_buffer::Event,
17031 window: &mut Window,
17032 cx: &mut Context<Self>,
17033 ) {
17034 match event {
17035 multi_buffer::Event::Edited {
17036 singleton_buffer_edited,
17037 edited_buffer: buffer_edited,
17038 } => {
17039 self.scrollbar_marker_state.dirty = true;
17040 self.active_indent_guides_state.dirty = true;
17041 self.refresh_active_diagnostics(cx);
17042 self.refresh_code_actions(window, cx);
17043 if self.has_active_inline_completion() {
17044 self.update_visible_inline_completion(window, cx);
17045 }
17046 if let Some(buffer) = buffer_edited {
17047 let buffer_id = buffer.read(cx).remote_id();
17048 if !self.registered_buffers.contains_key(&buffer_id) {
17049 if let Some(project) = self.project.as_ref() {
17050 project.update(cx, |project, cx| {
17051 self.registered_buffers.insert(
17052 buffer_id,
17053 project.register_buffer_with_language_servers(&buffer, cx),
17054 );
17055 })
17056 }
17057 }
17058 }
17059 cx.emit(EditorEvent::BufferEdited);
17060 cx.emit(SearchEvent::MatchesInvalidated);
17061 if *singleton_buffer_edited {
17062 if let Some(project) = &self.project {
17063 #[allow(clippy::mutable_key_type)]
17064 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17065 multibuffer
17066 .all_buffers()
17067 .into_iter()
17068 .filter_map(|buffer| {
17069 buffer.update(cx, |buffer, cx| {
17070 let language = buffer.language()?;
17071 let should_discard = project.update(cx, |project, cx| {
17072 project.is_local()
17073 && !project.has_language_servers_for(buffer, cx)
17074 });
17075 should_discard.not().then_some(language.clone())
17076 })
17077 })
17078 .collect::<HashSet<_>>()
17079 });
17080 if !languages_affected.is_empty() {
17081 self.refresh_inlay_hints(
17082 InlayHintRefreshReason::BufferEdited(languages_affected),
17083 cx,
17084 );
17085 }
17086 }
17087 }
17088
17089 let Some(project) = &self.project else { return };
17090 let (telemetry, is_via_ssh) = {
17091 let project = project.read(cx);
17092 let telemetry = project.client().telemetry().clone();
17093 let is_via_ssh = project.is_via_ssh();
17094 (telemetry, is_via_ssh)
17095 };
17096 refresh_linked_ranges(self, window, cx);
17097 telemetry.log_edit_event("editor", is_via_ssh);
17098 }
17099 multi_buffer::Event::ExcerptsAdded {
17100 buffer,
17101 predecessor,
17102 excerpts,
17103 } => {
17104 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17105 let buffer_id = buffer.read(cx).remote_id();
17106 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17107 if let Some(project) = &self.project {
17108 get_uncommitted_diff_for_buffer(
17109 project,
17110 [buffer.clone()],
17111 self.buffer.clone(),
17112 cx,
17113 )
17114 .detach();
17115 }
17116 }
17117 cx.emit(EditorEvent::ExcerptsAdded {
17118 buffer: buffer.clone(),
17119 predecessor: *predecessor,
17120 excerpts: excerpts.clone(),
17121 });
17122 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17123 }
17124 multi_buffer::Event::ExcerptsRemoved { ids } => {
17125 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17126 let buffer = self.buffer.read(cx);
17127 self.registered_buffers
17128 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17129 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17130 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
17131 }
17132 multi_buffer::Event::ExcerptsEdited {
17133 excerpt_ids,
17134 buffer_ids,
17135 } => {
17136 self.display_map.update(cx, |map, cx| {
17137 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17138 });
17139 cx.emit(EditorEvent::ExcerptsEdited {
17140 ids: excerpt_ids.clone(),
17141 })
17142 }
17143 multi_buffer::Event::ExcerptsExpanded { ids } => {
17144 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17145 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17146 }
17147 multi_buffer::Event::Reparsed(buffer_id) => {
17148 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17149 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17150
17151 cx.emit(EditorEvent::Reparsed(*buffer_id));
17152 }
17153 multi_buffer::Event::DiffHunksToggled => {
17154 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17155 }
17156 multi_buffer::Event::LanguageChanged(buffer_id) => {
17157 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17158 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17159 cx.emit(EditorEvent::Reparsed(*buffer_id));
17160 cx.notify();
17161 }
17162 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17163 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17164 multi_buffer::Event::FileHandleChanged
17165 | multi_buffer::Event::Reloaded
17166 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17167 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17168 multi_buffer::Event::DiagnosticsUpdated => {
17169 self.refresh_active_diagnostics(cx);
17170 self.refresh_inline_diagnostics(true, window, cx);
17171 self.scrollbar_marker_state.dirty = true;
17172 cx.notify();
17173 }
17174 _ => {}
17175 };
17176 }
17177
17178 fn on_display_map_changed(
17179 &mut self,
17180 _: Entity<DisplayMap>,
17181 _: &mut Window,
17182 cx: &mut Context<Self>,
17183 ) {
17184 cx.notify();
17185 }
17186
17187 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17188 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17189 self.update_edit_prediction_settings(cx);
17190 self.refresh_inline_completion(true, false, window, cx);
17191 self.refresh_inlay_hints(
17192 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17193 self.selections.newest_anchor().head(),
17194 &self.buffer.read(cx).snapshot(cx),
17195 cx,
17196 )),
17197 cx,
17198 );
17199
17200 let old_cursor_shape = self.cursor_shape;
17201
17202 {
17203 let editor_settings = EditorSettings::get_global(cx);
17204 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17205 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17206 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17207 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17208 }
17209
17210 if old_cursor_shape != self.cursor_shape {
17211 cx.emit(EditorEvent::CursorShapeChanged);
17212 }
17213
17214 let project_settings = ProjectSettings::get_global(cx);
17215 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17216
17217 if self.mode == EditorMode::Full {
17218 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17219 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17220 if self.show_inline_diagnostics != show_inline_diagnostics {
17221 self.show_inline_diagnostics = show_inline_diagnostics;
17222 self.refresh_inline_diagnostics(false, window, cx);
17223 }
17224
17225 if self.git_blame_inline_enabled != inline_blame_enabled {
17226 self.toggle_git_blame_inline_internal(false, window, cx);
17227 }
17228 }
17229
17230 cx.notify();
17231 }
17232
17233 pub fn set_searchable(&mut self, searchable: bool) {
17234 self.searchable = searchable;
17235 }
17236
17237 pub fn searchable(&self) -> bool {
17238 self.searchable
17239 }
17240
17241 fn open_proposed_changes_editor(
17242 &mut self,
17243 _: &OpenProposedChangesEditor,
17244 window: &mut Window,
17245 cx: &mut Context<Self>,
17246 ) {
17247 let Some(workspace) = self.workspace() else {
17248 cx.propagate();
17249 return;
17250 };
17251
17252 let selections = self.selections.all::<usize>(cx);
17253 let multi_buffer = self.buffer.read(cx);
17254 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17255 let mut new_selections_by_buffer = HashMap::default();
17256 for selection in selections {
17257 for (buffer, range, _) in
17258 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17259 {
17260 let mut range = range.to_point(buffer);
17261 range.start.column = 0;
17262 range.end.column = buffer.line_len(range.end.row);
17263 new_selections_by_buffer
17264 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17265 .or_insert(Vec::new())
17266 .push(range)
17267 }
17268 }
17269
17270 let proposed_changes_buffers = new_selections_by_buffer
17271 .into_iter()
17272 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17273 .collect::<Vec<_>>();
17274 let proposed_changes_editor = cx.new(|cx| {
17275 ProposedChangesEditor::new(
17276 "Proposed changes",
17277 proposed_changes_buffers,
17278 self.project.clone(),
17279 window,
17280 cx,
17281 )
17282 });
17283
17284 window.defer(cx, move |window, cx| {
17285 workspace.update(cx, |workspace, cx| {
17286 workspace.active_pane().update(cx, |pane, cx| {
17287 pane.add_item(
17288 Box::new(proposed_changes_editor),
17289 true,
17290 true,
17291 None,
17292 window,
17293 cx,
17294 );
17295 });
17296 });
17297 });
17298 }
17299
17300 pub fn open_excerpts_in_split(
17301 &mut self,
17302 _: &OpenExcerptsSplit,
17303 window: &mut Window,
17304 cx: &mut Context<Self>,
17305 ) {
17306 self.open_excerpts_common(None, true, window, cx)
17307 }
17308
17309 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17310 self.open_excerpts_common(None, false, window, cx)
17311 }
17312
17313 fn open_excerpts_common(
17314 &mut self,
17315 jump_data: Option<JumpData>,
17316 split: bool,
17317 window: &mut Window,
17318 cx: &mut Context<Self>,
17319 ) {
17320 let Some(workspace) = self.workspace() else {
17321 cx.propagate();
17322 return;
17323 };
17324
17325 if self.buffer.read(cx).is_singleton() {
17326 cx.propagate();
17327 return;
17328 }
17329
17330 let mut new_selections_by_buffer = HashMap::default();
17331 match &jump_data {
17332 Some(JumpData::MultiBufferPoint {
17333 excerpt_id,
17334 position,
17335 anchor,
17336 line_offset_from_top,
17337 }) => {
17338 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17339 if let Some(buffer) = multi_buffer_snapshot
17340 .buffer_id_for_excerpt(*excerpt_id)
17341 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17342 {
17343 let buffer_snapshot = buffer.read(cx).snapshot();
17344 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17345 language::ToPoint::to_point(anchor, &buffer_snapshot)
17346 } else {
17347 buffer_snapshot.clip_point(*position, Bias::Left)
17348 };
17349 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17350 new_selections_by_buffer.insert(
17351 buffer,
17352 (
17353 vec![jump_to_offset..jump_to_offset],
17354 Some(*line_offset_from_top),
17355 ),
17356 );
17357 }
17358 }
17359 Some(JumpData::MultiBufferRow {
17360 row,
17361 line_offset_from_top,
17362 }) => {
17363 let point = MultiBufferPoint::new(row.0, 0);
17364 if let Some((buffer, buffer_point, _)) =
17365 self.buffer.read(cx).point_to_buffer_point(point, cx)
17366 {
17367 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17368 new_selections_by_buffer
17369 .entry(buffer)
17370 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17371 .0
17372 .push(buffer_offset..buffer_offset)
17373 }
17374 }
17375 None => {
17376 let selections = self.selections.all::<usize>(cx);
17377 let multi_buffer = self.buffer.read(cx);
17378 for selection in selections {
17379 for (snapshot, range, _, anchor) in multi_buffer
17380 .snapshot(cx)
17381 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17382 {
17383 if let Some(anchor) = anchor {
17384 // selection is in a deleted hunk
17385 let Some(buffer_id) = anchor.buffer_id else {
17386 continue;
17387 };
17388 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17389 continue;
17390 };
17391 let offset = text::ToOffset::to_offset(
17392 &anchor.text_anchor,
17393 &buffer_handle.read(cx).snapshot(),
17394 );
17395 let range = offset..offset;
17396 new_selections_by_buffer
17397 .entry(buffer_handle)
17398 .or_insert((Vec::new(), None))
17399 .0
17400 .push(range)
17401 } else {
17402 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17403 else {
17404 continue;
17405 };
17406 new_selections_by_buffer
17407 .entry(buffer_handle)
17408 .or_insert((Vec::new(), None))
17409 .0
17410 .push(range)
17411 }
17412 }
17413 }
17414 }
17415 }
17416
17417 new_selections_by_buffer
17418 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
17419
17420 if new_selections_by_buffer.is_empty() {
17421 return;
17422 }
17423
17424 // We defer the pane interaction because we ourselves are a workspace item
17425 // and activating a new item causes the pane to call a method on us reentrantly,
17426 // which panics if we're on the stack.
17427 window.defer(cx, move |window, cx| {
17428 workspace.update(cx, |workspace, cx| {
17429 let pane = if split {
17430 workspace.adjacent_pane(window, cx)
17431 } else {
17432 workspace.active_pane().clone()
17433 };
17434
17435 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17436 let editor = buffer
17437 .read(cx)
17438 .file()
17439 .is_none()
17440 .then(|| {
17441 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17442 // so `workspace.open_project_item` will never find them, always opening a new editor.
17443 // Instead, we try to activate the existing editor in the pane first.
17444 let (editor, pane_item_index) =
17445 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17446 let editor = item.downcast::<Editor>()?;
17447 let singleton_buffer =
17448 editor.read(cx).buffer().read(cx).as_singleton()?;
17449 if singleton_buffer == buffer {
17450 Some((editor, i))
17451 } else {
17452 None
17453 }
17454 })?;
17455 pane.update(cx, |pane, cx| {
17456 pane.activate_item(pane_item_index, true, true, window, cx)
17457 });
17458 Some(editor)
17459 })
17460 .flatten()
17461 .unwrap_or_else(|| {
17462 workspace.open_project_item::<Self>(
17463 pane.clone(),
17464 buffer,
17465 true,
17466 true,
17467 window,
17468 cx,
17469 )
17470 });
17471
17472 editor.update(cx, |editor, cx| {
17473 let autoscroll = match scroll_offset {
17474 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17475 None => Autoscroll::newest(),
17476 };
17477 let nav_history = editor.nav_history.take();
17478 editor.change_selections(Some(autoscroll), window, cx, |s| {
17479 s.select_ranges(ranges);
17480 });
17481 editor.nav_history = nav_history;
17482 });
17483 }
17484 })
17485 });
17486 }
17487
17488 // For now, don't allow opening excerpts in buffers that aren't backed by
17489 // regular project files.
17490 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
17491 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
17492 }
17493
17494 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17495 let snapshot = self.buffer.read(cx).read(cx);
17496 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17497 Some(
17498 ranges
17499 .iter()
17500 .map(move |range| {
17501 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17502 })
17503 .collect(),
17504 )
17505 }
17506
17507 fn selection_replacement_ranges(
17508 &self,
17509 range: Range<OffsetUtf16>,
17510 cx: &mut App,
17511 ) -> Vec<Range<OffsetUtf16>> {
17512 let selections = self.selections.all::<OffsetUtf16>(cx);
17513 let newest_selection = selections
17514 .iter()
17515 .max_by_key(|selection| selection.id)
17516 .unwrap();
17517 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17518 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17519 let snapshot = self.buffer.read(cx).read(cx);
17520 selections
17521 .into_iter()
17522 .map(|mut selection| {
17523 selection.start.0 =
17524 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17525 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17526 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17527 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17528 })
17529 .collect()
17530 }
17531
17532 fn report_editor_event(
17533 &self,
17534 event_type: &'static str,
17535 file_extension: Option<String>,
17536 cx: &App,
17537 ) {
17538 if cfg!(any(test, feature = "test-support")) {
17539 return;
17540 }
17541
17542 let Some(project) = &self.project else { return };
17543
17544 // If None, we are in a file without an extension
17545 let file = self
17546 .buffer
17547 .read(cx)
17548 .as_singleton()
17549 .and_then(|b| b.read(cx).file());
17550 let file_extension = file_extension.or(file
17551 .as_ref()
17552 .and_then(|file| Path::new(file.file_name(cx)).extension())
17553 .and_then(|e| e.to_str())
17554 .map(|a| a.to_string()));
17555
17556 let vim_mode = cx
17557 .global::<SettingsStore>()
17558 .raw_user_settings()
17559 .get("vim_mode")
17560 == Some(&serde_json::Value::Bool(true));
17561
17562 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17563 let copilot_enabled = edit_predictions_provider
17564 == language::language_settings::EditPredictionProvider::Copilot;
17565 let copilot_enabled_for_language = self
17566 .buffer
17567 .read(cx)
17568 .language_settings(cx)
17569 .show_edit_predictions;
17570
17571 let project = project.read(cx);
17572 telemetry::event!(
17573 event_type,
17574 file_extension,
17575 vim_mode,
17576 copilot_enabled,
17577 copilot_enabled_for_language,
17578 edit_predictions_provider,
17579 is_via_ssh = project.is_via_ssh(),
17580 );
17581 }
17582
17583 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17584 /// with each line being an array of {text, highlight} objects.
17585 fn copy_highlight_json(
17586 &mut self,
17587 _: &CopyHighlightJson,
17588 window: &mut Window,
17589 cx: &mut Context<Self>,
17590 ) {
17591 #[derive(Serialize)]
17592 struct Chunk<'a> {
17593 text: String,
17594 highlight: Option<&'a str>,
17595 }
17596
17597 let snapshot = self.buffer.read(cx).snapshot(cx);
17598 let range = self
17599 .selected_text_range(false, window, cx)
17600 .and_then(|selection| {
17601 if selection.range.is_empty() {
17602 None
17603 } else {
17604 Some(selection.range)
17605 }
17606 })
17607 .unwrap_or_else(|| 0..snapshot.len());
17608
17609 let chunks = snapshot.chunks(range, true);
17610 let mut lines = Vec::new();
17611 let mut line: VecDeque<Chunk> = VecDeque::new();
17612
17613 let Some(style) = self.style.as_ref() else {
17614 return;
17615 };
17616
17617 for chunk in chunks {
17618 let highlight = chunk
17619 .syntax_highlight_id
17620 .and_then(|id| id.name(&style.syntax));
17621 let mut chunk_lines = chunk.text.split('\n').peekable();
17622 while let Some(text) = chunk_lines.next() {
17623 let mut merged_with_last_token = false;
17624 if let Some(last_token) = line.back_mut() {
17625 if last_token.highlight == highlight {
17626 last_token.text.push_str(text);
17627 merged_with_last_token = true;
17628 }
17629 }
17630
17631 if !merged_with_last_token {
17632 line.push_back(Chunk {
17633 text: text.into(),
17634 highlight,
17635 });
17636 }
17637
17638 if chunk_lines.peek().is_some() {
17639 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17640 line.pop_front();
17641 }
17642 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17643 line.pop_back();
17644 }
17645
17646 lines.push(mem::take(&mut line));
17647 }
17648 }
17649 }
17650
17651 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17652 return;
17653 };
17654 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17655 }
17656
17657 pub fn open_context_menu(
17658 &mut self,
17659 _: &OpenContextMenu,
17660 window: &mut Window,
17661 cx: &mut Context<Self>,
17662 ) {
17663 self.request_autoscroll(Autoscroll::newest(), cx);
17664 let position = self.selections.newest_display(cx).start;
17665 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17666 }
17667
17668 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17669 &self.inlay_hint_cache
17670 }
17671
17672 pub fn replay_insert_event(
17673 &mut self,
17674 text: &str,
17675 relative_utf16_range: Option<Range<isize>>,
17676 window: &mut Window,
17677 cx: &mut Context<Self>,
17678 ) {
17679 if !self.input_enabled {
17680 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17681 return;
17682 }
17683 if let Some(relative_utf16_range) = relative_utf16_range {
17684 let selections = self.selections.all::<OffsetUtf16>(cx);
17685 self.change_selections(None, window, cx, |s| {
17686 let new_ranges = selections.into_iter().map(|range| {
17687 let start = OffsetUtf16(
17688 range
17689 .head()
17690 .0
17691 .saturating_add_signed(relative_utf16_range.start),
17692 );
17693 let end = OffsetUtf16(
17694 range
17695 .head()
17696 .0
17697 .saturating_add_signed(relative_utf16_range.end),
17698 );
17699 start..end
17700 });
17701 s.select_ranges(new_ranges);
17702 });
17703 }
17704
17705 self.handle_input(text, window, cx);
17706 }
17707
17708 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17709 let Some(provider) = self.semantics_provider.as_ref() else {
17710 return false;
17711 };
17712
17713 let mut supports = false;
17714 self.buffer().update(cx, |this, cx| {
17715 this.for_each_buffer(|buffer| {
17716 supports |= provider.supports_inlay_hints(buffer, cx);
17717 });
17718 });
17719
17720 supports
17721 }
17722
17723 pub fn is_focused(&self, window: &Window) -> bool {
17724 self.focus_handle.is_focused(window)
17725 }
17726
17727 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17728 cx.emit(EditorEvent::Focused);
17729
17730 if let Some(descendant) = self
17731 .last_focused_descendant
17732 .take()
17733 .and_then(|descendant| descendant.upgrade())
17734 {
17735 window.focus(&descendant);
17736 } else {
17737 if let Some(blame) = self.blame.as_ref() {
17738 blame.update(cx, GitBlame::focus)
17739 }
17740
17741 self.blink_manager.update(cx, BlinkManager::enable);
17742 self.show_cursor_names(window, cx);
17743 self.buffer.update(cx, |buffer, cx| {
17744 buffer.finalize_last_transaction(cx);
17745 if self.leader_peer_id.is_none() {
17746 buffer.set_active_selections(
17747 &self.selections.disjoint_anchors(),
17748 self.selections.line_mode,
17749 self.cursor_shape,
17750 cx,
17751 );
17752 }
17753 });
17754 }
17755 }
17756
17757 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17758 cx.emit(EditorEvent::FocusedIn)
17759 }
17760
17761 fn handle_focus_out(
17762 &mut self,
17763 event: FocusOutEvent,
17764 _window: &mut Window,
17765 cx: &mut Context<Self>,
17766 ) {
17767 if event.blurred != self.focus_handle {
17768 self.last_focused_descendant = Some(event.blurred);
17769 }
17770 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17771 }
17772
17773 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17774 self.blink_manager.update(cx, BlinkManager::disable);
17775 self.buffer
17776 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17777
17778 if let Some(blame) = self.blame.as_ref() {
17779 blame.update(cx, GitBlame::blur)
17780 }
17781 if !self.hover_state.focused(window, cx) {
17782 hide_hover(self, cx);
17783 }
17784 if !self
17785 .context_menu
17786 .borrow()
17787 .as_ref()
17788 .is_some_and(|context_menu| context_menu.focused(window, cx))
17789 {
17790 self.hide_context_menu(window, cx);
17791 }
17792 self.discard_inline_completion(false, cx);
17793 cx.emit(EditorEvent::Blurred);
17794 cx.notify();
17795 }
17796
17797 pub fn register_action<A: Action>(
17798 &mut self,
17799 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17800 ) -> Subscription {
17801 let id = self.next_editor_action_id.post_inc();
17802 let listener = Arc::new(listener);
17803 self.editor_actions.borrow_mut().insert(
17804 id,
17805 Box::new(move |window, _| {
17806 let listener = listener.clone();
17807 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17808 let action = action.downcast_ref().unwrap();
17809 if phase == DispatchPhase::Bubble {
17810 listener(action, window, cx)
17811 }
17812 })
17813 }),
17814 );
17815
17816 let editor_actions = self.editor_actions.clone();
17817 Subscription::new(move || {
17818 editor_actions.borrow_mut().remove(&id);
17819 })
17820 }
17821
17822 pub fn file_header_size(&self) -> u32 {
17823 FILE_HEADER_HEIGHT
17824 }
17825
17826 pub fn restore(
17827 &mut self,
17828 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17829 window: &mut Window,
17830 cx: &mut Context<Self>,
17831 ) {
17832 let workspace = self.workspace();
17833 let project = self.project.as_ref();
17834 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17835 let mut tasks = Vec::new();
17836 for (buffer_id, changes) in revert_changes {
17837 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17838 buffer.update(cx, |buffer, cx| {
17839 buffer.edit(
17840 changes
17841 .into_iter()
17842 .map(|(range, text)| (range, text.to_string())),
17843 None,
17844 cx,
17845 );
17846 });
17847
17848 if let Some(project) =
17849 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17850 {
17851 project.update(cx, |project, cx| {
17852 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17853 })
17854 }
17855 }
17856 }
17857 tasks
17858 });
17859 cx.spawn_in(window, async move |_, cx| {
17860 for (buffer, task) in save_tasks {
17861 let result = task.await;
17862 if result.is_err() {
17863 let Some(path) = buffer
17864 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17865 .ok()
17866 else {
17867 continue;
17868 };
17869 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17870 let Some(task) = cx
17871 .update_window_entity(&workspace, |workspace, window, cx| {
17872 workspace
17873 .open_path_preview(path, None, false, false, false, window, cx)
17874 })
17875 .ok()
17876 else {
17877 continue;
17878 };
17879 task.await.log_err();
17880 }
17881 }
17882 }
17883 })
17884 .detach();
17885 self.change_selections(None, window, cx, |selections| selections.refresh());
17886 }
17887
17888 pub fn to_pixel_point(
17889 &self,
17890 source: multi_buffer::Anchor,
17891 editor_snapshot: &EditorSnapshot,
17892 window: &mut Window,
17893 ) -> Option<gpui::Point<Pixels>> {
17894 let source_point = source.to_display_point(editor_snapshot);
17895 self.display_to_pixel_point(source_point, editor_snapshot, window)
17896 }
17897
17898 pub fn display_to_pixel_point(
17899 &self,
17900 source: DisplayPoint,
17901 editor_snapshot: &EditorSnapshot,
17902 window: &mut Window,
17903 ) -> Option<gpui::Point<Pixels>> {
17904 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17905 let text_layout_details = self.text_layout_details(window);
17906 let scroll_top = text_layout_details
17907 .scroll_anchor
17908 .scroll_position(editor_snapshot)
17909 .y;
17910
17911 if source.row().as_f32() < scroll_top.floor() {
17912 return None;
17913 }
17914 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17915 let source_y = line_height * (source.row().as_f32() - scroll_top);
17916 Some(gpui::Point::new(source_x, source_y))
17917 }
17918
17919 pub fn has_visible_completions_menu(&self) -> bool {
17920 !self.edit_prediction_preview_is_active()
17921 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17922 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17923 })
17924 }
17925
17926 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17927 self.addons
17928 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17929 }
17930
17931 pub fn unregister_addon<T: Addon>(&mut self) {
17932 self.addons.remove(&std::any::TypeId::of::<T>());
17933 }
17934
17935 pub fn addon<T: Addon>(&self) -> Option<&T> {
17936 let type_id = std::any::TypeId::of::<T>();
17937 self.addons
17938 .get(&type_id)
17939 .and_then(|item| item.to_any().downcast_ref::<T>())
17940 }
17941
17942 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17943 let text_layout_details = self.text_layout_details(window);
17944 let style = &text_layout_details.editor_style;
17945 let font_id = window.text_system().resolve_font(&style.text.font());
17946 let font_size = style.text.font_size.to_pixels(window.rem_size());
17947 let line_height = style.text.line_height_in_pixels(window.rem_size());
17948 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17949
17950 gpui::Size::new(em_width, line_height)
17951 }
17952
17953 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17954 self.load_diff_task.clone()
17955 }
17956
17957 fn read_metadata_from_db(
17958 &mut self,
17959 item_id: u64,
17960 workspace_id: WorkspaceId,
17961 window: &mut Window,
17962 cx: &mut Context<Editor>,
17963 ) {
17964 if self.is_singleton(cx)
17965 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
17966 {
17967 let buffer_snapshot = OnceCell::new();
17968
17969 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
17970 if !folds.is_empty() {
17971 let snapshot =
17972 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17973 self.fold_ranges(
17974 folds
17975 .into_iter()
17976 .map(|(start, end)| {
17977 snapshot.clip_offset(start, Bias::Left)
17978 ..snapshot.clip_offset(end, Bias::Right)
17979 })
17980 .collect(),
17981 false,
17982 window,
17983 cx,
17984 );
17985 }
17986 }
17987
17988 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
17989 if !selections.is_empty() {
17990 let snapshot =
17991 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17992 self.change_selections(None, window, cx, |s| {
17993 s.select_ranges(selections.into_iter().map(|(start, end)| {
17994 snapshot.clip_offset(start, Bias::Left)
17995 ..snapshot.clip_offset(end, Bias::Right)
17996 }));
17997 });
17998 }
17999 };
18000 }
18001
18002 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18003 }
18004}
18005
18006// Consider user intent and default settings
18007fn choose_completion_range(
18008 completion: &Completion,
18009 intent: CompletionIntent,
18010 buffer: &Entity<Buffer>,
18011 cx: &mut Context<Editor>,
18012) -> Range<usize> {
18013 fn should_replace(
18014 completion: &Completion,
18015 insert_range: &Range<text::Anchor>,
18016 intent: CompletionIntent,
18017 completion_mode_setting: LspInsertMode,
18018 buffer: &Buffer,
18019 ) -> bool {
18020 // specific actions take precedence over settings
18021 match intent {
18022 CompletionIntent::CompleteWithInsert => return false,
18023 CompletionIntent::CompleteWithReplace => return true,
18024 CompletionIntent::Complete | CompletionIntent::Compose => {}
18025 }
18026
18027 match completion_mode_setting {
18028 LspInsertMode::Insert => false,
18029 LspInsertMode::Replace => true,
18030 LspInsertMode::ReplaceSubsequence => {
18031 let mut text_to_replace = buffer.chars_for_range(
18032 buffer.anchor_before(completion.replace_range.start)
18033 ..buffer.anchor_after(completion.replace_range.end),
18034 );
18035 let mut completion_text = completion.new_text.chars();
18036
18037 // is `text_to_replace` a subsequence of `completion_text`
18038 text_to_replace
18039 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18040 }
18041 LspInsertMode::ReplaceSuffix => {
18042 let range_after_cursor = insert_range.end..completion.replace_range.end;
18043
18044 let text_after_cursor = buffer
18045 .text_for_range(
18046 buffer.anchor_before(range_after_cursor.start)
18047 ..buffer.anchor_after(range_after_cursor.end),
18048 )
18049 .collect::<String>();
18050 completion.new_text.ends_with(&text_after_cursor)
18051 }
18052 }
18053 }
18054
18055 let buffer = buffer.read(cx);
18056
18057 if let CompletionSource::Lsp {
18058 insert_range: Some(insert_range),
18059 ..
18060 } = &completion.source
18061 {
18062 let completion_mode_setting =
18063 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18064 .completions
18065 .lsp_insert_mode;
18066
18067 if !should_replace(
18068 completion,
18069 &insert_range,
18070 intent,
18071 completion_mode_setting,
18072 buffer,
18073 ) {
18074 return insert_range.to_offset(buffer);
18075 }
18076 }
18077
18078 completion.replace_range.to_offset(buffer)
18079}
18080
18081fn insert_extra_newline_brackets(
18082 buffer: &MultiBufferSnapshot,
18083 range: Range<usize>,
18084 language: &language::LanguageScope,
18085) -> bool {
18086 let leading_whitespace_len = buffer
18087 .reversed_chars_at(range.start)
18088 .take_while(|c| c.is_whitespace() && *c != '\n')
18089 .map(|c| c.len_utf8())
18090 .sum::<usize>();
18091 let trailing_whitespace_len = buffer
18092 .chars_at(range.end)
18093 .take_while(|c| c.is_whitespace() && *c != '\n')
18094 .map(|c| c.len_utf8())
18095 .sum::<usize>();
18096 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18097
18098 language.brackets().any(|(pair, enabled)| {
18099 let pair_start = pair.start.trim_end();
18100 let pair_end = pair.end.trim_start();
18101
18102 enabled
18103 && pair.newline
18104 && buffer.contains_str_at(range.end, pair_end)
18105 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18106 })
18107}
18108
18109fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18110 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18111 [(buffer, range, _)] => (*buffer, range.clone()),
18112 _ => return false,
18113 };
18114 let pair = {
18115 let mut result: Option<BracketMatch> = None;
18116
18117 for pair in buffer
18118 .all_bracket_ranges(range.clone())
18119 .filter(move |pair| {
18120 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18121 })
18122 {
18123 let len = pair.close_range.end - pair.open_range.start;
18124
18125 if let Some(existing) = &result {
18126 let existing_len = existing.close_range.end - existing.open_range.start;
18127 if len > existing_len {
18128 continue;
18129 }
18130 }
18131
18132 result = Some(pair);
18133 }
18134
18135 result
18136 };
18137 let Some(pair) = pair else {
18138 return false;
18139 };
18140 pair.newline_only
18141 && buffer
18142 .chars_for_range(pair.open_range.end..range.start)
18143 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18144 .all(|c| c.is_whitespace() && c != '\n')
18145}
18146
18147fn get_uncommitted_diff_for_buffer(
18148 project: &Entity<Project>,
18149 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18150 buffer: Entity<MultiBuffer>,
18151 cx: &mut App,
18152) -> Task<()> {
18153 let mut tasks = Vec::new();
18154 project.update(cx, |project, cx| {
18155 for buffer in buffers {
18156 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18157 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18158 }
18159 }
18160 });
18161 cx.spawn(async move |cx| {
18162 let diffs = future::join_all(tasks).await;
18163 buffer
18164 .update(cx, |buffer, cx| {
18165 for diff in diffs.into_iter().flatten() {
18166 buffer.add_diff(diff, cx);
18167 }
18168 })
18169 .ok();
18170 })
18171}
18172
18173fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18174 let tab_size = tab_size.get() as usize;
18175 let mut width = offset;
18176
18177 for ch in text.chars() {
18178 width += if ch == '\t' {
18179 tab_size - (width % tab_size)
18180 } else {
18181 1
18182 };
18183 }
18184
18185 width - offset
18186}
18187
18188#[cfg(test)]
18189mod tests {
18190 use super::*;
18191
18192 #[test]
18193 fn test_string_size_with_expanded_tabs() {
18194 let nz = |val| NonZeroU32::new(val).unwrap();
18195 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18196 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18197 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18198 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18199 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18200 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18201 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18202 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18203 }
18204}
18205
18206/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18207struct WordBreakingTokenizer<'a> {
18208 input: &'a str,
18209}
18210
18211impl<'a> WordBreakingTokenizer<'a> {
18212 fn new(input: &'a str) -> Self {
18213 Self { input }
18214 }
18215}
18216
18217fn is_char_ideographic(ch: char) -> bool {
18218 use unicode_script::Script::*;
18219 use unicode_script::UnicodeScript;
18220 matches!(ch.script(), Han | Tangut | Yi)
18221}
18222
18223fn is_grapheme_ideographic(text: &str) -> bool {
18224 text.chars().any(is_char_ideographic)
18225}
18226
18227fn is_grapheme_whitespace(text: &str) -> bool {
18228 text.chars().any(|x| x.is_whitespace())
18229}
18230
18231fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18232 text.chars().next().map_or(false, |ch| {
18233 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18234 })
18235}
18236
18237#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18238enum WordBreakToken<'a> {
18239 Word { token: &'a str, grapheme_len: usize },
18240 InlineWhitespace { token: &'a str, grapheme_len: usize },
18241 Newline,
18242}
18243
18244impl<'a> Iterator for WordBreakingTokenizer<'a> {
18245 /// Yields a span, the count of graphemes in the token, and whether it was
18246 /// whitespace. Note that it also breaks at word boundaries.
18247 type Item = WordBreakToken<'a>;
18248
18249 fn next(&mut self) -> Option<Self::Item> {
18250 use unicode_segmentation::UnicodeSegmentation;
18251 if self.input.is_empty() {
18252 return None;
18253 }
18254
18255 let mut iter = self.input.graphemes(true).peekable();
18256 let mut offset = 0;
18257 let mut grapheme_len = 0;
18258 if let Some(first_grapheme) = iter.next() {
18259 let is_newline = first_grapheme == "\n";
18260 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18261 offset += first_grapheme.len();
18262 grapheme_len += 1;
18263 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18264 if let Some(grapheme) = iter.peek().copied() {
18265 if should_stay_with_preceding_ideograph(grapheme) {
18266 offset += grapheme.len();
18267 grapheme_len += 1;
18268 }
18269 }
18270 } else {
18271 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18272 let mut next_word_bound = words.peek().copied();
18273 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18274 next_word_bound = words.next();
18275 }
18276 while let Some(grapheme) = iter.peek().copied() {
18277 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18278 break;
18279 };
18280 if is_grapheme_whitespace(grapheme) != is_whitespace
18281 || (grapheme == "\n") != is_newline
18282 {
18283 break;
18284 };
18285 offset += grapheme.len();
18286 grapheme_len += 1;
18287 iter.next();
18288 }
18289 }
18290 let token = &self.input[..offset];
18291 self.input = &self.input[offset..];
18292 if token == "\n" {
18293 Some(WordBreakToken::Newline)
18294 } else if is_whitespace {
18295 Some(WordBreakToken::InlineWhitespace {
18296 token,
18297 grapheme_len,
18298 })
18299 } else {
18300 Some(WordBreakToken::Word {
18301 token,
18302 grapheme_len,
18303 })
18304 }
18305 } else {
18306 None
18307 }
18308 }
18309}
18310
18311#[test]
18312fn test_word_breaking_tokenizer() {
18313 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18314 ("", &[]),
18315 (" ", &[whitespace(" ", 2)]),
18316 ("Ʒ", &[word("Ʒ", 1)]),
18317 ("Ǽ", &[word("Ǽ", 1)]),
18318 ("⋑", &[word("⋑", 1)]),
18319 ("⋑⋑", &[word("⋑⋑", 2)]),
18320 (
18321 "原理,进而",
18322 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18323 ),
18324 (
18325 "hello world",
18326 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18327 ),
18328 (
18329 "hello, world",
18330 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18331 ),
18332 (
18333 " hello world",
18334 &[
18335 whitespace(" ", 2),
18336 word("hello", 5),
18337 whitespace(" ", 1),
18338 word("world", 5),
18339 ],
18340 ),
18341 (
18342 "这是什么 \n 钢笔",
18343 &[
18344 word("这", 1),
18345 word("是", 1),
18346 word("什", 1),
18347 word("么", 1),
18348 whitespace(" ", 1),
18349 newline(),
18350 whitespace(" ", 1),
18351 word("钢", 1),
18352 word("笔", 1),
18353 ],
18354 ),
18355 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
18356 ];
18357
18358 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18359 WordBreakToken::Word {
18360 token,
18361 grapheme_len,
18362 }
18363 }
18364
18365 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18366 WordBreakToken::InlineWhitespace {
18367 token,
18368 grapheme_len,
18369 }
18370 }
18371
18372 fn newline() -> WordBreakToken<'static> {
18373 WordBreakToken::Newline
18374 }
18375
18376 for (input, result) in tests {
18377 assert_eq!(
18378 WordBreakingTokenizer::new(input)
18379 .collect::<Vec<_>>()
18380 .as_slice(),
18381 *result,
18382 );
18383 }
18384}
18385
18386fn wrap_with_prefix(
18387 line_prefix: String,
18388 unwrapped_text: String,
18389 wrap_column: usize,
18390 tab_size: NonZeroU32,
18391 preserve_existing_whitespace: bool,
18392) -> String {
18393 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18394 let mut wrapped_text = String::new();
18395 let mut current_line = line_prefix.clone();
18396
18397 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18398 let mut current_line_len = line_prefix_len;
18399 let mut in_whitespace = false;
18400 for token in tokenizer {
18401 let have_preceding_whitespace = in_whitespace;
18402 match token {
18403 WordBreakToken::Word {
18404 token,
18405 grapheme_len,
18406 } => {
18407 in_whitespace = false;
18408 if current_line_len + grapheme_len > wrap_column
18409 && current_line_len != line_prefix_len
18410 {
18411 wrapped_text.push_str(current_line.trim_end());
18412 wrapped_text.push('\n');
18413 current_line.truncate(line_prefix.len());
18414 current_line_len = line_prefix_len;
18415 }
18416 current_line.push_str(token);
18417 current_line_len += grapheme_len;
18418 }
18419 WordBreakToken::InlineWhitespace {
18420 mut token,
18421 mut grapheme_len,
18422 } => {
18423 in_whitespace = true;
18424 if have_preceding_whitespace && !preserve_existing_whitespace {
18425 continue;
18426 }
18427 if !preserve_existing_whitespace {
18428 token = " ";
18429 grapheme_len = 1;
18430 }
18431 if current_line_len + grapheme_len > wrap_column {
18432 wrapped_text.push_str(current_line.trim_end());
18433 wrapped_text.push('\n');
18434 current_line.truncate(line_prefix.len());
18435 current_line_len = line_prefix_len;
18436 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18437 current_line.push_str(token);
18438 current_line_len += grapheme_len;
18439 }
18440 }
18441 WordBreakToken::Newline => {
18442 in_whitespace = true;
18443 if preserve_existing_whitespace {
18444 wrapped_text.push_str(current_line.trim_end());
18445 wrapped_text.push('\n');
18446 current_line.truncate(line_prefix.len());
18447 current_line_len = line_prefix_len;
18448 } else if have_preceding_whitespace {
18449 continue;
18450 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18451 {
18452 wrapped_text.push_str(current_line.trim_end());
18453 wrapped_text.push('\n');
18454 current_line.truncate(line_prefix.len());
18455 current_line_len = line_prefix_len;
18456 } else if current_line_len != line_prefix_len {
18457 current_line.push(' ');
18458 current_line_len += 1;
18459 }
18460 }
18461 }
18462 }
18463
18464 if !current_line.is_empty() {
18465 wrapped_text.push_str(¤t_line);
18466 }
18467 wrapped_text
18468}
18469
18470#[test]
18471fn test_wrap_with_prefix() {
18472 assert_eq!(
18473 wrap_with_prefix(
18474 "# ".to_string(),
18475 "abcdefg".to_string(),
18476 4,
18477 NonZeroU32::new(4).unwrap(),
18478 false,
18479 ),
18480 "# abcdefg"
18481 );
18482 assert_eq!(
18483 wrap_with_prefix(
18484 "".to_string(),
18485 "\thello world".to_string(),
18486 8,
18487 NonZeroU32::new(4).unwrap(),
18488 false,
18489 ),
18490 "hello\nworld"
18491 );
18492 assert_eq!(
18493 wrap_with_prefix(
18494 "// ".to_string(),
18495 "xx \nyy zz aa bb cc".to_string(),
18496 12,
18497 NonZeroU32::new(4).unwrap(),
18498 false,
18499 ),
18500 "// xx yy zz\n// aa bb cc"
18501 );
18502 assert_eq!(
18503 wrap_with_prefix(
18504 String::new(),
18505 "这是什么 \n 钢笔".to_string(),
18506 3,
18507 NonZeroU32::new(4).unwrap(),
18508 false,
18509 ),
18510 "这是什\n么 钢\n笔"
18511 );
18512}
18513
18514pub trait CollaborationHub {
18515 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18516 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18517 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18518}
18519
18520impl CollaborationHub for Entity<Project> {
18521 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18522 self.read(cx).collaborators()
18523 }
18524
18525 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18526 self.read(cx).user_store().read(cx).participant_indices()
18527 }
18528
18529 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18530 let this = self.read(cx);
18531 let user_ids = this.collaborators().values().map(|c| c.user_id);
18532 this.user_store().read_with(cx, |user_store, cx| {
18533 user_store.participant_names(user_ids, cx)
18534 })
18535 }
18536}
18537
18538pub trait SemanticsProvider {
18539 fn hover(
18540 &self,
18541 buffer: &Entity<Buffer>,
18542 position: text::Anchor,
18543 cx: &mut App,
18544 ) -> Option<Task<Vec<project::Hover>>>;
18545
18546 fn inlay_hints(
18547 &self,
18548 buffer_handle: Entity<Buffer>,
18549 range: Range<text::Anchor>,
18550 cx: &mut App,
18551 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18552
18553 fn resolve_inlay_hint(
18554 &self,
18555 hint: InlayHint,
18556 buffer_handle: Entity<Buffer>,
18557 server_id: LanguageServerId,
18558 cx: &mut App,
18559 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18560
18561 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18562
18563 fn document_highlights(
18564 &self,
18565 buffer: &Entity<Buffer>,
18566 position: text::Anchor,
18567 cx: &mut App,
18568 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18569
18570 fn definitions(
18571 &self,
18572 buffer: &Entity<Buffer>,
18573 position: text::Anchor,
18574 kind: GotoDefinitionKind,
18575 cx: &mut App,
18576 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18577
18578 fn range_for_rename(
18579 &self,
18580 buffer: &Entity<Buffer>,
18581 position: text::Anchor,
18582 cx: &mut App,
18583 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18584
18585 fn perform_rename(
18586 &self,
18587 buffer: &Entity<Buffer>,
18588 position: text::Anchor,
18589 new_name: String,
18590 cx: &mut App,
18591 ) -> Option<Task<Result<ProjectTransaction>>>;
18592}
18593
18594pub trait CompletionProvider {
18595 fn completions(
18596 &self,
18597 excerpt_id: ExcerptId,
18598 buffer: &Entity<Buffer>,
18599 buffer_position: text::Anchor,
18600 trigger: CompletionContext,
18601 window: &mut Window,
18602 cx: &mut Context<Editor>,
18603 ) -> Task<Result<Option<Vec<Completion>>>>;
18604
18605 fn resolve_completions(
18606 &self,
18607 buffer: Entity<Buffer>,
18608 completion_indices: Vec<usize>,
18609 completions: Rc<RefCell<Box<[Completion]>>>,
18610 cx: &mut Context<Editor>,
18611 ) -> Task<Result<bool>>;
18612
18613 fn apply_additional_edits_for_completion(
18614 &self,
18615 _buffer: Entity<Buffer>,
18616 _completions: Rc<RefCell<Box<[Completion]>>>,
18617 _completion_index: usize,
18618 _push_to_history: bool,
18619 _cx: &mut Context<Editor>,
18620 ) -> Task<Result<Option<language::Transaction>>> {
18621 Task::ready(Ok(None))
18622 }
18623
18624 fn is_completion_trigger(
18625 &self,
18626 buffer: &Entity<Buffer>,
18627 position: language::Anchor,
18628 text: &str,
18629 trigger_in_words: bool,
18630 cx: &mut Context<Editor>,
18631 ) -> bool;
18632
18633 fn sort_completions(&self) -> bool {
18634 true
18635 }
18636
18637 fn filter_completions(&self) -> bool {
18638 true
18639 }
18640}
18641
18642pub trait CodeActionProvider {
18643 fn id(&self) -> Arc<str>;
18644
18645 fn code_actions(
18646 &self,
18647 buffer: &Entity<Buffer>,
18648 range: Range<text::Anchor>,
18649 window: &mut Window,
18650 cx: &mut App,
18651 ) -> Task<Result<Vec<CodeAction>>>;
18652
18653 fn apply_code_action(
18654 &self,
18655 buffer_handle: Entity<Buffer>,
18656 action: CodeAction,
18657 excerpt_id: ExcerptId,
18658 push_to_history: bool,
18659 window: &mut Window,
18660 cx: &mut App,
18661 ) -> Task<Result<ProjectTransaction>>;
18662}
18663
18664impl CodeActionProvider for Entity<Project> {
18665 fn id(&self) -> Arc<str> {
18666 "project".into()
18667 }
18668
18669 fn code_actions(
18670 &self,
18671 buffer: &Entity<Buffer>,
18672 range: Range<text::Anchor>,
18673 _window: &mut Window,
18674 cx: &mut App,
18675 ) -> Task<Result<Vec<CodeAction>>> {
18676 self.update(cx, |project, cx| {
18677 let code_lens = project.code_lens(buffer, range.clone(), cx);
18678 let code_actions = project.code_actions(buffer, range, None, cx);
18679 cx.background_spawn(async move {
18680 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18681 Ok(code_lens
18682 .context("code lens fetch")?
18683 .into_iter()
18684 .chain(code_actions.context("code action fetch")?)
18685 .collect())
18686 })
18687 })
18688 }
18689
18690 fn apply_code_action(
18691 &self,
18692 buffer_handle: Entity<Buffer>,
18693 action: CodeAction,
18694 _excerpt_id: ExcerptId,
18695 push_to_history: bool,
18696 _window: &mut Window,
18697 cx: &mut App,
18698 ) -> Task<Result<ProjectTransaction>> {
18699 self.update(cx, |project, cx| {
18700 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18701 })
18702 }
18703}
18704
18705fn snippet_completions(
18706 project: &Project,
18707 buffer: &Entity<Buffer>,
18708 buffer_position: text::Anchor,
18709 cx: &mut App,
18710) -> Task<Result<Vec<Completion>>> {
18711 let language = buffer.read(cx).language_at(buffer_position);
18712 let language_name = language.as_ref().map(|language| language.lsp_id());
18713 let snippet_store = project.snippets().read(cx);
18714 let snippets = snippet_store.snippets_for(language_name, cx);
18715
18716 if snippets.is_empty() {
18717 return Task::ready(Ok(vec![]));
18718 }
18719 let snapshot = buffer.read(cx).text_snapshot();
18720 let chars: String = snapshot
18721 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18722 .collect();
18723
18724 let scope = language.map(|language| language.default_scope());
18725 let executor = cx.background_executor().clone();
18726
18727 cx.background_spawn(async move {
18728 let classifier = CharClassifier::new(scope).for_completion(true);
18729 let mut last_word = chars
18730 .chars()
18731 .take_while(|c| classifier.is_word(*c))
18732 .collect::<String>();
18733 last_word = last_word.chars().rev().collect();
18734
18735 if last_word.is_empty() {
18736 return Ok(vec![]);
18737 }
18738
18739 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18740 let to_lsp = |point: &text::Anchor| {
18741 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18742 point_to_lsp(end)
18743 };
18744 let lsp_end = to_lsp(&buffer_position);
18745
18746 let candidates = snippets
18747 .iter()
18748 .enumerate()
18749 .flat_map(|(ix, snippet)| {
18750 snippet
18751 .prefix
18752 .iter()
18753 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18754 })
18755 .collect::<Vec<StringMatchCandidate>>();
18756
18757 let mut matches = fuzzy::match_strings(
18758 &candidates,
18759 &last_word,
18760 last_word.chars().any(|c| c.is_uppercase()),
18761 100,
18762 &Default::default(),
18763 executor,
18764 )
18765 .await;
18766
18767 // Remove all candidates where the query's start does not match the start of any word in the candidate
18768 if let Some(query_start) = last_word.chars().next() {
18769 matches.retain(|string_match| {
18770 split_words(&string_match.string).any(|word| {
18771 // Check that the first codepoint of the word as lowercase matches the first
18772 // codepoint of the query as lowercase
18773 word.chars()
18774 .flat_map(|codepoint| codepoint.to_lowercase())
18775 .zip(query_start.to_lowercase())
18776 .all(|(word_cp, query_cp)| word_cp == query_cp)
18777 })
18778 });
18779 }
18780
18781 let matched_strings = matches
18782 .into_iter()
18783 .map(|m| m.string)
18784 .collect::<HashSet<_>>();
18785
18786 let result: Vec<Completion> = snippets
18787 .into_iter()
18788 .filter_map(|snippet| {
18789 let matching_prefix = snippet
18790 .prefix
18791 .iter()
18792 .find(|prefix| matched_strings.contains(*prefix))?;
18793 let start = as_offset - last_word.len();
18794 let start = snapshot.anchor_before(start);
18795 let range = start..buffer_position;
18796 let lsp_start = to_lsp(&start);
18797 let lsp_range = lsp::Range {
18798 start: lsp_start,
18799 end: lsp_end,
18800 };
18801 Some(Completion {
18802 replace_range: range,
18803 new_text: snippet.body.clone(),
18804 source: CompletionSource::Lsp {
18805 insert_range: None,
18806 server_id: LanguageServerId(usize::MAX),
18807 resolved: true,
18808 lsp_completion: Box::new(lsp::CompletionItem {
18809 label: snippet.prefix.first().unwrap().clone(),
18810 kind: Some(CompletionItemKind::SNIPPET),
18811 label_details: snippet.description.as_ref().map(|description| {
18812 lsp::CompletionItemLabelDetails {
18813 detail: Some(description.clone()),
18814 description: None,
18815 }
18816 }),
18817 insert_text_format: Some(InsertTextFormat::SNIPPET),
18818 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18819 lsp::InsertReplaceEdit {
18820 new_text: snippet.body.clone(),
18821 insert: lsp_range,
18822 replace: lsp_range,
18823 },
18824 )),
18825 filter_text: Some(snippet.body.clone()),
18826 sort_text: Some(char::MAX.to_string()),
18827 ..lsp::CompletionItem::default()
18828 }),
18829 lsp_defaults: None,
18830 },
18831 label: CodeLabel {
18832 text: matching_prefix.clone(),
18833 runs: Vec::new(),
18834 filter_range: 0..matching_prefix.len(),
18835 },
18836 icon_path: None,
18837 documentation: snippet
18838 .description
18839 .clone()
18840 .map(|description| CompletionDocumentation::SingleLine(description.into())),
18841 insert_text_mode: None,
18842 confirm: None,
18843 })
18844 })
18845 .collect();
18846
18847 Ok(result)
18848 })
18849}
18850
18851impl CompletionProvider for Entity<Project> {
18852 fn completions(
18853 &self,
18854 _excerpt_id: ExcerptId,
18855 buffer: &Entity<Buffer>,
18856 buffer_position: text::Anchor,
18857 options: CompletionContext,
18858 _window: &mut Window,
18859 cx: &mut Context<Editor>,
18860 ) -> Task<Result<Option<Vec<Completion>>>> {
18861 self.update(cx, |project, cx| {
18862 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18863 let project_completions = project.completions(buffer, buffer_position, options, cx);
18864 cx.background_spawn(async move {
18865 let snippets_completions = snippets.await?;
18866 match project_completions.await? {
18867 Some(mut completions) => {
18868 completions.extend(snippets_completions);
18869 Ok(Some(completions))
18870 }
18871 None => {
18872 if snippets_completions.is_empty() {
18873 Ok(None)
18874 } else {
18875 Ok(Some(snippets_completions))
18876 }
18877 }
18878 }
18879 })
18880 })
18881 }
18882
18883 fn resolve_completions(
18884 &self,
18885 buffer: Entity<Buffer>,
18886 completion_indices: Vec<usize>,
18887 completions: Rc<RefCell<Box<[Completion]>>>,
18888 cx: &mut Context<Editor>,
18889 ) -> Task<Result<bool>> {
18890 self.update(cx, |project, cx| {
18891 project.lsp_store().update(cx, |lsp_store, cx| {
18892 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18893 })
18894 })
18895 }
18896
18897 fn apply_additional_edits_for_completion(
18898 &self,
18899 buffer: Entity<Buffer>,
18900 completions: Rc<RefCell<Box<[Completion]>>>,
18901 completion_index: usize,
18902 push_to_history: bool,
18903 cx: &mut Context<Editor>,
18904 ) -> Task<Result<Option<language::Transaction>>> {
18905 self.update(cx, |project, cx| {
18906 project.lsp_store().update(cx, |lsp_store, cx| {
18907 lsp_store.apply_additional_edits_for_completion(
18908 buffer,
18909 completions,
18910 completion_index,
18911 push_to_history,
18912 cx,
18913 )
18914 })
18915 })
18916 }
18917
18918 fn is_completion_trigger(
18919 &self,
18920 buffer: &Entity<Buffer>,
18921 position: language::Anchor,
18922 text: &str,
18923 trigger_in_words: bool,
18924 cx: &mut Context<Editor>,
18925 ) -> bool {
18926 let mut chars = text.chars();
18927 let char = if let Some(char) = chars.next() {
18928 char
18929 } else {
18930 return false;
18931 };
18932 if chars.next().is_some() {
18933 return false;
18934 }
18935
18936 let buffer = buffer.read(cx);
18937 let snapshot = buffer.snapshot();
18938 if !snapshot.settings_at(position, cx).show_completions_on_input {
18939 return false;
18940 }
18941 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18942 if trigger_in_words && classifier.is_word(char) {
18943 return true;
18944 }
18945
18946 buffer.completion_triggers().contains(text)
18947 }
18948}
18949
18950impl SemanticsProvider for Entity<Project> {
18951 fn hover(
18952 &self,
18953 buffer: &Entity<Buffer>,
18954 position: text::Anchor,
18955 cx: &mut App,
18956 ) -> Option<Task<Vec<project::Hover>>> {
18957 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18958 }
18959
18960 fn document_highlights(
18961 &self,
18962 buffer: &Entity<Buffer>,
18963 position: text::Anchor,
18964 cx: &mut App,
18965 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18966 Some(self.update(cx, |project, cx| {
18967 project.document_highlights(buffer, position, cx)
18968 }))
18969 }
18970
18971 fn definitions(
18972 &self,
18973 buffer: &Entity<Buffer>,
18974 position: text::Anchor,
18975 kind: GotoDefinitionKind,
18976 cx: &mut App,
18977 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18978 Some(self.update(cx, |project, cx| match kind {
18979 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18980 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18981 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18982 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18983 }))
18984 }
18985
18986 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18987 // TODO: make this work for remote projects
18988 self.update(cx, |this, cx| {
18989 buffer.update(cx, |buffer, cx| {
18990 this.any_language_server_supports_inlay_hints(buffer, cx)
18991 })
18992 })
18993 }
18994
18995 fn inlay_hints(
18996 &self,
18997 buffer_handle: Entity<Buffer>,
18998 range: Range<text::Anchor>,
18999 cx: &mut App,
19000 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19001 Some(self.update(cx, |project, cx| {
19002 project.inlay_hints(buffer_handle, range, cx)
19003 }))
19004 }
19005
19006 fn resolve_inlay_hint(
19007 &self,
19008 hint: InlayHint,
19009 buffer_handle: Entity<Buffer>,
19010 server_id: LanguageServerId,
19011 cx: &mut App,
19012 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19013 Some(self.update(cx, |project, cx| {
19014 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19015 }))
19016 }
19017
19018 fn range_for_rename(
19019 &self,
19020 buffer: &Entity<Buffer>,
19021 position: text::Anchor,
19022 cx: &mut App,
19023 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19024 Some(self.update(cx, |project, cx| {
19025 let buffer = buffer.clone();
19026 let task = project.prepare_rename(buffer.clone(), position, cx);
19027 cx.spawn(async move |_, cx| {
19028 Ok(match task.await? {
19029 PrepareRenameResponse::Success(range) => Some(range),
19030 PrepareRenameResponse::InvalidPosition => None,
19031 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19032 // Fallback on using TreeSitter info to determine identifier range
19033 buffer.update(cx, |buffer, _| {
19034 let snapshot = buffer.snapshot();
19035 let (range, kind) = snapshot.surrounding_word(position);
19036 if kind != Some(CharKind::Word) {
19037 return None;
19038 }
19039 Some(
19040 snapshot.anchor_before(range.start)
19041 ..snapshot.anchor_after(range.end),
19042 )
19043 })?
19044 }
19045 })
19046 })
19047 }))
19048 }
19049
19050 fn perform_rename(
19051 &self,
19052 buffer: &Entity<Buffer>,
19053 position: text::Anchor,
19054 new_name: String,
19055 cx: &mut App,
19056 ) -> Option<Task<Result<ProjectTransaction>>> {
19057 Some(self.update(cx, |project, cx| {
19058 project.perform_rename(buffer.clone(), position, new_name, cx)
19059 }))
19060 }
19061}
19062
19063fn inlay_hint_settings(
19064 location: Anchor,
19065 snapshot: &MultiBufferSnapshot,
19066 cx: &mut Context<Editor>,
19067) -> InlayHintSettings {
19068 let file = snapshot.file_at(location);
19069 let language = snapshot.language_at(location).map(|l| l.name());
19070 language_settings(language, file, cx).inlay_hints
19071}
19072
19073fn consume_contiguous_rows(
19074 contiguous_row_selections: &mut Vec<Selection<Point>>,
19075 selection: &Selection<Point>,
19076 display_map: &DisplaySnapshot,
19077 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19078) -> (MultiBufferRow, MultiBufferRow) {
19079 contiguous_row_selections.push(selection.clone());
19080 let start_row = MultiBufferRow(selection.start.row);
19081 let mut end_row = ending_row(selection, display_map);
19082
19083 while let Some(next_selection) = selections.peek() {
19084 if next_selection.start.row <= end_row.0 {
19085 end_row = ending_row(next_selection, display_map);
19086 contiguous_row_selections.push(selections.next().unwrap().clone());
19087 } else {
19088 break;
19089 }
19090 }
19091 (start_row, end_row)
19092}
19093
19094fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19095 if next_selection.end.column > 0 || next_selection.is_empty() {
19096 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19097 } else {
19098 MultiBufferRow(next_selection.end.row)
19099 }
19100}
19101
19102impl EditorSnapshot {
19103 pub fn remote_selections_in_range<'a>(
19104 &'a self,
19105 range: &'a Range<Anchor>,
19106 collaboration_hub: &dyn CollaborationHub,
19107 cx: &'a App,
19108 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19109 let participant_names = collaboration_hub.user_names(cx);
19110 let participant_indices = collaboration_hub.user_participant_indices(cx);
19111 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19112 let collaborators_by_replica_id = collaborators_by_peer_id
19113 .iter()
19114 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19115 .collect::<HashMap<_, _>>();
19116 self.buffer_snapshot
19117 .selections_in_range(range, false)
19118 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19119 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19120 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19121 let user_name = participant_names.get(&collaborator.user_id).cloned();
19122 Some(RemoteSelection {
19123 replica_id,
19124 selection,
19125 cursor_shape,
19126 line_mode,
19127 participant_index,
19128 peer_id: collaborator.peer_id,
19129 user_name,
19130 })
19131 })
19132 }
19133
19134 pub fn hunks_for_ranges(
19135 &self,
19136 ranges: impl IntoIterator<Item = Range<Point>>,
19137 ) -> Vec<MultiBufferDiffHunk> {
19138 let mut hunks = Vec::new();
19139 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19140 HashMap::default();
19141 for query_range in ranges {
19142 let query_rows =
19143 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19144 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19145 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19146 ) {
19147 // Include deleted hunks that are adjacent to the query range, because
19148 // otherwise they would be missed.
19149 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19150 if hunk.status().is_deleted() {
19151 intersects_range |= hunk.row_range.start == query_rows.end;
19152 intersects_range |= hunk.row_range.end == query_rows.start;
19153 }
19154 if intersects_range {
19155 if !processed_buffer_rows
19156 .entry(hunk.buffer_id)
19157 .or_default()
19158 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19159 {
19160 continue;
19161 }
19162 hunks.push(hunk);
19163 }
19164 }
19165 }
19166
19167 hunks
19168 }
19169
19170 fn display_diff_hunks_for_rows<'a>(
19171 &'a self,
19172 display_rows: Range<DisplayRow>,
19173 folded_buffers: &'a HashSet<BufferId>,
19174 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19175 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19176 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19177
19178 self.buffer_snapshot
19179 .diff_hunks_in_range(buffer_start..buffer_end)
19180 .filter_map(|hunk| {
19181 if folded_buffers.contains(&hunk.buffer_id) {
19182 return None;
19183 }
19184
19185 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19186 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19187
19188 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19189 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19190
19191 let display_hunk = if hunk_display_start.column() != 0 {
19192 DisplayDiffHunk::Folded {
19193 display_row: hunk_display_start.row(),
19194 }
19195 } else {
19196 let mut end_row = hunk_display_end.row();
19197 if hunk_display_end.column() > 0 {
19198 end_row.0 += 1;
19199 }
19200 let is_created_file = hunk.is_created_file();
19201 DisplayDiffHunk::Unfolded {
19202 status: hunk.status(),
19203 diff_base_byte_range: hunk.diff_base_byte_range,
19204 display_row_range: hunk_display_start.row()..end_row,
19205 multi_buffer_range: Anchor::range_in_buffer(
19206 hunk.excerpt_id,
19207 hunk.buffer_id,
19208 hunk.buffer_range,
19209 ),
19210 is_created_file,
19211 }
19212 };
19213
19214 Some(display_hunk)
19215 })
19216 }
19217
19218 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19219 self.display_snapshot.buffer_snapshot.language_at(position)
19220 }
19221
19222 pub fn is_focused(&self) -> bool {
19223 self.is_focused
19224 }
19225
19226 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19227 self.placeholder_text.as_ref()
19228 }
19229
19230 pub fn scroll_position(&self) -> gpui::Point<f32> {
19231 self.scroll_anchor.scroll_position(&self.display_snapshot)
19232 }
19233
19234 fn gutter_dimensions(
19235 &self,
19236 font_id: FontId,
19237 font_size: Pixels,
19238 max_line_number_width: Pixels,
19239 cx: &App,
19240 ) -> Option<GutterDimensions> {
19241 if !self.show_gutter {
19242 return None;
19243 }
19244
19245 let descent = cx.text_system().descent(font_id, font_size);
19246 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19247 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19248
19249 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19250 matches!(
19251 ProjectSettings::get_global(cx).git.git_gutter,
19252 Some(GitGutterSetting::TrackedFiles)
19253 )
19254 });
19255 let gutter_settings = EditorSettings::get_global(cx).gutter;
19256 let show_line_numbers = self
19257 .show_line_numbers
19258 .unwrap_or(gutter_settings.line_numbers);
19259 let line_gutter_width = if show_line_numbers {
19260 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19261 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19262 max_line_number_width.max(min_width_for_number_on_gutter)
19263 } else {
19264 0.0.into()
19265 };
19266
19267 let show_code_actions = self
19268 .show_code_actions
19269 .unwrap_or(gutter_settings.code_actions);
19270
19271 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19272 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19273
19274 let git_blame_entries_width =
19275 self.git_blame_gutter_max_author_length
19276 .map(|max_author_length| {
19277 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19278 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19279
19280 /// The number of characters to dedicate to gaps and margins.
19281 const SPACING_WIDTH: usize = 4;
19282
19283 let max_char_count = max_author_length.min(renderer.max_author_length())
19284 + ::git::SHORT_SHA_LENGTH
19285 + MAX_RELATIVE_TIMESTAMP.len()
19286 + SPACING_WIDTH;
19287
19288 em_advance * max_char_count
19289 });
19290
19291 let is_singleton = self.buffer_snapshot.is_singleton();
19292
19293 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19294 left_padding += if !is_singleton {
19295 em_width * 4.0
19296 } else if show_code_actions || show_runnables || show_breakpoints {
19297 em_width * 3.0
19298 } else if show_git_gutter && show_line_numbers {
19299 em_width * 2.0
19300 } else if show_git_gutter || show_line_numbers {
19301 em_width
19302 } else {
19303 px(0.)
19304 };
19305
19306 let shows_folds = is_singleton && gutter_settings.folds;
19307
19308 let right_padding = if shows_folds && show_line_numbers {
19309 em_width * 4.0
19310 } else if shows_folds || (!is_singleton && show_line_numbers) {
19311 em_width * 3.0
19312 } else if show_line_numbers {
19313 em_width
19314 } else {
19315 px(0.)
19316 };
19317
19318 Some(GutterDimensions {
19319 left_padding,
19320 right_padding,
19321 width: line_gutter_width + left_padding + right_padding,
19322 margin: -descent,
19323 git_blame_entries_width,
19324 })
19325 }
19326
19327 pub fn render_crease_toggle(
19328 &self,
19329 buffer_row: MultiBufferRow,
19330 row_contains_cursor: bool,
19331 editor: Entity<Editor>,
19332 window: &mut Window,
19333 cx: &mut App,
19334 ) -> Option<AnyElement> {
19335 let folded = self.is_line_folded(buffer_row);
19336 let mut is_foldable = false;
19337
19338 if let Some(crease) = self
19339 .crease_snapshot
19340 .query_row(buffer_row, &self.buffer_snapshot)
19341 {
19342 is_foldable = true;
19343 match crease {
19344 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
19345 if let Some(render_toggle) = render_toggle {
19346 let toggle_callback =
19347 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
19348 if folded {
19349 editor.update(cx, |editor, cx| {
19350 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
19351 });
19352 } else {
19353 editor.update(cx, |editor, cx| {
19354 editor.unfold_at(
19355 &crate::UnfoldAt { buffer_row },
19356 window,
19357 cx,
19358 )
19359 });
19360 }
19361 });
19362 return Some((render_toggle)(
19363 buffer_row,
19364 folded,
19365 toggle_callback,
19366 window,
19367 cx,
19368 ));
19369 }
19370 }
19371 }
19372 }
19373
19374 is_foldable |= self.starts_indent(buffer_row);
19375
19376 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19377 Some(
19378 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19379 .toggle_state(folded)
19380 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19381 if folded {
19382 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
19383 } else {
19384 this.fold_at(&FoldAt { buffer_row }, window, cx);
19385 }
19386 }))
19387 .into_any_element(),
19388 )
19389 } else {
19390 None
19391 }
19392 }
19393
19394 pub fn render_crease_trailer(
19395 &self,
19396 buffer_row: MultiBufferRow,
19397 window: &mut Window,
19398 cx: &mut App,
19399 ) -> Option<AnyElement> {
19400 let folded = self.is_line_folded(buffer_row);
19401 if let Crease::Inline { render_trailer, .. } = self
19402 .crease_snapshot
19403 .query_row(buffer_row, &self.buffer_snapshot)?
19404 {
19405 let render_trailer = render_trailer.as_ref()?;
19406 Some(render_trailer(buffer_row, folded, window, cx))
19407 } else {
19408 None
19409 }
19410 }
19411}
19412
19413impl Deref for EditorSnapshot {
19414 type Target = DisplaySnapshot;
19415
19416 fn deref(&self) -> &Self::Target {
19417 &self.display_snapshot
19418 }
19419}
19420
19421#[derive(Clone, Debug, PartialEq, Eq)]
19422pub enum EditorEvent {
19423 InputIgnored {
19424 text: Arc<str>,
19425 },
19426 InputHandled {
19427 utf16_range_to_replace: Option<Range<isize>>,
19428 text: Arc<str>,
19429 },
19430 ExcerptsAdded {
19431 buffer: Entity<Buffer>,
19432 predecessor: ExcerptId,
19433 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19434 },
19435 ExcerptsRemoved {
19436 ids: Vec<ExcerptId>,
19437 },
19438 BufferFoldToggled {
19439 ids: Vec<ExcerptId>,
19440 folded: bool,
19441 },
19442 ExcerptsEdited {
19443 ids: Vec<ExcerptId>,
19444 },
19445 ExcerptsExpanded {
19446 ids: Vec<ExcerptId>,
19447 },
19448 BufferEdited,
19449 Edited {
19450 transaction_id: clock::Lamport,
19451 },
19452 Reparsed(BufferId),
19453 Focused,
19454 FocusedIn,
19455 Blurred,
19456 DirtyChanged,
19457 Saved,
19458 TitleChanged,
19459 DiffBaseChanged,
19460 SelectionsChanged {
19461 local: bool,
19462 },
19463 ScrollPositionChanged {
19464 local: bool,
19465 autoscroll: bool,
19466 },
19467 Closed,
19468 TransactionUndone {
19469 transaction_id: clock::Lamport,
19470 },
19471 TransactionBegun {
19472 transaction_id: clock::Lamport,
19473 },
19474 Reloaded,
19475 CursorShapeChanged,
19476 PushedToNavHistory {
19477 anchor: Anchor,
19478 is_deactivate: bool,
19479 },
19480}
19481
19482impl EventEmitter<EditorEvent> for Editor {}
19483
19484impl Focusable for Editor {
19485 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19486 self.focus_handle.clone()
19487 }
19488}
19489
19490impl Render for Editor {
19491 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19492 let settings = ThemeSettings::get_global(cx);
19493
19494 let mut text_style = match self.mode {
19495 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19496 color: cx.theme().colors().editor_foreground,
19497 font_family: settings.ui_font.family.clone(),
19498 font_features: settings.ui_font.features.clone(),
19499 font_fallbacks: settings.ui_font.fallbacks.clone(),
19500 font_size: rems(0.875).into(),
19501 font_weight: settings.ui_font.weight,
19502 line_height: relative(settings.buffer_line_height.value()),
19503 ..Default::default()
19504 },
19505 EditorMode::Full => TextStyle {
19506 color: cx.theme().colors().editor_foreground,
19507 font_family: settings.buffer_font.family.clone(),
19508 font_features: settings.buffer_font.features.clone(),
19509 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19510 font_size: settings.buffer_font_size(cx).into(),
19511 font_weight: settings.buffer_font.weight,
19512 line_height: relative(settings.buffer_line_height.value()),
19513 ..Default::default()
19514 },
19515 };
19516 if let Some(text_style_refinement) = &self.text_style_refinement {
19517 text_style.refine(text_style_refinement)
19518 }
19519
19520 let background = match self.mode {
19521 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19522 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19523 EditorMode::Full => cx.theme().colors().editor_background,
19524 };
19525
19526 EditorElement::new(
19527 &cx.entity(),
19528 EditorStyle {
19529 background,
19530 local_player: cx.theme().players().local(),
19531 text: text_style,
19532 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19533 syntax: cx.theme().syntax().clone(),
19534 status: cx.theme().status().clone(),
19535 inlay_hints_style: make_inlay_hints_style(cx),
19536 inline_completion_styles: make_suggestion_styles(cx),
19537 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19538 },
19539 )
19540 }
19541}
19542
19543impl EntityInputHandler for Editor {
19544 fn text_for_range(
19545 &mut self,
19546 range_utf16: Range<usize>,
19547 adjusted_range: &mut Option<Range<usize>>,
19548 _: &mut Window,
19549 cx: &mut Context<Self>,
19550 ) -> Option<String> {
19551 let snapshot = self.buffer.read(cx).read(cx);
19552 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19553 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19554 if (start.0..end.0) != range_utf16 {
19555 adjusted_range.replace(start.0..end.0);
19556 }
19557 Some(snapshot.text_for_range(start..end).collect())
19558 }
19559
19560 fn selected_text_range(
19561 &mut self,
19562 ignore_disabled_input: bool,
19563 _: &mut Window,
19564 cx: &mut Context<Self>,
19565 ) -> Option<UTF16Selection> {
19566 // Prevent the IME menu from appearing when holding down an alphabetic key
19567 // while input is disabled.
19568 if !ignore_disabled_input && !self.input_enabled {
19569 return None;
19570 }
19571
19572 let selection = self.selections.newest::<OffsetUtf16>(cx);
19573 let range = selection.range();
19574
19575 Some(UTF16Selection {
19576 range: range.start.0..range.end.0,
19577 reversed: selection.reversed,
19578 })
19579 }
19580
19581 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19582 let snapshot = self.buffer.read(cx).read(cx);
19583 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19584 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19585 }
19586
19587 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19588 self.clear_highlights::<InputComposition>(cx);
19589 self.ime_transaction.take();
19590 }
19591
19592 fn replace_text_in_range(
19593 &mut self,
19594 range_utf16: Option<Range<usize>>,
19595 text: &str,
19596 window: &mut Window,
19597 cx: &mut Context<Self>,
19598 ) {
19599 if !self.input_enabled {
19600 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19601 return;
19602 }
19603
19604 self.transact(window, cx, |this, window, cx| {
19605 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19606 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19607 Some(this.selection_replacement_ranges(range_utf16, cx))
19608 } else {
19609 this.marked_text_ranges(cx)
19610 };
19611
19612 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19613 let newest_selection_id = this.selections.newest_anchor().id;
19614 this.selections
19615 .all::<OffsetUtf16>(cx)
19616 .iter()
19617 .zip(ranges_to_replace.iter())
19618 .find_map(|(selection, range)| {
19619 if selection.id == newest_selection_id {
19620 Some(
19621 (range.start.0 as isize - selection.head().0 as isize)
19622 ..(range.end.0 as isize - selection.head().0 as isize),
19623 )
19624 } else {
19625 None
19626 }
19627 })
19628 });
19629
19630 cx.emit(EditorEvent::InputHandled {
19631 utf16_range_to_replace: range_to_replace,
19632 text: text.into(),
19633 });
19634
19635 if let Some(new_selected_ranges) = new_selected_ranges {
19636 this.change_selections(None, window, cx, |selections| {
19637 selections.select_ranges(new_selected_ranges)
19638 });
19639 this.backspace(&Default::default(), window, cx);
19640 }
19641
19642 this.handle_input(text, window, cx);
19643 });
19644
19645 if let Some(transaction) = self.ime_transaction {
19646 self.buffer.update(cx, |buffer, cx| {
19647 buffer.group_until_transaction(transaction, cx);
19648 });
19649 }
19650
19651 self.unmark_text(window, cx);
19652 }
19653
19654 fn replace_and_mark_text_in_range(
19655 &mut self,
19656 range_utf16: Option<Range<usize>>,
19657 text: &str,
19658 new_selected_range_utf16: Option<Range<usize>>,
19659 window: &mut Window,
19660 cx: &mut Context<Self>,
19661 ) {
19662 if !self.input_enabled {
19663 return;
19664 }
19665
19666 let transaction = self.transact(window, cx, |this, window, cx| {
19667 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19668 let snapshot = this.buffer.read(cx).read(cx);
19669 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19670 for marked_range in &mut marked_ranges {
19671 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19672 marked_range.start.0 += relative_range_utf16.start;
19673 marked_range.start =
19674 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19675 marked_range.end =
19676 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19677 }
19678 }
19679 Some(marked_ranges)
19680 } else if let Some(range_utf16) = range_utf16 {
19681 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19682 Some(this.selection_replacement_ranges(range_utf16, cx))
19683 } else {
19684 None
19685 };
19686
19687 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19688 let newest_selection_id = this.selections.newest_anchor().id;
19689 this.selections
19690 .all::<OffsetUtf16>(cx)
19691 .iter()
19692 .zip(ranges_to_replace.iter())
19693 .find_map(|(selection, range)| {
19694 if selection.id == newest_selection_id {
19695 Some(
19696 (range.start.0 as isize - selection.head().0 as isize)
19697 ..(range.end.0 as isize - selection.head().0 as isize),
19698 )
19699 } else {
19700 None
19701 }
19702 })
19703 });
19704
19705 cx.emit(EditorEvent::InputHandled {
19706 utf16_range_to_replace: range_to_replace,
19707 text: text.into(),
19708 });
19709
19710 if let Some(ranges) = ranges_to_replace {
19711 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19712 }
19713
19714 let marked_ranges = {
19715 let snapshot = this.buffer.read(cx).read(cx);
19716 this.selections
19717 .disjoint_anchors()
19718 .iter()
19719 .map(|selection| {
19720 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19721 })
19722 .collect::<Vec<_>>()
19723 };
19724
19725 if text.is_empty() {
19726 this.unmark_text(window, cx);
19727 } else {
19728 this.highlight_text::<InputComposition>(
19729 marked_ranges.clone(),
19730 HighlightStyle {
19731 underline: Some(UnderlineStyle {
19732 thickness: px(1.),
19733 color: None,
19734 wavy: false,
19735 }),
19736 ..Default::default()
19737 },
19738 cx,
19739 );
19740 }
19741
19742 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19743 let use_autoclose = this.use_autoclose;
19744 let use_auto_surround = this.use_auto_surround;
19745 this.set_use_autoclose(false);
19746 this.set_use_auto_surround(false);
19747 this.handle_input(text, window, cx);
19748 this.set_use_autoclose(use_autoclose);
19749 this.set_use_auto_surround(use_auto_surround);
19750
19751 if let Some(new_selected_range) = new_selected_range_utf16 {
19752 let snapshot = this.buffer.read(cx).read(cx);
19753 let new_selected_ranges = marked_ranges
19754 .into_iter()
19755 .map(|marked_range| {
19756 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19757 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19758 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19759 snapshot.clip_offset_utf16(new_start, Bias::Left)
19760 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19761 })
19762 .collect::<Vec<_>>();
19763
19764 drop(snapshot);
19765 this.change_selections(None, window, cx, |selections| {
19766 selections.select_ranges(new_selected_ranges)
19767 });
19768 }
19769 });
19770
19771 self.ime_transaction = self.ime_transaction.or(transaction);
19772 if let Some(transaction) = self.ime_transaction {
19773 self.buffer.update(cx, |buffer, cx| {
19774 buffer.group_until_transaction(transaction, cx);
19775 });
19776 }
19777
19778 if self.text_highlights::<InputComposition>(cx).is_none() {
19779 self.ime_transaction.take();
19780 }
19781 }
19782
19783 fn bounds_for_range(
19784 &mut self,
19785 range_utf16: Range<usize>,
19786 element_bounds: gpui::Bounds<Pixels>,
19787 window: &mut Window,
19788 cx: &mut Context<Self>,
19789 ) -> Option<gpui::Bounds<Pixels>> {
19790 let text_layout_details = self.text_layout_details(window);
19791 let gpui::Size {
19792 width: em_width,
19793 height: line_height,
19794 } = self.character_size(window);
19795
19796 let snapshot = self.snapshot(window, cx);
19797 let scroll_position = snapshot.scroll_position();
19798 let scroll_left = scroll_position.x * em_width;
19799
19800 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19801 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19802 + self.gutter_dimensions.width
19803 + self.gutter_dimensions.margin;
19804 let y = line_height * (start.row().as_f32() - scroll_position.y);
19805
19806 Some(Bounds {
19807 origin: element_bounds.origin + point(x, y),
19808 size: size(em_width, line_height),
19809 })
19810 }
19811
19812 fn character_index_for_point(
19813 &mut self,
19814 point: gpui::Point<Pixels>,
19815 _window: &mut Window,
19816 _cx: &mut Context<Self>,
19817 ) -> Option<usize> {
19818 let position_map = self.last_position_map.as_ref()?;
19819 if !position_map.text_hitbox.contains(&point) {
19820 return None;
19821 }
19822 let display_point = position_map.point_for_position(point).previous_valid;
19823 let anchor = position_map
19824 .snapshot
19825 .display_point_to_anchor(display_point, Bias::Left);
19826 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19827 Some(utf16_offset.0)
19828 }
19829}
19830
19831trait SelectionExt {
19832 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19833 fn spanned_rows(
19834 &self,
19835 include_end_if_at_line_start: bool,
19836 map: &DisplaySnapshot,
19837 ) -> Range<MultiBufferRow>;
19838}
19839
19840impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19841 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19842 let start = self
19843 .start
19844 .to_point(&map.buffer_snapshot)
19845 .to_display_point(map);
19846 let end = self
19847 .end
19848 .to_point(&map.buffer_snapshot)
19849 .to_display_point(map);
19850 if self.reversed {
19851 end..start
19852 } else {
19853 start..end
19854 }
19855 }
19856
19857 fn spanned_rows(
19858 &self,
19859 include_end_if_at_line_start: bool,
19860 map: &DisplaySnapshot,
19861 ) -> Range<MultiBufferRow> {
19862 let start = self.start.to_point(&map.buffer_snapshot);
19863 let mut end = self.end.to_point(&map.buffer_snapshot);
19864 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19865 end.row -= 1;
19866 }
19867
19868 let buffer_start = map.prev_line_boundary(start).0;
19869 let buffer_end = map.next_line_boundary(end).0;
19870 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19871 }
19872}
19873
19874impl<T: InvalidationRegion> InvalidationStack<T> {
19875 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19876 where
19877 S: Clone + ToOffset,
19878 {
19879 while let Some(region) = self.last() {
19880 let all_selections_inside_invalidation_ranges =
19881 if selections.len() == region.ranges().len() {
19882 selections
19883 .iter()
19884 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19885 .all(|(selection, invalidation_range)| {
19886 let head = selection.head().to_offset(buffer);
19887 invalidation_range.start <= head && invalidation_range.end >= head
19888 })
19889 } else {
19890 false
19891 };
19892
19893 if all_selections_inside_invalidation_ranges {
19894 break;
19895 } else {
19896 self.pop();
19897 }
19898 }
19899 }
19900}
19901
19902impl<T> Default for InvalidationStack<T> {
19903 fn default() -> Self {
19904 Self(Default::default())
19905 }
19906}
19907
19908impl<T> Deref for InvalidationStack<T> {
19909 type Target = Vec<T>;
19910
19911 fn deref(&self) -> &Self::Target {
19912 &self.0
19913 }
19914}
19915
19916impl<T> DerefMut for InvalidationStack<T> {
19917 fn deref_mut(&mut self) -> &mut Self::Target {
19918 &mut self.0
19919 }
19920}
19921
19922impl InvalidationRegion for SnippetState {
19923 fn ranges(&self) -> &[Range<Anchor>] {
19924 &self.ranges[self.active_index]
19925 }
19926}
19927
19928pub fn diagnostic_block_renderer(
19929 diagnostic: Diagnostic,
19930 max_message_rows: Option<u8>,
19931 allow_closing: bool,
19932) -> RenderBlock {
19933 let (text_without_backticks, code_ranges) =
19934 highlight_diagnostic_message(&diagnostic, max_message_rows);
19935
19936 Arc::new(move |cx: &mut BlockContext| {
19937 let group_id: SharedString = cx.block_id.to_string().into();
19938
19939 let mut text_style = cx.window.text_style().clone();
19940 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19941 let theme_settings = ThemeSettings::get_global(cx);
19942 text_style.font_family = theme_settings.buffer_font.family.clone();
19943 text_style.font_style = theme_settings.buffer_font.style;
19944 text_style.font_features = theme_settings.buffer_font.features.clone();
19945 text_style.font_weight = theme_settings.buffer_font.weight;
19946
19947 let multi_line_diagnostic = diagnostic.message.contains('\n');
19948
19949 let buttons = |diagnostic: &Diagnostic| {
19950 if multi_line_diagnostic {
19951 v_flex()
19952 } else {
19953 h_flex()
19954 }
19955 .when(allow_closing, |div| {
19956 div.children(diagnostic.is_primary.then(|| {
19957 IconButton::new("close-block", IconName::XCircle)
19958 .icon_color(Color::Muted)
19959 .size(ButtonSize::Compact)
19960 .style(ButtonStyle::Transparent)
19961 .visible_on_hover(group_id.clone())
19962 .on_click(move |_click, window, cx| {
19963 window.dispatch_action(Box::new(Cancel), cx)
19964 })
19965 .tooltip(|window, cx| {
19966 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19967 })
19968 }))
19969 })
19970 .child(
19971 IconButton::new("copy-block", IconName::Copy)
19972 .icon_color(Color::Muted)
19973 .size(ButtonSize::Compact)
19974 .style(ButtonStyle::Transparent)
19975 .visible_on_hover(group_id.clone())
19976 .on_click({
19977 let message = diagnostic.message.clone();
19978 move |_click, _, cx| {
19979 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19980 }
19981 })
19982 .tooltip(Tooltip::text("Copy diagnostic message")),
19983 )
19984 };
19985
19986 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19987 AvailableSpace::min_size(),
19988 cx.window,
19989 cx.app,
19990 );
19991
19992 h_flex()
19993 .id(cx.block_id)
19994 .group(group_id.clone())
19995 .relative()
19996 .size_full()
19997 .block_mouse_down()
19998 .pl(cx.gutter_dimensions.width)
19999 .w(cx.max_width - cx.gutter_dimensions.full_width())
20000 .child(
20001 div()
20002 .flex()
20003 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
20004 .flex_shrink(),
20005 )
20006 .child(buttons(&diagnostic))
20007 .child(div().flex().flex_shrink_0().child(
20008 StyledText::new(text_without_backticks.clone()).with_default_highlights(
20009 &text_style,
20010 code_ranges.iter().map(|range| {
20011 (
20012 range.clone(),
20013 HighlightStyle {
20014 font_weight: Some(FontWeight::BOLD),
20015 ..Default::default()
20016 },
20017 )
20018 }),
20019 ),
20020 ))
20021 .into_any_element()
20022 })
20023}
20024
20025fn inline_completion_edit_text(
20026 current_snapshot: &BufferSnapshot,
20027 edits: &[(Range<Anchor>, String)],
20028 edit_preview: &EditPreview,
20029 include_deletions: bool,
20030 cx: &App,
20031) -> HighlightedText {
20032 let edits = edits
20033 .iter()
20034 .map(|(anchor, text)| {
20035 (
20036 anchor.start.text_anchor..anchor.end.text_anchor,
20037 text.clone(),
20038 )
20039 })
20040 .collect::<Vec<_>>();
20041
20042 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20043}
20044
20045pub fn highlight_diagnostic_message(
20046 diagnostic: &Diagnostic,
20047 mut max_message_rows: Option<u8>,
20048) -> (SharedString, Vec<Range<usize>>) {
20049 let mut text_without_backticks = String::new();
20050 let mut code_ranges = Vec::new();
20051
20052 if let Some(source) = &diagnostic.source {
20053 text_without_backticks.push_str(source);
20054 code_ranges.push(0..source.len());
20055 text_without_backticks.push_str(": ");
20056 }
20057
20058 let mut prev_offset = 0;
20059 let mut in_code_block = false;
20060 let has_row_limit = max_message_rows.is_some();
20061 let mut newline_indices = diagnostic
20062 .message
20063 .match_indices('\n')
20064 .filter(|_| has_row_limit)
20065 .map(|(ix, _)| ix)
20066 .fuse()
20067 .peekable();
20068
20069 for (quote_ix, _) in diagnostic
20070 .message
20071 .match_indices('`')
20072 .chain([(diagnostic.message.len(), "")])
20073 {
20074 let mut first_newline_ix = None;
20075 let mut last_newline_ix = None;
20076 while let Some(newline_ix) = newline_indices.peek() {
20077 if *newline_ix < quote_ix {
20078 if first_newline_ix.is_none() {
20079 first_newline_ix = Some(*newline_ix);
20080 }
20081 last_newline_ix = Some(*newline_ix);
20082
20083 if let Some(rows_left) = &mut max_message_rows {
20084 if *rows_left == 0 {
20085 break;
20086 } else {
20087 *rows_left -= 1;
20088 }
20089 }
20090 let _ = newline_indices.next();
20091 } else {
20092 break;
20093 }
20094 }
20095 let prev_len = text_without_backticks.len();
20096 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
20097 text_without_backticks.push_str(new_text);
20098 if in_code_block {
20099 code_ranges.push(prev_len..text_without_backticks.len());
20100 }
20101 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
20102 in_code_block = !in_code_block;
20103 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
20104 text_without_backticks.push_str("...");
20105 break;
20106 }
20107 }
20108
20109 (text_without_backticks.into(), code_ranges)
20110}
20111
20112fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20113 match severity {
20114 DiagnosticSeverity::ERROR => colors.error,
20115 DiagnosticSeverity::WARNING => colors.warning,
20116 DiagnosticSeverity::INFORMATION => colors.info,
20117 DiagnosticSeverity::HINT => colors.info,
20118 _ => colors.ignored,
20119 }
20120}
20121
20122pub fn styled_runs_for_code_label<'a>(
20123 label: &'a CodeLabel,
20124 syntax_theme: &'a theme::SyntaxTheme,
20125) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20126 let fade_out = HighlightStyle {
20127 fade_out: Some(0.35),
20128 ..Default::default()
20129 };
20130
20131 let mut prev_end = label.filter_range.end;
20132 label
20133 .runs
20134 .iter()
20135 .enumerate()
20136 .flat_map(move |(ix, (range, highlight_id))| {
20137 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20138 style
20139 } else {
20140 return Default::default();
20141 };
20142 let mut muted_style = style;
20143 muted_style.highlight(fade_out);
20144
20145 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20146 if range.start >= label.filter_range.end {
20147 if range.start > prev_end {
20148 runs.push((prev_end..range.start, fade_out));
20149 }
20150 runs.push((range.clone(), muted_style));
20151 } else if range.end <= label.filter_range.end {
20152 runs.push((range.clone(), style));
20153 } else {
20154 runs.push((range.start..label.filter_range.end, style));
20155 runs.push((label.filter_range.end..range.end, muted_style));
20156 }
20157 prev_end = cmp::max(prev_end, range.end);
20158
20159 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20160 runs.push((prev_end..label.text.len(), fade_out));
20161 }
20162
20163 runs
20164 })
20165}
20166
20167pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20168 let mut prev_index = 0;
20169 let mut prev_codepoint: Option<char> = None;
20170 text.char_indices()
20171 .chain([(text.len(), '\0')])
20172 .filter_map(move |(index, codepoint)| {
20173 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20174 let is_boundary = index == text.len()
20175 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20176 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20177 if is_boundary {
20178 let chunk = &text[prev_index..index];
20179 prev_index = index;
20180 Some(chunk)
20181 } else {
20182 None
20183 }
20184 })
20185}
20186
20187pub trait RangeToAnchorExt: Sized {
20188 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20189
20190 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20191 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20192 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20193 }
20194}
20195
20196impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20197 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20198 let start_offset = self.start.to_offset(snapshot);
20199 let end_offset = self.end.to_offset(snapshot);
20200 if start_offset == end_offset {
20201 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20202 } else {
20203 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20204 }
20205 }
20206}
20207
20208pub trait RowExt {
20209 fn as_f32(&self) -> f32;
20210
20211 fn next_row(&self) -> Self;
20212
20213 fn previous_row(&self) -> Self;
20214
20215 fn minus(&self, other: Self) -> u32;
20216}
20217
20218impl RowExt for DisplayRow {
20219 fn as_f32(&self) -> f32 {
20220 self.0 as f32
20221 }
20222
20223 fn next_row(&self) -> Self {
20224 Self(self.0 + 1)
20225 }
20226
20227 fn previous_row(&self) -> Self {
20228 Self(self.0.saturating_sub(1))
20229 }
20230
20231 fn minus(&self, other: Self) -> u32 {
20232 self.0 - other.0
20233 }
20234}
20235
20236impl RowExt for MultiBufferRow {
20237 fn as_f32(&self) -> f32 {
20238 self.0 as f32
20239 }
20240
20241 fn next_row(&self) -> Self {
20242 Self(self.0 + 1)
20243 }
20244
20245 fn previous_row(&self) -> Self {
20246 Self(self.0.saturating_sub(1))
20247 }
20248
20249 fn minus(&self, other: Self) -> u32 {
20250 self.0 - other.0
20251 }
20252}
20253
20254trait RowRangeExt {
20255 type Row;
20256
20257 fn len(&self) -> usize;
20258
20259 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20260}
20261
20262impl RowRangeExt for Range<MultiBufferRow> {
20263 type Row = MultiBufferRow;
20264
20265 fn len(&self) -> usize {
20266 (self.end.0 - self.start.0) as usize
20267 }
20268
20269 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20270 (self.start.0..self.end.0).map(MultiBufferRow)
20271 }
20272}
20273
20274impl RowRangeExt for Range<DisplayRow> {
20275 type Row = DisplayRow;
20276
20277 fn len(&self) -> usize {
20278 (self.end.0 - self.start.0) as usize
20279 }
20280
20281 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20282 (self.start.0..self.end.0).map(DisplayRow)
20283 }
20284}
20285
20286/// If select range has more than one line, we
20287/// just point the cursor to range.start.
20288fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20289 if range.start.row == range.end.row {
20290 range
20291 } else {
20292 range.start..range.start
20293 }
20294}
20295pub struct KillRing(ClipboardItem);
20296impl Global for KillRing {}
20297
20298const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20299
20300enum BreakpointPromptEditAction {
20301 Log,
20302 Condition,
20303 HitCondition,
20304}
20305
20306struct BreakpointPromptEditor {
20307 pub(crate) prompt: Entity<Editor>,
20308 editor: WeakEntity<Editor>,
20309 breakpoint_anchor: Anchor,
20310 breakpoint: Breakpoint,
20311 edit_action: BreakpointPromptEditAction,
20312 block_ids: HashSet<CustomBlockId>,
20313 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20314 _subscriptions: Vec<Subscription>,
20315}
20316
20317impl BreakpointPromptEditor {
20318 const MAX_LINES: u8 = 4;
20319
20320 fn new(
20321 editor: WeakEntity<Editor>,
20322 breakpoint_anchor: Anchor,
20323 breakpoint: Breakpoint,
20324 edit_action: BreakpointPromptEditAction,
20325 window: &mut Window,
20326 cx: &mut Context<Self>,
20327 ) -> Self {
20328 let base_text = match edit_action {
20329 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20330 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20331 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20332 }
20333 .map(|msg| msg.to_string())
20334 .unwrap_or_default();
20335
20336 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20337 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20338
20339 let prompt = cx.new(|cx| {
20340 let mut prompt = Editor::new(
20341 EditorMode::AutoHeight {
20342 max_lines: Self::MAX_LINES as usize,
20343 },
20344 buffer,
20345 None,
20346 window,
20347 cx,
20348 );
20349 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20350 prompt.set_show_cursor_when_unfocused(false, cx);
20351 prompt.set_placeholder_text(
20352 match edit_action {
20353 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20354 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20355 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20356 },
20357 cx,
20358 );
20359
20360 prompt
20361 });
20362
20363 Self {
20364 prompt,
20365 editor,
20366 breakpoint_anchor,
20367 breakpoint,
20368 edit_action,
20369 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20370 block_ids: Default::default(),
20371 _subscriptions: vec![],
20372 }
20373 }
20374
20375 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20376 self.block_ids.extend(block_ids)
20377 }
20378
20379 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20380 if let Some(editor) = self.editor.upgrade() {
20381 let message = self
20382 .prompt
20383 .read(cx)
20384 .buffer
20385 .read(cx)
20386 .as_singleton()
20387 .expect("A multi buffer in breakpoint prompt isn't possible")
20388 .read(cx)
20389 .as_rope()
20390 .to_string();
20391
20392 editor.update(cx, |editor, cx| {
20393 editor.edit_breakpoint_at_anchor(
20394 self.breakpoint_anchor,
20395 self.breakpoint.clone(),
20396 match self.edit_action {
20397 BreakpointPromptEditAction::Log => {
20398 BreakpointEditAction::EditLogMessage(message.into())
20399 }
20400 BreakpointPromptEditAction::Condition => {
20401 BreakpointEditAction::EditCondition(message.into())
20402 }
20403 BreakpointPromptEditAction::HitCondition => {
20404 BreakpointEditAction::EditHitCondition(message.into())
20405 }
20406 },
20407 cx,
20408 );
20409
20410 editor.remove_blocks(self.block_ids.clone(), None, cx);
20411 cx.focus_self(window);
20412 });
20413 }
20414 }
20415
20416 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20417 self.editor
20418 .update(cx, |editor, cx| {
20419 editor.remove_blocks(self.block_ids.clone(), None, cx);
20420 window.focus(&editor.focus_handle);
20421 })
20422 .log_err();
20423 }
20424
20425 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20426 let settings = ThemeSettings::get_global(cx);
20427 let text_style = TextStyle {
20428 color: if self.prompt.read(cx).read_only(cx) {
20429 cx.theme().colors().text_disabled
20430 } else {
20431 cx.theme().colors().text
20432 },
20433 font_family: settings.buffer_font.family.clone(),
20434 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20435 font_size: settings.buffer_font_size(cx).into(),
20436 font_weight: settings.buffer_font.weight,
20437 line_height: relative(settings.buffer_line_height.value()),
20438 ..Default::default()
20439 };
20440 EditorElement::new(
20441 &self.prompt,
20442 EditorStyle {
20443 background: cx.theme().colors().editor_background,
20444 local_player: cx.theme().players().local(),
20445 text: text_style,
20446 ..Default::default()
20447 },
20448 )
20449 }
20450}
20451
20452impl Render for BreakpointPromptEditor {
20453 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20454 let gutter_dimensions = *self.gutter_dimensions.lock();
20455 h_flex()
20456 .key_context("Editor")
20457 .bg(cx.theme().colors().editor_background)
20458 .border_y_1()
20459 .border_color(cx.theme().status().info_border)
20460 .size_full()
20461 .py(window.line_height() / 2.5)
20462 .on_action(cx.listener(Self::confirm))
20463 .on_action(cx.listener(Self::cancel))
20464 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20465 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20466 }
20467}
20468
20469impl Focusable for BreakpointPromptEditor {
20470 fn focus_handle(&self, cx: &App) -> FocusHandle {
20471 self.prompt.focus_handle(cx)
20472 }
20473}
20474
20475fn all_edits_insertions_or_deletions(
20476 edits: &Vec<(Range<Anchor>, String)>,
20477 snapshot: &MultiBufferSnapshot,
20478) -> bool {
20479 let mut all_insertions = true;
20480 let mut all_deletions = true;
20481
20482 for (range, new_text) in edits.iter() {
20483 let range_is_empty = range.to_offset(&snapshot).is_empty();
20484 let text_is_empty = new_text.is_empty();
20485
20486 if range_is_empty != text_is_empty {
20487 if range_is_empty {
20488 all_deletions = false;
20489 } else {
20490 all_insertions = false;
20491 }
20492 } else {
20493 return false;
20494 }
20495
20496 if !all_insertions && !all_deletions {
20497 return false;
20498 }
20499 }
20500 all_insertions || all_deletions
20501}
20502
20503struct MissingEditPredictionKeybindingTooltip;
20504
20505impl Render for MissingEditPredictionKeybindingTooltip {
20506 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20507 ui::tooltip_container(window, cx, |container, _, cx| {
20508 container
20509 .flex_shrink_0()
20510 .max_w_80()
20511 .min_h(rems_from_px(124.))
20512 .justify_between()
20513 .child(
20514 v_flex()
20515 .flex_1()
20516 .text_ui_sm(cx)
20517 .child(Label::new("Conflict with Accept Keybinding"))
20518 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20519 )
20520 .child(
20521 h_flex()
20522 .pb_1()
20523 .gap_1()
20524 .items_end()
20525 .w_full()
20526 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20527 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20528 }))
20529 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20530 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20531 })),
20532 )
20533 })
20534 }
20535}
20536
20537#[derive(Debug, Clone, Copy, PartialEq)]
20538pub struct LineHighlight {
20539 pub background: Background,
20540 pub border: Option<gpui::Hsla>,
20541}
20542
20543impl From<Hsla> for LineHighlight {
20544 fn from(hsla: Hsla) -> Self {
20545 Self {
20546 background: hsla.into(),
20547 border: None,
20548 }
20549 }
20550}
20551
20552impl From<Background> for LineHighlight {
20553 fn from(background: Background) -> Self {
20554 Self {
20555 background,
20556 border: None,
20557 }
20558 }
20559}
20560
20561fn render_diff_hunk_controls(
20562 row: u32,
20563 status: &DiffHunkStatus,
20564 hunk_range: Range<Anchor>,
20565 is_created_file: bool,
20566 line_height: Pixels,
20567 editor: &Entity<Editor>,
20568 _window: &mut Window,
20569 cx: &mut App,
20570) -> AnyElement {
20571 h_flex()
20572 .h(line_height)
20573 .mr_1()
20574 .gap_1()
20575 .px_0p5()
20576 .pb_1()
20577 .border_x_1()
20578 .border_b_1()
20579 .border_color(cx.theme().colors().border_variant)
20580 .rounded_b_lg()
20581 .bg(cx.theme().colors().editor_background)
20582 .gap_1()
20583 .occlude()
20584 .shadow_md()
20585 .child(if status.has_secondary_hunk() {
20586 Button::new(("stage", row as u64), "Stage")
20587 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20588 .tooltip({
20589 let focus_handle = editor.focus_handle(cx);
20590 move |window, cx| {
20591 Tooltip::for_action_in(
20592 "Stage Hunk",
20593 &::git::ToggleStaged,
20594 &focus_handle,
20595 window,
20596 cx,
20597 )
20598 }
20599 })
20600 .on_click({
20601 let editor = editor.clone();
20602 move |_event, _window, cx| {
20603 editor.update(cx, |editor, cx| {
20604 editor.stage_or_unstage_diff_hunks(
20605 true,
20606 vec![hunk_range.start..hunk_range.start],
20607 cx,
20608 );
20609 });
20610 }
20611 })
20612 } else {
20613 Button::new(("unstage", row as u64), "Unstage")
20614 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20615 .tooltip({
20616 let focus_handle = editor.focus_handle(cx);
20617 move |window, cx| {
20618 Tooltip::for_action_in(
20619 "Unstage Hunk",
20620 &::git::ToggleStaged,
20621 &focus_handle,
20622 window,
20623 cx,
20624 )
20625 }
20626 })
20627 .on_click({
20628 let editor = editor.clone();
20629 move |_event, _window, cx| {
20630 editor.update(cx, |editor, cx| {
20631 editor.stage_or_unstage_diff_hunks(
20632 false,
20633 vec![hunk_range.start..hunk_range.start],
20634 cx,
20635 );
20636 });
20637 }
20638 })
20639 })
20640 .child(
20641 Button::new(("restore", row as u64), "Restore")
20642 .tooltip({
20643 let focus_handle = editor.focus_handle(cx);
20644 move |window, cx| {
20645 Tooltip::for_action_in(
20646 "Restore Hunk",
20647 &::git::Restore,
20648 &focus_handle,
20649 window,
20650 cx,
20651 )
20652 }
20653 })
20654 .on_click({
20655 let editor = editor.clone();
20656 move |_event, window, cx| {
20657 editor.update(cx, |editor, cx| {
20658 let snapshot = editor.snapshot(window, cx);
20659 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20660 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20661 });
20662 }
20663 })
20664 .disabled(is_created_file),
20665 )
20666 .when(
20667 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20668 |el| {
20669 el.child(
20670 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20671 .shape(IconButtonShape::Square)
20672 .icon_size(IconSize::Small)
20673 // .disabled(!has_multiple_hunks)
20674 .tooltip({
20675 let focus_handle = editor.focus_handle(cx);
20676 move |window, cx| {
20677 Tooltip::for_action_in(
20678 "Next Hunk",
20679 &GoToHunk,
20680 &focus_handle,
20681 window,
20682 cx,
20683 )
20684 }
20685 })
20686 .on_click({
20687 let editor = editor.clone();
20688 move |_event, window, cx| {
20689 editor.update(cx, |editor, cx| {
20690 let snapshot = editor.snapshot(window, cx);
20691 let position =
20692 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20693 editor.go_to_hunk_before_or_after_position(
20694 &snapshot,
20695 position,
20696 Direction::Next,
20697 window,
20698 cx,
20699 );
20700 editor.expand_selected_diff_hunks(cx);
20701 });
20702 }
20703 }),
20704 )
20705 .child(
20706 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20707 .shape(IconButtonShape::Square)
20708 .icon_size(IconSize::Small)
20709 // .disabled(!has_multiple_hunks)
20710 .tooltip({
20711 let focus_handle = editor.focus_handle(cx);
20712 move |window, cx| {
20713 Tooltip::for_action_in(
20714 "Previous Hunk",
20715 &GoToPreviousHunk,
20716 &focus_handle,
20717 window,
20718 cx,
20719 )
20720 }
20721 })
20722 .on_click({
20723 let editor = editor.clone();
20724 move |_event, window, cx| {
20725 editor.update(cx, |editor, cx| {
20726 let snapshot = editor.snapshot(window, cx);
20727 let point =
20728 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20729 editor.go_to_hunk_before_or_after_position(
20730 &snapshot,
20731 point,
20732 Direction::Prev,
20733 window,
20734 cx,
20735 );
20736 editor.expand_selected_diff_hunks(cx);
20737 });
20738 }
20739 }),
20740 )
20741 },
20742 )
20743 .into_any_element()
20744}