1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod editor_tests;
44#[cfg(test)]
45mod inline_completion_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
52use aho_corasick::AhoCorasick;
53use anyhow::{Context as _, Result, anyhow};
54use blink_manager::BlinkManager;
55use buffer_diff::DiffHunkStatus;
56use client::{Collaborator, ParticipantIndex};
57use clock::ReplicaId;
58use collections::{BTreeMap, HashMap, HashSet, VecDeque};
59use convert_case::{Case, Casing};
60use display_map::*;
61pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
62use editor_settings::GoToDefinitionFallback;
63pub use editor_settings::{
64 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
65 ShowScrollbar,
66};
67pub use editor_settings_controls::*;
68use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
69pub use element::{
70 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
71};
72use feature_flags::{Debugger, FeatureFlagAppExt};
73use futures::{
74 FutureExt,
75 future::{self, Shared, join},
76};
77use fuzzy::StringMatchCandidate;
78
79use ::git::Restore;
80use code_context_menus::{
81 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
82 CompletionsMenu, ContextMenuOrigin,
83};
84use git::blame::{GitBlame, GlobalBlameRenderer};
85use gpui::{
86 Action, Animation, AnimationExt, AnyElement, AnyWeakEntity, App, AppContext,
87 AsyncWindowContext, AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry,
88 ClipboardItem, Context, DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter,
89 FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla,
90 KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render,
91 SharedString, Size, Stateful, Styled, StyledText, Subscription, Task, TextStyle,
92 TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity,
93 WeakFocusHandle, Window, div, impl_actions, point, prelude::*, pulsating_between, px, relative,
94 size,
95};
96use highlight_matching_bracket::refresh_matching_bracket_highlights;
97use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
98pub use hover_popover::hover_markdown_style;
99use hover_popover::{HoverState, hide_hover};
100use indent_guides::ActiveIndentGuidesState;
101use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
102pub use inline_completion::Direction;
103use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
104pub use items::MAX_TAB_TITLE_LEN;
105use itertools::Itertools;
106use language::{
107 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
108 CursorShape, Diagnostic, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
109 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
110 TransactionId, TreeSitterOptions, WordsQuery,
111 language_settings::{
112 self, InlayHintSettings, RewrapBehavior, WordsCompletionMode, all_language_settings,
113 language_settings,
114 },
115 point_from_lsp, text_diff_with_options,
116};
117use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
118use linked_editing_ranges::refresh_linked_ranges;
119use mouse_context_menu::MouseContextMenu;
120use persistence::DB;
121use project::{
122 ProjectPath,
123 debugger::breakpoint_store::{
124 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
125 },
126};
127
128pub use git::blame::BlameRenderer;
129pub use proposed_changes_editor::{
130 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
131};
132use smallvec::smallvec;
133use std::{cell::OnceCell, iter::Peekable};
134use task::{ResolvedTask, 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
1266 }
1267
1268 pub fn new(
1269 mode: EditorMode,
1270 buffer: Entity<MultiBuffer>,
1271 project: Option<Entity<Project>>,
1272 window: &mut Window,
1273 cx: &mut Context<Self>,
1274 ) -> Self {
1275 let style = window.text_style();
1276 let font_size = style.font_size.to_pixels(window.rem_size());
1277 let editor = cx.entity().downgrade();
1278 let fold_placeholder = FoldPlaceholder {
1279 constrain_width: true,
1280 render: Arc::new(move |fold_id, fold_range, cx| {
1281 let editor = editor.clone();
1282 div()
1283 .id(fold_id)
1284 .bg(cx.theme().colors().ghost_element_background)
1285 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1286 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1287 .rounded_xs()
1288 .size_full()
1289 .cursor_pointer()
1290 .child("⋯")
1291 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1292 .on_click(move |_, _window, cx| {
1293 editor
1294 .update(cx, |editor, cx| {
1295 editor.unfold_ranges(
1296 &[fold_range.start..fold_range.end],
1297 true,
1298 false,
1299 cx,
1300 );
1301 cx.stop_propagation();
1302 })
1303 .ok();
1304 })
1305 .into_any()
1306 }),
1307 merge_adjacent: true,
1308 ..Default::default()
1309 };
1310 let display_map = cx.new(|cx| {
1311 DisplayMap::new(
1312 buffer.clone(),
1313 style.font(),
1314 font_size,
1315 None,
1316 FILE_HEADER_HEIGHT,
1317 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1318 fold_placeholder,
1319 cx,
1320 )
1321 });
1322
1323 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1324
1325 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1326
1327 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1328 .then(|| language_settings::SoftWrap::None);
1329
1330 let mut project_subscriptions = Vec::new();
1331 if mode == EditorMode::Full {
1332 if let Some(project) = project.as_ref() {
1333 project_subscriptions.push(cx.subscribe_in(
1334 project,
1335 window,
1336 |editor, _, event, window, cx| match event {
1337 project::Event::RefreshCodeLens => {
1338 // we always query lens with actions, without storing them, always refreshing them
1339 }
1340 project::Event::RefreshInlayHints => {
1341 editor
1342 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1343 }
1344 project::Event::SnippetEdit(id, snippet_edits) => {
1345 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1346 let focus_handle = editor.focus_handle(cx);
1347 if focus_handle.is_focused(window) {
1348 let snapshot = buffer.read(cx).snapshot();
1349 for (range, snippet) in snippet_edits {
1350 let editor_range =
1351 language::range_from_lsp(*range).to_offset(&snapshot);
1352 editor
1353 .insert_snippet(
1354 &[editor_range],
1355 snippet.clone(),
1356 window,
1357 cx,
1358 )
1359 .ok();
1360 }
1361 }
1362 }
1363 }
1364 _ => {}
1365 },
1366 ));
1367 if let Some(task_inventory) = project
1368 .read(cx)
1369 .task_store()
1370 .read(cx)
1371 .task_inventory()
1372 .cloned()
1373 {
1374 project_subscriptions.push(cx.observe_in(
1375 &task_inventory,
1376 window,
1377 |editor, _, window, cx| {
1378 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1379 },
1380 ));
1381 };
1382
1383 project_subscriptions.push(cx.subscribe_in(
1384 &project.read(cx).breakpoint_store(),
1385 window,
1386 |editor, _, event, window, cx| match event {
1387 BreakpointStoreEvent::ActiveDebugLineChanged => {
1388 if editor.go_to_active_debug_line(window, cx) {
1389 cx.stop_propagation();
1390 }
1391 }
1392 _ => {}
1393 },
1394 ));
1395 }
1396 }
1397
1398 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1399
1400 let inlay_hint_settings =
1401 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1402 let focus_handle = cx.focus_handle();
1403 cx.on_focus(&focus_handle, window, Self::handle_focus)
1404 .detach();
1405 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1406 .detach();
1407 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1408 .detach();
1409 cx.on_blur(&focus_handle, window, Self::handle_blur)
1410 .detach();
1411
1412 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1413 Some(false)
1414 } else {
1415 None
1416 };
1417
1418 let breakpoint_store = match (mode, project.as_ref()) {
1419 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1420 _ => None,
1421 };
1422
1423 let mut code_action_providers = Vec::new();
1424 let mut load_uncommitted_diff = None;
1425 if let Some(project) = project.clone() {
1426 load_uncommitted_diff = Some(
1427 get_uncommitted_diff_for_buffer(
1428 &project,
1429 buffer.read(cx).all_buffers(),
1430 buffer.clone(),
1431 cx,
1432 )
1433 .shared(),
1434 );
1435 code_action_providers.push(Rc::new(project) as Rc<_>);
1436 }
1437
1438 let mut this = Self {
1439 focus_handle,
1440 show_cursor_when_unfocused: false,
1441 last_focused_descendant: None,
1442 buffer: buffer.clone(),
1443 display_map: display_map.clone(),
1444 selections,
1445 scroll_manager: ScrollManager::new(cx),
1446 columnar_selection_tail: None,
1447 add_selections_state: None,
1448 select_next_state: None,
1449 select_prev_state: None,
1450 selection_history: Default::default(),
1451 autoclose_regions: Default::default(),
1452 snippet_stack: Default::default(),
1453 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1454 ime_transaction: Default::default(),
1455 active_diagnostics: None,
1456 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1457 inline_diagnostics_update: Task::ready(()),
1458 inline_diagnostics: Vec::new(),
1459 soft_wrap_mode_override,
1460 hard_wrap: None,
1461 completion_provider: project.clone().map(|project| Box::new(project) as _),
1462 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1463 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1464 project,
1465 blink_manager: blink_manager.clone(),
1466 show_local_selections: true,
1467 show_scrollbars: true,
1468 mode,
1469 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1470 show_gutter: mode == EditorMode::Full,
1471 show_line_numbers: None,
1472 use_relative_line_numbers: None,
1473 show_git_diff_gutter: None,
1474 show_code_actions: None,
1475 show_runnables: None,
1476 show_breakpoints: None,
1477 show_wrap_guides: None,
1478 show_indent_guides,
1479 placeholder_text: None,
1480 highlight_order: 0,
1481 highlighted_rows: HashMap::default(),
1482 background_highlights: Default::default(),
1483 gutter_highlights: TreeMap::default(),
1484 scrollbar_marker_state: ScrollbarMarkerState::default(),
1485 active_indent_guides_state: ActiveIndentGuidesState::default(),
1486 nav_history: None,
1487 context_menu: RefCell::new(None),
1488 context_menu_options: None,
1489 mouse_context_menu: None,
1490 completion_tasks: Default::default(),
1491 signature_help_state: SignatureHelpState::default(),
1492 auto_signature_help: None,
1493 find_all_references_task_sources: Vec::new(),
1494 next_completion_id: 0,
1495 next_inlay_id: 0,
1496 code_action_providers,
1497 available_code_actions: Default::default(),
1498 code_actions_task: Default::default(),
1499 selection_highlight_task: Default::default(),
1500 document_highlights_task: Default::default(),
1501 linked_editing_range_task: Default::default(),
1502 pending_rename: Default::default(),
1503 searchable: true,
1504 cursor_shape: EditorSettings::get_global(cx)
1505 .cursor_shape
1506 .unwrap_or_default(),
1507 current_line_highlight: None,
1508 autoindent_mode: Some(AutoindentMode::EachLine),
1509 collapse_matches: false,
1510 workspace: None,
1511 input_enabled: true,
1512 use_modal_editing: mode == EditorMode::Full,
1513 read_only: false,
1514 use_autoclose: true,
1515 use_auto_surround: true,
1516 auto_replace_emoji_shortcode: false,
1517 jsx_tag_auto_close_enabled_in_any_buffer: false,
1518 leader_peer_id: None,
1519 remote_id: None,
1520 hover_state: Default::default(),
1521 pending_mouse_down: None,
1522 hovered_link_state: Default::default(),
1523 edit_prediction_provider: None,
1524 active_inline_completion: None,
1525 stale_inline_completion_in_menu: None,
1526 edit_prediction_preview: EditPredictionPreview::Inactive {
1527 released_too_fast: false,
1528 },
1529 inline_diagnostics_enabled: mode == EditorMode::Full,
1530 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1531
1532 gutter_hovered: false,
1533 pixel_position_of_newest_cursor: None,
1534 last_bounds: None,
1535 last_position_map: None,
1536 expect_bounds_change: None,
1537 gutter_dimensions: GutterDimensions::default(),
1538 style: None,
1539 show_cursor_names: false,
1540 hovered_cursors: Default::default(),
1541 next_editor_action_id: EditorActionId::default(),
1542 editor_actions: Rc::default(),
1543 inline_completions_hidden_for_vim_mode: false,
1544 show_inline_completions_override: None,
1545 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1546 edit_prediction_settings: EditPredictionSettings::Disabled,
1547 edit_prediction_indent_conflict: false,
1548 edit_prediction_requires_modifier_in_indent_conflict: true,
1549 custom_context_menu: None,
1550 show_git_blame_gutter: false,
1551 show_git_blame_inline: false,
1552 show_selection_menu: None,
1553 show_git_blame_inline_delay_task: None,
1554 git_blame_inline_tooltip: None,
1555 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1556 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1557 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1558 .session
1559 .restore_unsaved_buffers,
1560 blame: None,
1561 blame_subscription: None,
1562 tasks: Default::default(),
1563
1564 breakpoint_store,
1565 gutter_breakpoint_indicator: (None, None),
1566 _subscriptions: vec![
1567 cx.observe(&buffer, Self::on_buffer_changed),
1568 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1569 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1570 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1571 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1572 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1573 cx.observe_window_activation(window, |editor, window, cx| {
1574 let active = window.is_window_active();
1575 editor.blink_manager.update(cx, |blink_manager, cx| {
1576 if active {
1577 blink_manager.enable(cx);
1578 } else {
1579 blink_manager.disable(cx);
1580 }
1581 });
1582 }),
1583 ],
1584 tasks_update_task: None,
1585 linked_edit_ranges: Default::default(),
1586 in_project_search: false,
1587 previous_search_ranges: None,
1588 breadcrumb_header: None,
1589 focused_block: None,
1590 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1591 addons: HashMap::default(),
1592 registered_buffers: HashMap::default(),
1593 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1594 selection_mark_mode: false,
1595 toggle_fold_multiple_buffers: Task::ready(()),
1596 serialize_selections: Task::ready(()),
1597 serialize_folds: Task::ready(()),
1598 text_style_refinement: None,
1599 load_diff_task: load_uncommitted_diff,
1600 mouse_cursor_hidden: false,
1601 hide_mouse_mode: EditorSettings::get_global(cx)
1602 .hide_mouse
1603 .unwrap_or_default(),
1604 };
1605 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1606 this._subscriptions
1607 .push(cx.observe(breakpoints, |_, _, cx| {
1608 cx.notify();
1609 }));
1610 }
1611 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1612 this._subscriptions.extend(project_subscriptions);
1613
1614 this._subscriptions.push(cx.subscribe_in(
1615 &cx.entity(),
1616 window,
1617 |editor, _, e: &EditorEvent, window, cx| {
1618 if let EditorEvent::SelectionsChanged { local } = e {
1619 if *local {
1620 let new_anchor = editor.scroll_manager.anchor();
1621 let snapshot = editor.snapshot(window, cx);
1622 editor.update_restoration_data(cx, move |data| {
1623 data.scroll_position = (
1624 new_anchor.top_row(&snapshot.buffer_snapshot),
1625 new_anchor.offset,
1626 );
1627 });
1628 }
1629 }
1630 },
1631 ));
1632
1633 this.end_selection(window, cx);
1634 this.scroll_manager.show_scrollbars(window, cx);
1635 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1636
1637 if mode == EditorMode::Full {
1638 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1639 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1640
1641 if this.git_blame_inline_enabled {
1642 this.git_blame_inline_enabled = true;
1643 this.start_git_blame_inline(false, window, cx);
1644 }
1645
1646 this.go_to_active_debug_line(window, cx);
1647
1648 if let Some(buffer) = buffer.read(cx).as_singleton() {
1649 if let Some(project) = this.project.as_ref() {
1650 let handle = project.update(cx, |project, cx| {
1651 project.register_buffer_with_language_servers(&buffer, cx)
1652 });
1653 this.registered_buffers
1654 .insert(buffer.read(cx).remote_id(), handle);
1655 }
1656 }
1657 }
1658
1659 this.report_editor_event("Editor Opened", None, cx);
1660 this
1661 }
1662
1663 pub fn deploy_mouse_context_menu(
1664 &mut self,
1665 position: gpui::Point<Pixels>,
1666 context_menu: Entity<ContextMenu>,
1667 window: &mut Window,
1668 cx: &mut Context<Self>,
1669 ) {
1670 self.mouse_context_menu = Some(MouseContextMenu::new(
1671 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1672 context_menu,
1673 window,
1674 cx,
1675 ));
1676 }
1677
1678 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1679 self.mouse_context_menu
1680 .as_ref()
1681 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1682 }
1683
1684 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1685 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1686 }
1687
1688 fn key_context_internal(
1689 &self,
1690 has_active_edit_prediction: bool,
1691 window: &Window,
1692 cx: &App,
1693 ) -> KeyContext {
1694 let mut key_context = KeyContext::new_with_defaults();
1695 key_context.add("Editor");
1696 let mode = match self.mode {
1697 EditorMode::SingleLine { .. } => "single_line",
1698 EditorMode::AutoHeight { .. } => "auto_height",
1699 EditorMode::Full => "full",
1700 };
1701
1702 if EditorSettings::jupyter_enabled(cx) {
1703 key_context.add("jupyter");
1704 }
1705
1706 key_context.set("mode", mode);
1707 if self.pending_rename.is_some() {
1708 key_context.add("renaming");
1709 }
1710
1711 match self.context_menu.borrow().as_ref() {
1712 Some(CodeContextMenu::Completions(_)) => {
1713 key_context.add("menu");
1714 key_context.add("showing_completions");
1715 }
1716 Some(CodeContextMenu::CodeActions(_)) => {
1717 key_context.add("menu");
1718 key_context.add("showing_code_actions")
1719 }
1720 None => {}
1721 }
1722
1723 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1724 if !self.focus_handle(cx).contains_focused(window, cx)
1725 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1726 {
1727 for addon in self.addons.values() {
1728 addon.extend_key_context(&mut key_context, cx)
1729 }
1730 }
1731
1732 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1733 if let Some(extension) = singleton_buffer
1734 .read(cx)
1735 .file()
1736 .and_then(|file| file.path().extension()?.to_str())
1737 {
1738 key_context.set("extension", extension.to_string());
1739 }
1740 } else {
1741 key_context.add("multibuffer");
1742 }
1743
1744 if has_active_edit_prediction {
1745 if self.edit_prediction_in_conflict() {
1746 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1747 } else {
1748 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1749 key_context.add("copilot_suggestion");
1750 }
1751 }
1752
1753 if self.selection_mark_mode {
1754 key_context.add("selection_mode");
1755 }
1756
1757 key_context
1758 }
1759
1760 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1761 self.mouse_cursor_hidden = match origin {
1762 HideMouseCursorOrigin::TypingAction => {
1763 matches!(
1764 self.hide_mouse_mode,
1765 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1766 )
1767 }
1768 HideMouseCursorOrigin::MovementAction => {
1769 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
1770 }
1771 };
1772 }
1773
1774 pub fn edit_prediction_in_conflict(&self) -> bool {
1775 if !self.show_edit_predictions_in_menu() {
1776 return false;
1777 }
1778
1779 let showing_completions = self
1780 .context_menu
1781 .borrow()
1782 .as_ref()
1783 .map_or(false, |context| {
1784 matches!(context, CodeContextMenu::Completions(_))
1785 });
1786
1787 showing_completions
1788 || self.edit_prediction_requires_modifier()
1789 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1790 // bindings to insert tab characters.
1791 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1792 }
1793
1794 pub fn accept_edit_prediction_keybind(
1795 &self,
1796 window: &Window,
1797 cx: &App,
1798 ) -> AcceptEditPredictionBinding {
1799 let key_context = self.key_context_internal(true, window, cx);
1800 let in_conflict = self.edit_prediction_in_conflict();
1801
1802 AcceptEditPredictionBinding(
1803 window
1804 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1805 .into_iter()
1806 .filter(|binding| {
1807 !in_conflict
1808 || binding
1809 .keystrokes()
1810 .first()
1811 .map_or(false, |keystroke| keystroke.modifiers.modified())
1812 })
1813 .rev()
1814 .min_by_key(|binding| {
1815 binding
1816 .keystrokes()
1817 .first()
1818 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1819 }),
1820 )
1821 }
1822
1823 pub fn new_file(
1824 workspace: &mut Workspace,
1825 _: &workspace::NewFile,
1826 window: &mut Window,
1827 cx: &mut Context<Workspace>,
1828 ) {
1829 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1830 "Failed to create buffer",
1831 window,
1832 cx,
1833 |e, _, _| match e.error_code() {
1834 ErrorCode::RemoteUpgradeRequired => Some(format!(
1835 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1836 e.error_tag("required").unwrap_or("the latest version")
1837 )),
1838 _ => None,
1839 },
1840 );
1841 }
1842
1843 pub fn new_in_workspace(
1844 workspace: &mut Workspace,
1845 window: &mut Window,
1846 cx: &mut Context<Workspace>,
1847 ) -> Task<Result<Entity<Editor>>> {
1848 let project = workspace.project().clone();
1849 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1850
1851 cx.spawn_in(window, async move |workspace, cx| {
1852 let buffer = create.await?;
1853 workspace.update_in(cx, |workspace, window, cx| {
1854 let editor =
1855 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1856 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1857 editor
1858 })
1859 })
1860 }
1861
1862 fn new_file_vertical(
1863 workspace: &mut Workspace,
1864 _: &workspace::NewFileSplitVertical,
1865 window: &mut Window,
1866 cx: &mut Context<Workspace>,
1867 ) {
1868 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1869 }
1870
1871 fn new_file_horizontal(
1872 workspace: &mut Workspace,
1873 _: &workspace::NewFileSplitHorizontal,
1874 window: &mut Window,
1875 cx: &mut Context<Workspace>,
1876 ) {
1877 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1878 }
1879
1880 fn new_file_in_direction(
1881 workspace: &mut Workspace,
1882 direction: SplitDirection,
1883 window: &mut Window,
1884 cx: &mut Context<Workspace>,
1885 ) {
1886 let project = workspace.project().clone();
1887 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1888
1889 cx.spawn_in(window, async move |workspace, cx| {
1890 let buffer = create.await?;
1891 workspace.update_in(cx, move |workspace, window, cx| {
1892 workspace.split_item(
1893 direction,
1894 Box::new(
1895 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1896 ),
1897 window,
1898 cx,
1899 )
1900 })?;
1901 anyhow::Ok(())
1902 })
1903 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1904 match e.error_code() {
1905 ErrorCode::RemoteUpgradeRequired => Some(format!(
1906 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1907 e.error_tag("required").unwrap_or("the latest version")
1908 )),
1909 _ => None,
1910 }
1911 });
1912 }
1913
1914 pub fn leader_peer_id(&self) -> Option<PeerId> {
1915 self.leader_peer_id
1916 }
1917
1918 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1919 &self.buffer
1920 }
1921
1922 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1923 self.workspace.as_ref()?.0.upgrade()
1924 }
1925
1926 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1927 self.buffer().read(cx).title(cx)
1928 }
1929
1930 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1931 let git_blame_gutter_max_author_length = self
1932 .render_git_blame_gutter(cx)
1933 .then(|| {
1934 if let Some(blame) = self.blame.as_ref() {
1935 let max_author_length =
1936 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1937 Some(max_author_length)
1938 } else {
1939 None
1940 }
1941 })
1942 .flatten();
1943
1944 EditorSnapshot {
1945 mode: self.mode,
1946 show_gutter: self.show_gutter,
1947 show_line_numbers: self.show_line_numbers,
1948 show_git_diff_gutter: self.show_git_diff_gutter,
1949 show_code_actions: self.show_code_actions,
1950 show_runnables: self.show_runnables,
1951 show_breakpoints: self.show_breakpoints,
1952 git_blame_gutter_max_author_length,
1953 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1954 scroll_anchor: self.scroll_manager.anchor(),
1955 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1956 placeholder_text: self.placeholder_text.clone(),
1957 is_focused: self.focus_handle.is_focused(window),
1958 current_line_highlight: self
1959 .current_line_highlight
1960 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1961 gutter_hovered: self.gutter_hovered,
1962 }
1963 }
1964
1965 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1966 self.buffer.read(cx).language_at(point, cx)
1967 }
1968
1969 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1970 self.buffer.read(cx).read(cx).file_at(point).cloned()
1971 }
1972
1973 pub fn active_excerpt(
1974 &self,
1975 cx: &App,
1976 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1977 self.buffer
1978 .read(cx)
1979 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1980 }
1981
1982 pub fn mode(&self) -> EditorMode {
1983 self.mode
1984 }
1985
1986 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1987 self.collaboration_hub.as_deref()
1988 }
1989
1990 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1991 self.collaboration_hub = Some(hub);
1992 }
1993
1994 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1995 self.in_project_search = in_project_search;
1996 }
1997
1998 pub fn set_custom_context_menu(
1999 &mut self,
2000 f: impl 'static
2001 + Fn(
2002 &mut Self,
2003 DisplayPoint,
2004 &mut Window,
2005 &mut Context<Self>,
2006 ) -> Option<Entity<ui::ContextMenu>>,
2007 ) {
2008 self.custom_context_menu = Some(Box::new(f))
2009 }
2010
2011 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2012 self.completion_provider = provider;
2013 }
2014
2015 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2016 self.semantics_provider.clone()
2017 }
2018
2019 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2020 self.semantics_provider = provider;
2021 }
2022
2023 pub fn set_edit_prediction_provider<T>(
2024 &mut self,
2025 provider: Option<Entity<T>>,
2026 window: &mut Window,
2027 cx: &mut Context<Self>,
2028 ) where
2029 T: EditPredictionProvider,
2030 {
2031 self.edit_prediction_provider =
2032 provider.map(|provider| RegisteredInlineCompletionProvider {
2033 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2034 if this.focus_handle.is_focused(window) {
2035 this.update_visible_inline_completion(window, cx);
2036 }
2037 }),
2038 provider: Arc::new(provider),
2039 });
2040 self.update_edit_prediction_settings(cx);
2041 self.refresh_inline_completion(false, false, window, cx);
2042 }
2043
2044 pub fn placeholder_text(&self) -> Option<&str> {
2045 self.placeholder_text.as_deref()
2046 }
2047
2048 pub fn set_placeholder_text(
2049 &mut self,
2050 placeholder_text: impl Into<Arc<str>>,
2051 cx: &mut Context<Self>,
2052 ) {
2053 let placeholder_text = Some(placeholder_text.into());
2054 if self.placeholder_text != placeholder_text {
2055 self.placeholder_text = placeholder_text;
2056 cx.notify();
2057 }
2058 }
2059
2060 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2061 self.cursor_shape = cursor_shape;
2062
2063 // Disrupt blink for immediate user feedback that the cursor shape has changed
2064 self.blink_manager.update(cx, BlinkManager::show_cursor);
2065
2066 cx.notify();
2067 }
2068
2069 pub fn set_current_line_highlight(
2070 &mut self,
2071 current_line_highlight: Option<CurrentLineHighlight>,
2072 ) {
2073 self.current_line_highlight = current_line_highlight;
2074 }
2075
2076 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2077 self.collapse_matches = collapse_matches;
2078 }
2079
2080 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2081 let buffers = self.buffer.read(cx).all_buffers();
2082 let Some(project) = self.project.as_ref() else {
2083 return;
2084 };
2085 project.update(cx, |project, cx| {
2086 for buffer in buffers {
2087 self.registered_buffers
2088 .entry(buffer.read(cx).remote_id())
2089 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2090 }
2091 })
2092 }
2093
2094 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2095 if self.collapse_matches {
2096 return range.start..range.start;
2097 }
2098 range.clone()
2099 }
2100
2101 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2102 if self.display_map.read(cx).clip_at_line_ends != clip {
2103 self.display_map
2104 .update(cx, |map, _| map.clip_at_line_ends = clip);
2105 }
2106 }
2107
2108 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2109 self.input_enabled = input_enabled;
2110 }
2111
2112 pub fn set_inline_completions_hidden_for_vim_mode(
2113 &mut self,
2114 hidden: bool,
2115 window: &mut Window,
2116 cx: &mut Context<Self>,
2117 ) {
2118 if hidden != self.inline_completions_hidden_for_vim_mode {
2119 self.inline_completions_hidden_for_vim_mode = hidden;
2120 if hidden {
2121 self.update_visible_inline_completion(window, cx);
2122 } else {
2123 self.refresh_inline_completion(true, false, window, cx);
2124 }
2125 }
2126 }
2127
2128 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2129 self.menu_inline_completions_policy = value;
2130 }
2131
2132 pub fn set_autoindent(&mut self, autoindent: bool) {
2133 if autoindent {
2134 self.autoindent_mode = Some(AutoindentMode::EachLine);
2135 } else {
2136 self.autoindent_mode = None;
2137 }
2138 }
2139
2140 pub fn read_only(&self, cx: &App) -> bool {
2141 self.read_only || self.buffer.read(cx).read_only()
2142 }
2143
2144 pub fn set_read_only(&mut self, read_only: bool) {
2145 self.read_only = read_only;
2146 }
2147
2148 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2149 self.use_autoclose = autoclose;
2150 }
2151
2152 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2153 self.use_auto_surround = auto_surround;
2154 }
2155
2156 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2157 self.auto_replace_emoji_shortcode = auto_replace;
2158 }
2159
2160 pub fn toggle_edit_predictions(
2161 &mut self,
2162 _: &ToggleEditPrediction,
2163 window: &mut Window,
2164 cx: &mut Context<Self>,
2165 ) {
2166 if self.show_inline_completions_override.is_some() {
2167 self.set_show_edit_predictions(None, window, cx);
2168 } else {
2169 let show_edit_predictions = !self.edit_predictions_enabled();
2170 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2171 }
2172 }
2173
2174 pub fn set_show_edit_predictions(
2175 &mut self,
2176 show_edit_predictions: Option<bool>,
2177 window: &mut Window,
2178 cx: &mut Context<Self>,
2179 ) {
2180 self.show_inline_completions_override = show_edit_predictions;
2181 self.update_edit_prediction_settings(cx);
2182
2183 if let Some(false) = show_edit_predictions {
2184 self.discard_inline_completion(false, cx);
2185 } else {
2186 self.refresh_inline_completion(false, true, window, cx);
2187 }
2188 }
2189
2190 fn inline_completions_disabled_in_scope(
2191 &self,
2192 buffer: &Entity<Buffer>,
2193 buffer_position: language::Anchor,
2194 cx: &App,
2195 ) -> bool {
2196 let snapshot = buffer.read(cx).snapshot();
2197 let settings = snapshot.settings_at(buffer_position, cx);
2198
2199 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2200 return false;
2201 };
2202
2203 scope.override_name().map_or(false, |scope_name| {
2204 settings
2205 .edit_predictions_disabled_in
2206 .iter()
2207 .any(|s| s == scope_name)
2208 })
2209 }
2210
2211 pub fn set_use_modal_editing(&mut self, to: bool) {
2212 self.use_modal_editing = to;
2213 }
2214
2215 pub fn use_modal_editing(&self) -> bool {
2216 self.use_modal_editing
2217 }
2218
2219 fn selections_did_change(
2220 &mut self,
2221 local: bool,
2222 old_cursor_position: &Anchor,
2223 show_completions: bool,
2224 window: &mut Window,
2225 cx: &mut Context<Self>,
2226 ) {
2227 window.invalidate_character_coordinates();
2228
2229 // Copy selections to primary selection buffer
2230 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2231 if local {
2232 let selections = self.selections.all::<usize>(cx);
2233 let buffer_handle = self.buffer.read(cx).read(cx);
2234
2235 let mut text = String::new();
2236 for (index, selection) in selections.iter().enumerate() {
2237 let text_for_selection = buffer_handle
2238 .text_for_range(selection.start..selection.end)
2239 .collect::<String>();
2240
2241 text.push_str(&text_for_selection);
2242 if index != selections.len() - 1 {
2243 text.push('\n');
2244 }
2245 }
2246
2247 if !text.is_empty() {
2248 cx.write_to_primary(ClipboardItem::new_string(text));
2249 }
2250 }
2251
2252 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2253 self.buffer.update(cx, |buffer, cx| {
2254 buffer.set_active_selections(
2255 &self.selections.disjoint_anchors(),
2256 self.selections.line_mode,
2257 self.cursor_shape,
2258 cx,
2259 )
2260 });
2261 }
2262 let display_map = self
2263 .display_map
2264 .update(cx, |display_map, cx| display_map.snapshot(cx));
2265 let buffer = &display_map.buffer_snapshot;
2266 self.add_selections_state = None;
2267 self.select_next_state = None;
2268 self.select_prev_state = None;
2269 self.select_syntax_node_history.try_clear();
2270 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2271 self.snippet_stack
2272 .invalidate(&self.selections.disjoint_anchors(), buffer);
2273 self.take_rename(false, window, cx);
2274
2275 let new_cursor_position = self.selections.newest_anchor().head();
2276
2277 self.push_to_nav_history(
2278 *old_cursor_position,
2279 Some(new_cursor_position.to_point(buffer)),
2280 false,
2281 cx,
2282 );
2283
2284 if local {
2285 let new_cursor_position = self.selections.newest_anchor().head();
2286 let mut context_menu = self.context_menu.borrow_mut();
2287 let completion_menu = match context_menu.as_ref() {
2288 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2289 _ => {
2290 *context_menu = None;
2291 None
2292 }
2293 };
2294 if let Some(buffer_id) = new_cursor_position.buffer_id {
2295 if !self.registered_buffers.contains_key(&buffer_id) {
2296 if let Some(project) = self.project.as_ref() {
2297 project.update(cx, |project, cx| {
2298 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2299 return;
2300 };
2301 self.registered_buffers.insert(
2302 buffer_id,
2303 project.register_buffer_with_language_servers(&buffer, cx),
2304 );
2305 })
2306 }
2307 }
2308 }
2309
2310 if let Some(completion_menu) = completion_menu {
2311 let cursor_position = new_cursor_position.to_offset(buffer);
2312 let (word_range, kind) =
2313 buffer.surrounding_word(completion_menu.initial_position, true);
2314 if kind == Some(CharKind::Word)
2315 && word_range.to_inclusive().contains(&cursor_position)
2316 {
2317 let mut completion_menu = completion_menu.clone();
2318 drop(context_menu);
2319
2320 let query = Self::completion_query(buffer, cursor_position);
2321 cx.spawn(async move |this, cx| {
2322 completion_menu
2323 .filter(query.as_deref(), cx.background_executor().clone())
2324 .await;
2325
2326 this.update(cx, |this, cx| {
2327 let mut context_menu = this.context_menu.borrow_mut();
2328 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2329 else {
2330 return;
2331 };
2332
2333 if menu.id > completion_menu.id {
2334 return;
2335 }
2336
2337 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2338 drop(context_menu);
2339 cx.notify();
2340 })
2341 })
2342 .detach();
2343
2344 if show_completions {
2345 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2346 }
2347 } else {
2348 drop(context_menu);
2349 self.hide_context_menu(window, cx);
2350 }
2351 } else {
2352 drop(context_menu);
2353 }
2354
2355 hide_hover(self, cx);
2356
2357 if old_cursor_position.to_display_point(&display_map).row()
2358 != new_cursor_position.to_display_point(&display_map).row()
2359 {
2360 self.available_code_actions.take();
2361 }
2362 self.refresh_code_actions(window, cx);
2363 self.refresh_document_highlights(cx);
2364 self.refresh_selected_text_highlights(window, cx);
2365 refresh_matching_bracket_highlights(self, window, cx);
2366 self.update_visible_inline_completion(window, cx);
2367 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2368 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2369 if self.git_blame_inline_enabled {
2370 self.start_inline_blame_timer(window, cx);
2371 }
2372 }
2373
2374 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2375 cx.emit(EditorEvent::SelectionsChanged { local });
2376
2377 let selections = &self.selections.disjoint;
2378 if selections.len() == 1 {
2379 cx.emit(SearchEvent::ActiveMatchChanged)
2380 }
2381 if local {
2382 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2383 let inmemory_selections = selections
2384 .iter()
2385 .map(|s| {
2386 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2387 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2388 })
2389 .collect();
2390 self.update_restoration_data(cx, |data| {
2391 data.selections = inmemory_selections;
2392 });
2393
2394 if WorkspaceSettings::get(None, cx).restore_on_startup
2395 != RestoreOnStartupBehavior::None
2396 {
2397 if let Some(workspace_id) =
2398 self.workspace.as_ref().and_then(|workspace| workspace.1)
2399 {
2400 let snapshot = self.buffer().read(cx).snapshot(cx);
2401 let selections = selections.clone();
2402 let background_executor = cx.background_executor().clone();
2403 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2404 self.serialize_selections = cx.background_spawn(async move {
2405 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2406 let db_selections = selections
2407 .iter()
2408 .map(|selection| {
2409 (
2410 selection.start.to_offset(&snapshot),
2411 selection.end.to_offset(&snapshot),
2412 )
2413 })
2414 .collect();
2415
2416 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2417 .await
2418 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2419 .log_err();
2420 });
2421 }
2422 }
2423 }
2424 }
2425
2426 cx.notify();
2427 }
2428
2429 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2430 use text::ToOffset as _;
2431 use text::ToPoint as _;
2432
2433 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2434 return;
2435 }
2436
2437 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2438 return;
2439 };
2440
2441 let snapshot = singleton.read(cx).snapshot();
2442 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2443 let display_snapshot = display_map.snapshot(cx);
2444
2445 display_snapshot
2446 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2447 .map(|fold| {
2448 fold.range.start.text_anchor.to_point(&snapshot)
2449 ..fold.range.end.text_anchor.to_point(&snapshot)
2450 })
2451 .collect()
2452 });
2453 self.update_restoration_data(cx, |data| {
2454 data.folds = inmemory_folds;
2455 });
2456
2457 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2458 return;
2459 };
2460 let background_executor = cx.background_executor().clone();
2461 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2462 let db_folds = self.display_map.update(cx, |display_map, cx| {
2463 display_map
2464 .snapshot(cx)
2465 .folds_in_range(0..snapshot.len())
2466 .map(|fold| {
2467 (
2468 fold.range.start.text_anchor.to_offset(&snapshot),
2469 fold.range.end.text_anchor.to_offset(&snapshot),
2470 )
2471 })
2472 .collect()
2473 });
2474 self.serialize_folds = cx.background_spawn(async move {
2475 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2476 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2477 .await
2478 .with_context(|| {
2479 format!(
2480 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2481 )
2482 })
2483 .log_err();
2484 });
2485 }
2486
2487 pub fn sync_selections(
2488 &mut self,
2489 other: Entity<Editor>,
2490 cx: &mut Context<Self>,
2491 ) -> gpui::Subscription {
2492 let other_selections = other.read(cx).selections.disjoint.to_vec();
2493 self.selections.change_with(cx, |selections| {
2494 selections.select_anchors(other_selections);
2495 });
2496
2497 let other_subscription =
2498 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2499 EditorEvent::SelectionsChanged { local: true } => {
2500 let other_selections = other.read(cx).selections.disjoint.to_vec();
2501 if other_selections.is_empty() {
2502 return;
2503 }
2504 this.selections.change_with(cx, |selections| {
2505 selections.select_anchors(other_selections);
2506 });
2507 }
2508 _ => {}
2509 });
2510
2511 let this_subscription =
2512 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2513 EditorEvent::SelectionsChanged { local: true } => {
2514 let these_selections = this.selections.disjoint.to_vec();
2515 if these_selections.is_empty() {
2516 return;
2517 }
2518 other.update(cx, |other_editor, cx| {
2519 other_editor.selections.change_with(cx, |selections| {
2520 selections.select_anchors(these_selections);
2521 })
2522 });
2523 }
2524 _ => {}
2525 });
2526
2527 Subscription::join(other_subscription, this_subscription)
2528 }
2529
2530 pub fn change_selections<R>(
2531 &mut self,
2532 autoscroll: Option<Autoscroll>,
2533 window: &mut Window,
2534 cx: &mut Context<Self>,
2535 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2536 ) -> R {
2537 self.change_selections_inner(autoscroll, true, window, cx, change)
2538 }
2539
2540 fn change_selections_inner<R>(
2541 &mut self,
2542 autoscroll: Option<Autoscroll>,
2543 request_completions: bool,
2544 window: &mut Window,
2545 cx: &mut Context<Self>,
2546 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2547 ) -> R {
2548 let old_cursor_position = self.selections.newest_anchor().head();
2549 self.push_to_selection_history();
2550
2551 let (changed, result) = self.selections.change_with(cx, change);
2552
2553 if changed {
2554 if let Some(autoscroll) = autoscroll {
2555 self.request_autoscroll(autoscroll, cx);
2556 }
2557 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2558
2559 if self.should_open_signature_help_automatically(
2560 &old_cursor_position,
2561 self.signature_help_state.backspace_pressed(),
2562 cx,
2563 ) {
2564 self.show_signature_help(&ShowSignatureHelp, window, cx);
2565 }
2566 self.signature_help_state.set_backspace_pressed(false);
2567 }
2568
2569 result
2570 }
2571
2572 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2573 where
2574 I: IntoIterator<Item = (Range<S>, T)>,
2575 S: ToOffset,
2576 T: Into<Arc<str>>,
2577 {
2578 if self.read_only(cx) {
2579 return;
2580 }
2581
2582 self.buffer
2583 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2584 }
2585
2586 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2587 where
2588 I: IntoIterator<Item = (Range<S>, T)>,
2589 S: ToOffset,
2590 T: Into<Arc<str>>,
2591 {
2592 if self.read_only(cx) {
2593 return;
2594 }
2595
2596 self.buffer.update(cx, |buffer, cx| {
2597 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2598 });
2599 }
2600
2601 pub fn edit_with_block_indent<I, S, T>(
2602 &mut self,
2603 edits: I,
2604 original_indent_columns: Vec<Option<u32>>,
2605 cx: &mut Context<Self>,
2606 ) where
2607 I: IntoIterator<Item = (Range<S>, T)>,
2608 S: ToOffset,
2609 T: Into<Arc<str>>,
2610 {
2611 if self.read_only(cx) {
2612 return;
2613 }
2614
2615 self.buffer.update(cx, |buffer, cx| {
2616 buffer.edit(
2617 edits,
2618 Some(AutoindentMode::Block {
2619 original_indent_columns,
2620 }),
2621 cx,
2622 )
2623 });
2624 }
2625
2626 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2627 self.hide_context_menu(window, cx);
2628
2629 match phase {
2630 SelectPhase::Begin {
2631 position,
2632 add,
2633 click_count,
2634 } => self.begin_selection(position, add, click_count, window, cx),
2635 SelectPhase::BeginColumnar {
2636 position,
2637 goal_column,
2638 reset,
2639 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2640 SelectPhase::Extend {
2641 position,
2642 click_count,
2643 } => self.extend_selection(position, click_count, window, cx),
2644 SelectPhase::Update {
2645 position,
2646 goal_column,
2647 scroll_delta,
2648 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2649 SelectPhase::End => self.end_selection(window, cx),
2650 }
2651 }
2652
2653 fn extend_selection(
2654 &mut self,
2655 position: DisplayPoint,
2656 click_count: usize,
2657 window: &mut Window,
2658 cx: &mut Context<Self>,
2659 ) {
2660 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2661 let tail = self.selections.newest::<usize>(cx).tail();
2662 self.begin_selection(position, false, click_count, window, cx);
2663
2664 let position = position.to_offset(&display_map, Bias::Left);
2665 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2666
2667 let mut pending_selection = self
2668 .selections
2669 .pending_anchor()
2670 .expect("extend_selection not called with pending selection");
2671 if position >= tail {
2672 pending_selection.start = tail_anchor;
2673 } else {
2674 pending_selection.end = tail_anchor;
2675 pending_selection.reversed = true;
2676 }
2677
2678 let mut pending_mode = self.selections.pending_mode().unwrap();
2679 match &mut pending_mode {
2680 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2681 _ => {}
2682 }
2683
2684 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2685 s.set_pending(pending_selection, pending_mode)
2686 });
2687 }
2688
2689 fn begin_selection(
2690 &mut self,
2691 position: DisplayPoint,
2692 add: bool,
2693 click_count: usize,
2694 window: &mut Window,
2695 cx: &mut Context<Self>,
2696 ) {
2697 if !self.focus_handle.is_focused(window) {
2698 self.last_focused_descendant = None;
2699 window.focus(&self.focus_handle);
2700 }
2701
2702 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2703 let buffer = &display_map.buffer_snapshot;
2704 let newest_selection = self.selections.newest_anchor().clone();
2705 let position = display_map.clip_point(position, Bias::Left);
2706
2707 let start;
2708 let end;
2709 let mode;
2710 let mut auto_scroll;
2711 match click_count {
2712 1 => {
2713 start = buffer.anchor_before(position.to_point(&display_map));
2714 end = start;
2715 mode = SelectMode::Character;
2716 auto_scroll = true;
2717 }
2718 2 => {
2719 let range = movement::surrounding_word(&display_map, position);
2720 start = buffer.anchor_before(range.start.to_point(&display_map));
2721 end = buffer.anchor_before(range.end.to_point(&display_map));
2722 mode = SelectMode::Word(start..end);
2723 auto_scroll = true;
2724 }
2725 3 => {
2726 let position = display_map
2727 .clip_point(position, Bias::Left)
2728 .to_point(&display_map);
2729 let line_start = display_map.prev_line_boundary(position).0;
2730 let next_line_start = buffer.clip_point(
2731 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2732 Bias::Left,
2733 );
2734 start = buffer.anchor_before(line_start);
2735 end = buffer.anchor_before(next_line_start);
2736 mode = SelectMode::Line(start..end);
2737 auto_scroll = true;
2738 }
2739 _ => {
2740 start = buffer.anchor_before(0);
2741 end = buffer.anchor_before(buffer.len());
2742 mode = SelectMode::All;
2743 auto_scroll = false;
2744 }
2745 }
2746 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2747
2748 let point_to_delete: Option<usize> = {
2749 let selected_points: Vec<Selection<Point>> =
2750 self.selections.disjoint_in_range(start..end, cx);
2751
2752 if !add || click_count > 1 {
2753 None
2754 } else if !selected_points.is_empty() {
2755 Some(selected_points[0].id)
2756 } else {
2757 let clicked_point_already_selected =
2758 self.selections.disjoint.iter().find(|selection| {
2759 selection.start.to_point(buffer) == start.to_point(buffer)
2760 || selection.end.to_point(buffer) == end.to_point(buffer)
2761 });
2762
2763 clicked_point_already_selected.map(|selection| selection.id)
2764 }
2765 };
2766
2767 let selections_count = self.selections.count();
2768
2769 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2770 if let Some(point_to_delete) = point_to_delete {
2771 s.delete(point_to_delete);
2772
2773 if selections_count == 1 {
2774 s.set_pending_anchor_range(start..end, mode);
2775 }
2776 } else {
2777 if !add {
2778 s.clear_disjoint();
2779 } else if click_count > 1 {
2780 s.delete(newest_selection.id)
2781 }
2782
2783 s.set_pending_anchor_range(start..end, mode);
2784 }
2785 });
2786 }
2787
2788 fn begin_columnar_selection(
2789 &mut self,
2790 position: DisplayPoint,
2791 goal_column: u32,
2792 reset: bool,
2793 window: &mut Window,
2794 cx: &mut Context<Self>,
2795 ) {
2796 if !self.focus_handle.is_focused(window) {
2797 self.last_focused_descendant = None;
2798 window.focus(&self.focus_handle);
2799 }
2800
2801 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2802
2803 if reset {
2804 let pointer_position = display_map
2805 .buffer_snapshot
2806 .anchor_before(position.to_point(&display_map));
2807
2808 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2809 s.clear_disjoint();
2810 s.set_pending_anchor_range(
2811 pointer_position..pointer_position,
2812 SelectMode::Character,
2813 );
2814 });
2815 }
2816
2817 let tail = self.selections.newest::<Point>(cx).tail();
2818 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2819
2820 if !reset {
2821 self.select_columns(
2822 tail.to_display_point(&display_map),
2823 position,
2824 goal_column,
2825 &display_map,
2826 window,
2827 cx,
2828 );
2829 }
2830 }
2831
2832 fn update_selection(
2833 &mut self,
2834 position: DisplayPoint,
2835 goal_column: u32,
2836 scroll_delta: gpui::Point<f32>,
2837 window: &mut Window,
2838 cx: &mut Context<Self>,
2839 ) {
2840 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2841
2842 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2843 let tail = tail.to_display_point(&display_map);
2844 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2845 } else if let Some(mut pending) = self.selections.pending_anchor() {
2846 let buffer = self.buffer.read(cx).snapshot(cx);
2847 let head;
2848 let tail;
2849 let mode = self.selections.pending_mode().unwrap();
2850 match &mode {
2851 SelectMode::Character => {
2852 head = position.to_point(&display_map);
2853 tail = pending.tail().to_point(&buffer);
2854 }
2855 SelectMode::Word(original_range) => {
2856 let original_display_range = original_range.start.to_display_point(&display_map)
2857 ..original_range.end.to_display_point(&display_map);
2858 let original_buffer_range = original_display_range.start.to_point(&display_map)
2859 ..original_display_range.end.to_point(&display_map);
2860 if movement::is_inside_word(&display_map, position)
2861 || original_display_range.contains(&position)
2862 {
2863 let word_range = movement::surrounding_word(&display_map, position);
2864 if word_range.start < original_display_range.start {
2865 head = word_range.start.to_point(&display_map);
2866 } else {
2867 head = word_range.end.to_point(&display_map);
2868 }
2869 } else {
2870 head = position.to_point(&display_map);
2871 }
2872
2873 if head <= original_buffer_range.start {
2874 tail = original_buffer_range.end;
2875 } else {
2876 tail = original_buffer_range.start;
2877 }
2878 }
2879 SelectMode::Line(original_range) => {
2880 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2881
2882 let position = display_map
2883 .clip_point(position, Bias::Left)
2884 .to_point(&display_map);
2885 let line_start = display_map.prev_line_boundary(position).0;
2886 let next_line_start = buffer.clip_point(
2887 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2888 Bias::Left,
2889 );
2890
2891 if line_start < original_range.start {
2892 head = line_start
2893 } else {
2894 head = next_line_start
2895 }
2896
2897 if head <= original_range.start {
2898 tail = original_range.end;
2899 } else {
2900 tail = original_range.start;
2901 }
2902 }
2903 SelectMode::All => {
2904 return;
2905 }
2906 };
2907
2908 if head < tail {
2909 pending.start = buffer.anchor_before(head);
2910 pending.end = buffer.anchor_before(tail);
2911 pending.reversed = true;
2912 } else {
2913 pending.start = buffer.anchor_before(tail);
2914 pending.end = buffer.anchor_before(head);
2915 pending.reversed = false;
2916 }
2917
2918 self.change_selections(None, window, cx, |s| {
2919 s.set_pending(pending, mode);
2920 });
2921 } else {
2922 log::error!("update_selection dispatched with no pending selection");
2923 return;
2924 }
2925
2926 self.apply_scroll_delta(scroll_delta, window, cx);
2927 cx.notify();
2928 }
2929
2930 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2931 self.columnar_selection_tail.take();
2932 if self.selections.pending_anchor().is_some() {
2933 let selections = self.selections.all::<usize>(cx);
2934 self.change_selections(None, window, cx, |s| {
2935 s.select(selections);
2936 s.clear_pending();
2937 });
2938 }
2939 }
2940
2941 fn select_columns(
2942 &mut self,
2943 tail: DisplayPoint,
2944 head: DisplayPoint,
2945 goal_column: u32,
2946 display_map: &DisplaySnapshot,
2947 window: &mut Window,
2948 cx: &mut Context<Self>,
2949 ) {
2950 let start_row = cmp::min(tail.row(), head.row());
2951 let end_row = cmp::max(tail.row(), head.row());
2952 let start_column = cmp::min(tail.column(), goal_column);
2953 let end_column = cmp::max(tail.column(), goal_column);
2954 let reversed = start_column < tail.column();
2955
2956 let selection_ranges = (start_row.0..=end_row.0)
2957 .map(DisplayRow)
2958 .filter_map(|row| {
2959 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2960 let start = display_map
2961 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2962 .to_point(display_map);
2963 let end = display_map
2964 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2965 .to_point(display_map);
2966 if reversed {
2967 Some(end..start)
2968 } else {
2969 Some(start..end)
2970 }
2971 } else {
2972 None
2973 }
2974 })
2975 .collect::<Vec<_>>();
2976
2977 self.change_selections(None, window, cx, |s| {
2978 s.select_ranges(selection_ranges);
2979 });
2980 cx.notify();
2981 }
2982
2983 pub fn has_pending_nonempty_selection(&self) -> bool {
2984 let pending_nonempty_selection = match self.selections.pending_anchor() {
2985 Some(Selection { start, end, .. }) => start != end,
2986 None => false,
2987 };
2988
2989 pending_nonempty_selection
2990 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2991 }
2992
2993 pub fn has_pending_selection(&self) -> bool {
2994 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2995 }
2996
2997 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2998 self.selection_mark_mode = false;
2999
3000 if self.clear_expanded_diff_hunks(cx) {
3001 cx.notify();
3002 return;
3003 }
3004 if self.dismiss_menus_and_popups(true, window, cx) {
3005 return;
3006 }
3007
3008 if self.mode == EditorMode::Full
3009 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3010 {
3011 return;
3012 }
3013
3014 cx.propagate();
3015 }
3016
3017 pub fn dismiss_menus_and_popups(
3018 &mut self,
3019 is_user_requested: bool,
3020 window: &mut Window,
3021 cx: &mut Context<Self>,
3022 ) -> bool {
3023 if self.take_rename(false, window, cx).is_some() {
3024 return true;
3025 }
3026
3027 if hide_hover(self, cx) {
3028 return true;
3029 }
3030
3031 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3032 return true;
3033 }
3034
3035 if self.hide_context_menu(window, cx).is_some() {
3036 return true;
3037 }
3038
3039 if self.mouse_context_menu.take().is_some() {
3040 return true;
3041 }
3042
3043 if is_user_requested && self.discard_inline_completion(true, cx) {
3044 return true;
3045 }
3046
3047 if self.snippet_stack.pop().is_some() {
3048 return true;
3049 }
3050
3051 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
3052 self.dismiss_diagnostics(cx);
3053 return true;
3054 }
3055
3056 false
3057 }
3058
3059 fn linked_editing_ranges_for(
3060 &self,
3061 selection: Range<text::Anchor>,
3062 cx: &App,
3063 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3064 if self.linked_edit_ranges.is_empty() {
3065 return None;
3066 }
3067 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3068 selection.end.buffer_id.and_then(|end_buffer_id| {
3069 if selection.start.buffer_id != Some(end_buffer_id) {
3070 return None;
3071 }
3072 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3073 let snapshot = buffer.read(cx).snapshot();
3074 self.linked_edit_ranges
3075 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3076 .map(|ranges| (ranges, snapshot, buffer))
3077 })?;
3078 use text::ToOffset as TO;
3079 // find offset from the start of current range to current cursor position
3080 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3081
3082 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3083 let start_difference = start_offset - start_byte_offset;
3084 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3085 let end_difference = end_offset - start_byte_offset;
3086 // Current range has associated linked ranges.
3087 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3088 for range in linked_ranges.iter() {
3089 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3090 let end_offset = start_offset + end_difference;
3091 let start_offset = start_offset + start_difference;
3092 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3093 continue;
3094 }
3095 if self.selections.disjoint_anchor_ranges().any(|s| {
3096 if s.start.buffer_id != selection.start.buffer_id
3097 || s.end.buffer_id != selection.end.buffer_id
3098 {
3099 return false;
3100 }
3101 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3102 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3103 }) {
3104 continue;
3105 }
3106 let start = buffer_snapshot.anchor_after(start_offset);
3107 let end = buffer_snapshot.anchor_after(end_offset);
3108 linked_edits
3109 .entry(buffer.clone())
3110 .or_default()
3111 .push(start..end);
3112 }
3113 Some(linked_edits)
3114 }
3115
3116 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3117 let text: Arc<str> = text.into();
3118
3119 if self.read_only(cx) {
3120 return;
3121 }
3122
3123 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3124
3125 let selections = self.selections.all_adjusted(cx);
3126 let mut bracket_inserted = false;
3127 let mut edits = Vec::new();
3128 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3129 let mut new_selections = Vec::with_capacity(selections.len());
3130 let mut new_autoclose_regions = Vec::new();
3131 let snapshot = self.buffer.read(cx).read(cx);
3132
3133 for (selection, autoclose_region) in
3134 self.selections_with_autoclose_regions(selections, &snapshot)
3135 {
3136 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3137 // Determine if the inserted text matches the opening or closing
3138 // bracket of any of this language's bracket pairs.
3139 let mut bracket_pair = None;
3140 let mut is_bracket_pair_start = false;
3141 let mut is_bracket_pair_end = false;
3142 if !text.is_empty() {
3143 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3144 // and they are removing the character that triggered IME popup.
3145 for (pair, enabled) in scope.brackets() {
3146 if !pair.close && !pair.surround {
3147 continue;
3148 }
3149
3150 if enabled && pair.start.ends_with(text.as_ref()) {
3151 let prefix_len = pair.start.len() - text.len();
3152 let preceding_text_matches_prefix = prefix_len == 0
3153 || (selection.start.column >= (prefix_len as u32)
3154 && snapshot.contains_str_at(
3155 Point::new(
3156 selection.start.row,
3157 selection.start.column - (prefix_len as u32),
3158 ),
3159 &pair.start[..prefix_len],
3160 ));
3161 if preceding_text_matches_prefix {
3162 bracket_pair = Some(pair.clone());
3163 is_bracket_pair_start = true;
3164 break;
3165 }
3166 }
3167 if pair.end.as_str() == text.as_ref() {
3168 bracket_pair = Some(pair.clone());
3169 is_bracket_pair_end = true;
3170 break;
3171 }
3172 }
3173 }
3174
3175 if let Some(bracket_pair) = bracket_pair {
3176 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3177 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3178 let auto_surround =
3179 self.use_auto_surround && snapshot_settings.use_auto_surround;
3180 if selection.is_empty() {
3181 if is_bracket_pair_start {
3182 // If the inserted text is a suffix of an opening bracket and the
3183 // selection is preceded by the rest of the opening bracket, then
3184 // insert the closing bracket.
3185 let following_text_allows_autoclose = snapshot
3186 .chars_at(selection.start)
3187 .next()
3188 .map_or(true, |c| scope.should_autoclose_before(c));
3189
3190 let preceding_text_allows_autoclose = selection.start.column == 0
3191 || snapshot.reversed_chars_at(selection.start).next().map_or(
3192 true,
3193 |c| {
3194 bracket_pair.start != bracket_pair.end
3195 || !snapshot
3196 .char_classifier_at(selection.start)
3197 .is_word(c)
3198 },
3199 );
3200
3201 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3202 && bracket_pair.start.len() == 1
3203 {
3204 let target = bracket_pair.start.chars().next().unwrap();
3205 let current_line_count = snapshot
3206 .reversed_chars_at(selection.start)
3207 .take_while(|&c| c != '\n')
3208 .filter(|&c| c == target)
3209 .count();
3210 current_line_count % 2 == 1
3211 } else {
3212 false
3213 };
3214
3215 if autoclose
3216 && bracket_pair.close
3217 && following_text_allows_autoclose
3218 && preceding_text_allows_autoclose
3219 && !is_closing_quote
3220 {
3221 let anchor = snapshot.anchor_before(selection.end);
3222 new_selections.push((selection.map(|_| anchor), text.len()));
3223 new_autoclose_regions.push((
3224 anchor,
3225 text.len(),
3226 selection.id,
3227 bracket_pair.clone(),
3228 ));
3229 edits.push((
3230 selection.range(),
3231 format!("{}{}", text, bracket_pair.end).into(),
3232 ));
3233 bracket_inserted = true;
3234 continue;
3235 }
3236 }
3237
3238 if let Some(region) = autoclose_region {
3239 // If the selection is followed by an auto-inserted closing bracket,
3240 // then don't insert that closing bracket again; just move the selection
3241 // past the closing bracket.
3242 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3243 && text.as_ref() == region.pair.end.as_str();
3244 if should_skip {
3245 let anchor = snapshot.anchor_after(selection.end);
3246 new_selections
3247 .push((selection.map(|_| anchor), region.pair.end.len()));
3248 continue;
3249 }
3250 }
3251
3252 let always_treat_brackets_as_autoclosed = snapshot
3253 .language_settings_at(selection.start, cx)
3254 .always_treat_brackets_as_autoclosed;
3255 if always_treat_brackets_as_autoclosed
3256 && is_bracket_pair_end
3257 && snapshot.contains_str_at(selection.end, text.as_ref())
3258 {
3259 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3260 // and the inserted text is a closing bracket and the selection is followed
3261 // by the closing bracket then move the selection past the closing bracket.
3262 let anchor = snapshot.anchor_after(selection.end);
3263 new_selections.push((selection.map(|_| anchor), text.len()));
3264 continue;
3265 }
3266 }
3267 // If an opening bracket is 1 character long and is typed while
3268 // text is selected, then surround that text with the bracket pair.
3269 else if auto_surround
3270 && bracket_pair.surround
3271 && is_bracket_pair_start
3272 && bracket_pair.start.chars().count() == 1
3273 {
3274 edits.push((selection.start..selection.start, text.clone()));
3275 edits.push((
3276 selection.end..selection.end,
3277 bracket_pair.end.as_str().into(),
3278 ));
3279 bracket_inserted = true;
3280 new_selections.push((
3281 Selection {
3282 id: selection.id,
3283 start: snapshot.anchor_after(selection.start),
3284 end: snapshot.anchor_before(selection.end),
3285 reversed: selection.reversed,
3286 goal: selection.goal,
3287 },
3288 0,
3289 ));
3290 continue;
3291 }
3292 }
3293 }
3294
3295 if self.auto_replace_emoji_shortcode
3296 && selection.is_empty()
3297 && text.as_ref().ends_with(':')
3298 {
3299 if let Some(possible_emoji_short_code) =
3300 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3301 {
3302 if !possible_emoji_short_code.is_empty() {
3303 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3304 let emoji_shortcode_start = Point::new(
3305 selection.start.row,
3306 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3307 );
3308
3309 // Remove shortcode from buffer
3310 edits.push((
3311 emoji_shortcode_start..selection.start,
3312 "".to_string().into(),
3313 ));
3314 new_selections.push((
3315 Selection {
3316 id: selection.id,
3317 start: snapshot.anchor_after(emoji_shortcode_start),
3318 end: snapshot.anchor_before(selection.start),
3319 reversed: selection.reversed,
3320 goal: selection.goal,
3321 },
3322 0,
3323 ));
3324
3325 // Insert emoji
3326 let selection_start_anchor = snapshot.anchor_after(selection.start);
3327 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3328 edits.push((selection.start..selection.end, emoji.to_string().into()));
3329
3330 continue;
3331 }
3332 }
3333 }
3334 }
3335
3336 // If not handling any auto-close operation, then just replace the selected
3337 // text with the given input and move the selection to the end of the
3338 // newly inserted text.
3339 let anchor = snapshot.anchor_after(selection.end);
3340 if !self.linked_edit_ranges.is_empty() {
3341 let start_anchor = snapshot.anchor_before(selection.start);
3342
3343 let is_word_char = text.chars().next().map_or(true, |char| {
3344 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3345 classifier.is_word(char)
3346 });
3347
3348 if is_word_char {
3349 if let Some(ranges) = self
3350 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3351 {
3352 for (buffer, edits) in ranges {
3353 linked_edits
3354 .entry(buffer.clone())
3355 .or_default()
3356 .extend(edits.into_iter().map(|range| (range, text.clone())));
3357 }
3358 }
3359 }
3360 }
3361
3362 new_selections.push((selection.map(|_| anchor), 0));
3363 edits.push((selection.start..selection.end, text.clone()));
3364 }
3365
3366 drop(snapshot);
3367
3368 self.transact(window, cx, |this, window, cx| {
3369 let initial_buffer_versions =
3370 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3371
3372 this.buffer.update(cx, |buffer, cx| {
3373 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3374 });
3375 for (buffer, edits) in linked_edits {
3376 buffer.update(cx, |buffer, cx| {
3377 let snapshot = buffer.snapshot();
3378 let edits = edits
3379 .into_iter()
3380 .map(|(range, text)| {
3381 use text::ToPoint as TP;
3382 let end_point = TP::to_point(&range.end, &snapshot);
3383 let start_point = TP::to_point(&range.start, &snapshot);
3384 (start_point..end_point, text)
3385 })
3386 .sorted_by_key(|(range, _)| range.start);
3387 buffer.edit(edits, None, cx);
3388 })
3389 }
3390 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3391 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3392 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3393 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3394 .zip(new_selection_deltas)
3395 .map(|(selection, delta)| Selection {
3396 id: selection.id,
3397 start: selection.start + delta,
3398 end: selection.end + delta,
3399 reversed: selection.reversed,
3400 goal: SelectionGoal::None,
3401 })
3402 .collect::<Vec<_>>();
3403
3404 let mut i = 0;
3405 for (position, delta, selection_id, pair) in new_autoclose_regions {
3406 let position = position.to_offset(&map.buffer_snapshot) + delta;
3407 let start = map.buffer_snapshot.anchor_before(position);
3408 let end = map.buffer_snapshot.anchor_after(position);
3409 while let Some(existing_state) = this.autoclose_regions.get(i) {
3410 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3411 Ordering::Less => i += 1,
3412 Ordering::Greater => break,
3413 Ordering::Equal => {
3414 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3415 Ordering::Less => i += 1,
3416 Ordering::Equal => break,
3417 Ordering::Greater => break,
3418 }
3419 }
3420 }
3421 }
3422 this.autoclose_regions.insert(
3423 i,
3424 AutocloseRegion {
3425 selection_id,
3426 range: start..end,
3427 pair,
3428 },
3429 );
3430 }
3431
3432 let had_active_inline_completion = this.has_active_inline_completion();
3433 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3434 s.select(new_selections)
3435 });
3436
3437 if !bracket_inserted {
3438 if let Some(on_type_format_task) =
3439 this.trigger_on_type_formatting(text.to_string(), window, cx)
3440 {
3441 on_type_format_task.detach_and_log_err(cx);
3442 }
3443 }
3444
3445 let editor_settings = EditorSettings::get_global(cx);
3446 if bracket_inserted
3447 && (editor_settings.auto_signature_help
3448 || editor_settings.show_signature_help_after_edits)
3449 {
3450 this.show_signature_help(&ShowSignatureHelp, window, cx);
3451 }
3452
3453 let trigger_in_words =
3454 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3455 if this.hard_wrap.is_some() {
3456 let latest: Range<Point> = this.selections.newest(cx).range();
3457 if latest.is_empty()
3458 && this
3459 .buffer()
3460 .read(cx)
3461 .snapshot(cx)
3462 .line_len(MultiBufferRow(latest.start.row))
3463 == latest.start.column
3464 {
3465 this.rewrap_impl(
3466 RewrapOptions {
3467 override_language_settings: true,
3468 preserve_existing_whitespace: true,
3469 },
3470 cx,
3471 )
3472 }
3473 }
3474 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3475 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3476 this.refresh_inline_completion(true, false, window, cx);
3477 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3478 });
3479 }
3480
3481 fn find_possible_emoji_shortcode_at_position(
3482 snapshot: &MultiBufferSnapshot,
3483 position: Point,
3484 ) -> Option<String> {
3485 let mut chars = Vec::new();
3486 let mut found_colon = false;
3487 for char in snapshot.reversed_chars_at(position).take(100) {
3488 // Found a possible emoji shortcode in the middle of the buffer
3489 if found_colon {
3490 if char.is_whitespace() {
3491 chars.reverse();
3492 return Some(chars.iter().collect());
3493 }
3494 // If the previous character is not a whitespace, we are in the middle of a word
3495 // and we only want to complete the shortcode if the word is made up of other emojis
3496 let mut containing_word = String::new();
3497 for ch in snapshot
3498 .reversed_chars_at(position)
3499 .skip(chars.len() + 1)
3500 .take(100)
3501 {
3502 if ch.is_whitespace() {
3503 break;
3504 }
3505 containing_word.push(ch);
3506 }
3507 let containing_word = containing_word.chars().rev().collect::<String>();
3508 if util::word_consists_of_emojis(containing_word.as_str()) {
3509 chars.reverse();
3510 return Some(chars.iter().collect());
3511 }
3512 }
3513
3514 if char.is_whitespace() || !char.is_ascii() {
3515 return None;
3516 }
3517 if char == ':' {
3518 found_colon = true;
3519 } else {
3520 chars.push(char);
3521 }
3522 }
3523 // Found a possible emoji shortcode at the beginning of the buffer
3524 chars.reverse();
3525 Some(chars.iter().collect())
3526 }
3527
3528 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3529 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3530 self.transact(window, cx, |this, window, cx| {
3531 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3532 let selections = this.selections.all::<usize>(cx);
3533 let multi_buffer = this.buffer.read(cx);
3534 let buffer = multi_buffer.snapshot(cx);
3535 selections
3536 .iter()
3537 .map(|selection| {
3538 let start_point = selection.start.to_point(&buffer);
3539 let mut indent =
3540 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3541 indent.len = cmp::min(indent.len, start_point.column);
3542 let start = selection.start;
3543 let end = selection.end;
3544 let selection_is_empty = start == end;
3545 let language_scope = buffer.language_scope_at(start);
3546 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3547 &language_scope
3548 {
3549 let insert_extra_newline =
3550 insert_extra_newline_brackets(&buffer, start..end, language)
3551 || insert_extra_newline_tree_sitter(&buffer, start..end);
3552
3553 // Comment extension on newline is allowed only for cursor selections
3554 let comment_delimiter = maybe!({
3555 if !selection_is_empty {
3556 return None;
3557 }
3558
3559 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3560 return None;
3561 }
3562
3563 let delimiters = language.line_comment_prefixes();
3564 let max_len_of_delimiter =
3565 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3566 let (snapshot, range) =
3567 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3568
3569 let mut index_of_first_non_whitespace = 0;
3570 let comment_candidate = snapshot
3571 .chars_for_range(range)
3572 .skip_while(|c| {
3573 let should_skip = c.is_whitespace();
3574 if should_skip {
3575 index_of_first_non_whitespace += 1;
3576 }
3577 should_skip
3578 })
3579 .take(max_len_of_delimiter)
3580 .collect::<String>();
3581 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3582 comment_candidate.starts_with(comment_prefix.as_ref())
3583 })?;
3584 let cursor_is_placed_after_comment_marker =
3585 index_of_first_non_whitespace + comment_prefix.len()
3586 <= start_point.column as usize;
3587 if cursor_is_placed_after_comment_marker {
3588 Some(comment_prefix.clone())
3589 } else {
3590 None
3591 }
3592 });
3593 (comment_delimiter, insert_extra_newline)
3594 } else {
3595 (None, false)
3596 };
3597
3598 let capacity_for_delimiter = comment_delimiter
3599 .as_deref()
3600 .map(str::len)
3601 .unwrap_or_default();
3602 let mut new_text =
3603 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3604 new_text.push('\n');
3605 new_text.extend(indent.chars());
3606 if let Some(delimiter) = &comment_delimiter {
3607 new_text.push_str(delimiter);
3608 }
3609 if insert_extra_newline {
3610 new_text = new_text.repeat(2);
3611 }
3612
3613 let anchor = buffer.anchor_after(end);
3614 let new_selection = selection.map(|_| anchor);
3615 (
3616 (start..end, new_text),
3617 (insert_extra_newline, new_selection),
3618 )
3619 })
3620 .unzip()
3621 };
3622
3623 this.edit_with_autoindent(edits, cx);
3624 let buffer = this.buffer.read(cx).snapshot(cx);
3625 let new_selections = selection_fixup_info
3626 .into_iter()
3627 .map(|(extra_newline_inserted, new_selection)| {
3628 let mut cursor = new_selection.end.to_point(&buffer);
3629 if extra_newline_inserted {
3630 cursor.row -= 1;
3631 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3632 }
3633 new_selection.map(|_| cursor)
3634 })
3635 .collect();
3636
3637 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3638 s.select(new_selections)
3639 });
3640 this.refresh_inline_completion(true, false, window, cx);
3641 });
3642 }
3643
3644 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3645 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3646
3647 let buffer = self.buffer.read(cx);
3648 let snapshot = buffer.snapshot(cx);
3649
3650 let mut edits = Vec::new();
3651 let mut rows = Vec::new();
3652
3653 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3654 let cursor = selection.head();
3655 let row = cursor.row;
3656
3657 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3658
3659 let newline = "\n".to_string();
3660 edits.push((start_of_line..start_of_line, newline));
3661
3662 rows.push(row + rows_inserted as u32);
3663 }
3664
3665 self.transact(window, cx, |editor, window, cx| {
3666 editor.edit(edits, cx);
3667
3668 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3669 let mut index = 0;
3670 s.move_cursors_with(|map, _, _| {
3671 let row = rows[index];
3672 index += 1;
3673
3674 let point = Point::new(row, 0);
3675 let boundary = map.next_line_boundary(point).1;
3676 let clipped = map.clip_point(boundary, Bias::Left);
3677
3678 (clipped, SelectionGoal::None)
3679 });
3680 });
3681
3682 let mut indent_edits = Vec::new();
3683 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3684 for row in rows {
3685 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3686 for (row, indent) in indents {
3687 if indent.len == 0 {
3688 continue;
3689 }
3690
3691 let text = match indent.kind {
3692 IndentKind::Space => " ".repeat(indent.len as usize),
3693 IndentKind::Tab => "\t".repeat(indent.len as usize),
3694 };
3695 let point = Point::new(row.0, 0);
3696 indent_edits.push((point..point, text));
3697 }
3698 }
3699 editor.edit(indent_edits, cx);
3700 });
3701 }
3702
3703 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3704 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3705
3706 let buffer = self.buffer.read(cx);
3707 let snapshot = buffer.snapshot(cx);
3708
3709 let mut edits = Vec::new();
3710 let mut rows = Vec::new();
3711 let mut rows_inserted = 0;
3712
3713 for selection in self.selections.all_adjusted(cx) {
3714 let cursor = selection.head();
3715 let row = cursor.row;
3716
3717 let point = Point::new(row + 1, 0);
3718 let start_of_line = snapshot.clip_point(point, Bias::Left);
3719
3720 let newline = "\n".to_string();
3721 edits.push((start_of_line..start_of_line, newline));
3722
3723 rows_inserted += 1;
3724 rows.push(row + rows_inserted);
3725 }
3726
3727 self.transact(window, cx, |editor, window, cx| {
3728 editor.edit(edits, cx);
3729
3730 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3731 let mut index = 0;
3732 s.move_cursors_with(|map, _, _| {
3733 let row = rows[index];
3734 index += 1;
3735
3736 let point = Point::new(row, 0);
3737 let boundary = map.next_line_boundary(point).1;
3738 let clipped = map.clip_point(boundary, Bias::Left);
3739
3740 (clipped, SelectionGoal::None)
3741 });
3742 });
3743
3744 let mut indent_edits = Vec::new();
3745 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3746 for row in rows {
3747 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3748 for (row, indent) in indents {
3749 if indent.len == 0 {
3750 continue;
3751 }
3752
3753 let text = match indent.kind {
3754 IndentKind::Space => " ".repeat(indent.len as usize),
3755 IndentKind::Tab => "\t".repeat(indent.len as usize),
3756 };
3757 let point = Point::new(row.0, 0);
3758 indent_edits.push((point..point, text));
3759 }
3760 }
3761 editor.edit(indent_edits, cx);
3762 });
3763 }
3764
3765 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3766 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3767 original_indent_columns: Vec::new(),
3768 });
3769 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3770 }
3771
3772 fn insert_with_autoindent_mode(
3773 &mut self,
3774 text: &str,
3775 autoindent_mode: Option<AutoindentMode>,
3776 window: &mut Window,
3777 cx: &mut Context<Self>,
3778 ) {
3779 if self.read_only(cx) {
3780 return;
3781 }
3782
3783 let text: Arc<str> = text.into();
3784 self.transact(window, cx, |this, window, cx| {
3785 let old_selections = this.selections.all_adjusted(cx);
3786 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3787 let anchors = {
3788 let snapshot = buffer.read(cx);
3789 old_selections
3790 .iter()
3791 .map(|s| {
3792 let anchor = snapshot.anchor_after(s.head());
3793 s.map(|_| anchor)
3794 })
3795 .collect::<Vec<_>>()
3796 };
3797 buffer.edit(
3798 old_selections
3799 .iter()
3800 .map(|s| (s.start..s.end, text.clone())),
3801 autoindent_mode,
3802 cx,
3803 );
3804 anchors
3805 });
3806
3807 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3808 s.select_anchors(selection_anchors);
3809 });
3810
3811 cx.notify();
3812 });
3813 }
3814
3815 fn trigger_completion_on_input(
3816 &mut self,
3817 text: &str,
3818 trigger_in_words: bool,
3819 window: &mut Window,
3820 cx: &mut Context<Self>,
3821 ) {
3822 let ignore_completion_provider = self
3823 .context_menu
3824 .borrow()
3825 .as_ref()
3826 .map(|menu| match menu {
3827 CodeContextMenu::Completions(completions_menu) => {
3828 completions_menu.ignore_completion_provider
3829 }
3830 CodeContextMenu::CodeActions(_) => false,
3831 })
3832 .unwrap_or(false);
3833
3834 if ignore_completion_provider {
3835 self.show_word_completions(&ShowWordCompletions, window, cx);
3836 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3837 self.show_completions(
3838 &ShowCompletions {
3839 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3840 },
3841 window,
3842 cx,
3843 );
3844 } else {
3845 self.hide_context_menu(window, cx);
3846 }
3847 }
3848
3849 fn is_completion_trigger(
3850 &self,
3851 text: &str,
3852 trigger_in_words: bool,
3853 cx: &mut Context<Self>,
3854 ) -> bool {
3855 let position = self.selections.newest_anchor().head();
3856 let multibuffer = self.buffer.read(cx);
3857 let Some(buffer) = position
3858 .buffer_id
3859 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3860 else {
3861 return false;
3862 };
3863
3864 if let Some(completion_provider) = &self.completion_provider {
3865 completion_provider.is_completion_trigger(
3866 &buffer,
3867 position.text_anchor,
3868 text,
3869 trigger_in_words,
3870 cx,
3871 )
3872 } else {
3873 false
3874 }
3875 }
3876
3877 /// If any empty selections is touching the start of its innermost containing autoclose
3878 /// region, expand it to select the brackets.
3879 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3880 let selections = self.selections.all::<usize>(cx);
3881 let buffer = self.buffer.read(cx).read(cx);
3882 let new_selections = self
3883 .selections_with_autoclose_regions(selections, &buffer)
3884 .map(|(mut selection, region)| {
3885 if !selection.is_empty() {
3886 return selection;
3887 }
3888
3889 if let Some(region) = region {
3890 let mut range = region.range.to_offset(&buffer);
3891 if selection.start == range.start && range.start >= region.pair.start.len() {
3892 range.start -= region.pair.start.len();
3893 if buffer.contains_str_at(range.start, ®ion.pair.start)
3894 && buffer.contains_str_at(range.end, ®ion.pair.end)
3895 {
3896 range.end += region.pair.end.len();
3897 selection.start = range.start;
3898 selection.end = range.end;
3899
3900 return selection;
3901 }
3902 }
3903 }
3904
3905 let always_treat_brackets_as_autoclosed = buffer
3906 .language_settings_at(selection.start, cx)
3907 .always_treat_brackets_as_autoclosed;
3908
3909 if !always_treat_brackets_as_autoclosed {
3910 return selection;
3911 }
3912
3913 if let Some(scope) = buffer.language_scope_at(selection.start) {
3914 for (pair, enabled) in scope.brackets() {
3915 if !enabled || !pair.close {
3916 continue;
3917 }
3918
3919 if buffer.contains_str_at(selection.start, &pair.end) {
3920 let pair_start_len = pair.start.len();
3921 if buffer.contains_str_at(
3922 selection.start.saturating_sub(pair_start_len),
3923 &pair.start,
3924 ) {
3925 selection.start -= pair_start_len;
3926 selection.end += pair.end.len();
3927
3928 return selection;
3929 }
3930 }
3931 }
3932 }
3933
3934 selection
3935 })
3936 .collect();
3937
3938 drop(buffer);
3939 self.change_selections(None, window, cx, |selections| {
3940 selections.select(new_selections)
3941 });
3942 }
3943
3944 /// Iterate the given selections, and for each one, find the smallest surrounding
3945 /// autoclose region. This uses the ordering of the selections and the autoclose
3946 /// regions to avoid repeated comparisons.
3947 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3948 &'a self,
3949 selections: impl IntoIterator<Item = Selection<D>>,
3950 buffer: &'a MultiBufferSnapshot,
3951 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3952 let mut i = 0;
3953 let mut regions = self.autoclose_regions.as_slice();
3954 selections.into_iter().map(move |selection| {
3955 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3956
3957 let mut enclosing = None;
3958 while let Some(pair_state) = regions.get(i) {
3959 if pair_state.range.end.to_offset(buffer) < range.start {
3960 regions = ®ions[i + 1..];
3961 i = 0;
3962 } else if pair_state.range.start.to_offset(buffer) > range.end {
3963 break;
3964 } else {
3965 if pair_state.selection_id == selection.id {
3966 enclosing = Some(pair_state);
3967 }
3968 i += 1;
3969 }
3970 }
3971
3972 (selection, enclosing)
3973 })
3974 }
3975
3976 /// Remove any autoclose regions that no longer contain their selection.
3977 fn invalidate_autoclose_regions(
3978 &mut self,
3979 mut selections: &[Selection<Anchor>],
3980 buffer: &MultiBufferSnapshot,
3981 ) {
3982 self.autoclose_regions.retain(|state| {
3983 let mut i = 0;
3984 while let Some(selection) = selections.get(i) {
3985 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3986 selections = &selections[1..];
3987 continue;
3988 }
3989 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3990 break;
3991 }
3992 if selection.id == state.selection_id {
3993 return true;
3994 } else {
3995 i += 1;
3996 }
3997 }
3998 false
3999 });
4000 }
4001
4002 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4003 let offset = position.to_offset(buffer);
4004 let (word_range, kind) = buffer.surrounding_word(offset, true);
4005 if offset > word_range.start && kind == Some(CharKind::Word) {
4006 Some(
4007 buffer
4008 .text_for_range(word_range.start..offset)
4009 .collect::<String>(),
4010 )
4011 } else {
4012 None
4013 }
4014 }
4015
4016 pub fn toggle_inlay_hints(
4017 &mut self,
4018 _: &ToggleInlayHints,
4019 _: &mut Window,
4020 cx: &mut Context<Self>,
4021 ) {
4022 self.refresh_inlay_hints(
4023 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4024 cx,
4025 );
4026 }
4027
4028 pub fn inlay_hints_enabled(&self) -> bool {
4029 self.inlay_hint_cache.enabled
4030 }
4031
4032 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4033 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
4034 return;
4035 }
4036
4037 let reason_description = reason.description();
4038 let ignore_debounce = matches!(
4039 reason,
4040 InlayHintRefreshReason::SettingsChange(_)
4041 | InlayHintRefreshReason::Toggle(_)
4042 | InlayHintRefreshReason::ExcerptsRemoved(_)
4043 | InlayHintRefreshReason::ModifiersChanged(_)
4044 );
4045 let (invalidate_cache, required_languages) = match reason {
4046 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4047 match self.inlay_hint_cache.modifiers_override(enabled) {
4048 Some(enabled) => {
4049 if enabled {
4050 (InvalidationStrategy::RefreshRequested, None)
4051 } else {
4052 self.splice_inlays(
4053 &self
4054 .visible_inlay_hints(cx)
4055 .iter()
4056 .map(|inlay| inlay.id)
4057 .collect::<Vec<InlayId>>(),
4058 Vec::new(),
4059 cx,
4060 );
4061 return;
4062 }
4063 }
4064 None => return,
4065 }
4066 }
4067 InlayHintRefreshReason::Toggle(enabled) => {
4068 if self.inlay_hint_cache.toggle(enabled) {
4069 if enabled {
4070 (InvalidationStrategy::RefreshRequested, None)
4071 } else {
4072 self.splice_inlays(
4073 &self
4074 .visible_inlay_hints(cx)
4075 .iter()
4076 .map(|inlay| inlay.id)
4077 .collect::<Vec<InlayId>>(),
4078 Vec::new(),
4079 cx,
4080 );
4081 return;
4082 }
4083 } else {
4084 return;
4085 }
4086 }
4087 InlayHintRefreshReason::SettingsChange(new_settings) => {
4088 match self.inlay_hint_cache.update_settings(
4089 &self.buffer,
4090 new_settings,
4091 self.visible_inlay_hints(cx),
4092 cx,
4093 ) {
4094 ControlFlow::Break(Some(InlaySplice {
4095 to_remove,
4096 to_insert,
4097 })) => {
4098 self.splice_inlays(&to_remove, to_insert, cx);
4099 return;
4100 }
4101 ControlFlow::Break(None) => return,
4102 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4103 }
4104 }
4105 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4106 if let Some(InlaySplice {
4107 to_remove,
4108 to_insert,
4109 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
4110 {
4111 self.splice_inlays(&to_remove, to_insert, cx);
4112 }
4113 return;
4114 }
4115 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4116 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4117 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4118 }
4119 InlayHintRefreshReason::RefreshRequested => {
4120 (InvalidationStrategy::RefreshRequested, None)
4121 }
4122 };
4123
4124 if let Some(InlaySplice {
4125 to_remove,
4126 to_insert,
4127 }) = self.inlay_hint_cache.spawn_hint_refresh(
4128 reason_description,
4129 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4130 invalidate_cache,
4131 ignore_debounce,
4132 cx,
4133 ) {
4134 self.splice_inlays(&to_remove, to_insert, cx);
4135 }
4136 }
4137
4138 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4139 self.display_map
4140 .read(cx)
4141 .current_inlays()
4142 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4143 .cloned()
4144 .collect()
4145 }
4146
4147 pub fn excerpts_for_inlay_hints_query(
4148 &self,
4149 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4150 cx: &mut Context<Editor>,
4151 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4152 let Some(project) = self.project.as_ref() else {
4153 return HashMap::default();
4154 };
4155 let project = project.read(cx);
4156 let multi_buffer = self.buffer().read(cx);
4157 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4158 let multi_buffer_visible_start = self
4159 .scroll_manager
4160 .anchor()
4161 .anchor
4162 .to_point(&multi_buffer_snapshot);
4163 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4164 multi_buffer_visible_start
4165 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4166 Bias::Left,
4167 );
4168 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4169 multi_buffer_snapshot
4170 .range_to_buffer_ranges(multi_buffer_visible_range)
4171 .into_iter()
4172 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4173 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4174 let buffer_file = project::File::from_dyn(buffer.file())?;
4175 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4176 let worktree_entry = buffer_worktree
4177 .read(cx)
4178 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4179 if worktree_entry.is_ignored {
4180 return None;
4181 }
4182
4183 let language = buffer.language()?;
4184 if let Some(restrict_to_languages) = restrict_to_languages {
4185 if !restrict_to_languages.contains(language) {
4186 return None;
4187 }
4188 }
4189 Some((
4190 excerpt_id,
4191 (
4192 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4193 buffer.version().clone(),
4194 excerpt_visible_range,
4195 ),
4196 ))
4197 })
4198 .collect()
4199 }
4200
4201 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4202 TextLayoutDetails {
4203 text_system: window.text_system().clone(),
4204 editor_style: self.style.clone().unwrap(),
4205 rem_size: window.rem_size(),
4206 scroll_anchor: self.scroll_manager.anchor(),
4207 visible_rows: self.visible_line_count(),
4208 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4209 }
4210 }
4211
4212 pub fn splice_inlays(
4213 &self,
4214 to_remove: &[InlayId],
4215 to_insert: Vec<Inlay>,
4216 cx: &mut Context<Self>,
4217 ) {
4218 self.display_map.update(cx, |display_map, cx| {
4219 display_map.splice_inlays(to_remove, to_insert, cx)
4220 });
4221 cx.notify();
4222 }
4223
4224 fn trigger_on_type_formatting(
4225 &self,
4226 input: String,
4227 window: &mut Window,
4228 cx: &mut Context<Self>,
4229 ) -> Option<Task<Result<()>>> {
4230 if input.len() != 1 {
4231 return None;
4232 }
4233
4234 let project = self.project.as_ref()?;
4235 let position = self.selections.newest_anchor().head();
4236 let (buffer, buffer_position) = self
4237 .buffer
4238 .read(cx)
4239 .text_anchor_for_position(position, cx)?;
4240
4241 let settings = language_settings::language_settings(
4242 buffer
4243 .read(cx)
4244 .language_at(buffer_position)
4245 .map(|l| l.name()),
4246 buffer.read(cx).file(),
4247 cx,
4248 );
4249 if !settings.use_on_type_format {
4250 return None;
4251 }
4252
4253 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4254 // hence we do LSP request & edit on host side only — add formats to host's history.
4255 let push_to_lsp_host_history = true;
4256 // If this is not the host, append its history with new edits.
4257 let push_to_client_history = project.read(cx).is_via_collab();
4258
4259 let on_type_formatting = project.update(cx, |project, cx| {
4260 project.on_type_format(
4261 buffer.clone(),
4262 buffer_position,
4263 input,
4264 push_to_lsp_host_history,
4265 cx,
4266 )
4267 });
4268 Some(cx.spawn_in(window, async move |editor, cx| {
4269 if let Some(transaction) = on_type_formatting.await? {
4270 if push_to_client_history {
4271 buffer
4272 .update(cx, |buffer, _| {
4273 buffer.push_transaction(transaction, Instant::now());
4274 })
4275 .ok();
4276 }
4277 editor.update(cx, |editor, cx| {
4278 editor.refresh_document_highlights(cx);
4279 })?;
4280 }
4281 Ok(())
4282 }))
4283 }
4284
4285 pub fn show_word_completions(
4286 &mut self,
4287 _: &ShowWordCompletions,
4288 window: &mut Window,
4289 cx: &mut Context<Self>,
4290 ) {
4291 self.open_completions_menu(true, None, window, cx);
4292 }
4293
4294 pub fn show_completions(
4295 &mut self,
4296 options: &ShowCompletions,
4297 window: &mut Window,
4298 cx: &mut Context<Self>,
4299 ) {
4300 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4301 }
4302
4303 fn open_completions_menu(
4304 &mut self,
4305 ignore_completion_provider: bool,
4306 trigger: Option<&str>,
4307 window: &mut Window,
4308 cx: &mut Context<Self>,
4309 ) {
4310 if self.pending_rename.is_some() {
4311 return;
4312 }
4313 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4314 return;
4315 }
4316
4317 let position = self.selections.newest_anchor().head();
4318 if position.diff_base_anchor.is_some() {
4319 return;
4320 }
4321 let (buffer, buffer_position) =
4322 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4323 output
4324 } else {
4325 return;
4326 };
4327 let buffer_snapshot = buffer.read(cx).snapshot();
4328 let show_completion_documentation = buffer_snapshot
4329 .settings_at(buffer_position, cx)
4330 .show_completion_documentation;
4331
4332 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4333
4334 let trigger_kind = match trigger {
4335 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4336 CompletionTriggerKind::TRIGGER_CHARACTER
4337 }
4338 _ => CompletionTriggerKind::INVOKED,
4339 };
4340 let completion_context = CompletionContext {
4341 trigger_character: trigger.and_then(|trigger| {
4342 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4343 Some(String::from(trigger))
4344 } else {
4345 None
4346 }
4347 }),
4348 trigger_kind,
4349 };
4350
4351 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4352 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4353 let word_to_exclude = buffer_snapshot
4354 .text_for_range(old_range.clone())
4355 .collect::<String>();
4356 (
4357 buffer_snapshot.anchor_before(old_range.start)
4358 ..buffer_snapshot.anchor_after(old_range.end),
4359 Some(word_to_exclude),
4360 )
4361 } else {
4362 (buffer_position..buffer_position, None)
4363 };
4364
4365 let completion_settings = language_settings(
4366 buffer_snapshot
4367 .language_at(buffer_position)
4368 .map(|language| language.name()),
4369 buffer_snapshot.file(),
4370 cx,
4371 )
4372 .completions;
4373
4374 // The document can be large, so stay in reasonable bounds when searching for words,
4375 // otherwise completion pop-up might be slow to appear.
4376 const WORD_LOOKUP_ROWS: u32 = 5_000;
4377 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4378 let min_word_search = buffer_snapshot.clip_point(
4379 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4380 Bias::Left,
4381 );
4382 let max_word_search = buffer_snapshot.clip_point(
4383 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4384 Bias::Right,
4385 );
4386 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4387 ..buffer_snapshot.point_to_offset(max_word_search);
4388
4389 let provider = self
4390 .completion_provider
4391 .as_ref()
4392 .filter(|_| !ignore_completion_provider);
4393 let skip_digits = query
4394 .as_ref()
4395 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4396
4397 let (mut words, provided_completions) = match provider {
4398 Some(provider) => {
4399 let completions = provider.completions(
4400 position.excerpt_id,
4401 &buffer,
4402 buffer_position,
4403 completion_context,
4404 window,
4405 cx,
4406 );
4407
4408 let words = match completion_settings.words {
4409 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4410 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4411 .background_spawn(async move {
4412 buffer_snapshot.words_in_range(WordsQuery {
4413 fuzzy_contents: None,
4414 range: word_search_range,
4415 skip_digits,
4416 })
4417 }),
4418 };
4419
4420 (words, completions)
4421 }
4422 None => (
4423 cx.background_spawn(async move {
4424 buffer_snapshot.words_in_range(WordsQuery {
4425 fuzzy_contents: None,
4426 range: word_search_range,
4427 skip_digits,
4428 })
4429 }),
4430 Task::ready(Ok(None)),
4431 ),
4432 };
4433
4434 let sort_completions = provider
4435 .as_ref()
4436 .map_or(false, |provider| provider.sort_completions());
4437
4438 let filter_completions = provider
4439 .as_ref()
4440 .map_or(true, |provider| provider.filter_completions());
4441
4442 let id = post_inc(&mut self.next_completion_id);
4443 let task = cx.spawn_in(window, async move |editor, cx| {
4444 async move {
4445 editor.update(cx, |this, _| {
4446 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4447 })?;
4448
4449 let mut completions = Vec::new();
4450 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4451 completions.extend(provided_completions);
4452 if completion_settings.words == WordsCompletionMode::Fallback {
4453 words = Task::ready(BTreeMap::default());
4454 }
4455 }
4456
4457 let mut words = words.await;
4458 if let Some(word_to_exclude) = &word_to_exclude {
4459 words.remove(word_to_exclude);
4460 }
4461 for lsp_completion in &completions {
4462 words.remove(&lsp_completion.new_text);
4463 }
4464 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4465 old_range: old_range.clone(),
4466 new_text: word.clone(),
4467 label: CodeLabel::plain(word, None),
4468 icon_path: None,
4469 documentation: None,
4470 source: CompletionSource::BufferWord {
4471 word_range,
4472 resolved: false,
4473 },
4474 insert_text_mode: Some(InsertTextMode::AS_IS),
4475 confirm: None,
4476 }));
4477
4478 let menu = if completions.is_empty() {
4479 None
4480 } else {
4481 let mut menu = CompletionsMenu::new(
4482 id,
4483 sort_completions,
4484 show_completion_documentation,
4485 ignore_completion_provider,
4486 position,
4487 buffer.clone(),
4488 completions.into(),
4489 );
4490
4491 menu.filter(
4492 if filter_completions {
4493 query.as_deref()
4494 } else {
4495 None
4496 },
4497 cx.background_executor().clone(),
4498 )
4499 .await;
4500
4501 menu.visible().then_some(menu)
4502 };
4503
4504 editor.update_in(cx, |editor, window, cx| {
4505 match editor.context_menu.borrow().as_ref() {
4506 None => {}
4507 Some(CodeContextMenu::Completions(prev_menu)) => {
4508 if prev_menu.id > id {
4509 return;
4510 }
4511 }
4512 _ => return,
4513 }
4514
4515 if editor.focus_handle.is_focused(window) && menu.is_some() {
4516 let mut menu = menu.unwrap();
4517 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4518
4519 *editor.context_menu.borrow_mut() =
4520 Some(CodeContextMenu::Completions(menu));
4521
4522 if editor.show_edit_predictions_in_menu() {
4523 editor.update_visible_inline_completion(window, cx);
4524 } else {
4525 editor.discard_inline_completion(false, cx);
4526 }
4527
4528 cx.notify();
4529 } else if editor.completion_tasks.len() <= 1 {
4530 // If there are no more completion tasks and the last menu was
4531 // empty, we should hide it.
4532 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4533 // If it was already hidden and we don't show inline
4534 // completions in the menu, we should also show the
4535 // inline-completion when available.
4536 if was_hidden && editor.show_edit_predictions_in_menu() {
4537 editor.update_visible_inline_completion(window, cx);
4538 }
4539 }
4540 })?;
4541
4542 anyhow::Ok(())
4543 }
4544 .log_err()
4545 .await
4546 });
4547
4548 self.completion_tasks.push((id, task));
4549 }
4550
4551 #[cfg(feature = "test-support")]
4552 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4553 let menu = self.context_menu.borrow();
4554 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4555 let completions = menu.completions.borrow();
4556 Some(completions.to_vec())
4557 } else {
4558 None
4559 }
4560 }
4561
4562 pub fn confirm_completion(
4563 &mut self,
4564 action: &ConfirmCompletion,
4565 window: &mut Window,
4566 cx: &mut Context<Self>,
4567 ) -> Option<Task<Result<()>>> {
4568 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4569 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4570 }
4571
4572 pub fn compose_completion(
4573 &mut self,
4574 action: &ComposeCompletion,
4575 window: &mut Window,
4576 cx: &mut Context<Self>,
4577 ) -> Option<Task<Result<()>>> {
4578 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4579 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4580 }
4581
4582 fn do_completion(
4583 &mut self,
4584 item_ix: Option<usize>,
4585 intent: CompletionIntent,
4586 window: &mut Window,
4587 cx: &mut Context<Editor>,
4588 ) -> Option<Task<Result<()>>> {
4589 use language::ToOffset as _;
4590
4591 let completions_menu =
4592 if let CodeContextMenu::Completions(menu) = self.hide_context_menu(window, cx)? {
4593 menu
4594 } else {
4595 return None;
4596 };
4597
4598 let candidate_id = {
4599 let entries = completions_menu.entries.borrow();
4600 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4601 if self.show_edit_predictions_in_menu() {
4602 self.discard_inline_completion(true, cx);
4603 }
4604 mat.candidate_id
4605 };
4606
4607 let buffer_handle = completions_menu.buffer;
4608 let completion = completions_menu
4609 .completions
4610 .borrow()
4611 .get(candidate_id)?
4612 .clone();
4613 cx.stop_propagation();
4614
4615 let snippet;
4616 let new_text;
4617 if completion.is_snippet() {
4618 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4619 new_text = snippet.as_ref().unwrap().text.clone();
4620 } else {
4621 snippet = None;
4622 new_text = completion.new_text.clone();
4623 };
4624 let selections = self.selections.all::<usize>(cx);
4625 let buffer = buffer_handle.read(cx);
4626 let old_range = completion.old_range.to_offset(buffer);
4627 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
4628
4629 let newest_selection = self.selections.newest_anchor();
4630 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4631 return None;
4632 }
4633
4634 let lookbehind = newest_selection
4635 .start
4636 .text_anchor
4637 .to_offset(buffer)
4638 .saturating_sub(old_range.start);
4639 let lookahead = old_range
4640 .end
4641 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4642 let mut common_prefix_len = 0;
4643 for (a, b) in old_text.chars().zip(new_text.chars()) {
4644 if a == b {
4645 common_prefix_len += a.len_utf8();
4646 } else {
4647 break;
4648 }
4649 }
4650
4651 let snapshot = self.buffer.read(cx).snapshot(cx);
4652 let mut range_to_replace: Option<Range<usize>> = None;
4653 let mut ranges = Vec::new();
4654 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4655 for selection in &selections {
4656 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4657 let start = selection.start.saturating_sub(lookbehind);
4658 let end = selection.end + lookahead;
4659 if selection.id == newest_selection.id {
4660 range_to_replace = Some(start + common_prefix_len..end);
4661 }
4662 ranges.push(start + common_prefix_len..end);
4663 } else {
4664 common_prefix_len = 0;
4665 ranges.clear();
4666 ranges.extend(selections.iter().map(|s| {
4667 if s.id == newest_selection.id {
4668 range_to_replace = Some(old_range.clone());
4669 old_range.clone()
4670 } else {
4671 s.start..s.end
4672 }
4673 }));
4674 break;
4675 }
4676 if !self.linked_edit_ranges.is_empty() {
4677 let start_anchor = snapshot.anchor_before(selection.head());
4678 let end_anchor = snapshot.anchor_after(selection.tail());
4679 if let Some(ranges) = self
4680 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4681 {
4682 for (buffer, edits) in ranges {
4683 linked_edits.entry(buffer.clone()).or_default().extend(
4684 edits
4685 .into_iter()
4686 .map(|range| (range, new_text[common_prefix_len..].to_owned())),
4687 );
4688 }
4689 }
4690 }
4691 }
4692 let text = &new_text[common_prefix_len..];
4693
4694 let utf16_range_to_replace = range_to_replace.map(|range| {
4695 let newest_selection = self.selections.newest::<OffsetUtf16>(cx).range();
4696 let selection_start_utf16 = newest_selection.start.0 as isize;
4697
4698 range.start.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16
4699 ..range.end.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16
4700 });
4701 cx.emit(EditorEvent::InputHandled {
4702 utf16_range_to_replace,
4703 text: text.into(),
4704 });
4705
4706 self.transact(window, cx, |this, window, cx| {
4707 if let Some(mut snippet) = snippet {
4708 snippet.text = text.to_string();
4709 for tabstop in snippet
4710 .tabstops
4711 .iter_mut()
4712 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4713 {
4714 tabstop.start -= common_prefix_len as isize;
4715 tabstop.end -= common_prefix_len as isize;
4716 }
4717
4718 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4719 } else {
4720 this.buffer.update(cx, |buffer, cx| {
4721 let edits = ranges.iter().map(|range| (range.clone(), text));
4722 let auto_indent = if completion.insert_text_mode == Some(InsertTextMode::AS_IS)
4723 {
4724 None
4725 } else {
4726 this.autoindent_mode.clone()
4727 };
4728 buffer.edit(edits, auto_indent, cx);
4729 });
4730 }
4731 for (buffer, edits) in linked_edits {
4732 buffer.update(cx, |buffer, cx| {
4733 let snapshot = buffer.snapshot();
4734 let edits = edits
4735 .into_iter()
4736 .map(|(range, text)| {
4737 use text::ToPoint as TP;
4738 let end_point = TP::to_point(&range.end, &snapshot);
4739 let start_point = TP::to_point(&range.start, &snapshot);
4740 (start_point..end_point, text)
4741 })
4742 .sorted_by_key(|(range, _)| range.start);
4743 buffer.edit(edits, None, cx);
4744 })
4745 }
4746
4747 this.refresh_inline_completion(true, false, window, cx);
4748 });
4749
4750 let show_new_completions_on_confirm = completion
4751 .confirm
4752 .as_ref()
4753 .map_or(false, |confirm| confirm(intent, window, cx));
4754 if show_new_completions_on_confirm {
4755 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4756 }
4757
4758 let provider = self.completion_provider.as_ref()?;
4759 drop(completion);
4760 let apply_edits = provider.apply_additional_edits_for_completion(
4761 buffer_handle,
4762 completions_menu.completions.clone(),
4763 candidate_id,
4764 true,
4765 cx,
4766 );
4767
4768 let editor_settings = EditorSettings::get_global(cx);
4769 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4770 // After the code completion is finished, users often want to know what signatures are needed.
4771 // so we should automatically call signature_help
4772 self.show_signature_help(&ShowSignatureHelp, window, cx);
4773 }
4774
4775 Some(cx.foreground_executor().spawn(async move {
4776 apply_edits.await?;
4777 Ok(())
4778 }))
4779 }
4780
4781 pub fn toggle_code_actions(
4782 &mut self,
4783 action: &ToggleCodeActions,
4784 window: &mut Window,
4785 cx: &mut Context<Self>,
4786 ) {
4787 let mut context_menu = self.context_menu.borrow_mut();
4788 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4789 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4790 // Toggle if we're selecting the same one
4791 *context_menu = None;
4792 cx.notify();
4793 return;
4794 } else {
4795 // Otherwise, clear it and start a new one
4796 *context_menu = None;
4797 cx.notify();
4798 }
4799 }
4800 drop(context_menu);
4801 let snapshot = self.snapshot(window, cx);
4802 let deployed_from_indicator = action.deployed_from_indicator;
4803 let mut task = self.code_actions_task.take();
4804 let action = action.clone();
4805 cx.spawn_in(window, async move |editor, cx| {
4806 while let Some(prev_task) = task {
4807 prev_task.await.log_err();
4808 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4809 }
4810
4811 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4812 if editor.focus_handle.is_focused(window) {
4813 let multibuffer_point = action
4814 .deployed_from_indicator
4815 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4816 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4817 let (buffer, buffer_row) = snapshot
4818 .buffer_snapshot
4819 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4820 .and_then(|(buffer_snapshot, range)| {
4821 editor
4822 .buffer
4823 .read(cx)
4824 .buffer(buffer_snapshot.remote_id())
4825 .map(|buffer| (buffer, range.start.row))
4826 })?;
4827 let (_, code_actions) = editor
4828 .available_code_actions
4829 .clone()
4830 .and_then(|(location, code_actions)| {
4831 let snapshot = location.buffer.read(cx).snapshot();
4832 let point_range = location.range.to_point(&snapshot);
4833 let point_range = point_range.start.row..=point_range.end.row;
4834 if point_range.contains(&buffer_row) {
4835 Some((location, code_actions))
4836 } else {
4837 None
4838 }
4839 })
4840 .unzip();
4841 let buffer_id = buffer.read(cx).remote_id();
4842 let tasks = editor
4843 .tasks
4844 .get(&(buffer_id, buffer_row))
4845 .map(|t| Arc::new(t.to_owned()));
4846 if tasks.is_none() && code_actions.is_none() {
4847 return None;
4848 }
4849
4850 editor.completion_tasks.clear();
4851 editor.discard_inline_completion(false, cx);
4852 let task_context =
4853 tasks
4854 .as_ref()
4855 .zip(editor.project.clone())
4856 .map(|(tasks, project)| {
4857 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4858 });
4859
4860 let debugger_flag = cx.has_flag::<Debugger>();
4861
4862 Some(cx.spawn_in(window, async move |editor, cx| {
4863 let task_context = match task_context {
4864 Some(task_context) => task_context.await,
4865 None => None,
4866 };
4867 let resolved_tasks =
4868 tasks.zip(task_context).map(|(tasks, task_context)| {
4869 Rc::new(ResolvedTasks {
4870 templates: tasks.resolve(&task_context).collect(),
4871 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4872 multibuffer_point.row,
4873 tasks.column,
4874 )),
4875 })
4876 });
4877 let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
4878 tasks
4879 .templates
4880 .iter()
4881 .filter(|task| {
4882 if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
4883 debugger_flag
4884 } else {
4885 true
4886 }
4887 })
4888 .count()
4889 == 1
4890 }) && code_actions
4891 .as_ref()
4892 .map_or(true, |actions| actions.is_empty());
4893 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4894 *editor.context_menu.borrow_mut() =
4895 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4896 buffer,
4897 actions: CodeActionContents {
4898 tasks: resolved_tasks,
4899 actions: code_actions,
4900 },
4901 selected_item: Default::default(),
4902 scroll_handle: UniformListScrollHandle::default(),
4903 deployed_from_indicator,
4904 }));
4905 if spawn_straight_away {
4906 if let Some(task) = editor.confirm_code_action(
4907 &ConfirmCodeAction { item_ix: Some(0) },
4908 window,
4909 cx,
4910 ) {
4911 cx.notify();
4912 return task;
4913 }
4914 }
4915 cx.notify();
4916 Task::ready(Ok(()))
4917 }) {
4918 task.await
4919 } else {
4920 Ok(())
4921 }
4922 }))
4923 } else {
4924 Some(Task::ready(Ok(())))
4925 }
4926 })?;
4927 if let Some(task) = spawned_test_task {
4928 task.await?;
4929 }
4930
4931 Ok::<_, anyhow::Error>(())
4932 })
4933 .detach_and_log_err(cx);
4934 }
4935
4936 pub fn confirm_code_action(
4937 &mut self,
4938 action: &ConfirmCodeAction,
4939 window: &mut Window,
4940 cx: &mut Context<Self>,
4941 ) -> Option<Task<Result<()>>> {
4942 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4943
4944 let actions_menu =
4945 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4946 menu
4947 } else {
4948 return None;
4949 };
4950
4951 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4952 let action = actions_menu.actions.get(action_ix)?;
4953 let title = action.label();
4954 let buffer = actions_menu.buffer;
4955 let workspace = self.workspace()?;
4956
4957 match action {
4958 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4959 match resolved_task.task_type() {
4960 task::TaskType::Script => workspace.update(cx, |workspace, cx| {
4961 workspace::tasks::schedule_resolved_task(
4962 workspace,
4963 task_source_kind,
4964 resolved_task,
4965 false,
4966 cx,
4967 );
4968
4969 Some(Task::ready(Ok(())))
4970 }),
4971 task::TaskType::Debug(debug_args) => {
4972 if debug_args.locator.is_some() {
4973 workspace.update(cx, |workspace, cx| {
4974 workspace::tasks::schedule_resolved_task(
4975 workspace,
4976 task_source_kind,
4977 resolved_task,
4978 false,
4979 cx,
4980 );
4981 });
4982
4983 return Some(Task::ready(Ok(())));
4984 }
4985
4986 if let Some(project) = self.project.as_ref() {
4987 project
4988 .update(cx, |project, cx| {
4989 project.start_debug_session(
4990 resolved_task.resolved_debug_adapter_config().unwrap(),
4991 cx,
4992 )
4993 })
4994 .detach_and_log_err(cx);
4995 Some(Task::ready(Ok(())))
4996 } else {
4997 Some(Task::ready(Ok(())))
4998 }
4999 }
5000 }
5001 }
5002 CodeActionsItem::CodeAction {
5003 excerpt_id,
5004 action,
5005 provider,
5006 } => {
5007 let apply_code_action =
5008 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5009 let workspace = workspace.downgrade();
5010 Some(cx.spawn_in(window, async move |editor, cx| {
5011 let project_transaction = apply_code_action.await?;
5012 Self::open_project_transaction(
5013 &editor,
5014 workspace,
5015 project_transaction,
5016 title,
5017 cx,
5018 )
5019 .await
5020 }))
5021 }
5022 }
5023 }
5024
5025 pub async fn open_project_transaction(
5026 this: &WeakEntity<Editor>,
5027 workspace: WeakEntity<Workspace>,
5028 transaction: ProjectTransaction,
5029 title: String,
5030 cx: &mut AsyncWindowContext,
5031 ) -> Result<()> {
5032 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5033 cx.update(|_, cx| {
5034 entries.sort_unstable_by_key(|(buffer, _)| {
5035 buffer.read(cx).file().map(|f| f.path().clone())
5036 });
5037 })?;
5038
5039 // If the project transaction's edits are all contained within this editor, then
5040 // avoid opening a new editor to display them.
5041
5042 if let Some((buffer, transaction)) = entries.first() {
5043 if entries.len() == 1 {
5044 let excerpt = this.update(cx, |editor, cx| {
5045 editor
5046 .buffer()
5047 .read(cx)
5048 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5049 })?;
5050 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5051 if excerpted_buffer == *buffer {
5052 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5053 let excerpt_range = excerpt_range.to_offset(buffer);
5054 buffer
5055 .edited_ranges_for_transaction::<usize>(transaction)
5056 .all(|range| {
5057 excerpt_range.start <= range.start
5058 && excerpt_range.end >= range.end
5059 })
5060 })?;
5061
5062 if all_edits_within_excerpt {
5063 return Ok(());
5064 }
5065 }
5066 }
5067 }
5068 } else {
5069 return Ok(());
5070 }
5071
5072 let mut ranges_to_highlight = Vec::new();
5073 let excerpt_buffer = cx.new(|cx| {
5074 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5075 for (buffer_handle, transaction) in &entries {
5076 let edited_ranges = buffer_handle
5077 .read(cx)
5078 .edited_ranges_for_transaction::<Point>(transaction)
5079 .collect::<Vec<_>>();
5080 let (ranges, _) = multibuffer.set_excerpts_for_path(
5081 PathKey::for_buffer(buffer_handle, cx),
5082 buffer_handle.clone(),
5083 edited_ranges,
5084 DEFAULT_MULTIBUFFER_CONTEXT,
5085 cx,
5086 );
5087
5088 ranges_to_highlight.extend(ranges);
5089 }
5090 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5091 multibuffer
5092 })?;
5093
5094 workspace.update_in(cx, |workspace, window, cx| {
5095 let project = workspace.project().clone();
5096 let editor =
5097 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5098 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5099 editor.update(cx, |editor, cx| {
5100 editor.highlight_background::<Self>(
5101 &ranges_to_highlight,
5102 |theme| theme.editor_highlighted_line_background,
5103 cx,
5104 );
5105 });
5106 })?;
5107
5108 Ok(())
5109 }
5110
5111 pub fn clear_code_action_providers(&mut self) {
5112 self.code_action_providers.clear();
5113 self.available_code_actions.take();
5114 }
5115
5116 pub fn add_code_action_provider(
5117 &mut self,
5118 provider: Rc<dyn CodeActionProvider>,
5119 window: &mut Window,
5120 cx: &mut Context<Self>,
5121 ) {
5122 if self
5123 .code_action_providers
5124 .iter()
5125 .any(|existing_provider| existing_provider.id() == provider.id())
5126 {
5127 return;
5128 }
5129
5130 self.code_action_providers.push(provider);
5131 self.refresh_code_actions(window, cx);
5132 }
5133
5134 pub fn remove_code_action_provider(
5135 &mut self,
5136 id: Arc<str>,
5137 window: &mut Window,
5138 cx: &mut Context<Self>,
5139 ) {
5140 self.code_action_providers
5141 .retain(|provider| provider.id() != id);
5142 self.refresh_code_actions(window, cx);
5143 }
5144
5145 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5146 let buffer = self.buffer.read(cx);
5147 let newest_selection = self.selections.newest_anchor().clone();
5148 if newest_selection.head().diff_base_anchor.is_some() {
5149 return None;
5150 }
5151 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
5152 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
5153 if start_buffer != end_buffer {
5154 return None;
5155 }
5156
5157 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5158 cx.background_executor()
5159 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5160 .await;
5161
5162 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5163 let providers = this.code_action_providers.clone();
5164 let tasks = this
5165 .code_action_providers
5166 .iter()
5167 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5168 .collect::<Vec<_>>();
5169 (providers, tasks)
5170 })?;
5171
5172 let mut actions = Vec::new();
5173 for (provider, provider_actions) in
5174 providers.into_iter().zip(future::join_all(tasks).await)
5175 {
5176 if let Some(provider_actions) = provider_actions.log_err() {
5177 actions.extend(provider_actions.into_iter().map(|action| {
5178 AvailableCodeAction {
5179 excerpt_id: newest_selection.start.excerpt_id,
5180 action,
5181 provider: provider.clone(),
5182 }
5183 }));
5184 }
5185 }
5186
5187 this.update(cx, |this, cx| {
5188 this.available_code_actions = if actions.is_empty() {
5189 None
5190 } else {
5191 Some((
5192 Location {
5193 buffer: start_buffer,
5194 range: start..end,
5195 },
5196 actions.into(),
5197 ))
5198 };
5199 cx.notify();
5200 })
5201 }));
5202 None
5203 }
5204
5205 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5206 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5207 self.show_git_blame_inline = false;
5208
5209 self.show_git_blame_inline_delay_task =
5210 Some(cx.spawn_in(window, async move |this, cx| {
5211 cx.background_executor().timer(delay).await;
5212
5213 this.update(cx, |this, cx| {
5214 this.show_git_blame_inline = true;
5215 cx.notify();
5216 })
5217 .log_err();
5218 }));
5219 }
5220 }
5221
5222 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5223 if self.pending_rename.is_some() {
5224 return None;
5225 }
5226
5227 let provider = self.semantics_provider.clone()?;
5228 let buffer = self.buffer.read(cx);
5229 let newest_selection = self.selections.newest_anchor().clone();
5230 let cursor_position = newest_selection.head();
5231 let (cursor_buffer, cursor_buffer_position) =
5232 buffer.text_anchor_for_position(cursor_position, cx)?;
5233 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5234 if cursor_buffer != tail_buffer {
5235 return None;
5236 }
5237 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5238 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5239 cx.background_executor()
5240 .timer(Duration::from_millis(debounce))
5241 .await;
5242
5243 let highlights = if let Some(highlights) = cx
5244 .update(|cx| {
5245 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5246 })
5247 .ok()
5248 .flatten()
5249 {
5250 highlights.await.log_err()
5251 } else {
5252 None
5253 };
5254
5255 if let Some(highlights) = highlights {
5256 this.update(cx, |this, cx| {
5257 if this.pending_rename.is_some() {
5258 return;
5259 }
5260
5261 let buffer_id = cursor_position.buffer_id;
5262 let buffer = this.buffer.read(cx);
5263 if !buffer
5264 .text_anchor_for_position(cursor_position, cx)
5265 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5266 {
5267 return;
5268 }
5269
5270 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5271 let mut write_ranges = Vec::new();
5272 let mut read_ranges = Vec::new();
5273 for highlight in highlights {
5274 for (excerpt_id, excerpt_range) in
5275 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5276 {
5277 let start = highlight
5278 .range
5279 .start
5280 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5281 let end = highlight
5282 .range
5283 .end
5284 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5285 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5286 continue;
5287 }
5288
5289 let range = Anchor {
5290 buffer_id,
5291 excerpt_id,
5292 text_anchor: start,
5293 diff_base_anchor: None,
5294 }..Anchor {
5295 buffer_id,
5296 excerpt_id,
5297 text_anchor: end,
5298 diff_base_anchor: None,
5299 };
5300 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5301 write_ranges.push(range);
5302 } else {
5303 read_ranges.push(range);
5304 }
5305 }
5306 }
5307
5308 this.highlight_background::<DocumentHighlightRead>(
5309 &read_ranges,
5310 |theme| theme.editor_document_highlight_read_background,
5311 cx,
5312 );
5313 this.highlight_background::<DocumentHighlightWrite>(
5314 &write_ranges,
5315 |theme| theme.editor_document_highlight_write_background,
5316 cx,
5317 );
5318 cx.notify();
5319 })
5320 .log_err();
5321 }
5322 }));
5323 None
5324 }
5325
5326 pub fn refresh_selected_text_highlights(
5327 &mut self,
5328 window: &mut Window,
5329 cx: &mut Context<Editor>,
5330 ) {
5331 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5332 return;
5333 }
5334 self.selection_highlight_task.take();
5335 if !EditorSettings::get_global(cx).selection_highlight {
5336 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5337 return;
5338 }
5339 if self.selections.count() != 1 || self.selections.line_mode {
5340 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5341 return;
5342 }
5343 let selection = self.selections.newest::<Point>(cx);
5344 if selection.is_empty() || selection.start.row != selection.end.row {
5345 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5346 return;
5347 }
5348 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5349 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5350 cx.background_executor()
5351 .timer(Duration::from_millis(debounce))
5352 .await;
5353 let Some(Some(matches_task)) = editor
5354 .update_in(cx, |editor, _, cx| {
5355 if editor.selections.count() != 1 || editor.selections.line_mode {
5356 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5357 return None;
5358 }
5359 let selection = editor.selections.newest::<Point>(cx);
5360 if selection.is_empty() || selection.start.row != selection.end.row {
5361 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5362 return None;
5363 }
5364 let buffer = editor.buffer().read(cx).snapshot(cx);
5365 let query = buffer.text_for_range(selection.range()).collect::<String>();
5366 if query.trim().is_empty() {
5367 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5368 return None;
5369 }
5370 Some(cx.background_spawn(async move {
5371 let mut ranges = Vec::new();
5372 let selection_anchors = selection.range().to_anchors(&buffer);
5373 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5374 for (search_buffer, search_range, excerpt_id) in
5375 buffer.range_to_buffer_ranges(range)
5376 {
5377 ranges.extend(
5378 project::search::SearchQuery::text(
5379 query.clone(),
5380 false,
5381 false,
5382 false,
5383 Default::default(),
5384 Default::default(),
5385 None,
5386 )
5387 .unwrap()
5388 .search(search_buffer, Some(search_range.clone()))
5389 .await
5390 .into_iter()
5391 .filter_map(
5392 |match_range| {
5393 let start = search_buffer.anchor_after(
5394 search_range.start + match_range.start,
5395 );
5396 let end = search_buffer.anchor_before(
5397 search_range.start + match_range.end,
5398 );
5399 let range = Anchor::range_in_buffer(
5400 excerpt_id,
5401 search_buffer.remote_id(),
5402 start..end,
5403 );
5404 (range != selection_anchors).then_some(range)
5405 },
5406 ),
5407 );
5408 }
5409 }
5410 ranges
5411 }))
5412 })
5413 .log_err()
5414 else {
5415 return;
5416 };
5417 let matches = matches_task.await;
5418 editor
5419 .update_in(cx, |editor, _, cx| {
5420 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5421 if !matches.is_empty() {
5422 editor.highlight_background::<SelectedTextHighlight>(
5423 &matches,
5424 |theme| theme.editor_document_highlight_bracket_background,
5425 cx,
5426 )
5427 }
5428 })
5429 .log_err();
5430 }));
5431 }
5432
5433 pub fn refresh_inline_completion(
5434 &mut self,
5435 debounce: bool,
5436 user_requested: bool,
5437 window: &mut Window,
5438 cx: &mut Context<Self>,
5439 ) -> Option<()> {
5440 let provider = self.edit_prediction_provider()?;
5441 let cursor = self.selections.newest_anchor().head();
5442 let (buffer, cursor_buffer_position) =
5443 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5444
5445 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5446 self.discard_inline_completion(false, cx);
5447 return None;
5448 }
5449
5450 if !user_requested
5451 && (!self.should_show_edit_predictions()
5452 || !self.is_focused(window)
5453 || buffer.read(cx).is_empty())
5454 {
5455 self.discard_inline_completion(false, cx);
5456 return None;
5457 }
5458
5459 self.update_visible_inline_completion(window, cx);
5460 provider.refresh(
5461 self.project.clone(),
5462 buffer,
5463 cursor_buffer_position,
5464 debounce,
5465 cx,
5466 );
5467 Some(())
5468 }
5469
5470 fn show_edit_predictions_in_menu(&self) -> bool {
5471 match self.edit_prediction_settings {
5472 EditPredictionSettings::Disabled => false,
5473 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5474 }
5475 }
5476
5477 pub fn edit_predictions_enabled(&self) -> bool {
5478 match self.edit_prediction_settings {
5479 EditPredictionSettings::Disabled => false,
5480 EditPredictionSettings::Enabled { .. } => true,
5481 }
5482 }
5483
5484 fn edit_prediction_requires_modifier(&self) -> bool {
5485 match self.edit_prediction_settings {
5486 EditPredictionSettings::Disabled => false,
5487 EditPredictionSettings::Enabled {
5488 preview_requires_modifier,
5489 ..
5490 } => preview_requires_modifier,
5491 }
5492 }
5493
5494 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5495 if self.edit_prediction_provider.is_none() {
5496 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5497 } else {
5498 let selection = self.selections.newest_anchor();
5499 let cursor = selection.head();
5500
5501 if let Some((buffer, cursor_buffer_position)) =
5502 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5503 {
5504 self.edit_prediction_settings =
5505 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5506 }
5507 }
5508 }
5509
5510 fn edit_prediction_settings_at_position(
5511 &self,
5512 buffer: &Entity<Buffer>,
5513 buffer_position: language::Anchor,
5514 cx: &App,
5515 ) -> EditPredictionSettings {
5516 if self.mode != EditorMode::Full
5517 || !self.show_inline_completions_override.unwrap_or(true)
5518 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5519 {
5520 return EditPredictionSettings::Disabled;
5521 }
5522
5523 let buffer = buffer.read(cx);
5524
5525 let file = buffer.file();
5526
5527 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5528 return EditPredictionSettings::Disabled;
5529 };
5530
5531 let by_provider = matches!(
5532 self.menu_inline_completions_policy,
5533 MenuInlineCompletionsPolicy::ByProvider
5534 );
5535
5536 let show_in_menu = by_provider
5537 && self
5538 .edit_prediction_provider
5539 .as_ref()
5540 .map_or(false, |provider| {
5541 provider.provider.show_completions_in_menu()
5542 });
5543
5544 let preview_requires_modifier =
5545 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5546
5547 EditPredictionSettings::Enabled {
5548 show_in_menu,
5549 preview_requires_modifier,
5550 }
5551 }
5552
5553 fn should_show_edit_predictions(&self) -> bool {
5554 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5555 }
5556
5557 pub fn edit_prediction_preview_is_active(&self) -> bool {
5558 matches!(
5559 self.edit_prediction_preview,
5560 EditPredictionPreview::Active { .. }
5561 )
5562 }
5563
5564 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5565 let cursor = self.selections.newest_anchor().head();
5566 if let Some((buffer, cursor_position)) =
5567 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5568 {
5569 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5570 } else {
5571 false
5572 }
5573 }
5574
5575 fn edit_predictions_enabled_in_buffer(
5576 &self,
5577 buffer: &Entity<Buffer>,
5578 buffer_position: language::Anchor,
5579 cx: &App,
5580 ) -> bool {
5581 maybe!({
5582 if self.read_only(cx) {
5583 return Some(false);
5584 }
5585 let provider = self.edit_prediction_provider()?;
5586 if !provider.is_enabled(&buffer, buffer_position, cx) {
5587 return Some(false);
5588 }
5589 let buffer = buffer.read(cx);
5590 let Some(file) = buffer.file() else {
5591 return Some(true);
5592 };
5593 let settings = all_language_settings(Some(file), cx);
5594 Some(settings.edit_predictions_enabled_for_file(file, cx))
5595 })
5596 .unwrap_or(false)
5597 }
5598
5599 fn cycle_inline_completion(
5600 &mut self,
5601 direction: Direction,
5602 window: &mut Window,
5603 cx: &mut Context<Self>,
5604 ) -> Option<()> {
5605 let provider = self.edit_prediction_provider()?;
5606 let cursor = self.selections.newest_anchor().head();
5607 let (buffer, cursor_buffer_position) =
5608 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5609 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5610 return None;
5611 }
5612
5613 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5614 self.update_visible_inline_completion(window, cx);
5615
5616 Some(())
5617 }
5618
5619 pub fn show_inline_completion(
5620 &mut self,
5621 _: &ShowEditPrediction,
5622 window: &mut Window,
5623 cx: &mut Context<Self>,
5624 ) {
5625 if !self.has_active_inline_completion() {
5626 self.refresh_inline_completion(false, true, window, cx);
5627 return;
5628 }
5629
5630 self.update_visible_inline_completion(window, cx);
5631 }
5632
5633 pub fn display_cursor_names(
5634 &mut self,
5635 _: &DisplayCursorNames,
5636 window: &mut Window,
5637 cx: &mut Context<Self>,
5638 ) {
5639 self.show_cursor_names(window, cx);
5640 }
5641
5642 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5643 self.show_cursor_names = true;
5644 cx.notify();
5645 cx.spawn_in(window, async move |this, cx| {
5646 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5647 this.update(cx, |this, cx| {
5648 this.show_cursor_names = false;
5649 cx.notify()
5650 })
5651 .ok()
5652 })
5653 .detach();
5654 }
5655
5656 pub fn next_edit_prediction(
5657 &mut self,
5658 _: &NextEditPrediction,
5659 window: &mut Window,
5660 cx: &mut Context<Self>,
5661 ) {
5662 if self.has_active_inline_completion() {
5663 self.cycle_inline_completion(Direction::Next, window, cx);
5664 } else {
5665 let is_copilot_disabled = self
5666 .refresh_inline_completion(false, true, window, cx)
5667 .is_none();
5668 if is_copilot_disabled {
5669 cx.propagate();
5670 }
5671 }
5672 }
5673
5674 pub fn previous_edit_prediction(
5675 &mut self,
5676 _: &PreviousEditPrediction,
5677 window: &mut Window,
5678 cx: &mut Context<Self>,
5679 ) {
5680 if self.has_active_inline_completion() {
5681 self.cycle_inline_completion(Direction::Prev, window, cx);
5682 } else {
5683 let is_copilot_disabled = self
5684 .refresh_inline_completion(false, true, window, cx)
5685 .is_none();
5686 if is_copilot_disabled {
5687 cx.propagate();
5688 }
5689 }
5690 }
5691
5692 pub fn accept_edit_prediction(
5693 &mut self,
5694 _: &AcceptEditPrediction,
5695 window: &mut Window,
5696 cx: &mut Context<Self>,
5697 ) {
5698 if self.show_edit_predictions_in_menu() {
5699 self.hide_context_menu(window, cx);
5700 }
5701
5702 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5703 return;
5704 };
5705
5706 self.report_inline_completion_event(
5707 active_inline_completion.completion_id.clone(),
5708 true,
5709 cx,
5710 );
5711
5712 match &active_inline_completion.completion {
5713 InlineCompletion::Move { target, .. } => {
5714 let target = *target;
5715
5716 if let Some(position_map) = &self.last_position_map {
5717 if position_map
5718 .visible_row_range
5719 .contains(&target.to_display_point(&position_map.snapshot).row())
5720 || !self.edit_prediction_requires_modifier()
5721 {
5722 self.unfold_ranges(&[target..target], true, false, cx);
5723 // Note that this is also done in vim's handler of the Tab action.
5724 self.change_selections(
5725 Some(Autoscroll::newest()),
5726 window,
5727 cx,
5728 |selections| {
5729 selections.select_anchor_ranges([target..target]);
5730 },
5731 );
5732 self.clear_row_highlights::<EditPredictionPreview>();
5733
5734 self.edit_prediction_preview
5735 .set_previous_scroll_position(None);
5736 } else {
5737 self.edit_prediction_preview
5738 .set_previous_scroll_position(Some(
5739 position_map.snapshot.scroll_anchor,
5740 ));
5741
5742 self.highlight_rows::<EditPredictionPreview>(
5743 target..target,
5744 cx.theme().colors().editor_highlighted_line_background,
5745 true,
5746 cx,
5747 );
5748 self.request_autoscroll(Autoscroll::fit(), cx);
5749 }
5750 }
5751 }
5752 InlineCompletion::Edit { edits, .. } => {
5753 if let Some(provider) = self.edit_prediction_provider() {
5754 provider.accept(cx);
5755 }
5756
5757 let snapshot = self.buffer.read(cx).snapshot(cx);
5758 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5759
5760 self.buffer.update(cx, |buffer, cx| {
5761 buffer.edit(edits.iter().cloned(), None, cx)
5762 });
5763
5764 self.change_selections(None, window, cx, |s| {
5765 s.select_anchor_ranges([last_edit_end..last_edit_end])
5766 });
5767
5768 self.update_visible_inline_completion(window, cx);
5769 if self.active_inline_completion.is_none() {
5770 self.refresh_inline_completion(true, true, window, cx);
5771 }
5772
5773 cx.notify();
5774 }
5775 }
5776
5777 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5778 }
5779
5780 pub fn accept_partial_inline_completion(
5781 &mut self,
5782 _: &AcceptPartialEditPrediction,
5783 window: &mut Window,
5784 cx: &mut Context<Self>,
5785 ) {
5786 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5787 return;
5788 };
5789 if self.selections.count() != 1 {
5790 return;
5791 }
5792
5793 self.report_inline_completion_event(
5794 active_inline_completion.completion_id.clone(),
5795 true,
5796 cx,
5797 );
5798
5799 match &active_inline_completion.completion {
5800 InlineCompletion::Move { target, .. } => {
5801 let target = *target;
5802 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5803 selections.select_anchor_ranges([target..target]);
5804 });
5805 }
5806 InlineCompletion::Edit { edits, .. } => {
5807 // Find an insertion that starts at the cursor position.
5808 let snapshot = self.buffer.read(cx).snapshot(cx);
5809 let cursor_offset = self.selections.newest::<usize>(cx).head();
5810 let insertion = edits.iter().find_map(|(range, text)| {
5811 let range = range.to_offset(&snapshot);
5812 if range.is_empty() && range.start == cursor_offset {
5813 Some(text)
5814 } else {
5815 None
5816 }
5817 });
5818
5819 if let Some(text) = insertion {
5820 let mut partial_completion = text
5821 .chars()
5822 .by_ref()
5823 .take_while(|c| c.is_alphabetic())
5824 .collect::<String>();
5825 if partial_completion.is_empty() {
5826 partial_completion = text
5827 .chars()
5828 .by_ref()
5829 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5830 .collect::<String>();
5831 }
5832
5833 cx.emit(EditorEvent::InputHandled {
5834 utf16_range_to_replace: None,
5835 text: partial_completion.clone().into(),
5836 });
5837
5838 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5839
5840 self.refresh_inline_completion(true, true, window, cx);
5841 cx.notify();
5842 } else {
5843 self.accept_edit_prediction(&Default::default(), window, cx);
5844 }
5845 }
5846 }
5847 }
5848
5849 fn discard_inline_completion(
5850 &mut self,
5851 should_report_inline_completion_event: bool,
5852 cx: &mut Context<Self>,
5853 ) -> bool {
5854 if should_report_inline_completion_event {
5855 let completion_id = self
5856 .active_inline_completion
5857 .as_ref()
5858 .and_then(|active_completion| active_completion.completion_id.clone());
5859
5860 self.report_inline_completion_event(completion_id, false, cx);
5861 }
5862
5863 if let Some(provider) = self.edit_prediction_provider() {
5864 provider.discard(cx);
5865 }
5866
5867 self.take_active_inline_completion(cx)
5868 }
5869
5870 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5871 let Some(provider) = self.edit_prediction_provider() else {
5872 return;
5873 };
5874
5875 let Some((_, buffer, _)) = self
5876 .buffer
5877 .read(cx)
5878 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5879 else {
5880 return;
5881 };
5882
5883 let extension = buffer
5884 .read(cx)
5885 .file()
5886 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5887
5888 let event_type = match accepted {
5889 true => "Edit Prediction Accepted",
5890 false => "Edit Prediction Discarded",
5891 };
5892 telemetry::event!(
5893 event_type,
5894 provider = provider.name(),
5895 prediction_id = id,
5896 suggestion_accepted = accepted,
5897 file_extension = extension,
5898 );
5899 }
5900
5901 pub fn has_active_inline_completion(&self) -> bool {
5902 self.active_inline_completion.is_some()
5903 }
5904
5905 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5906 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5907 return false;
5908 };
5909
5910 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5911 self.clear_highlights::<InlineCompletionHighlight>(cx);
5912 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5913 true
5914 }
5915
5916 /// Returns true when we're displaying the edit prediction popover below the cursor
5917 /// like we are not previewing and the LSP autocomplete menu is visible
5918 /// or we are in `when_holding_modifier` mode.
5919 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5920 if self.edit_prediction_preview_is_active()
5921 || !self.show_edit_predictions_in_menu()
5922 || !self.edit_predictions_enabled()
5923 {
5924 return false;
5925 }
5926
5927 if self.has_visible_completions_menu() {
5928 return true;
5929 }
5930
5931 has_completion && self.edit_prediction_requires_modifier()
5932 }
5933
5934 fn handle_modifiers_changed(
5935 &mut self,
5936 modifiers: Modifiers,
5937 position_map: &PositionMap,
5938 window: &mut Window,
5939 cx: &mut Context<Self>,
5940 ) {
5941 if self.show_edit_predictions_in_menu() {
5942 self.update_edit_prediction_preview(&modifiers, window, cx);
5943 }
5944
5945 self.update_selection_mode(&modifiers, position_map, window, cx);
5946
5947 let mouse_position = window.mouse_position();
5948 if !position_map.text_hitbox.is_hovered(window) {
5949 return;
5950 }
5951
5952 self.update_hovered_link(
5953 position_map.point_for_position(mouse_position),
5954 &position_map.snapshot,
5955 modifiers,
5956 window,
5957 cx,
5958 )
5959 }
5960
5961 fn update_selection_mode(
5962 &mut self,
5963 modifiers: &Modifiers,
5964 position_map: &PositionMap,
5965 window: &mut Window,
5966 cx: &mut Context<Self>,
5967 ) {
5968 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
5969 return;
5970 }
5971
5972 let mouse_position = window.mouse_position();
5973 let point_for_position = position_map.point_for_position(mouse_position);
5974 let position = point_for_position.previous_valid;
5975
5976 self.select(
5977 SelectPhase::BeginColumnar {
5978 position,
5979 reset: false,
5980 goal_column: point_for_position.exact_unclipped.column(),
5981 },
5982 window,
5983 cx,
5984 );
5985 }
5986
5987 fn update_edit_prediction_preview(
5988 &mut self,
5989 modifiers: &Modifiers,
5990 window: &mut Window,
5991 cx: &mut Context<Self>,
5992 ) {
5993 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
5994 let Some(accept_keystroke) = accept_keybind.keystroke() else {
5995 return;
5996 };
5997
5998 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
5999 if matches!(
6000 self.edit_prediction_preview,
6001 EditPredictionPreview::Inactive { .. }
6002 ) {
6003 self.edit_prediction_preview = EditPredictionPreview::Active {
6004 previous_scroll_position: None,
6005 since: Instant::now(),
6006 };
6007
6008 self.update_visible_inline_completion(window, cx);
6009 cx.notify();
6010 }
6011 } else if let EditPredictionPreview::Active {
6012 previous_scroll_position,
6013 since,
6014 } = self.edit_prediction_preview
6015 {
6016 if let (Some(previous_scroll_position), Some(position_map)) =
6017 (previous_scroll_position, self.last_position_map.as_ref())
6018 {
6019 self.set_scroll_position(
6020 previous_scroll_position
6021 .scroll_position(&position_map.snapshot.display_snapshot),
6022 window,
6023 cx,
6024 );
6025 }
6026
6027 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6028 released_too_fast: since.elapsed() < Duration::from_millis(200),
6029 };
6030 self.clear_row_highlights::<EditPredictionPreview>();
6031 self.update_visible_inline_completion(window, cx);
6032 cx.notify();
6033 }
6034 }
6035
6036 fn update_visible_inline_completion(
6037 &mut self,
6038 _window: &mut Window,
6039 cx: &mut Context<Self>,
6040 ) -> Option<()> {
6041 let selection = self.selections.newest_anchor();
6042 let cursor = selection.head();
6043 let multibuffer = self.buffer.read(cx).snapshot(cx);
6044 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6045 let excerpt_id = cursor.excerpt_id;
6046
6047 let show_in_menu = self.show_edit_predictions_in_menu();
6048 let completions_menu_has_precedence = !show_in_menu
6049 && (self.context_menu.borrow().is_some()
6050 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6051
6052 if completions_menu_has_precedence
6053 || !offset_selection.is_empty()
6054 || self
6055 .active_inline_completion
6056 .as_ref()
6057 .map_or(false, |completion| {
6058 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6059 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6060 !invalidation_range.contains(&offset_selection.head())
6061 })
6062 {
6063 self.discard_inline_completion(false, cx);
6064 return None;
6065 }
6066
6067 self.take_active_inline_completion(cx);
6068 let Some(provider) = self.edit_prediction_provider() else {
6069 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6070 return None;
6071 };
6072
6073 let (buffer, cursor_buffer_position) =
6074 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6075
6076 self.edit_prediction_settings =
6077 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6078
6079 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6080
6081 if self.edit_prediction_indent_conflict {
6082 let cursor_point = cursor.to_point(&multibuffer);
6083
6084 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6085
6086 if let Some((_, indent)) = indents.iter().next() {
6087 if indent.len == cursor_point.column {
6088 self.edit_prediction_indent_conflict = false;
6089 }
6090 }
6091 }
6092
6093 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6094 let edits = inline_completion
6095 .edits
6096 .into_iter()
6097 .flat_map(|(range, new_text)| {
6098 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6099 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6100 Some((start..end, new_text))
6101 })
6102 .collect::<Vec<_>>();
6103 if edits.is_empty() {
6104 return None;
6105 }
6106
6107 let first_edit_start = edits.first().unwrap().0.start;
6108 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6109 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6110
6111 let last_edit_end = edits.last().unwrap().0.end;
6112 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6113 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6114
6115 let cursor_row = cursor.to_point(&multibuffer).row;
6116
6117 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6118
6119 let mut inlay_ids = Vec::new();
6120 let invalidation_row_range;
6121 let move_invalidation_row_range = if cursor_row < edit_start_row {
6122 Some(cursor_row..edit_end_row)
6123 } else if cursor_row > edit_end_row {
6124 Some(edit_start_row..cursor_row)
6125 } else {
6126 None
6127 };
6128 let is_move =
6129 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6130 let completion = if is_move {
6131 invalidation_row_range =
6132 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6133 let target = first_edit_start;
6134 InlineCompletion::Move { target, snapshot }
6135 } else {
6136 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6137 && !self.inline_completions_hidden_for_vim_mode;
6138
6139 if show_completions_in_buffer {
6140 if edits
6141 .iter()
6142 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6143 {
6144 let mut inlays = Vec::new();
6145 for (range, new_text) in &edits {
6146 let inlay = Inlay::inline_completion(
6147 post_inc(&mut self.next_inlay_id),
6148 range.start,
6149 new_text.as_str(),
6150 );
6151 inlay_ids.push(inlay.id);
6152 inlays.push(inlay);
6153 }
6154
6155 self.splice_inlays(&[], inlays, cx);
6156 } else {
6157 let background_color = cx.theme().status().deleted_background;
6158 self.highlight_text::<InlineCompletionHighlight>(
6159 edits.iter().map(|(range, _)| range.clone()).collect(),
6160 HighlightStyle {
6161 background_color: Some(background_color),
6162 ..Default::default()
6163 },
6164 cx,
6165 );
6166 }
6167 }
6168
6169 invalidation_row_range = edit_start_row..edit_end_row;
6170
6171 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6172 if provider.show_tab_accept_marker() {
6173 EditDisplayMode::TabAccept
6174 } else {
6175 EditDisplayMode::Inline
6176 }
6177 } else {
6178 EditDisplayMode::DiffPopover
6179 };
6180
6181 InlineCompletion::Edit {
6182 edits,
6183 edit_preview: inline_completion.edit_preview,
6184 display_mode,
6185 snapshot,
6186 }
6187 };
6188
6189 let invalidation_range = multibuffer
6190 .anchor_before(Point::new(invalidation_row_range.start, 0))
6191 ..multibuffer.anchor_after(Point::new(
6192 invalidation_row_range.end,
6193 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6194 ));
6195
6196 self.stale_inline_completion_in_menu = None;
6197 self.active_inline_completion = Some(InlineCompletionState {
6198 inlay_ids,
6199 completion,
6200 completion_id: inline_completion.id,
6201 invalidation_range,
6202 });
6203
6204 cx.notify();
6205
6206 Some(())
6207 }
6208
6209 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6210 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6211 }
6212
6213 fn render_code_actions_indicator(
6214 &self,
6215 _style: &EditorStyle,
6216 row: DisplayRow,
6217 is_active: bool,
6218 breakpoint: Option<&(Anchor, Breakpoint)>,
6219 cx: &mut Context<Self>,
6220 ) -> Option<IconButton> {
6221 let color = Color::Muted;
6222 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6223 let show_tooltip = !self.context_menu_visible();
6224
6225 if self.available_code_actions.is_some() {
6226 Some(
6227 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6228 .shape(ui::IconButtonShape::Square)
6229 .icon_size(IconSize::XSmall)
6230 .icon_color(color)
6231 .toggle_state(is_active)
6232 .when(show_tooltip, |this| {
6233 this.tooltip({
6234 let focus_handle = self.focus_handle.clone();
6235 move |window, cx| {
6236 Tooltip::for_action_in(
6237 "Toggle Code Actions",
6238 &ToggleCodeActions {
6239 deployed_from_indicator: None,
6240 },
6241 &focus_handle,
6242 window,
6243 cx,
6244 )
6245 }
6246 })
6247 })
6248 .on_click(cx.listener(move |editor, _e, window, cx| {
6249 window.focus(&editor.focus_handle(cx));
6250 editor.toggle_code_actions(
6251 &ToggleCodeActions {
6252 deployed_from_indicator: Some(row),
6253 },
6254 window,
6255 cx,
6256 );
6257 }))
6258 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6259 editor.set_breakpoint_context_menu(
6260 row,
6261 position,
6262 event.down.position,
6263 window,
6264 cx,
6265 );
6266 })),
6267 )
6268 } else {
6269 None
6270 }
6271 }
6272
6273 fn clear_tasks(&mut self) {
6274 self.tasks.clear()
6275 }
6276
6277 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6278 if self.tasks.insert(key, value).is_some() {
6279 // This case should hopefully be rare, but just in case...
6280 log::error!(
6281 "multiple different run targets found on a single line, only the last target will be rendered"
6282 )
6283 }
6284 }
6285
6286 /// Get all display points of breakpoints that will be rendered within editor
6287 ///
6288 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6289 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6290 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6291 fn active_breakpoints(
6292 &self,
6293 range: Range<DisplayRow>,
6294 window: &mut Window,
6295 cx: &mut Context<Self>,
6296 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6297 let mut breakpoint_display_points = HashMap::default();
6298
6299 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6300 return breakpoint_display_points;
6301 };
6302
6303 let snapshot = self.snapshot(window, cx);
6304
6305 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6306 let Some(project) = self.project.as_ref() else {
6307 return breakpoint_display_points;
6308 };
6309
6310 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6311 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6312
6313 for (buffer_snapshot, range, excerpt_id) in
6314 multi_buffer_snapshot.range_to_buffer_ranges(range)
6315 {
6316 let Some(buffer) = project.read_with(cx, |this, cx| {
6317 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6318 }) else {
6319 continue;
6320 };
6321 let breakpoints = breakpoint_store.read(cx).breakpoints(
6322 &buffer,
6323 Some(
6324 buffer_snapshot.anchor_before(range.start)
6325 ..buffer_snapshot.anchor_after(range.end),
6326 ),
6327 buffer_snapshot,
6328 cx,
6329 );
6330 for (anchor, breakpoint) in breakpoints {
6331 let multi_buffer_anchor =
6332 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6333 let position = multi_buffer_anchor
6334 .to_point(&multi_buffer_snapshot)
6335 .to_display_point(&snapshot);
6336
6337 breakpoint_display_points
6338 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6339 }
6340 }
6341
6342 breakpoint_display_points
6343 }
6344
6345 fn breakpoint_context_menu(
6346 &self,
6347 anchor: Anchor,
6348 window: &mut Window,
6349 cx: &mut Context<Self>,
6350 ) -> Entity<ui::ContextMenu> {
6351 let weak_editor = cx.weak_entity();
6352 let focus_handle = self.focus_handle(cx);
6353
6354 let row = self
6355 .buffer
6356 .read(cx)
6357 .snapshot(cx)
6358 .summary_for_anchor::<Point>(&anchor)
6359 .row;
6360
6361 let breakpoint = self
6362 .breakpoint_at_row(row, window, cx)
6363 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6364
6365 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6366 "Edit Log Breakpoint"
6367 } else {
6368 "Set Log Breakpoint"
6369 };
6370
6371 let condition_breakpoint_msg = if breakpoint
6372 .as_ref()
6373 .is_some_and(|bp| bp.1.condition.is_some())
6374 {
6375 "Edit Condition Breakpoint"
6376 } else {
6377 "Set Condition Breakpoint"
6378 };
6379
6380 let hit_condition_breakpoint_msg = if breakpoint
6381 .as_ref()
6382 .is_some_and(|bp| bp.1.hit_condition.is_some())
6383 {
6384 "Edit Hit Condition Breakpoint"
6385 } else {
6386 "Set Hit Condition Breakpoint"
6387 };
6388
6389 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6390 "Unset Breakpoint"
6391 } else {
6392 "Set Breakpoint"
6393 };
6394
6395 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6396 BreakpointState::Enabled => Some("Disable"),
6397 BreakpointState::Disabled => Some("Enable"),
6398 });
6399
6400 let (anchor, breakpoint) =
6401 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6402
6403 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6404 menu.on_blur_subscription(Subscription::new(|| {}))
6405 .context(focus_handle)
6406 .when_some(toggle_state_msg, |this, msg| {
6407 this.entry(msg, None, {
6408 let weak_editor = weak_editor.clone();
6409 let breakpoint = breakpoint.clone();
6410 move |_window, cx| {
6411 weak_editor
6412 .update(cx, |this, cx| {
6413 this.edit_breakpoint_at_anchor(
6414 anchor,
6415 breakpoint.as_ref().clone(),
6416 BreakpointEditAction::InvertState,
6417 cx,
6418 );
6419 })
6420 .log_err();
6421 }
6422 })
6423 })
6424 .entry(set_breakpoint_msg, None, {
6425 let weak_editor = weak_editor.clone();
6426 let breakpoint = breakpoint.clone();
6427 move |_window, cx| {
6428 weak_editor
6429 .update(cx, |this, cx| {
6430 this.edit_breakpoint_at_anchor(
6431 anchor,
6432 breakpoint.as_ref().clone(),
6433 BreakpointEditAction::Toggle,
6434 cx,
6435 );
6436 })
6437 .log_err();
6438 }
6439 })
6440 .entry(log_breakpoint_msg, None, {
6441 let breakpoint = breakpoint.clone();
6442 let weak_editor = weak_editor.clone();
6443 move |window, cx| {
6444 weak_editor
6445 .update(cx, |this, cx| {
6446 this.add_edit_breakpoint_block(
6447 anchor,
6448 breakpoint.as_ref(),
6449 BreakpointPromptEditAction::Log,
6450 window,
6451 cx,
6452 );
6453 })
6454 .log_err();
6455 }
6456 })
6457 .entry(condition_breakpoint_msg, None, {
6458 let breakpoint = breakpoint.clone();
6459 let weak_editor = weak_editor.clone();
6460 move |window, cx| {
6461 weak_editor
6462 .update(cx, |this, cx| {
6463 this.add_edit_breakpoint_block(
6464 anchor,
6465 breakpoint.as_ref(),
6466 BreakpointPromptEditAction::Condition,
6467 window,
6468 cx,
6469 );
6470 })
6471 .log_err();
6472 }
6473 })
6474 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6475 weak_editor
6476 .update(cx, |this, cx| {
6477 this.add_edit_breakpoint_block(
6478 anchor,
6479 breakpoint.as_ref(),
6480 BreakpointPromptEditAction::HitCondition,
6481 window,
6482 cx,
6483 );
6484 })
6485 .log_err();
6486 })
6487 })
6488 }
6489
6490 fn render_breakpoint(
6491 &self,
6492 position: Anchor,
6493 row: DisplayRow,
6494 breakpoint: &Breakpoint,
6495 cx: &mut Context<Self>,
6496 ) -> IconButton {
6497 let (color, icon) = {
6498 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6499 (false, false) => ui::IconName::DebugBreakpoint,
6500 (true, false) => ui::IconName::DebugLogBreakpoint,
6501 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6502 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6503 };
6504
6505 let color = if self
6506 .gutter_breakpoint_indicator
6507 .0
6508 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6509 {
6510 Color::Hint
6511 } else {
6512 Color::Debugger
6513 };
6514
6515 (color, icon)
6516 };
6517
6518 let breakpoint = Arc::from(breakpoint.clone());
6519
6520 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6521 .icon_size(IconSize::XSmall)
6522 .size(ui::ButtonSize::None)
6523 .icon_color(color)
6524 .style(ButtonStyle::Transparent)
6525 .on_click(cx.listener({
6526 let breakpoint = breakpoint.clone();
6527
6528 move |editor, event: &ClickEvent, window, cx| {
6529 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6530 BreakpointEditAction::InvertState
6531 } else {
6532 BreakpointEditAction::Toggle
6533 };
6534
6535 window.focus(&editor.focus_handle(cx));
6536 editor.edit_breakpoint_at_anchor(
6537 position,
6538 breakpoint.as_ref().clone(),
6539 edit_action,
6540 cx,
6541 );
6542 }
6543 }))
6544 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6545 editor.set_breakpoint_context_menu(
6546 row,
6547 Some(position),
6548 event.down.position,
6549 window,
6550 cx,
6551 );
6552 }))
6553 }
6554
6555 fn build_tasks_context(
6556 project: &Entity<Project>,
6557 buffer: &Entity<Buffer>,
6558 buffer_row: u32,
6559 tasks: &Arc<RunnableTasks>,
6560 cx: &mut Context<Self>,
6561 ) -> Task<Option<task::TaskContext>> {
6562 let position = Point::new(buffer_row, tasks.column);
6563 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6564 let location = Location {
6565 buffer: buffer.clone(),
6566 range: range_start..range_start,
6567 };
6568 // Fill in the environmental variables from the tree-sitter captures
6569 let mut captured_task_variables = TaskVariables::default();
6570 for (capture_name, value) in tasks.extra_variables.clone() {
6571 captured_task_variables.insert(
6572 task::VariableName::Custom(capture_name.into()),
6573 value.clone(),
6574 );
6575 }
6576 project.update(cx, |project, cx| {
6577 project.task_store().update(cx, |task_store, cx| {
6578 task_store.task_context_for_location(captured_task_variables, location, cx)
6579 })
6580 })
6581 }
6582
6583 pub fn spawn_nearest_task(
6584 &mut self,
6585 action: &SpawnNearestTask,
6586 window: &mut Window,
6587 cx: &mut Context<Self>,
6588 ) {
6589 let Some((workspace, _)) = self.workspace.clone() else {
6590 return;
6591 };
6592 let Some(project) = self.project.clone() else {
6593 return;
6594 };
6595
6596 // Try to find a closest, enclosing node using tree-sitter that has a
6597 // task
6598 let Some((buffer, buffer_row, tasks)) = self
6599 .find_enclosing_node_task(cx)
6600 // Or find the task that's closest in row-distance.
6601 .or_else(|| self.find_closest_task(cx))
6602 else {
6603 return;
6604 };
6605
6606 let reveal_strategy = action.reveal;
6607 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6608 cx.spawn_in(window, async move |_, cx| {
6609 let context = task_context.await?;
6610 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6611
6612 let resolved = resolved_task.resolved.as_mut()?;
6613 resolved.reveal = reveal_strategy;
6614
6615 workspace
6616 .update(cx, |workspace, cx| {
6617 workspace::tasks::schedule_resolved_task(
6618 workspace,
6619 task_source_kind,
6620 resolved_task,
6621 false,
6622 cx,
6623 );
6624 })
6625 .ok()
6626 })
6627 .detach();
6628 }
6629
6630 fn find_closest_task(
6631 &mut self,
6632 cx: &mut Context<Self>,
6633 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6634 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6635
6636 let ((buffer_id, row), tasks) = self
6637 .tasks
6638 .iter()
6639 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6640
6641 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6642 let tasks = Arc::new(tasks.to_owned());
6643 Some((buffer, *row, tasks))
6644 }
6645
6646 fn find_enclosing_node_task(
6647 &mut self,
6648 cx: &mut Context<Self>,
6649 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6650 let snapshot = self.buffer.read(cx).snapshot(cx);
6651 let offset = self.selections.newest::<usize>(cx).head();
6652 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6653 let buffer_id = excerpt.buffer().remote_id();
6654
6655 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6656 let mut cursor = layer.node().walk();
6657
6658 while cursor.goto_first_child_for_byte(offset).is_some() {
6659 if cursor.node().end_byte() == offset {
6660 cursor.goto_next_sibling();
6661 }
6662 }
6663
6664 // Ascend to the smallest ancestor that contains the range and has a task.
6665 loop {
6666 let node = cursor.node();
6667 let node_range = node.byte_range();
6668 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6669
6670 // Check if this node contains our offset
6671 if node_range.start <= offset && node_range.end >= offset {
6672 // If it contains offset, check for task
6673 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6674 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6675 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6676 }
6677 }
6678
6679 if !cursor.goto_parent() {
6680 break;
6681 }
6682 }
6683 None
6684 }
6685
6686 fn render_run_indicator(
6687 &self,
6688 _style: &EditorStyle,
6689 is_active: bool,
6690 row: DisplayRow,
6691 breakpoint: Option<(Anchor, Breakpoint)>,
6692 cx: &mut Context<Self>,
6693 ) -> IconButton {
6694 let color = Color::Muted;
6695 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6696
6697 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6698 .shape(ui::IconButtonShape::Square)
6699 .icon_size(IconSize::XSmall)
6700 .icon_color(color)
6701 .toggle_state(is_active)
6702 .on_click(cx.listener(move |editor, _e, window, cx| {
6703 window.focus(&editor.focus_handle(cx));
6704 editor.toggle_code_actions(
6705 &ToggleCodeActions {
6706 deployed_from_indicator: Some(row),
6707 },
6708 window,
6709 cx,
6710 );
6711 }))
6712 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6713 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6714 }))
6715 }
6716
6717 pub fn context_menu_visible(&self) -> bool {
6718 !self.edit_prediction_preview_is_active()
6719 && self
6720 .context_menu
6721 .borrow()
6722 .as_ref()
6723 .map_or(false, |menu| menu.visible())
6724 }
6725
6726 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6727 self.context_menu
6728 .borrow()
6729 .as_ref()
6730 .map(|menu| menu.origin())
6731 }
6732
6733 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6734 self.context_menu_options = Some(options);
6735 }
6736
6737 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6738 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6739
6740 fn render_edit_prediction_popover(
6741 &mut self,
6742 text_bounds: &Bounds<Pixels>,
6743 content_origin: gpui::Point<Pixels>,
6744 editor_snapshot: &EditorSnapshot,
6745 visible_row_range: Range<DisplayRow>,
6746 scroll_top: f32,
6747 scroll_bottom: f32,
6748 line_layouts: &[LineWithInvisibles],
6749 line_height: Pixels,
6750 scroll_pixel_position: gpui::Point<Pixels>,
6751 newest_selection_head: Option<DisplayPoint>,
6752 editor_width: Pixels,
6753 style: &EditorStyle,
6754 window: &mut Window,
6755 cx: &mut App,
6756 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6757 let active_inline_completion = self.active_inline_completion.as_ref()?;
6758
6759 if self.edit_prediction_visible_in_cursor_popover(true) {
6760 return None;
6761 }
6762
6763 match &active_inline_completion.completion {
6764 InlineCompletion::Move { target, .. } => {
6765 let target_display_point = target.to_display_point(editor_snapshot);
6766
6767 if self.edit_prediction_requires_modifier() {
6768 if !self.edit_prediction_preview_is_active() {
6769 return None;
6770 }
6771
6772 self.render_edit_prediction_modifier_jump_popover(
6773 text_bounds,
6774 content_origin,
6775 visible_row_range,
6776 line_layouts,
6777 line_height,
6778 scroll_pixel_position,
6779 newest_selection_head,
6780 target_display_point,
6781 window,
6782 cx,
6783 )
6784 } else {
6785 self.render_edit_prediction_eager_jump_popover(
6786 text_bounds,
6787 content_origin,
6788 editor_snapshot,
6789 visible_row_range,
6790 scroll_top,
6791 scroll_bottom,
6792 line_height,
6793 scroll_pixel_position,
6794 target_display_point,
6795 editor_width,
6796 window,
6797 cx,
6798 )
6799 }
6800 }
6801 InlineCompletion::Edit {
6802 display_mode: EditDisplayMode::Inline,
6803 ..
6804 } => None,
6805 InlineCompletion::Edit {
6806 display_mode: EditDisplayMode::TabAccept,
6807 edits,
6808 ..
6809 } => {
6810 let range = &edits.first()?.0;
6811 let target_display_point = range.end.to_display_point(editor_snapshot);
6812
6813 self.render_edit_prediction_end_of_line_popover(
6814 "Accept",
6815 editor_snapshot,
6816 visible_row_range,
6817 target_display_point,
6818 line_height,
6819 scroll_pixel_position,
6820 content_origin,
6821 editor_width,
6822 window,
6823 cx,
6824 )
6825 }
6826 InlineCompletion::Edit {
6827 edits,
6828 edit_preview,
6829 display_mode: EditDisplayMode::DiffPopover,
6830 snapshot,
6831 } => self.render_edit_prediction_diff_popover(
6832 text_bounds,
6833 content_origin,
6834 editor_snapshot,
6835 visible_row_range,
6836 line_layouts,
6837 line_height,
6838 scroll_pixel_position,
6839 newest_selection_head,
6840 editor_width,
6841 style,
6842 edits,
6843 edit_preview,
6844 snapshot,
6845 window,
6846 cx,
6847 ),
6848 }
6849 }
6850
6851 fn render_edit_prediction_modifier_jump_popover(
6852 &mut self,
6853 text_bounds: &Bounds<Pixels>,
6854 content_origin: gpui::Point<Pixels>,
6855 visible_row_range: Range<DisplayRow>,
6856 line_layouts: &[LineWithInvisibles],
6857 line_height: Pixels,
6858 scroll_pixel_position: gpui::Point<Pixels>,
6859 newest_selection_head: Option<DisplayPoint>,
6860 target_display_point: DisplayPoint,
6861 window: &mut Window,
6862 cx: &mut App,
6863 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6864 let scrolled_content_origin =
6865 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6866
6867 const SCROLL_PADDING_Y: Pixels = px(12.);
6868
6869 if target_display_point.row() < visible_row_range.start {
6870 return self.render_edit_prediction_scroll_popover(
6871 |_| SCROLL_PADDING_Y,
6872 IconName::ArrowUp,
6873 visible_row_range,
6874 line_layouts,
6875 newest_selection_head,
6876 scrolled_content_origin,
6877 window,
6878 cx,
6879 );
6880 } else if target_display_point.row() >= visible_row_range.end {
6881 return self.render_edit_prediction_scroll_popover(
6882 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6883 IconName::ArrowDown,
6884 visible_row_range,
6885 line_layouts,
6886 newest_selection_head,
6887 scrolled_content_origin,
6888 window,
6889 cx,
6890 );
6891 }
6892
6893 const POLE_WIDTH: Pixels = px(2.);
6894
6895 let line_layout =
6896 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6897 let target_column = target_display_point.column() as usize;
6898
6899 let target_x = line_layout.x_for_index(target_column);
6900 let target_y =
6901 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6902
6903 let flag_on_right = target_x < text_bounds.size.width / 2.;
6904
6905 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6906 border_color.l += 0.001;
6907
6908 let mut element = v_flex()
6909 .items_end()
6910 .when(flag_on_right, |el| el.items_start())
6911 .child(if flag_on_right {
6912 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6913 .rounded_bl(px(0.))
6914 .rounded_tl(px(0.))
6915 .border_l_2()
6916 .border_color(border_color)
6917 } else {
6918 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6919 .rounded_br(px(0.))
6920 .rounded_tr(px(0.))
6921 .border_r_2()
6922 .border_color(border_color)
6923 })
6924 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6925 .into_any();
6926
6927 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6928
6929 let mut origin = scrolled_content_origin + point(target_x, target_y)
6930 - point(
6931 if flag_on_right {
6932 POLE_WIDTH
6933 } else {
6934 size.width - POLE_WIDTH
6935 },
6936 size.height - line_height,
6937 );
6938
6939 origin.x = origin.x.max(content_origin.x);
6940
6941 element.prepaint_at(origin, window, cx);
6942
6943 Some((element, origin))
6944 }
6945
6946 fn render_edit_prediction_scroll_popover(
6947 &mut self,
6948 to_y: impl Fn(Size<Pixels>) -> Pixels,
6949 scroll_icon: IconName,
6950 visible_row_range: Range<DisplayRow>,
6951 line_layouts: &[LineWithInvisibles],
6952 newest_selection_head: Option<DisplayPoint>,
6953 scrolled_content_origin: gpui::Point<Pixels>,
6954 window: &mut Window,
6955 cx: &mut App,
6956 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6957 let mut element = self
6958 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
6959 .into_any();
6960
6961 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6962
6963 let cursor = newest_selection_head?;
6964 let cursor_row_layout =
6965 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
6966 let cursor_column = cursor.column() as usize;
6967
6968 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
6969
6970 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
6971
6972 element.prepaint_at(origin, window, cx);
6973 Some((element, origin))
6974 }
6975
6976 fn render_edit_prediction_eager_jump_popover(
6977 &mut self,
6978 text_bounds: &Bounds<Pixels>,
6979 content_origin: gpui::Point<Pixels>,
6980 editor_snapshot: &EditorSnapshot,
6981 visible_row_range: Range<DisplayRow>,
6982 scroll_top: f32,
6983 scroll_bottom: f32,
6984 line_height: Pixels,
6985 scroll_pixel_position: gpui::Point<Pixels>,
6986 target_display_point: DisplayPoint,
6987 editor_width: Pixels,
6988 window: &mut Window,
6989 cx: &mut App,
6990 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6991 if target_display_point.row().as_f32() < scroll_top {
6992 let mut element = self
6993 .render_edit_prediction_line_popover(
6994 "Jump to Edit",
6995 Some(IconName::ArrowUp),
6996 window,
6997 cx,
6998 )?
6999 .into_any();
7000
7001 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7002 let offset = point(
7003 (text_bounds.size.width - size.width) / 2.,
7004 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7005 );
7006
7007 let origin = text_bounds.origin + offset;
7008 element.prepaint_at(origin, window, cx);
7009 Some((element, origin))
7010 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7011 let mut element = self
7012 .render_edit_prediction_line_popover(
7013 "Jump to Edit",
7014 Some(IconName::ArrowDown),
7015 window,
7016 cx,
7017 )?
7018 .into_any();
7019
7020 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7021 let offset = point(
7022 (text_bounds.size.width - size.width) / 2.,
7023 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7024 );
7025
7026 let origin = text_bounds.origin + offset;
7027 element.prepaint_at(origin, window, cx);
7028 Some((element, origin))
7029 } else {
7030 self.render_edit_prediction_end_of_line_popover(
7031 "Jump to Edit",
7032 editor_snapshot,
7033 visible_row_range,
7034 target_display_point,
7035 line_height,
7036 scroll_pixel_position,
7037 content_origin,
7038 editor_width,
7039 window,
7040 cx,
7041 )
7042 }
7043 }
7044
7045 fn render_edit_prediction_end_of_line_popover(
7046 self: &mut Editor,
7047 label: &'static str,
7048 editor_snapshot: &EditorSnapshot,
7049 visible_row_range: Range<DisplayRow>,
7050 target_display_point: DisplayPoint,
7051 line_height: Pixels,
7052 scroll_pixel_position: gpui::Point<Pixels>,
7053 content_origin: gpui::Point<Pixels>,
7054 editor_width: Pixels,
7055 window: &mut Window,
7056 cx: &mut App,
7057 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7058 let target_line_end = DisplayPoint::new(
7059 target_display_point.row(),
7060 editor_snapshot.line_len(target_display_point.row()),
7061 );
7062
7063 let mut element = self
7064 .render_edit_prediction_line_popover(label, None, window, cx)?
7065 .into_any();
7066
7067 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7068
7069 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7070
7071 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7072 let mut origin = start_point
7073 + line_origin
7074 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7075 origin.x = origin.x.max(content_origin.x);
7076
7077 let max_x = content_origin.x + editor_width - size.width;
7078
7079 if origin.x > max_x {
7080 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7081
7082 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7083 origin.y += offset;
7084 IconName::ArrowUp
7085 } else {
7086 origin.y -= offset;
7087 IconName::ArrowDown
7088 };
7089
7090 element = self
7091 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7092 .into_any();
7093
7094 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7095
7096 origin.x = content_origin.x + editor_width - size.width - px(2.);
7097 }
7098
7099 element.prepaint_at(origin, window, cx);
7100 Some((element, origin))
7101 }
7102
7103 fn render_edit_prediction_diff_popover(
7104 self: &Editor,
7105 text_bounds: &Bounds<Pixels>,
7106 content_origin: gpui::Point<Pixels>,
7107 editor_snapshot: &EditorSnapshot,
7108 visible_row_range: Range<DisplayRow>,
7109 line_layouts: &[LineWithInvisibles],
7110 line_height: Pixels,
7111 scroll_pixel_position: gpui::Point<Pixels>,
7112 newest_selection_head: Option<DisplayPoint>,
7113 editor_width: Pixels,
7114 style: &EditorStyle,
7115 edits: &Vec<(Range<Anchor>, String)>,
7116 edit_preview: &Option<language::EditPreview>,
7117 snapshot: &language::BufferSnapshot,
7118 window: &mut Window,
7119 cx: &mut App,
7120 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7121 let edit_start = edits
7122 .first()
7123 .unwrap()
7124 .0
7125 .start
7126 .to_display_point(editor_snapshot);
7127 let edit_end = edits
7128 .last()
7129 .unwrap()
7130 .0
7131 .end
7132 .to_display_point(editor_snapshot);
7133
7134 let is_visible = visible_row_range.contains(&edit_start.row())
7135 || visible_row_range.contains(&edit_end.row());
7136 if !is_visible {
7137 return None;
7138 }
7139
7140 let highlighted_edits =
7141 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7142
7143 let styled_text = highlighted_edits.to_styled_text(&style.text);
7144 let line_count = highlighted_edits.text.lines().count();
7145
7146 const BORDER_WIDTH: Pixels = px(1.);
7147
7148 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7149 let has_keybind = keybind.is_some();
7150
7151 let mut element = h_flex()
7152 .items_start()
7153 .child(
7154 h_flex()
7155 .bg(cx.theme().colors().editor_background)
7156 .border(BORDER_WIDTH)
7157 .shadow_sm()
7158 .border_color(cx.theme().colors().border)
7159 .rounded_l_lg()
7160 .when(line_count > 1, |el| el.rounded_br_lg())
7161 .pr_1()
7162 .child(styled_text),
7163 )
7164 .child(
7165 h_flex()
7166 .h(line_height + BORDER_WIDTH * 2.)
7167 .px_1p5()
7168 .gap_1()
7169 // Workaround: For some reason, there's a gap if we don't do this
7170 .ml(-BORDER_WIDTH)
7171 .shadow(smallvec![gpui::BoxShadow {
7172 color: gpui::black().opacity(0.05),
7173 offset: point(px(1.), px(1.)),
7174 blur_radius: px(2.),
7175 spread_radius: px(0.),
7176 }])
7177 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7178 .border(BORDER_WIDTH)
7179 .border_color(cx.theme().colors().border)
7180 .rounded_r_lg()
7181 .id("edit_prediction_diff_popover_keybind")
7182 .when(!has_keybind, |el| {
7183 let status_colors = cx.theme().status();
7184
7185 el.bg(status_colors.error_background)
7186 .border_color(status_colors.error.opacity(0.6))
7187 .child(Icon::new(IconName::Info).color(Color::Error))
7188 .cursor_default()
7189 .hoverable_tooltip(move |_window, cx| {
7190 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7191 })
7192 })
7193 .children(keybind),
7194 )
7195 .into_any();
7196
7197 let longest_row =
7198 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7199 let longest_line_width = if visible_row_range.contains(&longest_row) {
7200 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7201 } else {
7202 layout_line(
7203 longest_row,
7204 editor_snapshot,
7205 style,
7206 editor_width,
7207 |_| false,
7208 window,
7209 cx,
7210 )
7211 .width
7212 };
7213
7214 let viewport_bounds =
7215 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7216 right: -EditorElement::SCROLLBAR_WIDTH,
7217 ..Default::default()
7218 });
7219
7220 let x_after_longest =
7221 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7222 - scroll_pixel_position.x;
7223
7224 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7225
7226 // Fully visible if it can be displayed within the window (allow overlapping other
7227 // panes). However, this is only allowed if the popover starts within text_bounds.
7228 let can_position_to_the_right = x_after_longest < text_bounds.right()
7229 && x_after_longest + element_bounds.width < viewport_bounds.right();
7230
7231 let mut origin = if can_position_to_the_right {
7232 point(
7233 x_after_longest,
7234 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7235 - scroll_pixel_position.y,
7236 )
7237 } else {
7238 let cursor_row = newest_selection_head.map(|head| head.row());
7239 let above_edit = edit_start
7240 .row()
7241 .0
7242 .checked_sub(line_count as u32)
7243 .map(DisplayRow);
7244 let below_edit = Some(edit_end.row() + 1);
7245 let above_cursor =
7246 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7247 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7248
7249 // Place the edit popover adjacent to the edit if there is a location
7250 // available that is onscreen and does not obscure the cursor. Otherwise,
7251 // place it adjacent to the cursor.
7252 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7253 .into_iter()
7254 .flatten()
7255 .find(|&start_row| {
7256 let end_row = start_row + line_count as u32;
7257 visible_row_range.contains(&start_row)
7258 && visible_row_range.contains(&end_row)
7259 && cursor_row.map_or(true, |cursor_row| {
7260 !((start_row..end_row).contains(&cursor_row))
7261 })
7262 })?;
7263
7264 content_origin
7265 + point(
7266 -scroll_pixel_position.x,
7267 row_target.as_f32() * line_height - scroll_pixel_position.y,
7268 )
7269 };
7270
7271 origin.x -= BORDER_WIDTH;
7272
7273 window.defer_draw(element, origin, 1);
7274
7275 // Do not return an element, since it will already be drawn due to defer_draw.
7276 None
7277 }
7278
7279 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7280 px(30.)
7281 }
7282
7283 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7284 if self.read_only(cx) {
7285 cx.theme().players().read_only()
7286 } else {
7287 self.style.as_ref().unwrap().local_player
7288 }
7289 }
7290
7291 fn render_edit_prediction_accept_keybind(
7292 &self,
7293 window: &mut Window,
7294 cx: &App,
7295 ) -> Option<AnyElement> {
7296 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7297 let accept_keystroke = accept_binding.keystroke()?;
7298
7299 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7300
7301 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7302 Color::Accent
7303 } else {
7304 Color::Muted
7305 };
7306
7307 h_flex()
7308 .px_0p5()
7309 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7310 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7311 .text_size(TextSize::XSmall.rems(cx))
7312 .child(h_flex().children(ui::render_modifiers(
7313 &accept_keystroke.modifiers,
7314 PlatformStyle::platform(),
7315 Some(modifiers_color),
7316 Some(IconSize::XSmall.rems().into()),
7317 true,
7318 )))
7319 .when(is_platform_style_mac, |parent| {
7320 parent.child(accept_keystroke.key.clone())
7321 })
7322 .when(!is_platform_style_mac, |parent| {
7323 parent.child(
7324 Key::new(
7325 util::capitalize(&accept_keystroke.key),
7326 Some(Color::Default),
7327 )
7328 .size(Some(IconSize::XSmall.rems().into())),
7329 )
7330 })
7331 .into_any()
7332 .into()
7333 }
7334
7335 fn render_edit_prediction_line_popover(
7336 &self,
7337 label: impl Into<SharedString>,
7338 icon: Option<IconName>,
7339 window: &mut Window,
7340 cx: &App,
7341 ) -> Option<Stateful<Div>> {
7342 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7343
7344 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7345 let has_keybind = keybind.is_some();
7346
7347 let result = h_flex()
7348 .id("ep-line-popover")
7349 .py_0p5()
7350 .pl_1()
7351 .pr(padding_right)
7352 .gap_1()
7353 .rounded_md()
7354 .border_1()
7355 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7356 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7357 .shadow_sm()
7358 .when(!has_keybind, |el| {
7359 let status_colors = cx.theme().status();
7360
7361 el.bg(status_colors.error_background)
7362 .border_color(status_colors.error.opacity(0.6))
7363 .pl_2()
7364 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7365 .cursor_default()
7366 .hoverable_tooltip(move |_window, cx| {
7367 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7368 })
7369 })
7370 .children(keybind)
7371 .child(
7372 Label::new(label)
7373 .size(LabelSize::Small)
7374 .when(!has_keybind, |el| {
7375 el.color(cx.theme().status().error.into()).strikethrough()
7376 }),
7377 )
7378 .when(!has_keybind, |el| {
7379 el.child(
7380 h_flex().ml_1().child(
7381 Icon::new(IconName::Info)
7382 .size(IconSize::Small)
7383 .color(cx.theme().status().error.into()),
7384 ),
7385 )
7386 })
7387 .when_some(icon, |element, icon| {
7388 element.child(
7389 div()
7390 .mt(px(1.5))
7391 .child(Icon::new(icon).size(IconSize::Small)),
7392 )
7393 });
7394
7395 Some(result)
7396 }
7397
7398 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7399 let accent_color = cx.theme().colors().text_accent;
7400 let editor_bg_color = cx.theme().colors().editor_background;
7401 editor_bg_color.blend(accent_color.opacity(0.1))
7402 }
7403
7404 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7405 let accent_color = cx.theme().colors().text_accent;
7406 let editor_bg_color = cx.theme().colors().editor_background;
7407 editor_bg_color.blend(accent_color.opacity(0.6))
7408 }
7409
7410 fn render_edit_prediction_cursor_popover(
7411 &self,
7412 min_width: Pixels,
7413 max_width: Pixels,
7414 cursor_point: Point,
7415 style: &EditorStyle,
7416 accept_keystroke: Option<&gpui::Keystroke>,
7417 _window: &Window,
7418 cx: &mut Context<Editor>,
7419 ) -> Option<AnyElement> {
7420 let provider = self.edit_prediction_provider.as_ref()?;
7421
7422 if provider.provider.needs_terms_acceptance(cx) {
7423 return Some(
7424 h_flex()
7425 .min_w(min_width)
7426 .flex_1()
7427 .px_2()
7428 .py_1()
7429 .gap_3()
7430 .elevation_2(cx)
7431 .hover(|style| style.bg(cx.theme().colors().element_hover))
7432 .id("accept-terms")
7433 .cursor_pointer()
7434 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7435 .on_click(cx.listener(|this, _event, window, cx| {
7436 cx.stop_propagation();
7437 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7438 window.dispatch_action(
7439 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7440 cx,
7441 );
7442 }))
7443 .child(
7444 h_flex()
7445 .flex_1()
7446 .gap_2()
7447 .child(Icon::new(IconName::ZedPredict))
7448 .child(Label::new("Accept Terms of Service"))
7449 .child(div().w_full())
7450 .child(
7451 Icon::new(IconName::ArrowUpRight)
7452 .color(Color::Muted)
7453 .size(IconSize::Small),
7454 )
7455 .into_any_element(),
7456 )
7457 .into_any(),
7458 );
7459 }
7460
7461 let is_refreshing = provider.provider.is_refreshing(cx);
7462
7463 fn pending_completion_container() -> Div {
7464 h_flex()
7465 .h_full()
7466 .flex_1()
7467 .gap_2()
7468 .child(Icon::new(IconName::ZedPredict))
7469 }
7470
7471 let completion = match &self.active_inline_completion {
7472 Some(prediction) => {
7473 if !self.has_visible_completions_menu() {
7474 const RADIUS: Pixels = px(6.);
7475 const BORDER_WIDTH: Pixels = px(1.);
7476
7477 return Some(
7478 h_flex()
7479 .elevation_2(cx)
7480 .border(BORDER_WIDTH)
7481 .border_color(cx.theme().colors().border)
7482 .when(accept_keystroke.is_none(), |el| {
7483 el.border_color(cx.theme().status().error)
7484 })
7485 .rounded(RADIUS)
7486 .rounded_tl(px(0.))
7487 .overflow_hidden()
7488 .child(div().px_1p5().child(match &prediction.completion {
7489 InlineCompletion::Move { target, snapshot } => {
7490 use text::ToPoint as _;
7491 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7492 {
7493 Icon::new(IconName::ZedPredictDown)
7494 } else {
7495 Icon::new(IconName::ZedPredictUp)
7496 }
7497 }
7498 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7499 }))
7500 .child(
7501 h_flex()
7502 .gap_1()
7503 .py_1()
7504 .px_2()
7505 .rounded_r(RADIUS - BORDER_WIDTH)
7506 .border_l_1()
7507 .border_color(cx.theme().colors().border)
7508 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7509 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7510 el.child(
7511 Label::new("Hold")
7512 .size(LabelSize::Small)
7513 .when(accept_keystroke.is_none(), |el| {
7514 el.strikethrough()
7515 })
7516 .line_height_style(LineHeightStyle::UiLabel),
7517 )
7518 })
7519 .id("edit_prediction_cursor_popover_keybind")
7520 .when(accept_keystroke.is_none(), |el| {
7521 let status_colors = cx.theme().status();
7522
7523 el.bg(status_colors.error_background)
7524 .border_color(status_colors.error.opacity(0.6))
7525 .child(Icon::new(IconName::Info).color(Color::Error))
7526 .cursor_default()
7527 .hoverable_tooltip(move |_window, cx| {
7528 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7529 .into()
7530 })
7531 })
7532 .when_some(
7533 accept_keystroke.as_ref(),
7534 |el, accept_keystroke| {
7535 el.child(h_flex().children(ui::render_modifiers(
7536 &accept_keystroke.modifiers,
7537 PlatformStyle::platform(),
7538 Some(Color::Default),
7539 Some(IconSize::XSmall.rems().into()),
7540 false,
7541 )))
7542 },
7543 ),
7544 )
7545 .into_any(),
7546 );
7547 }
7548
7549 self.render_edit_prediction_cursor_popover_preview(
7550 prediction,
7551 cursor_point,
7552 style,
7553 cx,
7554 )?
7555 }
7556
7557 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7558 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7559 stale_completion,
7560 cursor_point,
7561 style,
7562 cx,
7563 )?,
7564
7565 None => {
7566 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7567 }
7568 },
7569
7570 None => pending_completion_container().child(Label::new("No Prediction")),
7571 };
7572
7573 let completion = if is_refreshing {
7574 completion
7575 .with_animation(
7576 "loading-completion",
7577 Animation::new(Duration::from_secs(2))
7578 .repeat()
7579 .with_easing(pulsating_between(0.4, 0.8)),
7580 |label, delta| label.opacity(delta),
7581 )
7582 .into_any_element()
7583 } else {
7584 completion.into_any_element()
7585 };
7586
7587 let has_completion = self.active_inline_completion.is_some();
7588
7589 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7590 Some(
7591 h_flex()
7592 .min_w(min_width)
7593 .max_w(max_width)
7594 .flex_1()
7595 .elevation_2(cx)
7596 .border_color(cx.theme().colors().border)
7597 .child(
7598 div()
7599 .flex_1()
7600 .py_1()
7601 .px_2()
7602 .overflow_hidden()
7603 .child(completion),
7604 )
7605 .when_some(accept_keystroke, |el, accept_keystroke| {
7606 if !accept_keystroke.modifiers.modified() {
7607 return el;
7608 }
7609
7610 el.child(
7611 h_flex()
7612 .h_full()
7613 .border_l_1()
7614 .rounded_r_lg()
7615 .border_color(cx.theme().colors().border)
7616 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7617 .gap_1()
7618 .py_1()
7619 .px_2()
7620 .child(
7621 h_flex()
7622 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7623 .when(is_platform_style_mac, |parent| parent.gap_1())
7624 .child(h_flex().children(ui::render_modifiers(
7625 &accept_keystroke.modifiers,
7626 PlatformStyle::platform(),
7627 Some(if !has_completion {
7628 Color::Muted
7629 } else {
7630 Color::Default
7631 }),
7632 None,
7633 false,
7634 ))),
7635 )
7636 .child(Label::new("Preview").into_any_element())
7637 .opacity(if has_completion { 1.0 } else { 0.4 }),
7638 )
7639 })
7640 .into_any(),
7641 )
7642 }
7643
7644 fn render_edit_prediction_cursor_popover_preview(
7645 &self,
7646 completion: &InlineCompletionState,
7647 cursor_point: Point,
7648 style: &EditorStyle,
7649 cx: &mut Context<Editor>,
7650 ) -> Option<Div> {
7651 use text::ToPoint as _;
7652
7653 fn render_relative_row_jump(
7654 prefix: impl Into<String>,
7655 current_row: u32,
7656 target_row: u32,
7657 ) -> Div {
7658 let (row_diff, arrow) = if target_row < current_row {
7659 (current_row - target_row, IconName::ArrowUp)
7660 } else {
7661 (target_row - current_row, IconName::ArrowDown)
7662 };
7663
7664 h_flex()
7665 .child(
7666 Label::new(format!("{}{}", prefix.into(), row_diff))
7667 .color(Color::Muted)
7668 .size(LabelSize::Small),
7669 )
7670 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7671 }
7672
7673 match &completion.completion {
7674 InlineCompletion::Move {
7675 target, snapshot, ..
7676 } => Some(
7677 h_flex()
7678 .px_2()
7679 .gap_2()
7680 .flex_1()
7681 .child(
7682 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7683 Icon::new(IconName::ZedPredictDown)
7684 } else {
7685 Icon::new(IconName::ZedPredictUp)
7686 },
7687 )
7688 .child(Label::new("Jump to Edit")),
7689 ),
7690
7691 InlineCompletion::Edit {
7692 edits,
7693 edit_preview,
7694 snapshot,
7695 display_mode: _,
7696 } => {
7697 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7698
7699 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7700 &snapshot,
7701 &edits,
7702 edit_preview.as_ref()?,
7703 true,
7704 cx,
7705 )
7706 .first_line_preview();
7707
7708 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7709 .with_default_highlights(&style.text, highlighted_edits.highlights);
7710
7711 let preview = h_flex()
7712 .gap_1()
7713 .min_w_16()
7714 .child(styled_text)
7715 .when(has_more_lines, |parent| parent.child("…"));
7716
7717 let left = if first_edit_row != cursor_point.row {
7718 render_relative_row_jump("", cursor_point.row, first_edit_row)
7719 .into_any_element()
7720 } else {
7721 Icon::new(IconName::ZedPredict).into_any_element()
7722 };
7723
7724 Some(
7725 h_flex()
7726 .h_full()
7727 .flex_1()
7728 .gap_2()
7729 .pr_1()
7730 .overflow_x_hidden()
7731 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7732 .child(left)
7733 .child(preview),
7734 )
7735 }
7736 }
7737 }
7738
7739 fn render_context_menu(
7740 &self,
7741 style: &EditorStyle,
7742 max_height_in_lines: u32,
7743 window: &mut Window,
7744 cx: &mut Context<Editor>,
7745 ) -> Option<AnyElement> {
7746 let menu = self.context_menu.borrow();
7747 let menu = menu.as_ref()?;
7748 if !menu.visible() {
7749 return None;
7750 };
7751 Some(menu.render(style, max_height_in_lines, window, cx))
7752 }
7753
7754 fn render_context_menu_aside(
7755 &mut self,
7756 max_size: Size<Pixels>,
7757 window: &mut Window,
7758 cx: &mut Context<Editor>,
7759 ) -> Option<AnyElement> {
7760 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7761 if menu.visible() {
7762 menu.render_aside(self, max_size, window, cx)
7763 } else {
7764 None
7765 }
7766 })
7767 }
7768
7769 fn hide_context_menu(
7770 &mut self,
7771 window: &mut Window,
7772 cx: &mut Context<Self>,
7773 ) -> Option<CodeContextMenu> {
7774 cx.notify();
7775 self.completion_tasks.clear();
7776 let context_menu = self.context_menu.borrow_mut().take();
7777 self.stale_inline_completion_in_menu.take();
7778 self.update_visible_inline_completion(window, cx);
7779 context_menu
7780 }
7781
7782 fn show_snippet_choices(
7783 &mut self,
7784 choices: &Vec<String>,
7785 selection: Range<Anchor>,
7786 cx: &mut Context<Self>,
7787 ) {
7788 if selection.start.buffer_id.is_none() {
7789 return;
7790 }
7791 let buffer_id = selection.start.buffer_id.unwrap();
7792 let buffer = self.buffer().read(cx).buffer(buffer_id);
7793 let id = post_inc(&mut self.next_completion_id);
7794
7795 if let Some(buffer) = buffer {
7796 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7797 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7798 ));
7799 }
7800 }
7801
7802 pub fn insert_snippet(
7803 &mut self,
7804 insertion_ranges: &[Range<usize>],
7805 snippet: Snippet,
7806 window: &mut Window,
7807 cx: &mut Context<Self>,
7808 ) -> Result<()> {
7809 struct Tabstop<T> {
7810 is_end_tabstop: bool,
7811 ranges: Vec<Range<T>>,
7812 choices: Option<Vec<String>>,
7813 }
7814
7815 let tabstops = self.buffer.update(cx, |buffer, cx| {
7816 let snippet_text: Arc<str> = snippet.text.clone().into();
7817 let edits = insertion_ranges
7818 .iter()
7819 .cloned()
7820 .map(|range| (range, snippet_text.clone()));
7821 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
7822
7823 let snapshot = &*buffer.read(cx);
7824 let snippet = &snippet;
7825 snippet
7826 .tabstops
7827 .iter()
7828 .map(|tabstop| {
7829 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7830 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7831 });
7832 let mut tabstop_ranges = tabstop
7833 .ranges
7834 .iter()
7835 .flat_map(|tabstop_range| {
7836 let mut delta = 0_isize;
7837 insertion_ranges.iter().map(move |insertion_range| {
7838 let insertion_start = insertion_range.start as isize + delta;
7839 delta +=
7840 snippet.text.len() as isize - insertion_range.len() as isize;
7841
7842 let start = ((insertion_start + tabstop_range.start) as usize)
7843 .min(snapshot.len());
7844 let end = ((insertion_start + tabstop_range.end) as usize)
7845 .min(snapshot.len());
7846 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7847 })
7848 })
7849 .collect::<Vec<_>>();
7850 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7851
7852 Tabstop {
7853 is_end_tabstop,
7854 ranges: tabstop_ranges,
7855 choices: tabstop.choices.clone(),
7856 }
7857 })
7858 .collect::<Vec<_>>()
7859 });
7860 if let Some(tabstop) = tabstops.first() {
7861 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7862 s.select_ranges(tabstop.ranges.iter().cloned());
7863 });
7864
7865 if let Some(choices) = &tabstop.choices {
7866 if let Some(selection) = tabstop.ranges.first() {
7867 self.show_snippet_choices(choices, selection.clone(), cx)
7868 }
7869 }
7870
7871 // If we're already at the last tabstop and it's at the end of the snippet,
7872 // we're done, we don't need to keep the state around.
7873 if !tabstop.is_end_tabstop {
7874 let choices = tabstops
7875 .iter()
7876 .map(|tabstop| tabstop.choices.clone())
7877 .collect();
7878
7879 let ranges = tabstops
7880 .into_iter()
7881 .map(|tabstop| tabstop.ranges)
7882 .collect::<Vec<_>>();
7883
7884 self.snippet_stack.push(SnippetState {
7885 active_index: 0,
7886 ranges,
7887 choices,
7888 });
7889 }
7890
7891 // Check whether the just-entered snippet ends with an auto-closable bracket.
7892 if self.autoclose_regions.is_empty() {
7893 let snapshot = self.buffer.read(cx).snapshot(cx);
7894 for selection in &mut self.selections.all::<Point>(cx) {
7895 let selection_head = selection.head();
7896 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7897 continue;
7898 };
7899
7900 let mut bracket_pair = None;
7901 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7902 let prev_chars = snapshot
7903 .reversed_chars_at(selection_head)
7904 .collect::<String>();
7905 for (pair, enabled) in scope.brackets() {
7906 if enabled
7907 && pair.close
7908 && prev_chars.starts_with(pair.start.as_str())
7909 && next_chars.starts_with(pair.end.as_str())
7910 {
7911 bracket_pair = Some(pair.clone());
7912 break;
7913 }
7914 }
7915 if let Some(pair) = bracket_pair {
7916 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
7917 let autoclose_enabled =
7918 self.use_autoclose && snapshot_settings.use_autoclose;
7919 if autoclose_enabled {
7920 let start = snapshot.anchor_after(selection_head);
7921 let end = snapshot.anchor_after(selection_head);
7922 self.autoclose_regions.push(AutocloseRegion {
7923 selection_id: selection.id,
7924 range: start..end,
7925 pair,
7926 });
7927 }
7928 }
7929 }
7930 }
7931 }
7932 Ok(())
7933 }
7934
7935 pub fn move_to_next_snippet_tabstop(
7936 &mut self,
7937 window: &mut Window,
7938 cx: &mut Context<Self>,
7939 ) -> bool {
7940 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7941 }
7942
7943 pub fn move_to_prev_snippet_tabstop(
7944 &mut self,
7945 window: &mut Window,
7946 cx: &mut Context<Self>,
7947 ) -> bool {
7948 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7949 }
7950
7951 pub fn move_to_snippet_tabstop(
7952 &mut self,
7953 bias: Bias,
7954 window: &mut Window,
7955 cx: &mut Context<Self>,
7956 ) -> bool {
7957 if let Some(mut snippet) = self.snippet_stack.pop() {
7958 match bias {
7959 Bias::Left => {
7960 if snippet.active_index > 0 {
7961 snippet.active_index -= 1;
7962 } else {
7963 self.snippet_stack.push(snippet);
7964 return false;
7965 }
7966 }
7967 Bias::Right => {
7968 if snippet.active_index + 1 < snippet.ranges.len() {
7969 snippet.active_index += 1;
7970 } else {
7971 self.snippet_stack.push(snippet);
7972 return false;
7973 }
7974 }
7975 }
7976 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
7977 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7978 s.select_anchor_ranges(current_ranges.iter().cloned())
7979 });
7980
7981 if let Some(choices) = &snippet.choices[snippet.active_index] {
7982 if let Some(selection) = current_ranges.first() {
7983 self.show_snippet_choices(&choices, selection.clone(), cx);
7984 }
7985 }
7986
7987 // If snippet state is not at the last tabstop, push it back on the stack
7988 if snippet.active_index + 1 < snippet.ranges.len() {
7989 self.snippet_stack.push(snippet);
7990 }
7991 return true;
7992 }
7993 }
7994
7995 false
7996 }
7997
7998 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7999 self.transact(window, cx, |this, window, cx| {
8000 this.select_all(&SelectAll, window, cx);
8001 this.insert("", window, cx);
8002 });
8003 }
8004
8005 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8006 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8007 self.transact(window, cx, |this, window, cx| {
8008 this.select_autoclose_pair(window, cx);
8009 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8010 if !this.linked_edit_ranges.is_empty() {
8011 let selections = this.selections.all::<MultiBufferPoint>(cx);
8012 let snapshot = this.buffer.read(cx).snapshot(cx);
8013
8014 for selection in selections.iter() {
8015 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8016 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8017 if selection_start.buffer_id != selection_end.buffer_id {
8018 continue;
8019 }
8020 if let Some(ranges) =
8021 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8022 {
8023 for (buffer, entries) in ranges {
8024 linked_ranges.entry(buffer).or_default().extend(entries);
8025 }
8026 }
8027 }
8028 }
8029
8030 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8031 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8032 for selection in &mut selections {
8033 if selection.is_empty() {
8034 let old_head = selection.head();
8035 let mut new_head =
8036 movement::left(&display_map, old_head.to_display_point(&display_map))
8037 .to_point(&display_map);
8038 if let Some((buffer, line_buffer_range)) = display_map
8039 .buffer_snapshot
8040 .buffer_line_for_row(MultiBufferRow(old_head.row))
8041 {
8042 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8043 let indent_len = match indent_size.kind {
8044 IndentKind::Space => {
8045 buffer.settings_at(line_buffer_range.start, cx).tab_size
8046 }
8047 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8048 };
8049 if old_head.column <= indent_size.len && old_head.column > 0 {
8050 let indent_len = indent_len.get();
8051 new_head = cmp::min(
8052 new_head,
8053 MultiBufferPoint::new(
8054 old_head.row,
8055 ((old_head.column - 1) / indent_len) * indent_len,
8056 ),
8057 );
8058 }
8059 }
8060
8061 selection.set_head(new_head, SelectionGoal::None);
8062 }
8063 }
8064
8065 this.signature_help_state.set_backspace_pressed(true);
8066 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8067 s.select(selections)
8068 });
8069 this.insert("", window, cx);
8070 let empty_str: Arc<str> = Arc::from("");
8071 for (buffer, edits) in linked_ranges {
8072 let snapshot = buffer.read(cx).snapshot();
8073 use text::ToPoint as TP;
8074
8075 let edits = edits
8076 .into_iter()
8077 .map(|range| {
8078 let end_point = TP::to_point(&range.end, &snapshot);
8079 let mut start_point = TP::to_point(&range.start, &snapshot);
8080
8081 if end_point == start_point {
8082 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8083 .saturating_sub(1);
8084 start_point =
8085 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8086 };
8087
8088 (start_point..end_point, empty_str.clone())
8089 })
8090 .sorted_by_key(|(range, _)| range.start)
8091 .collect::<Vec<_>>();
8092 buffer.update(cx, |this, cx| {
8093 this.edit(edits, None, cx);
8094 })
8095 }
8096 this.refresh_inline_completion(true, false, window, cx);
8097 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8098 });
8099 }
8100
8101 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8102 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8103 self.transact(window, cx, |this, window, cx| {
8104 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8105 s.move_with(|map, selection| {
8106 if selection.is_empty() {
8107 let cursor = movement::right(map, selection.head());
8108 selection.end = cursor;
8109 selection.reversed = true;
8110 selection.goal = SelectionGoal::None;
8111 }
8112 })
8113 });
8114 this.insert("", window, cx);
8115 this.refresh_inline_completion(true, false, window, cx);
8116 });
8117 }
8118
8119 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8120 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8121 if self.move_to_prev_snippet_tabstop(window, cx) {
8122 return;
8123 }
8124 self.outdent(&Outdent, window, cx);
8125 }
8126
8127 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8128 if self.move_to_next_snippet_tabstop(window, cx) {
8129 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8130 return;
8131 }
8132 if self.read_only(cx) {
8133 return;
8134 }
8135 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8136 let mut selections = self.selections.all_adjusted(cx);
8137 let buffer = self.buffer.read(cx);
8138 let snapshot = buffer.snapshot(cx);
8139 let rows_iter = selections.iter().map(|s| s.head().row);
8140 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8141
8142 let mut edits = Vec::new();
8143 let mut prev_edited_row = 0;
8144 let mut row_delta = 0;
8145 for selection in &mut selections {
8146 if selection.start.row != prev_edited_row {
8147 row_delta = 0;
8148 }
8149 prev_edited_row = selection.end.row;
8150
8151 // If the selection is non-empty, then increase the indentation of the selected lines.
8152 if !selection.is_empty() {
8153 row_delta =
8154 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8155 continue;
8156 }
8157
8158 // If the selection is empty and the cursor is in the leading whitespace before the
8159 // suggested indentation, then auto-indent the line.
8160 let cursor = selection.head();
8161 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8162 if let Some(suggested_indent) =
8163 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8164 {
8165 if cursor.column < suggested_indent.len
8166 && cursor.column <= current_indent.len
8167 && current_indent.len <= suggested_indent.len
8168 {
8169 selection.start = Point::new(cursor.row, suggested_indent.len);
8170 selection.end = selection.start;
8171 if row_delta == 0 {
8172 edits.extend(Buffer::edit_for_indent_size_adjustment(
8173 cursor.row,
8174 current_indent,
8175 suggested_indent,
8176 ));
8177 row_delta = suggested_indent.len - current_indent.len;
8178 }
8179 continue;
8180 }
8181 }
8182
8183 // Otherwise, insert a hard or soft tab.
8184 let settings = buffer.language_settings_at(cursor, cx);
8185 let tab_size = if settings.hard_tabs {
8186 IndentSize::tab()
8187 } else {
8188 let tab_size = settings.tab_size.get();
8189 let char_column = snapshot
8190 .text_for_range(Point::new(cursor.row, 0)..cursor)
8191 .flat_map(str::chars)
8192 .count()
8193 + row_delta as usize;
8194 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
8195 IndentSize::spaces(chars_to_next_tab_stop)
8196 };
8197 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8198 selection.end = selection.start;
8199 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8200 row_delta += tab_size.len;
8201 }
8202
8203 self.transact(window, cx, |this, window, cx| {
8204 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8205 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8206 s.select(selections)
8207 });
8208 this.refresh_inline_completion(true, false, window, cx);
8209 });
8210 }
8211
8212 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8213 if self.read_only(cx) {
8214 return;
8215 }
8216 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8217 let mut selections = self.selections.all::<Point>(cx);
8218 let mut prev_edited_row = 0;
8219 let mut row_delta = 0;
8220 let mut edits = Vec::new();
8221 let buffer = self.buffer.read(cx);
8222 let snapshot = buffer.snapshot(cx);
8223 for selection in &mut selections {
8224 if selection.start.row != prev_edited_row {
8225 row_delta = 0;
8226 }
8227 prev_edited_row = selection.end.row;
8228
8229 row_delta =
8230 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8231 }
8232
8233 self.transact(window, cx, |this, window, cx| {
8234 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8235 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8236 s.select(selections)
8237 });
8238 });
8239 }
8240
8241 fn indent_selection(
8242 buffer: &MultiBuffer,
8243 snapshot: &MultiBufferSnapshot,
8244 selection: &mut Selection<Point>,
8245 edits: &mut Vec<(Range<Point>, String)>,
8246 delta_for_start_row: u32,
8247 cx: &App,
8248 ) -> u32 {
8249 let settings = buffer.language_settings_at(selection.start, cx);
8250 let tab_size = settings.tab_size.get();
8251 let indent_kind = if settings.hard_tabs {
8252 IndentKind::Tab
8253 } else {
8254 IndentKind::Space
8255 };
8256 let mut start_row = selection.start.row;
8257 let mut end_row = selection.end.row + 1;
8258
8259 // If a selection ends at the beginning of a line, don't indent
8260 // that last line.
8261 if selection.end.column == 0 && selection.end.row > selection.start.row {
8262 end_row -= 1;
8263 }
8264
8265 // Avoid re-indenting a row that has already been indented by a
8266 // previous selection, but still update this selection's column
8267 // to reflect that indentation.
8268 if delta_for_start_row > 0 {
8269 start_row += 1;
8270 selection.start.column += delta_for_start_row;
8271 if selection.end.row == selection.start.row {
8272 selection.end.column += delta_for_start_row;
8273 }
8274 }
8275
8276 let mut delta_for_end_row = 0;
8277 let has_multiple_rows = start_row + 1 != end_row;
8278 for row in start_row..end_row {
8279 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8280 let indent_delta = match (current_indent.kind, indent_kind) {
8281 (IndentKind::Space, IndentKind::Space) => {
8282 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8283 IndentSize::spaces(columns_to_next_tab_stop)
8284 }
8285 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8286 (_, IndentKind::Tab) => IndentSize::tab(),
8287 };
8288
8289 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8290 0
8291 } else {
8292 selection.start.column
8293 };
8294 let row_start = Point::new(row, start);
8295 edits.push((
8296 row_start..row_start,
8297 indent_delta.chars().collect::<String>(),
8298 ));
8299
8300 // Update this selection's endpoints to reflect the indentation.
8301 if row == selection.start.row {
8302 selection.start.column += indent_delta.len;
8303 }
8304 if row == selection.end.row {
8305 selection.end.column += indent_delta.len;
8306 delta_for_end_row = indent_delta.len;
8307 }
8308 }
8309
8310 if selection.start.row == selection.end.row {
8311 delta_for_start_row + delta_for_end_row
8312 } else {
8313 delta_for_end_row
8314 }
8315 }
8316
8317 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8318 if self.read_only(cx) {
8319 return;
8320 }
8321 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8322 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8323 let selections = self.selections.all::<Point>(cx);
8324 let mut deletion_ranges = Vec::new();
8325 let mut last_outdent = None;
8326 {
8327 let buffer = self.buffer.read(cx);
8328 let snapshot = buffer.snapshot(cx);
8329 for selection in &selections {
8330 let settings = buffer.language_settings_at(selection.start, cx);
8331 let tab_size = settings.tab_size.get();
8332 let mut rows = selection.spanned_rows(false, &display_map);
8333
8334 // Avoid re-outdenting a row that has already been outdented by a
8335 // previous selection.
8336 if let Some(last_row) = last_outdent {
8337 if last_row == rows.start {
8338 rows.start = rows.start.next_row();
8339 }
8340 }
8341 let has_multiple_rows = rows.len() > 1;
8342 for row in rows.iter_rows() {
8343 let indent_size = snapshot.indent_size_for_line(row);
8344 if indent_size.len > 0 {
8345 let deletion_len = match indent_size.kind {
8346 IndentKind::Space => {
8347 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8348 if columns_to_prev_tab_stop == 0 {
8349 tab_size
8350 } else {
8351 columns_to_prev_tab_stop
8352 }
8353 }
8354 IndentKind::Tab => 1,
8355 };
8356 let start = if has_multiple_rows
8357 || deletion_len > selection.start.column
8358 || indent_size.len < selection.start.column
8359 {
8360 0
8361 } else {
8362 selection.start.column - deletion_len
8363 };
8364 deletion_ranges.push(
8365 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8366 );
8367 last_outdent = Some(row);
8368 }
8369 }
8370 }
8371 }
8372
8373 self.transact(window, cx, |this, window, cx| {
8374 this.buffer.update(cx, |buffer, cx| {
8375 let empty_str: Arc<str> = Arc::default();
8376 buffer.edit(
8377 deletion_ranges
8378 .into_iter()
8379 .map(|range| (range, empty_str.clone())),
8380 None,
8381 cx,
8382 );
8383 });
8384 let selections = this.selections.all::<usize>(cx);
8385 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8386 s.select(selections)
8387 });
8388 });
8389 }
8390
8391 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8392 if self.read_only(cx) {
8393 return;
8394 }
8395 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8396 let selections = self
8397 .selections
8398 .all::<usize>(cx)
8399 .into_iter()
8400 .map(|s| s.range());
8401
8402 self.transact(window, cx, |this, window, cx| {
8403 this.buffer.update(cx, |buffer, cx| {
8404 buffer.autoindent_ranges(selections, cx);
8405 });
8406 let selections = this.selections.all::<usize>(cx);
8407 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8408 s.select(selections)
8409 });
8410 });
8411 }
8412
8413 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8414 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8415 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8416 let selections = self.selections.all::<Point>(cx);
8417
8418 let mut new_cursors = Vec::new();
8419 let mut edit_ranges = Vec::new();
8420 let mut selections = selections.iter().peekable();
8421 while let Some(selection) = selections.next() {
8422 let mut rows = selection.spanned_rows(false, &display_map);
8423 let goal_display_column = selection.head().to_display_point(&display_map).column();
8424
8425 // Accumulate contiguous regions of rows that we want to delete.
8426 while let Some(next_selection) = selections.peek() {
8427 let next_rows = next_selection.spanned_rows(false, &display_map);
8428 if next_rows.start <= rows.end {
8429 rows.end = next_rows.end;
8430 selections.next().unwrap();
8431 } else {
8432 break;
8433 }
8434 }
8435
8436 let buffer = &display_map.buffer_snapshot;
8437 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8438 let edit_end;
8439 let cursor_buffer_row;
8440 if buffer.max_point().row >= rows.end.0 {
8441 // If there's a line after the range, delete the \n from the end of the row range
8442 // and position the cursor on the next line.
8443 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8444 cursor_buffer_row = rows.end;
8445 } else {
8446 // If there isn't a line after the range, delete the \n from the line before the
8447 // start of the row range and position the cursor there.
8448 edit_start = edit_start.saturating_sub(1);
8449 edit_end = buffer.len();
8450 cursor_buffer_row = rows.start.previous_row();
8451 }
8452
8453 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8454 *cursor.column_mut() =
8455 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8456
8457 new_cursors.push((
8458 selection.id,
8459 buffer.anchor_after(cursor.to_point(&display_map)),
8460 ));
8461 edit_ranges.push(edit_start..edit_end);
8462 }
8463
8464 self.transact(window, cx, |this, window, cx| {
8465 let buffer = this.buffer.update(cx, |buffer, cx| {
8466 let empty_str: Arc<str> = Arc::default();
8467 buffer.edit(
8468 edit_ranges
8469 .into_iter()
8470 .map(|range| (range, empty_str.clone())),
8471 None,
8472 cx,
8473 );
8474 buffer.snapshot(cx)
8475 });
8476 let new_selections = new_cursors
8477 .into_iter()
8478 .map(|(id, cursor)| {
8479 let cursor = cursor.to_point(&buffer);
8480 Selection {
8481 id,
8482 start: cursor,
8483 end: cursor,
8484 reversed: false,
8485 goal: SelectionGoal::None,
8486 }
8487 })
8488 .collect();
8489
8490 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8491 s.select(new_selections);
8492 });
8493 });
8494 }
8495
8496 pub fn join_lines_impl(
8497 &mut self,
8498 insert_whitespace: bool,
8499 window: &mut Window,
8500 cx: &mut Context<Self>,
8501 ) {
8502 if self.read_only(cx) {
8503 return;
8504 }
8505 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8506 for selection in self.selections.all::<Point>(cx) {
8507 let start = MultiBufferRow(selection.start.row);
8508 // Treat single line selections as if they include the next line. Otherwise this action
8509 // would do nothing for single line selections individual cursors.
8510 let end = if selection.start.row == selection.end.row {
8511 MultiBufferRow(selection.start.row + 1)
8512 } else {
8513 MultiBufferRow(selection.end.row)
8514 };
8515
8516 if let Some(last_row_range) = row_ranges.last_mut() {
8517 if start <= last_row_range.end {
8518 last_row_range.end = end;
8519 continue;
8520 }
8521 }
8522 row_ranges.push(start..end);
8523 }
8524
8525 let snapshot = self.buffer.read(cx).snapshot(cx);
8526 let mut cursor_positions = Vec::new();
8527 for row_range in &row_ranges {
8528 let anchor = snapshot.anchor_before(Point::new(
8529 row_range.end.previous_row().0,
8530 snapshot.line_len(row_range.end.previous_row()),
8531 ));
8532 cursor_positions.push(anchor..anchor);
8533 }
8534
8535 self.transact(window, cx, |this, window, cx| {
8536 for row_range in row_ranges.into_iter().rev() {
8537 for row in row_range.iter_rows().rev() {
8538 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8539 let next_line_row = row.next_row();
8540 let indent = snapshot.indent_size_for_line(next_line_row);
8541 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8542
8543 let replace =
8544 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8545 " "
8546 } else {
8547 ""
8548 };
8549
8550 this.buffer.update(cx, |buffer, cx| {
8551 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8552 });
8553 }
8554 }
8555
8556 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8557 s.select_anchor_ranges(cursor_positions)
8558 });
8559 });
8560 }
8561
8562 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8563 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8564 self.join_lines_impl(true, window, cx);
8565 }
8566
8567 pub fn sort_lines_case_sensitive(
8568 &mut self,
8569 _: &SortLinesCaseSensitive,
8570 window: &mut Window,
8571 cx: &mut Context<Self>,
8572 ) {
8573 self.manipulate_lines(window, cx, |lines| lines.sort())
8574 }
8575
8576 pub fn sort_lines_case_insensitive(
8577 &mut self,
8578 _: &SortLinesCaseInsensitive,
8579 window: &mut Window,
8580 cx: &mut Context<Self>,
8581 ) {
8582 self.manipulate_lines(window, cx, |lines| {
8583 lines.sort_by_key(|line| line.to_lowercase())
8584 })
8585 }
8586
8587 pub fn unique_lines_case_insensitive(
8588 &mut self,
8589 _: &UniqueLinesCaseInsensitive,
8590 window: &mut Window,
8591 cx: &mut Context<Self>,
8592 ) {
8593 self.manipulate_lines(window, cx, |lines| {
8594 let mut seen = HashSet::default();
8595 lines.retain(|line| seen.insert(line.to_lowercase()));
8596 })
8597 }
8598
8599 pub fn unique_lines_case_sensitive(
8600 &mut self,
8601 _: &UniqueLinesCaseSensitive,
8602 window: &mut Window,
8603 cx: &mut Context<Self>,
8604 ) {
8605 self.manipulate_lines(window, cx, |lines| {
8606 let mut seen = HashSet::default();
8607 lines.retain(|line| seen.insert(*line));
8608 })
8609 }
8610
8611 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8612 let Some(project) = self.project.clone() else {
8613 return;
8614 };
8615 self.reload(project, window, cx)
8616 .detach_and_notify_err(window, cx);
8617 }
8618
8619 pub fn restore_file(
8620 &mut self,
8621 _: &::git::RestoreFile,
8622 window: &mut Window,
8623 cx: &mut Context<Self>,
8624 ) {
8625 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8626 let mut buffer_ids = HashSet::default();
8627 let snapshot = self.buffer().read(cx).snapshot(cx);
8628 for selection in self.selections.all::<usize>(cx) {
8629 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8630 }
8631
8632 let buffer = self.buffer().read(cx);
8633 let ranges = buffer_ids
8634 .into_iter()
8635 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8636 .collect::<Vec<_>>();
8637
8638 self.restore_hunks_in_ranges(ranges, window, cx);
8639 }
8640
8641 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8642 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8643 let selections = self
8644 .selections
8645 .all(cx)
8646 .into_iter()
8647 .map(|s| s.range())
8648 .collect();
8649 self.restore_hunks_in_ranges(selections, window, cx);
8650 }
8651
8652 pub fn restore_hunks_in_ranges(
8653 &mut self,
8654 ranges: Vec<Range<Point>>,
8655 window: &mut Window,
8656 cx: &mut Context<Editor>,
8657 ) {
8658 let mut revert_changes = HashMap::default();
8659 let chunk_by = self
8660 .snapshot(window, cx)
8661 .hunks_for_ranges(ranges)
8662 .into_iter()
8663 .chunk_by(|hunk| hunk.buffer_id);
8664 for (buffer_id, hunks) in &chunk_by {
8665 let hunks = hunks.collect::<Vec<_>>();
8666 for hunk in &hunks {
8667 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8668 }
8669 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8670 }
8671 drop(chunk_by);
8672 if !revert_changes.is_empty() {
8673 self.transact(window, cx, |editor, window, cx| {
8674 editor.restore(revert_changes, window, cx);
8675 });
8676 }
8677 }
8678
8679 pub fn open_active_item_in_terminal(
8680 &mut self,
8681 _: &OpenInTerminal,
8682 window: &mut Window,
8683 cx: &mut Context<Self>,
8684 ) {
8685 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8686 let project_path = buffer.read(cx).project_path(cx)?;
8687 let project = self.project.as_ref()?.read(cx);
8688 let entry = project.entry_for_path(&project_path, cx)?;
8689 let parent = match &entry.canonical_path {
8690 Some(canonical_path) => canonical_path.to_path_buf(),
8691 None => project.absolute_path(&project_path, cx)?,
8692 }
8693 .parent()?
8694 .to_path_buf();
8695 Some(parent)
8696 }) {
8697 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8698 }
8699 }
8700
8701 fn set_breakpoint_context_menu(
8702 &mut self,
8703 display_row: DisplayRow,
8704 position: Option<Anchor>,
8705 clicked_point: gpui::Point<Pixels>,
8706 window: &mut Window,
8707 cx: &mut Context<Self>,
8708 ) {
8709 if !cx.has_flag::<Debugger>() {
8710 return;
8711 }
8712 let source = self
8713 .buffer
8714 .read(cx)
8715 .snapshot(cx)
8716 .anchor_before(Point::new(display_row.0, 0u32));
8717
8718 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8719
8720 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8721 self,
8722 source,
8723 clicked_point,
8724 context_menu,
8725 window,
8726 cx,
8727 );
8728 }
8729
8730 fn add_edit_breakpoint_block(
8731 &mut self,
8732 anchor: Anchor,
8733 breakpoint: &Breakpoint,
8734 edit_action: BreakpointPromptEditAction,
8735 window: &mut Window,
8736 cx: &mut Context<Self>,
8737 ) {
8738 let weak_editor = cx.weak_entity();
8739 let bp_prompt = cx.new(|cx| {
8740 BreakpointPromptEditor::new(
8741 weak_editor,
8742 anchor,
8743 breakpoint.clone(),
8744 edit_action,
8745 window,
8746 cx,
8747 )
8748 });
8749
8750 let height = bp_prompt.update(cx, |this, cx| {
8751 this.prompt
8752 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8753 });
8754 let cloned_prompt = bp_prompt.clone();
8755 let blocks = vec![BlockProperties {
8756 style: BlockStyle::Sticky,
8757 placement: BlockPlacement::Above(anchor),
8758 height: Some(height),
8759 render: Arc::new(move |cx| {
8760 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8761 cloned_prompt.clone().into_any_element()
8762 }),
8763 priority: 0,
8764 }];
8765
8766 let focus_handle = bp_prompt.focus_handle(cx);
8767 window.focus(&focus_handle);
8768
8769 let block_ids = self.insert_blocks(blocks, None, cx);
8770 bp_prompt.update(cx, |prompt, _| {
8771 prompt.add_block_ids(block_ids);
8772 });
8773 }
8774
8775 fn breakpoint_at_cursor_head(
8776 &self,
8777 window: &mut Window,
8778 cx: &mut Context<Self>,
8779 ) -> Option<(Anchor, Breakpoint)> {
8780 let cursor_position: Point = self.selections.newest(cx).head();
8781 self.breakpoint_at_row(cursor_position.row, window, cx)
8782 }
8783
8784 pub(crate) fn breakpoint_at_row(
8785 &self,
8786 row: u32,
8787 window: &mut Window,
8788 cx: &mut Context<Self>,
8789 ) -> Option<(Anchor, Breakpoint)> {
8790 let snapshot = self.snapshot(window, cx);
8791 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
8792
8793 let project = self.project.clone()?;
8794
8795 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
8796 snapshot
8797 .buffer_snapshot
8798 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
8799 })?;
8800
8801 let enclosing_excerpt = breakpoint_position.excerpt_id;
8802 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8803 let buffer_snapshot = buffer.read(cx).snapshot();
8804
8805 let row = buffer_snapshot
8806 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
8807 .row;
8808
8809 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
8810 let anchor_end = snapshot
8811 .buffer_snapshot
8812 .anchor_after(Point::new(row, line_len));
8813
8814 let bp = self
8815 .breakpoint_store
8816 .as_ref()?
8817 .read_with(cx, |breakpoint_store, cx| {
8818 breakpoint_store
8819 .breakpoints(
8820 &buffer,
8821 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
8822 &buffer_snapshot,
8823 cx,
8824 )
8825 .next()
8826 .and_then(|(anchor, bp)| {
8827 let breakpoint_row = buffer_snapshot
8828 .summary_for_anchor::<text::PointUtf16>(anchor)
8829 .row;
8830
8831 if breakpoint_row == row {
8832 snapshot
8833 .buffer_snapshot
8834 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8835 .map(|anchor| (anchor, bp.clone()))
8836 } else {
8837 None
8838 }
8839 })
8840 });
8841 bp
8842 }
8843
8844 pub fn edit_log_breakpoint(
8845 &mut self,
8846 _: &EditLogBreakpoint,
8847 window: &mut Window,
8848 cx: &mut Context<Self>,
8849 ) {
8850 let (anchor, bp) = self
8851 .breakpoint_at_cursor_head(window, cx)
8852 .unwrap_or_else(|| {
8853 let cursor_position: Point = self.selections.newest(cx).head();
8854
8855 let breakpoint_position = self
8856 .snapshot(window, cx)
8857 .display_snapshot
8858 .buffer_snapshot
8859 .anchor_after(Point::new(cursor_position.row, 0));
8860
8861 (
8862 breakpoint_position,
8863 Breakpoint {
8864 message: None,
8865 state: BreakpointState::Enabled,
8866 condition: None,
8867 hit_condition: None,
8868 },
8869 )
8870 });
8871
8872 self.add_edit_breakpoint_block(anchor, &bp, BreakpointPromptEditAction::Log, window, cx);
8873 }
8874
8875 pub fn enable_breakpoint(
8876 &mut self,
8877 _: &crate::actions::EnableBreakpoint,
8878 window: &mut Window,
8879 cx: &mut Context<Self>,
8880 ) {
8881 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8882 if breakpoint.is_disabled() {
8883 self.edit_breakpoint_at_anchor(
8884 anchor,
8885 breakpoint,
8886 BreakpointEditAction::InvertState,
8887 cx,
8888 );
8889 }
8890 }
8891 }
8892
8893 pub fn disable_breakpoint(
8894 &mut self,
8895 _: &crate::actions::DisableBreakpoint,
8896 window: &mut Window,
8897 cx: &mut Context<Self>,
8898 ) {
8899 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8900 if breakpoint.is_enabled() {
8901 self.edit_breakpoint_at_anchor(
8902 anchor,
8903 breakpoint,
8904 BreakpointEditAction::InvertState,
8905 cx,
8906 );
8907 }
8908 }
8909 }
8910
8911 pub fn toggle_breakpoint(
8912 &mut self,
8913 _: &crate::actions::ToggleBreakpoint,
8914 window: &mut Window,
8915 cx: &mut Context<Self>,
8916 ) {
8917 let edit_action = BreakpointEditAction::Toggle;
8918
8919 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8920 self.edit_breakpoint_at_anchor(anchor, breakpoint, edit_action, cx);
8921 } else {
8922 let cursor_position: Point = self.selections.newest(cx).head();
8923
8924 let breakpoint_position = self
8925 .snapshot(window, cx)
8926 .display_snapshot
8927 .buffer_snapshot
8928 .anchor_after(Point::new(cursor_position.row, 0));
8929
8930 self.edit_breakpoint_at_anchor(
8931 breakpoint_position,
8932 Breakpoint::new_standard(),
8933 edit_action,
8934 cx,
8935 );
8936 }
8937 }
8938
8939 pub fn edit_breakpoint_at_anchor(
8940 &mut self,
8941 breakpoint_position: Anchor,
8942 breakpoint: Breakpoint,
8943 edit_action: BreakpointEditAction,
8944 cx: &mut Context<Self>,
8945 ) {
8946 let Some(breakpoint_store) = &self.breakpoint_store else {
8947 return;
8948 };
8949
8950 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8951 if breakpoint_position == Anchor::min() {
8952 self.buffer()
8953 .read(cx)
8954 .excerpt_buffer_ids()
8955 .into_iter()
8956 .next()
8957 } else {
8958 None
8959 }
8960 }) else {
8961 return;
8962 };
8963
8964 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8965 return;
8966 };
8967
8968 breakpoint_store.update(cx, |breakpoint_store, cx| {
8969 breakpoint_store.toggle_breakpoint(
8970 buffer,
8971 (breakpoint_position.text_anchor, breakpoint),
8972 edit_action,
8973 cx,
8974 );
8975 });
8976
8977 cx.notify();
8978 }
8979
8980 #[cfg(any(test, feature = "test-support"))]
8981 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
8982 self.breakpoint_store.clone()
8983 }
8984
8985 pub fn prepare_restore_change(
8986 &self,
8987 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
8988 hunk: &MultiBufferDiffHunk,
8989 cx: &mut App,
8990 ) -> Option<()> {
8991 if hunk.is_created_file() {
8992 return None;
8993 }
8994 let buffer = self.buffer.read(cx);
8995 let diff = buffer.diff_for(hunk.buffer_id)?;
8996 let buffer = buffer.buffer(hunk.buffer_id)?;
8997 let buffer = buffer.read(cx);
8998 let original_text = diff
8999 .read(cx)
9000 .base_text()
9001 .as_rope()
9002 .slice(hunk.diff_base_byte_range.clone());
9003 let buffer_snapshot = buffer.snapshot();
9004 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9005 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9006 probe
9007 .0
9008 .start
9009 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9010 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9011 }) {
9012 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9013 Some(())
9014 } else {
9015 None
9016 }
9017 }
9018
9019 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9020 self.manipulate_lines(window, cx, |lines| lines.reverse())
9021 }
9022
9023 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9024 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9025 }
9026
9027 fn manipulate_lines<Fn>(
9028 &mut self,
9029 window: &mut Window,
9030 cx: &mut Context<Self>,
9031 mut callback: Fn,
9032 ) where
9033 Fn: FnMut(&mut Vec<&str>),
9034 {
9035 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9036
9037 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9038 let buffer = self.buffer.read(cx).snapshot(cx);
9039
9040 let mut edits = Vec::new();
9041
9042 let selections = self.selections.all::<Point>(cx);
9043 let mut selections = selections.iter().peekable();
9044 let mut contiguous_row_selections = Vec::new();
9045 let mut new_selections = Vec::new();
9046 let mut added_lines = 0;
9047 let mut removed_lines = 0;
9048
9049 while let Some(selection) = selections.next() {
9050 let (start_row, end_row) = consume_contiguous_rows(
9051 &mut contiguous_row_selections,
9052 selection,
9053 &display_map,
9054 &mut selections,
9055 );
9056
9057 let start_point = Point::new(start_row.0, 0);
9058 let end_point = Point::new(
9059 end_row.previous_row().0,
9060 buffer.line_len(end_row.previous_row()),
9061 );
9062 let text = buffer
9063 .text_for_range(start_point..end_point)
9064 .collect::<String>();
9065
9066 let mut lines = text.split('\n').collect_vec();
9067
9068 let lines_before = lines.len();
9069 callback(&mut lines);
9070 let lines_after = lines.len();
9071
9072 edits.push((start_point..end_point, lines.join("\n")));
9073
9074 // Selections must change based on added and removed line count
9075 let start_row =
9076 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9077 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9078 new_selections.push(Selection {
9079 id: selection.id,
9080 start: start_row,
9081 end: end_row,
9082 goal: SelectionGoal::None,
9083 reversed: selection.reversed,
9084 });
9085
9086 if lines_after > lines_before {
9087 added_lines += lines_after - lines_before;
9088 } else if lines_before > lines_after {
9089 removed_lines += lines_before - lines_after;
9090 }
9091 }
9092
9093 self.transact(window, cx, |this, window, cx| {
9094 let buffer = this.buffer.update(cx, |buffer, cx| {
9095 buffer.edit(edits, None, cx);
9096 buffer.snapshot(cx)
9097 });
9098
9099 // Recalculate offsets on newly edited buffer
9100 let new_selections = new_selections
9101 .iter()
9102 .map(|s| {
9103 let start_point = Point::new(s.start.0, 0);
9104 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9105 Selection {
9106 id: s.id,
9107 start: buffer.point_to_offset(start_point),
9108 end: buffer.point_to_offset(end_point),
9109 goal: s.goal,
9110 reversed: s.reversed,
9111 }
9112 })
9113 .collect();
9114
9115 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9116 s.select(new_selections);
9117 });
9118
9119 this.request_autoscroll(Autoscroll::fit(), cx);
9120 });
9121 }
9122
9123 pub fn convert_to_upper_case(
9124 &mut self,
9125 _: &ConvertToUpperCase,
9126 window: &mut Window,
9127 cx: &mut Context<Self>,
9128 ) {
9129 self.manipulate_text(window, cx, |text| text.to_uppercase())
9130 }
9131
9132 pub fn convert_to_lower_case(
9133 &mut self,
9134 _: &ConvertToLowerCase,
9135 window: &mut Window,
9136 cx: &mut Context<Self>,
9137 ) {
9138 self.manipulate_text(window, cx, |text| text.to_lowercase())
9139 }
9140
9141 pub fn convert_to_title_case(
9142 &mut self,
9143 _: &ConvertToTitleCase,
9144 window: &mut Window,
9145 cx: &mut Context<Self>,
9146 ) {
9147 self.manipulate_text(window, cx, |text| {
9148 text.split('\n')
9149 .map(|line| line.to_case(Case::Title))
9150 .join("\n")
9151 })
9152 }
9153
9154 pub fn convert_to_snake_case(
9155 &mut self,
9156 _: &ConvertToSnakeCase,
9157 window: &mut Window,
9158 cx: &mut Context<Self>,
9159 ) {
9160 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9161 }
9162
9163 pub fn convert_to_kebab_case(
9164 &mut self,
9165 _: &ConvertToKebabCase,
9166 window: &mut Window,
9167 cx: &mut Context<Self>,
9168 ) {
9169 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9170 }
9171
9172 pub fn convert_to_upper_camel_case(
9173 &mut self,
9174 _: &ConvertToUpperCamelCase,
9175 window: &mut Window,
9176 cx: &mut Context<Self>,
9177 ) {
9178 self.manipulate_text(window, cx, |text| {
9179 text.split('\n')
9180 .map(|line| line.to_case(Case::UpperCamel))
9181 .join("\n")
9182 })
9183 }
9184
9185 pub fn convert_to_lower_camel_case(
9186 &mut self,
9187 _: &ConvertToLowerCamelCase,
9188 window: &mut Window,
9189 cx: &mut Context<Self>,
9190 ) {
9191 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9192 }
9193
9194 pub fn convert_to_opposite_case(
9195 &mut self,
9196 _: &ConvertToOppositeCase,
9197 window: &mut Window,
9198 cx: &mut Context<Self>,
9199 ) {
9200 self.manipulate_text(window, cx, |text| {
9201 text.chars()
9202 .fold(String::with_capacity(text.len()), |mut t, c| {
9203 if c.is_uppercase() {
9204 t.extend(c.to_lowercase());
9205 } else {
9206 t.extend(c.to_uppercase());
9207 }
9208 t
9209 })
9210 })
9211 }
9212
9213 pub fn convert_to_rot13(
9214 &mut self,
9215 _: &ConvertToRot13,
9216 window: &mut Window,
9217 cx: &mut Context<Self>,
9218 ) {
9219 self.manipulate_text(window, cx, |text| {
9220 text.chars()
9221 .map(|c| match c {
9222 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9223 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9224 _ => c,
9225 })
9226 .collect()
9227 })
9228 }
9229
9230 pub fn convert_to_rot47(
9231 &mut self,
9232 _: &ConvertToRot47,
9233 window: &mut Window,
9234 cx: &mut Context<Self>,
9235 ) {
9236 self.manipulate_text(window, cx, |text| {
9237 text.chars()
9238 .map(|c| {
9239 let code_point = c as u32;
9240 if code_point >= 33 && code_point <= 126 {
9241 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9242 }
9243 c
9244 })
9245 .collect()
9246 })
9247 }
9248
9249 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9250 where
9251 Fn: FnMut(&str) -> String,
9252 {
9253 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9254 let buffer = self.buffer.read(cx).snapshot(cx);
9255
9256 let mut new_selections = Vec::new();
9257 let mut edits = Vec::new();
9258 let mut selection_adjustment = 0i32;
9259
9260 for selection in self.selections.all::<usize>(cx) {
9261 let selection_is_empty = selection.is_empty();
9262
9263 let (start, end) = if selection_is_empty {
9264 let word_range = movement::surrounding_word(
9265 &display_map,
9266 selection.start.to_display_point(&display_map),
9267 );
9268 let start = word_range.start.to_offset(&display_map, Bias::Left);
9269 let end = word_range.end.to_offset(&display_map, Bias::Left);
9270 (start, end)
9271 } else {
9272 (selection.start, selection.end)
9273 };
9274
9275 let text = buffer.text_for_range(start..end).collect::<String>();
9276 let old_length = text.len() as i32;
9277 let text = callback(&text);
9278
9279 new_selections.push(Selection {
9280 start: (start as i32 - selection_adjustment) as usize,
9281 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9282 goal: SelectionGoal::None,
9283 ..selection
9284 });
9285
9286 selection_adjustment += old_length - text.len() as i32;
9287
9288 edits.push((start..end, text));
9289 }
9290
9291 self.transact(window, cx, |this, window, cx| {
9292 this.buffer.update(cx, |buffer, cx| {
9293 buffer.edit(edits, None, cx);
9294 });
9295
9296 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9297 s.select(new_selections);
9298 });
9299
9300 this.request_autoscroll(Autoscroll::fit(), cx);
9301 });
9302 }
9303
9304 pub fn duplicate(
9305 &mut self,
9306 upwards: bool,
9307 whole_lines: bool,
9308 window: &mut Window,
9309 cx: &mut Context<Self>,
9310 ) {
9311 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9312
9313 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9314 let buffer = &display_map.buffer_snapshot;
9315 let selections = self.selections.all::<Point>(cx);
9316
9317 let mut edits = Vec::new();
9318 let mut selections_iter = selections.iter().peekable();
9319 while let Some(selection) = selections_iter.next() {
9320 let mut rows = selection.spanned_rows(false, &display_map);
9321 // duplicate line-wise
9322 if whole_lines || selection.start == selection.end {
9323 // Avoid duplicating the same lines twice.
9324 while let Some(next_selection) = selections_iter.peek() {
9325 let next_rows = next_selection.spanned_rows(false, &display_map);
9326 if next_rows.start < rows.end {
9327 rows.end = next_rows.end;
9328 selections_iter.next().unwrap();
9329 } else {
9330 break;
9331 }
9332 }
9333
9334 // Copy the text from the selected row region and splice it either at the start
9335 // or end of the region.
9336 let start = Point::new(rows.start.0, 0);
9337 let end = Point::new(
9338 rows.end.previous_row().0,
9339 buffer.line_len(rows.end.previous_row()),
9340 );
9341 let text = buffer
9342 .text_for_range(start..end)
9343 .chain(Some("\n"))
9344 .collect::<String>();
9345 let insert_location = if upwards {
9346 Point::new(rows.end.0, 0)
9347 } else {
9348 start
9349 };
9350 edits.push((insert_location..insert_location, text));
9351 } else {
9352 // duplicate character-wise
9353 let start = selection.start;
9354 let end = selection.end;
9355 let text = buffer.text_for_range(start..end).collect::<String>();
9356 edits.push((selection.end..selection.end, text));
9357 }
9358 }
9359
9360 self.transact(window, cx, |this, _, cx| {
9361 this.buffer.update(cx, |buffer, cx| {
9362 buffer.edit(edits, None, cx);
9363 });
9364
9365 this.request_autoscroll(Autoscroll::fit(), cx);
9366 });
9367 }
9368
9369 pub fn duplicate_line_up(
9370 &mut self,
9371 _: &DuplicateLineUp,
9372 window: &mut Window,
9373 cx: &mut Context<Self>,
9374 ) {
9375 self.duplicate(true, true, window, cx);
9376 }
9377
9378 pub fn duplicate_line_down(
9379 &mut self,
9380 _: &DuplicateLineDown,
9381 window: &mut Window,
9382 cx: &mut Context<Self>,
9383 ) {
9384 self.duplicate(false, true, window, cx);
9385 }
9386
9387 pub fn duplicate_selection(
9388 &mut self,
9389 _: &DuplicateSelection,
9390 window: &mut Window,
9391 cx: &mut Context<Self>,
9392 ) {
9393 self.duplicate(false, false, window, cx);
9394 }
9395
9396 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9397 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9398
9399 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9400 let buffer = self.buffer.read(cx).snapshot(cx);
9401
9402 let mut edits = Vec::new();
9403 let mut unfold_ranges = Vec::new();
9404 let mut refold_creases = Vec::new();
9405
9406 let selections = self.selections.all::<Point>(cx);
9407 let mut selections = selections.iter().peekable();
9408 let mut contiguous_row_selections = Vec::new();
9409 let mut new_selections = Vec::new();
9410
9411 while let Some(selection) = selections.next() {
9412 // Find all the selections that span a contiguous row range
9413 let (start_row, end_row) = consume_contiguous_rows(
9414 &mut contiguous_row_selections,
9415 selection,
9416 &display_map,
9417 &mut selections,
9418 );
9419
9420 // Move the text spanned by the row range to be before the line preceding the row range
9421 if start_row.0 > 0 {
9422 let range_to_move = Point::new(
9423 start_row.previous_row().0,
9424 buffer.line_len(start_row.previous_row()),
9425 )
9426 ..Point::new(
9427 end_row.previous_row().0,
9428 buffer.line_len(end_row.previous_row()),
9429 );
9430 let insertion_point = display_map
9431 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9432 .0;
9433
9434 // Don't move lines across excerpts
9435 if buffer
9436 .excerpt_containing(insertion_point..range_to_move.end)
9437 .is_some()
9438 {
9439 let text = buffer
9440 .text_for_range(range_to_move.clone())
9441 .flat_map(|s| s.chars())
9442 .skip(1)
9443 .chain(['\n'])
9444 .collect::<String>();
9445
9446 edits.push((
9447 buffer.anchor_after(range_to_move.start)
9448 ..buffer.anchor_before(range_to_move.end),
9449 String::new(),
9450 ));
9451 let insertion_anchor = buffer.anchor_after(insertion_point);
9452 edits.push((insertion_anchor..insertion_anchor, text));
9453
9454 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9455
9456 // Move selections up
9457 new_selections.extend(contiguous_row_selections.drain(..).map(
9458 |mut selection| {
9459 selection.start.row -= row_delta;
9460 selection.end.row -= row_delta;
9461 selection
9462 },
9463 ));
9464
9465 // Move folds up
9466 unfold_ranges.push(range_to_move.clone());
9467 for fold in display_map.folds_in_range(
9468 buffer.anchor_before(range_to_move.start)
9469 ..buffer.anchor_after(range_to_move.end),
9470 ) {
9471 let mut start = fold.range.start.to_point(&buffer);
9472 let mut end = fold.range.end.to_point(&buffer);
9473 start.row -= row_delta;
9474 end.row -= row_delta;
9475 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9476 }
9477 }
9478 }
9479
9480 // If we didn't move line(s), preserve the existing selections
9481 new_selections.append(&mut contiguous_row_selections);
9482 }
9483
9484 self.transact(window, cx, |this, window, cx| {
9485 this.unfold_ranges(&unfold_ranges, true, true, cx);
9486 this.buffer.update(cx, |buffer, cx| {
9487 for (range, text) in edits {
9488 buffer.edit([(range, text)], None, cx);
9489 }
9490 });
9491 this.fold_creases(refold_creases, true, window, cx);
9492 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9493 s.select(new_selections);
9494 })
9495 });
9496 }
9497
9498 pub fn move_line_down(
9499 &mut self,
9500 _: &MoveLineDown,
9501 window: &mut Window,
9502 cx: &mut Context<Self>,
9503 ) {
9504 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9505
9506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9507 let buffer = self.buffer.read(cx).snapshot(cx);
9508
9509 let mut edits = Vec::new();
9510 let mut unfold_ranges = Vec::new();
9511 let mut refold_creases = Vec::new();
9512
9513 let selections = self.selections.all::<Point>(cx);
9514 let mut selections = selections.iter().peekable();
9515 let mut contiguous_row_selections = Vec::new();
9516 let mut new_selections = Vec::new();
9517
9518 while let Some(selection) = selections.next() {
9519 // Find all the selections that span a contiguous row range
9520 let (start_row, end_row) = consume_contiguous_rows(
9521 &mut contiguous_row_selections,
9522 selection,
9523 &display_map,
9524 &mut selections,
9525 );
9526
9527 // Move the text spanned by the row range to be after the last line of the row range
9528 if end_row.0 <= buffer.max_point().row {
9529 let range_to_move =
9530 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9531 let insertion_point = display_map
9532 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9533 .0;
9534
9535 // Don't move lines across excerpt boundaries
9536 if buffer
9537 .excerpt_containing(range_to_move.start..insertion_point)
9538 .is_some()
9539 {
9540 let mut text = String::from("\n");
9541 text.extend(buffer.text_for_range(range_to_move.clone()));
9542 text.pop(); // Drop trailing newline
9543 edits.push((
9544 buffer.anchor_after(range_to_move.start)
9545 ..buffer.anchor_before(range_to_move.end),
9546 String::new(),
9547 ));
9548 let insertion_anchor = buffer.anchor_after(insertion_point);
9549 edits.push((insertion_anchor..insertion_anchor, text));
9550
9551 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9552
9553 // Move selections down
9554 new_selections.extend(contiguous_row_selections.drain(..).map(
9555 |mut selection| {
9556 selection.start.row += row_delta;
9557 selection.end.row += row_delta;
9558 selection
9559 },
9560 ));
9561
9562 // Move folds down
9563 unfold_ranges.push(range_to_move.clone());
9564 for fold in display_map.folds_in_range(
9565 buffer.anchor_before(range_to_move.start)
9566 ..buffer.anchor_after(range_to_move.end),
9567 ) {
9568 let mut start = fold.range.start.to_point(&buffer);
9569 let mut end = fold.range.end.to_point(&buffer);
9570 start.row += row_delta;
9571 end.row += row_delta;
9572 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9573 }
9574 }
9575 }
9576
9577 // If we didn't move line(s), preserve the existing selections
9578 new_selections.append(&mut contiguous_row_selections);
9579 }
9580
9581 self.transact(window, cx, |this, window, cx| {
9582 this.unfold_ranges(&unfold_ranges, true, true, cx);
9583 this.buffer.update(cx, |buffer, cx| {
9584 for (range, text) in edits {
9585 buffer.edit([(range, text)], None, cx);
9586 }
9587 });
9588 this.fold_creases(refold_creases, true, window, cx);
9589 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9590 s.select(new_selections)
9591 });
9592 });
9593 }
9594
9595 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9596 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9597 let text_layout_details = &self.text_layout_details(window);
9598 self.transact(window, cx, |this, window, cx| {
9599 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9600 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9601 s.move_with(|display_map, selection| {
9602 if !selection.is_empty() {
9603 return;
9604 }
9605
9606 let mut head = selection.head();
9607 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9608 if head.column() == display_map.line_len(head.row()) {
9609 transpose_offset = display_map
9610 .buffer_snapshot
9611 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9612 }
9613
9614 if transpose_offset == 0 {
9615 return;
9616 }
9617
9618 *head.column_mut() += 1;
9619 head = display_map.clip_point(head, Bias::Right);
9620 let goal = SelectionGoal::HorizontalPosition(
9621 display_map
9622 .x_for_display_point(head, text_layout_details)
9623 .into(),
9624 );
9625 selection.collapse_to(head, goal);
9626
9627 let transpose_start = display_map
9628 .buffer_snapshot
9629 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9630 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9631 let transpose_end = display_map
9632 .buffer_snapshot
9633 .clip_offset(transpose_offset + 1, Bias::Right);
9634 if let Some(ch) =
9635 display_map.buffer_snapshot.chars_at(transpose_start).next()
9636 {
9637 edits.push((transpose_start..transpose_offset, String::new()));
9638 edits.push((transpose_end..transpose_end, ch.to_string()));
9639 }
9640 }
9641 });
9642 edits
9643 });
9644 this.buffer
9645 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9646 let selections = this.selections.all::<usize>(cx);
9647 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9648 s.select(selections);
9649 });
9650 });
9651 }
9652
9653 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9654 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9655 self.rewrap_impl(RewrapOptions::default(), cx)
9656 }
9657
9658 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9659 let buffer = self.buffer.read(cx).snapshot(cx);
9660 let selections = self.selections.all::<Point>(cx);
9661 let mut selections = selections.iter().peekable();
9662
9663 let mut edits = Vec::new();
9664 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9665
9666 while let Some(selection) = selections.next() {
9667 let mut start_row = selection.start.row;
9668 let mut end_row = selection.end.row;
9669
9670 // Skip selections that overlap with a range that has already been rewrapped.
9671 let selection_range = start_row..end_row;
9672 if rewrapped_row_ranges
9673 .iter()
9674 .any(|range| range.overlaps(&selection_range))
9675 {
9676 continue;
9677 }
9678
9679 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9680
9681 // Since not all lines in the selection may be at the same indent
9682 // level, choose the indent size that is the most common between all
9683 // of the lines.
9684 //
9685 // If there is a tie, we use the deepest indent.
9686 let (indent_size, indent_end) = {
9687 let mut indent_size_occurrences = HashMap::default();
9688 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9689
9690 for row in start_row..=end_row {
9691 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9692 rows_by_indent_size.entry(indent).or_default().push(row);
9693 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9694 }
9695
9696 let indent_size = indent_size_occurrences
9697 .into_iter()
9698 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9699 .map(|(indent, _)| indent)
9700 .unwrap_or_default();
9701 let row = rows_by_indent_size[&indent_size][0];
9702 let indent_end = Point::new(row, indent_size.len);
9703
9704 (indent_size, indent_end)
9705 };
9706
9707 let mut line_prefix = indent_size.chars().collect::<String>();
9708
9709 let mut inside_comment = false;
9710 if let Some(comment_prefix) =
9711 buffer
9712 .language_scope_at(selection.head())
9713 .and_then(|language| {
9714 language
9715 .line_comment_prefixes()
9716 .iter()
9717 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9718 .cloned()
9719 })
9720 {
9721 line_prefix.push_str(&comment_prefix);
9722 inside_comment = true;
9723 }
9724
9725 let language_settings = buffer.language_settings_at(selection.head(), cx);
9726 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9727 RewrapBehavior::InComments => inside_comment,
9728 RewrapBehavior::InSelections => !selection.is_empty(),
9729 RewrapBehavior::Anywhere => true,
9730 };
9731
9732 let should_rewrap = options.override_language_settings
9733 || allow_rewrap_based_on_language
9734 || self.hard_wrap.is_some();
9735 if !should_rewrap {
9736 continue;
9737 }
9738
9739 if selection.is_empty() {
9740 'expand_upwards: while start_row > 0 {
9741 let prev_row = start_row - 1;
9742 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9743 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9744 {
9745 start_row = prev_row;
9746 } else {
9747 break 'expand_upwards;
9748 }
9749 }
9750
9751 'expand_downwards: while end_row < buffer.max_point().row {
9752 let next_row = end_row + 1;
9753 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9754 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9755 {
9756 end_row = next_row;
9757 } else {
9758 break 'expand_downwards;
9759 }
9760 }
9761 }
9762
9763 let start = Point::new(start_row, 0);
9764 let start_offset = start.to_offset(&buffer);
9765 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9766 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9767 let Some(lines_without_prefixes) = selection_text
9768 .lines()
9769 .map(|line| {
9770 line.strip_prefix(&line_prefix)
9771 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9772 .ok_or_else(|| {
9773 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9774 })
9775 })
9776 .collect::<Result<Vec<_>, _>>()
9777 .log_err()
9778 else {
9779 continue;
9780 };
9781
9782 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9783 buffer
9784 .language_settings_at(Point::new(start_row, 0), cx)
9785 .preferred_line_length as usize
9786 });
9787 let wrapped_text = wrap_with_prefix(
9788 line_prefix,
9789 lines_without_prefixes.join("\n"),
9790 wrap_column,
9791 tab_size,
9792 options.preserve_existing_whitespace,
9793 );
9794
9795 // TODO: should always use char-based diff while still supporting cursor behavior that
9796 // matches vim.
9797 let mut diff_options = DiffOptions::default();
9798 if options.override_language_settings {
9799 diff_options.max_word_diff_len = 0;
9800 diff_options.max_word_diff_line_count = 0;
9801 } else {
9802 diff_options.max_word_diff_len = usize::MAX;
9803 diff_options.max_word_diff_line_count = usize::MAX;
9804 }
9805
9806 for (old_range, new_text) in
9807 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9808 {
9809 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9810 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9811 edits.push((edit_start..edit_end, new_text));
9812 }
9813
9814 rewrapped_row_ranges.push(start_row..=end_row);
9815 }
9816
9817 self.buffer
9818 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9819 }
9820
9821 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9822 let mut text = String::new();
9823 let buffer = self.buffer.read(cx).snapshot(cx);
9824 let mut selections = self.selections.all::<Point>(cx);
9825 let mut clipboard_selections = Vec::with_capacity(selections.len());
9826 {
9827 let max_point = buffer.max_point();
9828 let mut is_first = true;
9829 for selection in &mut selections {
9830 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9831 if is_entire_line {
9832 selection.start = Point::new(selection.start.row, 0);
9833 if !selection.is_empty() && selection.end.column == 0 {
9834 selection.end = cmp::min(max_point, selection.end);
9835 } else {
9836 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9837 }
9838 selection.goal = SelectionGoal::None;
9839 }
9840 if is_first {
9841 is_first = false;
9842 } else {
9843 text += "\n";
9844 }
9845 let mut len = 0;
9846 for chunk in buffer.text_for_range(selection.start..selection.end) {
9847 text.push_str(chunk);
9848 len += chunk.len();
9849 }
9850 clipboard_selections.push(ClipboardSelection {
9851 len,
9852 is_entire_line,
9853 first_line_indent: buffer
9854 .indent_size_for_line(MultiBufferRow(selection.start.row))
9855 .len,
9856 });
9857 }
9858 }
9859
9860 self.transact(window, cx, |this, window, cx| {
9861 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9862 s.select(selections);
9863 });
9864 this.insert("", window, cx);
9865 });
9866 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9867 }
9868
9869 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9870 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9871 let item = self.cut_common(window, cx);
9872 cx.write_to_clipboard(item);
9873 }
9874
9875 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9876 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9877 self.change_selections(None, window, cx, |s| {
9878 s.move_with(|snapshot, sel| {
9879 if sel.is_empty() {
9880 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9881 }
9882 });
9883 });
9884 let item = self.cut_common(window, cx);
9885 cx.set_global(KillRing(item))
9886 }
9887
9888 pub fn kill_ring_yank(
9889 &mut self,
9890 _: &KillRingYank,
9891 window: &mut Window,
9892 cx: &mut Context<Self>,
9893 ) {
9894 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9895 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9896 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9897 (kill_ring.text().to_string(), kill_ring.metadata_json())
9898 } else {
9899 return;
9900 }
9901 } else {
9902 return;
9903 };
9904 self.do_paste(&text, metadata, false, window, cx);
9905 }
9906
9907 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9908 self.do_copy(true, cx);
9909 }
9910
9911 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9912 self.do_copy(false, cx);
9913 }
9914
9915 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9916 let selections = self.selections.all::<Point>(cx);
9917 let buffer = self.buffer.read(cx).read(cx);
9918 let mut text = String::new();
9919
9920 let mut clipboard_selections = Vec::with_capacity(selections.len());
9921 {
9922 let max_point = buffer.max_point();
9923 let mut is_first = true;
9924 for selection in &selections {
9925 let mut start = selection.start;
9926 let mut end = selection.end;
9927 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9928 if is_entire_line {
9929 start = Point::new(start.row, 0);
9930 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9931 }
9932
9933 let mut trimmed_selections = Vec::new();
9934 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
9935 let row = MultiBufferRow(start.row);
9936 let first_indent = buffer.indent_size_for_line(row);
9937 if first_indent.len == 0 || start.column > first_indent.len {
9938 trimmed_selections.push(start..end);
9939 } else {
9940 trimmed_selections.push(
9941 Point::new(row.0, first_indent.len)
9942 ..Point::new(row.0, buffer.line_len(row)),
9943 );
9944 for row in start.row + 1..=end.row {
9945 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
9946 if row_indent_size.len >= first_indent.len {
9947 trimmed_selections.push(
9948 Point::new(row, first_indent.len)
9949 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
9950 );
9951 } else {
9952 trimmed_selections.clear();
9953 trimmed_selections.push(start..end);
9954 break;
9955 }
9956 }
9957 }
9958 } else {
9959 trimmed_selections.push(start..end);
9960 }
9961
9962 for trimmed_range in trimmed_selections {
9963 if is_first {
9964 is_first = false;
9965 } else {
9966 text += "\n";
9967 }
9968 let mut len = 0;
9969 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
9970 text.push_str(chunk);
9971 len += chunk.len();
9972 }
9973 clipboard_selections.push(ClipboardSelection {
9974 len,
9975 is_entire_line,
9976 first_line_indent: buffer
9977 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
9978 .len,
9979 });
9980 }
9981 }
9982 }
9983
9984 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
9985 text,
9986 clipboard_selections,
9987 ));
9988 }
9989
9990 pub fn do_paste(
9991 &mut self,
9992 text: &String,
9993 clipboard_selections: Option<Vec<ClipboardSelection>>,
9994 handle_entire_lines: bool,
9995 window: &mut Window,
9996 cx: &mut Context<Self>,
9997 ) {
9998 if self.read_only(cx) {
9999 return;
10000 }
10001
10002 let clipboard_text = Cow::Borrowed(text);
10003
10004 self.transact(window, cx, |this, window, cx| {
10005 if let Some(mut clipboard_selections) = clipboard_selections {
10006 let old_selections = this.selections.all::<usize>(cx);
10007 let all_selections_were_entire_line =
10008 clipboard_selections.iter().all(|s| s.is_entire_line);
10009 let first_selection_indent_column =
10010 clipboard_selections.first().map(|s| s.first_line_indent);
10011 if clipboard_selections.len() != old_selections.len() {
10012 clipboard_selections.drain(..);
10013 }
10014 let cursor_offset = this.selections.last::<usize>(cx).head();
10015 let mut auto_indent_on_paste = true;
10016
10017 this.buffer.update(cx, |buffer, cx| {
10018 let snapshot = buffer.read(cx);
10019 auto_indent_on_paste = snapshot
10020 .language_settings_at(cursor_offset, cx)
10021 .auto_indent_on_paste;
10022
10023 let mut start_offset = 0;
10024 let mut edits = Vec::new();
10025 let mut original_indent_columns = Vec::new();
10026 for (ix, selection) in old_selections.iter().enumerate() {
10027 let to_insert;
10028 let entire_line;
10029 let original_indent_column;
10030 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10031 let end_offset = start_offset + clipboard_selection.len;
10032 to_insert = &clipboard_text[start_offset..end_offset];
10033 entire_line = clipboard_selection.is_entire_line;
10034 start_offset = end_offset + 1;
10035 original_indent_column = Some(clipboard_selection.first_line_indent);
10036 } else {
10037 to_insert = clipboard_text.as_str();
10038 entire_line = all_selections_were_entire_line;
10039 original_indent_column = first_selection_indent_column
10040 }
10041
10042 // If the corresponding selection was empty when this slice of the
10043 // clipboard text was written, then the entire line containing the
10044 // selection was copied. If this selection is also currently empty,
10045 // then paste the line before the current line of the buffer.
10046 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10047 let column = selection.start.to_point(&snapshot).column as usize;
10048 let line_start = selection.start - column;
10049 line_start..line_start
10050 } else {
10051 selection.range()
10052 };
10053
10054 edits.push((range, to_insert));
10055 original_indent_columns.push(original_indent_column);
10056 }
10057 drop(snapshot);
10058
10059 buffer.edit(
10060 edits,
10061 if auto_indent_on_paste {
10062 Some(AutoindentMode::Block {
10063 original_indent_columns,
10064 })
10065 } else {
10066 None
10067 },
10068 cx,
10069 );
10070 });
10071
10072 let selections = this.selections.all::<usize>(cx);
10073 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10074 s.select(selections)
10075 });
10076 } else {
10077 this.insert(&clipboard_text, window, cx);
10078 }
10079 });
10080 }
10081
10082 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10083 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10084 if let Some(item) = cx.read_from_clipboard() {
10085 let entries = item.entries();
10086
10087 match entries.first() {
10088 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10089 // of all the pasted entries.
10090 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10091 .do_paste(
10092 clipboard_string.text(),
10093 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10094 true,
10095 window,
10096 cx,
10097 ),
10098 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10099 }
10100 }
10101 }
10102
10103 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10104 if self.read_only(cx) {
10105 return;
10106 }
10107
10108 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10109
10110 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10111 if let Some((selections, _)) =
10112 self.selection_history.transaction(transaction_id).cloned()
10113 {
10114 self.change_selections(None, window, cx, |s| {
10115 s.select_anchors(selections.to_vec());
10116 });
10117 } else {
10118 log::error!(
10119 "No entry in selection_history found for undo. \
10120 This may correspond to a bug where undo does not update the selection. \
10121 If this is occurring, please add details to \
10122 https://github.com/zed-industries/zed/issues/22692"
10123 );
10124 }
10125 self.request_autoscroll(Autoscroll::fit(), cx);
10126 self.unmark_text(window, cx);
10127 self.refresh_inline_completion(true, false, window, cx);
10128 cx.emit(EditorEvent::Edited { transaction_id });
10129 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10130 }
10131 }
10132
10133 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10134 if self.read_only(cx) {
10135 return;
10136 }
10137
10138 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10139
10140 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10141 if let Some((_, Some(selections))) =
10142 self.selection_history.transaction(transaction_id).cloned()
10143 {
10144 self.change_selections(None, window, cx, |s| {
10145 s.select_anchors(selections.to_vec());
10146 });
10147 } else {
10148 log::error!(
10149 "No entry in selection_history found for redo. \
10150 This may correspond to a bug where undo does not update the selection. \
10151 If this is occurring, please add details to \
10152 https://github.com/zed-industries/zed/issues/22692"
10153 );
10154 }
10155 self.request_autoscroll(Autoscroll::fit(), cx);
10156 self.unmark_text(window, cx);
10157 self.refresh_inline_completion(true, false, window, cx);
10158 cx.emit(EditorEvent::Edited { transaction_id });
10159 }
10160 }
10161
10162 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10163 self.buffer
10164 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10165 }
10166
10167 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10168 self.buffer
10169 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10170 }
10171
10172 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10173 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10174 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10175 s.move_with(|map, selection| {
10176 let cursor = if selection.is_empty() {
10177 movement::left(map, selection.start)
10178 } else {
10179 selection.start
10180 };
10181 selection.collapse_to(cursor, SelectionGoal::None);
10182 });
10183 })
10184 }
10185
10186 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10187 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10188 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10189 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10190 })
10191 }
10192
10193 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10194 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10195 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10196 s.move_with(|map, selection| {
10197 let cursor = if selection.is_empty() {
10198 movement::right(map, selection.end)
10199 } else {
10200 selection.end
10201 };
10202 selection.collapse_to(cursor, SelectionGoal::None)
10203 });
10204 })
10205 }
10206
10207 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10208 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10209 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10210 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10211 })
10212 }
10213
10214 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10215 if self.take_rename(true, window, cx).is_some() {
10216 return;
10217 }
10218
10219 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10220 cx.propagate();
10221 return;
10222 }
10223
10224 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10225
10226 let text_layout_details = &self.text_layout_details(window);
10227 let selection_count = self.selections.count();
10228 let first_selection = self.selections.first_anchor();
10229
10230 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10231 s.move_with(|map, selection| {
10232 if !selection.is_empty() {
10233 selection.goal = SelectionGoal::None;
10234 }
10235 let (cursor, goal) = movement::up(
10236 map,
10237 selection.start,
10238 selection.goal,
10239 false,
10240 text_layout_details,
10241 );
10242 selection.collapse_to(cursor, goal);
10243 });
10244 });
10245
10246 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10247 {
10248 cx.propagate();
10249 }
10250 }
10251
10252 pub fn move_up_by_lines(
10253 &mut self,
10254 action: &MoveUpByLines,
10255 window: &mut Window,
10256 cx: &mut Context<Self>,
10257 ) {
10258 if self.take_rename(true, window, cx).is_some() {
10259 return;
10260 }
10261
10262 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10263 cx.propagate();
10264 return;
10265 }
10266
10267 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10268
10269 let text_layout_details = &self.text_layout_details(window);
10270
10271 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10272 s.move_with(|map, selection| {
10273 if !selection.is_empty() {
10274 selection.goal = SelectionGoal::None;
10275 }
10276 let (cursor, goal) = movement::up_by_rows(
10277 map,
10278 selection.start,
10279 action.lines,
10280 selection.goal,
10281 false,
10282 text_layout_details,
10283 );
10284 selection.collapse_to(cursor, goal);
10285 });
10286 })
10287 }
10288
10289 pub fn move_down_by_lines(
10290 &mut self,
10291 action: &MoveDownByLines,
10292 window: &mut Window,
10293 cx: &mut Context<Self>,
10294 ) {
10295 if self.take_rename(true, window, cx).is_some() {
10296 return;
10297 }
10298
10299 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10300 cx.propagate();
10301 return;
10302 }
10303
10304 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10305
10306 let text_layout_details = &self.text_layout_details(window);
10307
10308 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10309 s.move_with(|map, selection| {
10310 if !selection.is_empty() {
10311 selection.goal = SelectionGoal::None;
10312 }
10313 let (cursor, goal) = movement::down_by_rows(
10314 map,
10315 selection.start,
10316 action.lines,
10317 selection.goal,
10318 false,
10319 text_layout_details,
10320 );
10321 selection.collapse_to(cursor, goal);
10322 });
10323 })
10324 }
10325
10326 pub fn select_down_by_lines(
10327 &mut self,
10328 action: &SelectDownByLines,
10329 window: &mut Window,
10330 cx: &mut Context<Self>,
10331 ) {
10332 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10333 let text_layout_details = &self.text_layout_details(window);
10334 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10335 s.move_heads_with(|map, head, goal| {
10336 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10337 })
10338 })
10339 }
10340
10341 pub fn select_up_by_lines(
10342 &mut self,
10343 action: &SelectUpByLines,
10344 window: &mut Window,
10345 cx: &mut Context<Self>,
10346 ) {
10347 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10348 let text_layout_details = &self.text_layout_details(window);
10349 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10350 s.move_heads_with(|map, head, goal| {
10351 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10352 })
10353 })
10354 }
10355
10356 pub fn select_page_up(
10357 &mut self,
10358 _: &SelectPageUp,
10359 window: &mut Window,
10360 cx: &mut Context<Self>,
10361 ) {
10362 let Some(row_count) = self.visible_row_count() else {
10363 return;
10364 };
10365
10366 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10367
10368 let text_layout_details = &self.text_layout_details(window);
10369
10370 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10371 s.move_heads_with(|map, head, goal| {
10372 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10373 })
10374 })
10375 }
10376
10377 pub fn move_page_up(
10378 &mut self,
10379 action: &MovePageUp,
10380 window: &mut Window,
10381 cx: &mut Context<Self>,
10382 ) {
10383 if self.take_rename(true, window, cx).is_some() {
10384 return;
10385 }
10386
10387 if self
10388 .context_menu
10389 .borrow_mut()
10390 .as_mut()
10391 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10392 .unwrap_or(false)
10393 {
10394 return;
10395 }
10396
10397 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10398 cx.propagate();
10399 return;
10400 }
10401
10402 let Some(row_count) = self.visible_row_count() else {
10403 return;
10404 };
10405
10406 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10407
10408 let autoscroll = if action.center_cursor {
10409 Autoscroll::center()
10410 } else {
10411 Autoscroll::fit()
10412 };
10413
10414 let text_layout_details = &self.text_layout_details(window);
10415
10416 self.change_selections(Some(autoscroll), window, cx, |s| {
10417 s.move_with(|map, selection| {
10418 if !selection.is_empty() {
10419 selection.goal = SelectionGoal::None;
10420 }
10421 let (cursor, goal) = movement::up_by_rows(
10422 map,
10423 selection.end,
10424 row_count,
10425 selection.goal,
10426 false,
10427 text_layout_details,
10428 );
10429 selection.collapse_to(cursor, goal);
10430 });
10431 });
10432 }
10433
10434 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10435 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10436 let text_layout_details = &self.text_layout_details(window);
10437 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10438 s.move_heads_with(|map, head, goal| {
10439 movement::up(map, head, goal, false, text_layout_details)
10440 })
10441 })
10442 }
10443
10444 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10445 self.take_rename(true, window, cx);
10446
10447 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10448 cx.propagate();
10449 return;
10450 }
10451
10452 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10453
10454 let text_layout_details = &self.text_layout_details(window);
10455 let selection_count = self.selections.count();
10456 let first_selection = self.selections.first_anchor();
10457
10458 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10459 s.move_with(|map, selection| {
10460 if !selection.is_empty() {
10461 selection.goal = SelectionGoal::None;
10462 }
10463 let (cursor, goal) = movement::down(
10464 map,
10465 selection.end,
10466 selection.goal,
10467 false,
10468 text_layout_details,
10469 );
10470 selection.collapse_to(cursor, goal);
10471 });
10472 });
10473
10474 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10475 {
10476 cx.propagate();
10477 }
10478 }
10479
10480 pub fn select_page_down(
10481 &mut self,
10482 _: &SelectPageDown,
10483 window: &mut Window,
10484 cx: &mut Context<Self>,
10485 ) {
10486 let Some(row_count) = self.visible_row_count() else {
10487 return;
10488 };
10489
10490 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10491
10492 let text_layout_details = &self.text_layout_details(window);
10493
10494 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10495 s.move_heads_with(|map, head, goal| {
10496 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10497 })
10498 })
10499 }
10500
10501 pub fn move_page_down(
10502 &mut self,
10503 action: &MovePageDown,
10504 window: &mut Window,
10505 cx: &mut Context<Self>,
10506 ) {
10507 if self.take_rename(true, window, cx).is_some() {
10508 return;
10509 }
10510
10511 if self
10512 .context_menu
10513 .borrow_mut()
10514 .as_mut()
10515 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10516 .unwrap_or(false)
10517 {
10518 return;
10519 }
10520
10521 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10522 cx.propagate();
10523 return;
10524 }
10525
10526 let Some(row_count) = self.visible_row_count() else {
10527 return;
10528 };
10529
10530 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10531
10532 let autoscroll = if action.center_cursor {
10533 Autoscroll::center()
10534 } else {
10535 Autoscroll::fit()
10536 };
10537
10538 let text_layout_details = &self.text_layout_details(window);
10539 self.change_selections(Some(autoscroll), window, cx, |s| {
10540 s.move_with(|map, selection| {
10541 if !selection.is_empty() {
10542 selection.goal = SelectionGoal::None;
10543 }
10544 let (cursor, goal) = movement::down_by_rows(
10545 map,
10546 selection.end,
10547 row_count,
10548 selection.goal,
10549 false,
10550 text_layout_details,
10551 );
10552 selection.collapse_to(cursor, goal);
10553 });
10554 });
10555 }
10556
10557 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10558 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10559 let text_layout_details = &self.text_layout_details(window);
10560 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10561 s.move_heads_with(|map, head, goal| {
10562 movement::down(map, head, goal, false, text_layout_details)
10563 })
10564 });
10565 }
10566
10567 pub fn context_menu_first(
10568 &mut self,
10569 _: &ContextMenuFirst,
10570 _window: &mut Window,
10571 cx: &mut Context<Self>,
10572 ) {
10573 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10574 context_menu.select_first(self.completion_provider.as_deref(), cx);
10575 }
10576 }
10577
10578 pub fn context_menu_prev(
10579 &mut self,
10580 _: &ContextMenuPrevious,
10581 _window: &mut Window,
10582 cx: &mut Context<Self>,
10583 ) {
10584 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10585 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10586 }
10587 }
10588
10589 pub fn context_menu_next(
10590 &mut self,
10591 _: &ContextMenuNext,
10592 _window: &mut Window,
10593 cx: &mut Context<Self>,
10594 ) {
10595 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10596 context_menu.select_next(self.completion_provider.as_deref(), cx);
10597 }
10598 }
10599
10600 pub fn context_menu_last(
10601 &mut self,
10602 _: &ContextMenuLast,
10603 _window: &mut Window,
10604 cx: &mut Context<Self>,
10605 ) {
10606 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10607 context_menu.select_last(self.completion_provider.as_deref(), cx);
10608 }
10609 }
10610
10611 pub fn move_to_previous_word_start(
10612 &mut self,
10613 _: &MoveToPreviousWordStart,
10614 window: &mut Window,
10615 cx: &mut Context<Self>,
10616 ) {
10617 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10618 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10619 s.move_cursors_with(|map, head, _| {
10620 (
10621 movement::previous_word_start(map, head),
10622 SelectionGoal::None,
10623 )
10624 });
10625 })
10626 }
10627
10628 pub fn move_to_previous_subword_start(
10629 &mut self,
10630 _: &MoveToPreviousSubwordStart,
10631 window: &mut Window,
10632 cx: &mut Context<Self>,
10633 ) {
10634 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10635 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10636 s.move_cursors_with(|map, head, _| {
10637 (
10638 movement::previous_subword_start(map, head),
10639 SelectionGoal::None,
10640 )
10641 });
10642 })
10643 }
10644
10645 pub fn select_to_previous_word_start(
10646 &mut self,
10647 _: &SelectToPreviousWordStart,
10648 window: &mut Window,
10649 cx: &mut Context<Self>,
10650 ) {
10651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10652 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10653 s.move_heads_with(|map, head, _| {
10654 (
10655 movement::previous_word_start(map, head),
10656 SelectionGoal::None,
10657 )
10658 });
10659 })
10660 }
10661
10662 pub fn select_to_previous_subword_start(
10663 &mut self,
10664 _: &SelectToPreviousSubwordStart,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10669 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10670 s.move_heads_with(|map, head, _| {
10671 (
10672 movement::previous_subword_start(map, head),
10673 SelectionGoal::None,
10674 )
10675 });
10676 })
10677 }
10678
10679 pub fn delete_to_previous_word_start(
10680 &mut self,
10681 action: &DeleteToPreviousWordStart,
10682 window: &mut Window,
10683 cx: &mut Context<Self>,
10684 ) {
10685 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10686 self.transact(window, cx, |this, window, cx| {
10687 this.select_autoclose_pair(window, cx);
10688 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10689 s.move_with(|map, selection| {
10690 if selection.is_empty() {
10691 let cursor = if action.ignore_newlines {
10692 movement::previous_word_start(map, selection.head())
10693 } else {
10694 movement::previous_word_start_or_newline(map, selection.head())
10695 };
10696 selection.set_head(cursor, SelectionGoal::None);
10697 }
10698 });
10699 });
10700 this.insert("", window, cx);
10701 });
10702 }
10703
10704 pub fn delete_to_previous_subword_start(
10705 &mut self,
10706 _: &DeleteToPreviousSubwordStart,
10707 window: &mut Window,
10708 cx: &mut Context<Self>,
10709 ) {
10710 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10711 self.transact(window, cx, |this, window, cx| {
10712 this.select_autoclose_pair(window, cx);
10713 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10714 s.move_with(|map, selection| {
10715 if selection.is_empty() {
10716 let cursor = movement::previous_subword_start(map, selection.head());
10717 selection.set_head(cursor, SelectionGoal::None);
10718 }
10719 });
10720 });
10721 this.insert("", window, cx);
10722 });
10723 }
10724
10725 pub fn move_to_next_word_end(
10726 &mut self,
10727 _: &MoveToNextWordEnd,
10728 window: &mut Window,
10729 cx: &mut Context<Self>,
10730 ) {
10731 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10732 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10733 s.move_cursors_with(|map, head, _| {
10734 (movement::next_word_end(map, head), SelectionGoal::None)
10735 });
10736 })
10737 }
10738
10739 pub fn move_to_next_subword_end(
10740 &mut self,
10741 _: &MoveToNextSubwordEnd,
10742 window: &mut Window,
10743 cx: &mut Context<Self>,
10744 ) {
10745 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10746 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10747 s.move_cursors_with(|map, head, _| {
10748 (movement::next_subword_end(map, head), SelectionGoal::None)
10749 });
10750 })
10751 }
10752
10753 pub fn select_to_next_word_end(
10754 &mut self,
10755 _: &SelectToNextWordEnd,
10756 window: &mut Window,
10757 cx: &mut Context<Self>,
10758 ) {
10759 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10760 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10761 s.move_heads_with(|map, head, _| {
10762 (movement::next_word_end(map, head), SelectionGoal::None)
10763 });
10764 })
10765 }
10766
10767 pub fn select_to_next_subword_end(
10768 &mut self,
10769 _: &SelectToNextSubwordEnd,
10770 window: &mut Window,
10771 cx: &mut Context<Self>,
10772 ) {
10773 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10774 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10775 s.move_heads_with(|map, head, _| {
10776 (movement::next_subword_end(map, head), SelectionGoal::None)
10777 });
10778 })
10779 }
10780
10781 pub fn delete_to_next_word_end(
10782 &mut self,
10783 action: &DeleteToNextWordEnd,
10784 window: &mut Window,
10785 cx: &mut Context<Self>,
10786 ) {
10787 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10788 self.transact(window, cx, |this, window, cx| {
10789 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10790 s.move_with(|map, selection| {
10791 if selection.is_empty() {
10792 let cursor = if action.ignore_newlines {
10793 movement::next_word_end(map, selection.head())
10794 } else {
10795 movement::next_word_end_or_newline(map, selection.head())
10796 };
10797 selection.set_head(cursor, SelectionGoal::None);
10798 }
10799 });
10800 });
10801 this.insert("", window, cx);
10802 });
10803 }
10804
10805 pub fn delete_to_next_subword_end(
10806 &mut self,
10807 _: &DeleteToNextSubwordEnd,
10808 window: &mut Window,
10809 cx: &mut Context<Self>,
10810 ) {
10811 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10812 self.transact(window, cx, |this, window, cx| {
10813 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10814 s.move_with(|map, selection| {
10815 if selection.is_empty() {
10816 let cursor = movement::next_subword_end(map, selection.head());
10817 selection.set_head(cursor, SelectionGoal::None);
10818 }
10819 });
10820 });
10821 this.insert("", window, cx);
10822 });
10823 }
10824
10825 pub fn move_to_beginning_of_line(
10826 &mut self,
10827 action: &MoveToBeginningOfLine,
10828 window: &mut Window,
10829 cx: &mut Context<Self>,
10830 ) {
10831 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10832 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10833 s.move_cursors_with(|map, head, _| {
10834 (
10835 movement::indented_line_beginning(
10836 map,
10837 head,
10838 action.stop_at_soft_wraps,
10839 action.stop_at_indent,
10840 ),
10841 SelectionGoal::None,
10842 )
10843 });
10844 })
10845 }
10846
10847 pub fn select_to_beginning_of_line(
10848 &mut self,
10849 action: &SelectToBeginningOfLine,
10850 window: &mut Window,
10851 cx: &mut Context<Self>,
10852 ) {
10853 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10854 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10855 s.move_heads_with(|map, head, _| {
10856 (
10857 movement::indented_line_beginning(
10858 map,
10859 head,
10860 action.stop_at_soft_wraps,
10861 action.stop_at_indent,
10862 ),
10863 SelectionGoal::None,
10864 )
10865 });
10866 });
10867 }
10868
10869 pub fn delete_to_beginning_of_line(
10870 &mut self,
10871 action: &DeleteToBeginningOfLine,
10872 window: &mut Window,
10873 cx: &mut Context<Self>,
10874 ) {
10875 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10876 self.transact(window, cx, |this, window, cx| {
10877 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10878 s.move_with(|_, selection| {
10879 selection.reversed = true;
10880 });
10881 });
10882
10883 this.select_to_beginning_of_line(
10884 &SelectToBeginningOfLine {
10885 stop_at_soft_wraps: false,
10886 stop_at_indent: action.stop_at_indent,
10887 },
10888 window,
10889 cx,
10890 );
10891 this.backspace(&Backspace, window, cx);
10892 });
10893 }
10894
10895 pub fn move_to_end_of_line(
10896 &mut self,
10897 action: &MoveToEndOfLine,
10898 window: &mut Window,
10899 cx: &mut Context<Self>,
10900 ) {
10901 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10902 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10903 s.move_cursors_with(|map, head, _| {
10904 (
10905 movement::line_end(map, head, action.stop_at_soft_wraps),
10906 SelectionGoal::None,
10907 )
10908 });
10909 })
10910 }
10911
10912 pub fn select_to_end_of_line(
10913 &mut self,
10914 action: &SelectToEndOfLine,
10915 window: &mut Window,
10916 cx: &mut Context<Self>,
10917 ) {
10918 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10919 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10920 s.move_heads_with(|map, head, _| {
10921 (
10922 movement::line_end(map, head, action.stop_at_soft_wraps),
10923 SelectionGoal::None,
10924 )
10925 });
10926 })
10927 }
10928
10929 pub fn delete_to_end_of_line(
10930 &mut self,
10931 _: &DeleteToEndOfLine,
10932 window: &mut Window,
10933 cx: &mut Context<Self>,
10934 ) {
10935 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10936 self.transact(window, cx, |this, window, cx| {
10937 this.select_to_end_of_line(
10938 &SelectToEndOfLine {
10939 stop_at_soft_wraps: false,
10940 },
10941 window,
10942 cx,
10943 );
10944 this.delete(&Delete, window, cx);
10945 });
10946 }
10947
10948 pub fn cut_to_end_of_line(
10949 &mut self,
10950 _: &CutToEndOfLine,
10951 window: &mut Window,
10952 cx: &mut Context<Self>,
10953 ) {
10954 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10955 self.transact(window, cx, |this, window, cx| {
10956 this.select_to_end_of_line(
10957 &SelectToEndOfLine {
10958 stop_at_soft_wraps: false,
10959 },
10960 window,
10961 cx,
10962 );
10963 this.cut(&Cut, window, cx);
10964 });
10965 }
10966
10967 pub fn move_to_start_of_paragraph(
10968 &mut self,
10969 _: &MoveToStartOfParagraph,
10970 window: &mut Window,
10971 cx: &mut Context<Self>,
10972 ) {
10973 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10974 cx.propagate();
10975 return;
10976 }
10977 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10978 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10979 s.move_with(|map, selection| {
10980 selection.collapse_to(
10981 movement::start_of_paragraph(map, selection.head(), 1),
10982 SelectionGoal::None,
10983 )
10984 });
10985 })
10986 }
10987
10988 pub fn move_to_end_of_paragraph(
10989 &mut self,
10990 _: &MoveToEndOfParagraph,
10991 window: &mut Window,
10992 cx: &mut Context<Self>,
10993 ) {
10994 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10995 cx.propagate();
10996 return;
10997 }
10998 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10999 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11000 s.move_with(|map, selection| {
11001 selection.collapse_to(
11002 movement::end_of_paragraph(map, selection.head(), 1),
11003 SelectionGoal::None,
11004 )
11005 });
11006 })
11007 }
11008
11009 pub fn select_to_start_of_paragraph(
11010 &mut self,
11011 _: &SelectToStartOfParagraph,
11012 window: &mut Window,
11013 cx: &mut Context<Self>,
11014 ) {
11015 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11016 cx.propagate();
11017 return;
11018 }
11019 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11020 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11021 s.move_heads_with(|map, head, _| {
11022 (
11023 movement::start_of_paragraph(map, head, 1),
11024 SelectionGoal::None,
11025 )
11026 });
11027 })
11028 }
11029
11030 pub fn select_to_end_of_paragraph(
11031 &mut self,
11032 _: &SelectToEndOfParagraph,
11033 window: &mut Window,
11034 cx: &mut Context<Self>,
11035 ) {
11036 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11037 cx.propagate();
11038 return;
11039 }
11040 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11041 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11042 s.move_heads_with(|map, head, _| {
11043 (
11044 movement::end_of_paragraph(map, head, 1),
11045 SelectionGoal::None,
11046 )
11047 });
11048 })
11049 }
11050
11051 pub fn move_to_start_of_excerpt(
11052 &mut self,
11053 _: &MoveToStartOfExcerpt,
11054 window: &mut Window,
11055 cx: &mut Context<Self>,
11056 ) {
11057 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11058 cx.propagate();
11059 return;
11060 }
11061 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11062 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11063 s.move_with(|map, selection| {
11064 selection.collapse_to(
11065 movement::start_of_excerpt(
11066 map,
11067 selection.head(),
11068 workspace::searchable::Direction::Prev,
11069 ),
11070 SelectionGoal::None,
11071 )
11072 });
11073 })
11074 }
11075
11076 pub fn move_to_start_of_next_excerpt(
11077 &mut self,
11078 _: &MoveToStartOfNextExcerpt,
11079 window: &mut Window,
11080 cx: &mut Context<Self>,
11081 ) {
11082 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11083 cx.propagate();
11084 return;
11085 }
11086
11087 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11088 s.move_with(|map, selection| {
11089 selection.collapse_to(
11090 movement::start_of_excerpt(
11091 map,
11092 selection.head(),
11093 workspace::searchable::Direction::Next,
11094 ),
11095 SelectionGoal::None,
11096 )
11097 });
11098 })
11099 }
11100
11101 pub fn move_to_end_of_excerpt(
11102 &mut self,
11103 _: &MoveToEndOfExcerpt,
11104 window: &mut Window,
11105 cx: &mut Context<Self>,
11106 ) {
11107 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11108 cx.propagate();
11109 return;
11110 }
11111 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11112 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11113 s.move_with(|map, selection| {
11114 selection.collapse_to(
11115 movement::end_of_excerpt(
11116 map,
11117 selection.head(),
11118 workspace::searchable::Direction::Next,
11119 ),
11120 SelectionGoal::None,
11121 )
11122 });
11123 })
11124 }
11125
11126 pub fn move_to_end_of_previous_excerpt(
11127 &mut self,
11128 _: &MoveToEndOfPreviousExcerpt,
11129 window: &mut Window,
11130 cx: &mut Context<Self>,
11131 ) {
11132 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11133 cx.propagate();
11134 return;
11135 }
11136 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11137 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11138 s.move_with(|map, selection| {
11139 selection.collapse_to(
11140 movement::end_of_excerpt(
11141 map,
11142 selection.head(),
11143 workspace::searchable::Direction::Prev,
11144 ),
11145 SelectionGoal::None,
11146 )
11147 });
11148 })
11149 }
11150
11151 pub fn select_to_start_of_excerpt(
11152 &mut self,
11153 _: &SelectToStartOfExcerpt,
11154 window: &mut Window,
11155 cx: &mut Context<Self>,
11156 ) {
11157 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11158 cx.propagate();
11159 return;
11160 }
11161 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11162 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11163 s.move_heads_with(|map, head, _| {
11164 (
11165 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11166 SelectionGoal::None,
11167 )
11168 });
11169 })
11170 }
11171
11172 pub fn select_to_start_of_next_excerpt(
11173 &mut self,
11174 _: &SelectToStartOfNextExcerpt,
11175 window: &mut Window,
11176 cx: &mut Context<Self>,
11177 ) {
11178 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11179 cx.propagate();
11180 return;
11181 }
11182 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11183 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11184 s.move_heads_with(|map, head, _| {
11185 (
11186 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11187 SelectionGoal::None,
11188 )
11189 });
11190 })
11191 }
11192
11193 pub fn select_to_end_of_excerpt(
11194 &mut self,
11195 _: &SelectToEndOfExcerpt,
11196 window: &mut Window,
11197 cx: &mut Context<Self>,
11198 ) {
11199 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11200 cx.propagate();
11201 return;
11202 }
11203 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11204 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11205 s.move_heads_with(|map, head, _| {
11206 (
11207 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11208 SelectionGoal::None,
11209 )
11210 });
11211 })
11212 }
11213
11214 pub fn select_to_end_of_previous_excerpt(
11215 &mut self,
11216 _: &SelectToEndOfPreviousExcerpt,
11217 window: &mut Window,
11218 cx: &mut Context<Self>,
11219 ) {
11220 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11221 cx.propagate();
11222 return;
11223 }
11224 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11225 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11226 s.move_heads_with(|map, head, _| {
11227 (
11228 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11229 SelectionGoal::None,
11230 )
11231 });
11232 })
11233 }
11234
11235 pub fn move_to_beginning(
11236 &mut self,
11237 _: &MoveToBeginning,
11238 window: &mut Window,
11239 cx: &mut Context<Self>,
11240 ) {
11241 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11242 cx.propagate();
11243 return;
11244 }
11245 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11246 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11247 s.select_ranges(vec![0..0]);
11248 });
11249 }
11250
11251 pub fn select_to_beginning(
11252 &mut self,
11253 _: &SelectToBeginning,
11254 window: &mut Window,
11255 cx: &mut Context<Self>,
11256 ) {
11257 let mut selection = self.selections.last::<Point>(cx);
11258 selection.set_head(Point::zero(), SelectionGoal::None);
11259 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11260 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11261 s.select(vec![selection]);
11262 });
11263 }
11264
11265 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11266 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11267 cx.propagate();
11268 return;
11269 }
11270 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11271 let cursor = self.buffer.read(cx).read(cx).len();
11272 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11273 s.select_ranges(vec![cursor..cursor])
11274 });
11275 }
11276
11277 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11278 self.nav_history = nav_history;
11279 }
11280
11281 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11282 self.nav_history.as_ref()
11283 }
11284
11285 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11286 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11287 }
11288
11289 fn push_to_nav_history(
11290 &mut self,
11291 cursor_anchor: Anchor,
11292 new_position: Option<Point>,
11293 is_deactivate: bool,
11294 cx: &mut Context<Self>,
11295 ) {
11296 if let Some(nav_history) = self.nav_history.as_mut() {
11297 let buffer = self.buffer.read(cx).read(cx);
11298 let cursor_position = cursor_anchor.to_point(&buffer);
11299 let scroll_state = self.scroll_manager.anchor();
11300 let scroll_top_row = scroll_state.top_row(&buffer);
11301 drop(buffer);
11302
11303 if let Some(new_position) = new_position {
11304 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11305 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11306 return;
11307 }
11308 }
11309
11310 nav_history.push(
11311 Some(NavigationData {
11312 cursor_anchor,
11313 cursor_position,
11314 scroll_anchor: scroll_state,
11315 scroll_top_row,
11316 }),
11317 cx,
11318 );
11319 cx.emit(EditorEvent::PushedToNavHistory {
11320 anchor: cursor_anchor,
11321 is_deactivate,
11322 })
11323 }
11324 }
11325
11326 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11327 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11328 let buffer = self.buffer.read(cx).snapshot(cx);
11329 let mut selection = self.selections.first::<usize>(cx);
11330 selection.set_head(buffer.len(), SelectionGoal::None);
11331 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11332 s.select(vec![selection]);
11333 });
11334 }
11335
11336 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11337 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11338 let end = self.buffer.read(cx).read(cx).len();
11339 self.change_selections(None, window, cx, |s| {
11340 s.select_ranges(vec![0..end]);
11341 });
11342 }
11343
11344 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11345 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11346 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11347 let mut selections = self.selections.all::<Point>(cx);
11348 let max_point = display_map.buffer_snapshot.max_point();
11349 for selection in &mut selections {
11350 let rows = selection.spanned_rows(true, &display_map);
11351 selection.start = Point::new(rows.start.0, 0);
11352 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11353 selection.reversed = false;
11354 }
11355 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11356 s.select(selections);
11357 });
11358 }
11359
11360 pub fn split_selection_into_lines(
11361 &mut self,
11362 _: &SplitSelectionIntoLines,
11363 window: &mut Window,
11364 cx: &mut Context<Self>,
11365 ) {
11366 let selections = self
11367 .selections
11368 .all::<Point>(cx)
11369 .into_iter()
11370 .map(|selection| selection.start..selection.end)
11371 .collect::<Vec<_>>();
11372 self.unfold_ranges(&selections, true, true, cx);
11373
11374 let mut new_selection_ranges = Vec::new();
11375 {
11376 let buffer = self.buffer.read(cx).read(cx);
11377 for selection in selections {
11378 for row in selection.start.row..selection.end.row {
11379 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11380 new_selection_ranges.push(cursor..cursor);
11381 }
11382
11383 let is_multiline_selection = selection.start.row != selection.end.row;
11384 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11385 // so this action feels more ergonomic when paired with other selection operations
11386 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11387 if !should_skip_last {
11388 new_selection_ranges.push(selection.end..selection.end);
11389 }
11390 }
11391 }
11392 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11393 s.select_ranges(new_selection_ranges);
11394 });
11395 }
11396
11397 pub fn add_selection_above(
11398 &mut self,
11399 _: &AddSelectionAbove,
11400 window: &mut Window,
11401 cx: &mut Context<Self>,
11402 ) {
11403 self.add_selection(true, window, cx);
11404 }
11405
11406 pub fn add_selection_below(
11407 &mut self,
11408 _: &AddSelectionBelow,
11409 window: &mut Window,
11410 cx: &mut Context<Self>,
11411 ) {
11412 self.add_selection(false, window, cx);
11413 }
11414
11415 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11416 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11417
11418 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11419 let mut selections = self.selections.all::<Point>(cx);
11420 let text_layout_details = self.text_layout_details(window);
11421 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11422 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11423 let range = oldest_selection.display_range(&display_map).sorted();
11424
11425 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11426 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11427 let positions = start_x.min(end_x)..start_x.max(end_x);
11428
11429 selections.clear();
11430 let mut stack = Vec::new();
11431 for row in range.start.row().0..=range.end.row().0 {
11432 if let Some(selection) = self.selections.build_columnar_selection(
11433 &display_map,
11434 DisplayRow(row),
11435 &positions,
11436 oldest_selection.reversed,
11437 &text_layout_details,
11438 ) {
11439 stack.push(selection.id);
11440 selections.push(selection);
11441 }
11442 }
11443
11444 if above {
11445 stack.reverse();
11446 }
11447
11448 AddSelectionsState { above, stack }
11449 });
11450
11451 let last_added_selection = *state.stack.last().unwrap();
11452 let mut new_selections = Vec::new();
11453 if above == state.above {
11454 let end_row = if above {
11455 DisplayRow(0)
11456 } else {
11457 display_map.max_point().row()
11458 };
11459
11460 'outer: for selection in selections {
11461 if selection.id == last_added_selection {
11462 let range = selection.display_range(&display_map).sorted();
11463 debug_assert_eq!(range.start.row(), range.end.row());
11464 let mut row = range.start.row();
11465 let positions =
11466 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11467 px(start)..px(end)
11468 } else {
11469 let start_x =
11470 display_map.x_for_display_point(range.start, &text_layout_details);
11471 let end_x =
11472 display_map.x_for_display_point(range.end, &text_layout_details);
11473 start_x.min(end_x)..start_x.max(end_x)
11474 };
11475
11476 while row != end_row {
11477 if above {
11478 row.0 -= 1;
11479 } else {
11480 row.0 += 1;
11481 }
11482
11483 if let Some(new_selection) = self.selections.build_columnar_selection(
11484 &display_map,
11485 row,
11486 &positions,
11487 selection.reversed,
11488 &text_layout_details,
11489 ) {
11490 state.stack.push(new_selection.id);
11491 if above {
11492 new_selections.push(new_selection);
11493 new_selections.push(selection);
11494 } else {
11495 new_selections.push(selection);
11496 new_selections.push(new_selection);
11497 }
11498
11499 continue 'outer;
11500 }
11501 }
11502 }
11503
11504 new_selections.push(selection);
11505 }
11506 } else {
11507 new_selections = selections;
11508 new_selections.retain(|s| s.id != last_added_selection);
11509 state.stack.pop();
11510 }
11511
11512 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11513 s.select(new_selections);
11514 });
11515 if state.stack.len() > 1 {
11516 self.add_selections_state = Some(state);
11517 }
11518 }
11519
11520 pub fn select_next_match_internal(
11521 &mut self,
11522 display_map: &DisplaySnapshot,
11523 replace_newest: bool,
11524 autoscroll: Option<Autoscroll>,
11525 window: &mut Window,
11526 cx: &mut Context<Self>,
11527 ) -> Result<()> {
11528 fn select_next_match_ranges(
11529 this: &mut Editor,
11530 range: Range<usize>,
11531 replace_newest: bool,
11532 auto_scroll: Option<Autoscroll>,
11533 window: &mut Window,
11534 cx: &mut Context<Editor>,
11535 ) {
11536 this.unfold_ranges(&[range.clone()], false, true, cx);
11537 this.change_selections(auto_scroll, window, cx, |s| {
11538 if replace_newest {
11539 s.delete(s.newest_anchor().id);
11540 }
11541 s.insert_range(range.clone());
11542 });
11543 }
11544
11545 let buffer = &display_map.buffer_snapshot;
11546 let mut selections = self.selections.all::<usize>(cx);
11547 if let Some(mut select_next_state) = self.select_next_state.take() {
11548 let query = &select_next_state.query;
11549 if !select_next_state.done {
11550 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11551 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11552 let mut next_selected_range = None;
11553
11554 let bytes_after_last_selection =
11555 buffer.bytes_in_range(last_selection.end..buffer.len());
11556 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11557 let query_matches = query
11558 .stream_find_iter(bytes_after_last_selection)
11559 .map(|result| (last_selection.end, result))
11560 .chain(
11561 query
11562 .stream_find_iter(bytes_before_first_selection)
11563 .map(|result| (0, result)),
11564 );
11565
11566 for (start_offset, query_match) in query_matches {
11567 let query_match = query_match.unwrap(); // can only fail due to I/O
11568 let offset_range =
11569 start_offset + query_match.start()..start_offset + query_match.end();
11570 let display_range = offset_range.start.to_display_point(display_map)
11571 ..offset_range.end.to_display_point(display_map);
11572
11573 if !select_next_state.wordwise
11574 || (!movement::is_inside_word(display_map, display_range.start)
11575 && !movement::is_inside_word(display_map, display_range.end))
11576 {
11577 // TODO: This is n^2, because we might check all the selections
11578 if !selections
11579 .iter()
11580 .any(|selection| selection.range().overlaps(&offset_range))
11581 {
11582 next_selected_range = Some(offset_range);
11583 break;
11584 }
11585 }
11586 }
11587
11588 if let Some(next_selected_range) = next_selected_range {
11589 select_next_match_ranges(
11590 self,
11591 next_selected_range,
11592 replace_newest,
11593 autoscroll,
11594 window,
11595 cx,
11596 );
11597 } else {
11598 select_next_state.done = true;
11599 }
11600 }
11601
11602 self.select_next_state = Some(select_next_state);
11603 } else {
11604 let mut only_carets = true;
11605 let mut same_text_selected = true;
11606 let mut selected_text = None;
11607
11608 let mut selections_iter = selections.iter().peekable();
11609 while let Some(selection) = selections_iter.next() {
11610 if selection.start != selection.end {
11611 only_carets = false;
11612 }
11613
11614 if same_text_selected {
11615 if selected_text.is_none() {
11616 selected_text =
11617 Some(buffer.text_for_range(selection.range()).collect::<String>());
11618 }
11619
11620 if let Some(next_selection) = selections_iter.peek() {
11621 if next_selection.range().len() == selection.range().len() {
11622 let next_selected_text = buffer
11623 .text_for_range(next_selection.range())
11624 .collect::<String>();
11625 if Some(next_selected_text) != selected_text {
11626 same_text_selected = false;
11627 selected_text = None;
11628 }
11629 } else {
11630 same_text_selected = false;
11631 selected_text = None;
11632 }
11633 }
11634 }
11635 }
11636
11637 if only_carets {
11638 for selection in &mut selections {
11639 let word_range = movement::surrounding_word(
11640 display_map,
11641 selection.start.to_display_point(display_map),
11642 );
11643 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11644 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11645 selection.goal = SelectionGoal::None;
11646 selection.reversed = false;
11647 select_next_match_ranges(
11648 self,
11649 selection.start..selection.end,
11650 replace_newest,
11651 autoscroll,
11652 window,
11653 cx,
11654 );
11655 }
11656
11657 if selections.len() == 1 {
11658 let selection = selections
11659 .last()
11660 .expect("ensured that there's only one selection");
11661 let query = buffer
11662 .text_for_range(selection.start..selection.end)
11663 .collect::<String>();
11664 let is_empty = query.is_empty();
11665 let select_state = SelectNextState {
11666 query: AhoCorasick::new(&[query])?,
11667 wordwise: true,
11668 done: is_empty,
11669 };
11670 self.select_next_state = Some(select_state);
11671 } else {
11672 self.select_next_state = None;
11673 }
11674 } else if let Some(selected_text) = selected_text {
11675 self.select_next_state = Some(SelectNextState {
11676 query: AhoCorasick::new(&[selected_text])?,
11677 wordwise: false,
11678 done: false,
11679 });
11680 self.select_next_match_internal(
11681 display_map,
11682 replace_newest,
11683 autoscroll,
11684 window,
11685 cx,
11686 )?;
11687 }
11688 }
11689 Ok(())
11690 }
11691
11692 pub fn select_all_matches(
11693 &mut self,
11694 _action: &SelectAllMatches,
11695 window: &mut Window,
11696 cx: &mut Context<Self>,
11697 ) -> Result<()> {
11698 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11699
11700 self.push_to_selection_history();
11701 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11702
11703 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11704 let Some(select_next_state) = self.select_next_state.as_mut() else {
11705 return Ok(());
11706 };
11707 if select_next_state.done {
11708 return Ok(());
11709 }
11710
11711 let mut new_selections = self.selections.all::<usize>(cx);
11712
11713 let buffer = &display_map.buffer_snapshot;
11714 let query_matches = select_next_state
11715 .query
11716 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11717
11718 for query_match in query_matches {
11719 let query_match = query_match.unwrap(); // can only fail due to I/O
11720 let offset_range = query_match.start()..query_match.end();
11721 let display_range = offset_range.start.to_display_point(&display_map)
11722 ..offset_range.end.to_display_point(&display_map);
11723
11724 if !select_next_state.wordwise
11725 || (!movement::is_inside_word(&display_map, display_range.start)
11726 && !movement::is_inside_word(&display_map, display_range.end))
11727 {
11728 self.selections.change_with(cx, |selections| {
11729 new_selections.push(Selection {
11730 id: selections.new_selection_id(),
11731 start: offset_range.start,
11732 end: offset_range.end,
11733 reversed: false,
11734 goal: SelectionGoal::None,
11735 });
11736 });
11737 }
11738 }
11739
11740 new_selections.sort_by_key(|selection| selection.start);
11741 let mut ix = 0;
11742 while ix + 1 < new_selections.len() {
11743 let current_selection = &new_selections[ix];
11744 let next_selection = &new_selections[ix + 1];
11745 if current_selection.range().overlaps(&next_selection.range()) {
11746 if current_selection.id < next_selection.id {
11747 new_selections.remove(ix + 1);
11748 } else {
11749 new_selections.remove(ix);
11750 }
11751 } else {
11752 ix += 1;
11753 }
11754 }
11755
11756 let reversed = self.selections.oldest::<usize>(cx).reversed;
11757
11758 for selection in new_selections.iter_mut() {
11759 selection.reversed = reversed;
11760 }
11761
11762 select_next_state.done = true;
11763 self.unfold_ranges(
11764 &new_selections
11765 .iter()
11766 .map(|selection| selection.range())
11767 .collect::<Vec<_>>(),
11768 false,
11769 false,
11770 cx,
11771 );
11772 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11773 selections.select(new_selections)
11774 });
11775
11776 Ok(())
11777 }
11778
11779 pub fn select_next(
11780 &mut self,
11781 action: &SelectNext,
11782 window: &mut Window,
11783 cx: &mut Context<Self>,
11784 ) -> Result<()> {
11785 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11786 self.push_to_selection_history();
11787 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11788 self.select_next_match_internal(
11789 &display_map,
11790 action.replace_newest,
11791 Some(Autoscroll::newest()),
11792 window,
11793 cx,
11794 )?;
11795 Ok(())
11796 }
11797
11798 pub fn select_previous(
11799 &mut self,
11800 action: &SelectPrevious,
11801 window: &mut Window,
11802 cx: &mut Context<Self>,
11803 ) -> Result<()> {
11804 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11805 self.push_to_selection_history();
11806 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11807 let buffer = &display_map.buffer_snapshot;
11808 let mut selections = self.selections.all::<usize>(cx);
11809 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11810 let query = &select_prev_state.query;
11811 if !select_prev_state.done {
11812 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11813 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11814 let mut next_selected_range = None;
11815 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11816 let bytes_before_last_selection =
11817 buffer.reversed_bytes_in_range(0..last_selection.start);
11818 let bytes_after_first_selection =
11819 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11820 let query_matches = query
11821 .stream_find_iter(bytes_before_last_selection)
11822 .map(|result| (last_selection.start, result))
11823 .chain(
11824 query
11825 .stream_find_iter(bytes_after_first_selection)
11826 .map(|result| (buffer.len(), result)),
11827 );
11828 for (end_offset, query_match) in query_matches {
11829 let query_match = query_match.unwrap(); // can only fail due to I/O
11830 let offset_range =
11831 end_offset - query_match.end()..end_offset - query_match.start();
11832 let display_range = offset_range.start.to_display_point(&display_map)
11833 ..offset_range.end.to_display_point(&display_map);
11834
11835 if !select_prev_state.wordwise
11836 || (!movement::is_inside_word(&display_map, display_range.start)
11837 && !movement::is_inside_word(&display_map, display_range.end))
11838 {
11839 next_selected_range = Some(offset_range);
11840 break;
11841 }
11842 }
11843
11844 if let Some(next_selected_range) = next_selected_range {
11845 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11846 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11847 if action.replace_newest {
11848 s.delete(s.newest_anchor().id);
11849 }
11850 s.insert_range(next_selected_range);
11851 });
11852 } else {
11853 select_prev_state.done = true;
11854 }
11855 }
11856
11857 self.select_prev_state = Some(select_prev_state);
11858 } else {
11859 let mut only_carets = true;
11860 let mut same_text_selected = true;
11861 let mut selected_text = None;
11862
11863 let mut selections_iter = selections.iter().peekable();
11864 while let Some(selection) = selections_iter.next() {
11865 if selection.start != selection.end {
11866 only_carets = false;
11867 }
11868
11869 if same_text_selected {
11870 if selected_text.is_none() {
11871 selected_text =
11872 Some(buffer.text_for_range(selection.range()).collect::<String>());
11873 }
11874
11875 if let Some(next_selection) = selections_iter.peek() {
11876 if next_selection.range().len() == selection.range().len() {
11877 let next_selected_text = buffer
11878 .text_for_range(next_selection.range())
11879 .collect::<String>();
11880 if Some(next_selected_text) != selected_text {
11881 same_text_selected = false;
11882 selected_text = None;
11883 }
11884 } else {
11885 same_text_selected = false;
11886 selected_text = None;
11887 }
11888 }
11889 }
11890 }
11891
11892 if only_carets {
11893 for selection in &mut selections {
11894 let word_range = movement::surrounding_word(
11895 &display_map,
11896 selection.start.to_display_point(&display_map),
11897 );
11898 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11899 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11900 selection.goal = SelectionGoal::None;
11901 selection.reversed = false;
11902 }
11903 if selections.len() == 1 {
11904 let selection = selections
11905 .last()
11906 .expect("ensured that there's only one selection");
11907 let query = buffer
11908 .text_for_range(selection.start..selection.end)
11909 .collect::<String>();
11910 let is_empty = query.is_empty();
11911 let select_state = SelectNextState {
11912 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11913 wordwise: true,
11914 done: is_empty,
11915 };
11916 self.select_prev_state = Some(select_state);
11917 } else {
11918 self.select_prev_state = None;
11919 }
11920
11921 self.unfold_ranges(
11922 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11923 false,
11924 true,
11925 cx,
11926 );
11927 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11928 s.select(selections);
11929 });
11930 } else if let Some(selected_text) = selected_text {
11931 self.select_prev_state = Some(SelectNextState {
11932 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11933 wordwise: false,
11934 done: false,
11935 });
11936 self.select_previous(action, window, cx)?;
11937 }
11938 }
11939 Ok(())
11940 }
11941
11942 pub fn toggle_comments(
11943 &mut self,
11944 action: &ToggleComments,
11945 window: &mut Window,
11946 cx: &mut Context<Self>,
11947 ) {
11948 if self.read_only(cx) {
11949 return;
11950 }
11951 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11952 let text_layout_details = &self.text_layout_details(window);
11953 self.transact(window, cx, |this, window, cx| {
11954 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11955 let mut edits = Vec::new();
11956 let mut selection_edit_ranges = Vec::new();
11957 let mut last_toggled_row = None;
11958 let snapshot = this.buffer.read(cx).read(cx);
11959 let empty_str: Arc<str> = Arc::default();
11960 let mut suffixes_inserted = Vec::new();
11961 let ignore_indent = action.ignore_indent;
11962
11963 fn comment_prefix_range(
11964 snapshot: &MultiBufferSnapshot,
11965 row: MultiBufferRow,
11966 comment_prefix: &str,
11967 comment_prefix_whitespace: &str,
11968 ignore_indent: bool,
11969 ) -> Range<Point> {
11970 let indent_size = if ignore_indent {
11971 0
11972 } else {
11973 snapshot.indent_size_for_line(row).len
11974 };
11975
11976 let start = Point::new(row.0, indent_size);
11977
11978 let mut line_bytes = snapshot
11979 .bytes_in_range(start..snapshot.max_point())
11980 .flatten()
11981 .copied();
11982
11983 // If this line currently begins with the line comment prefix, then record
11984 // the range containing the prefix.
11985 if line_bytes
11986 .by_ref()
11987 .take(comment_prefix.len())
11988 .eq(comment_prefix.bytes())
11989 {
11990 // Include any whitespace that matches the comment prefix.
11991 let matching_whitespace_len = line_bytes
11992 .zip(comment_prefix_whitespace.bytes())
11993 .take_while(|(a, b)| a == b)
11994 .count() as u32;
11995 let end = Point::new(
11996 start.row,
11997 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
11998 );
11999 start..end
12000 } else {
12001 start..start
12002 }
12003 }
12004
12005 fn comment_suffix_range(
12006 snapshot: &MultiBufferSnapshot,
12007 row: MultiBufferRow,
12008 comment_suffix: &str,
12009 comment_suffix_has_leading_space: bool,
12010 ) -> Range<Point> {
12011 let end = Point::new(row.0, snapshot.line_len(row));
12012 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12013
12014 let mut line_end_bytes = snapshot
12015 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12016 .flatten()
12017 .copied();
12018
12019 let leading_space_len = if suffix_start_column > 0
12020 && line_end_bytes.next() == Some(b' ')
12021 && comment_suffix_has_leading_space
12022 {
12023 1
12024 } else {
12025 0
12026 };
12027
12028 // If this line currently begins with the line comment prefix, then record
12029 // the range containing the prefix.
12030 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12031 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12032 start..end
12033 } else {
12034 end..end
12035 }
12036 }
12037
12038 // TODO: Handle selections that cross excerpts
12039 for selection in &mut selections {
12040 let start_column = snapshot
12041 .indent_size_for_line(MultiBufferRow(selection.start.row))
12042 .len;
12043 let language = if let Some(language) =
12044 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12045 {
12046 language
12047 } else {
12048 continue;
12049 };
12050
12051 selection_edit_ranges.clear();
12052
12053 // If multiple selections contain a given row, avoid processing that
12054 // row more than once.
12055 let mut start_row = MultiBufferRow(selection.start.row);
12056 if last_toggled_row == Some(start_row) {
12057 start_row = start_row.next_row();
12058 }
12059 let end_row =
12060 if selection.end.row > selection.start.row && selection.end.column == 0 {
12061 MultiBufferRow(selection.end.row - 1)
12062 } else {
12063 MultiBufferRow(selection.end.row)
12064 };
12065 last_toggled_row = Some(end_row);
12066
12067 if start_row > end_row {
12068 continue;
12069 }
12070
12071 // If the language has line comments, toggle those.
12072 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12073
12074 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12075 if ignore_indent {
12076 full_comment_prefixes = full_comment_prefixes
12077 .into_iter()
12078 .map(|s| Arc::from(s.trim_end()))
12079 .collect();
12080 }
12081
12082 if !full_comment_prefixes.is_empty() {
12083 let first_prefix = full_comment_prefixes
12084 .first()
12085 .expect("prefixes is non-empty");
12086 let prefix_trimmed_lengths = full_comment_prefixes
12087 .iter()
12088 .map(|p| p.trim_end_matches(' ').len())
12089 .collect::<SmallVec<[usize; 4]>>();
12090
12091 let mut all_selection_lines_are_comments = true;
12092
12093 for row in start_row.0..=end_row.0 {
12094 let row = MultiBufferRow(row);
12095 if start_row < end_row && snapshot.is_line_blank(row) {
12096 continue;
12097 }
12098
12099 let prefix_range = full_comment_prefixes
12100 .iter()
12101 .zip(prefix_trimmed_lengths.iter().copied())
12102 .map(|(prefix, trimmed_prefix_len)| {
12103 comment_prefix_range(
12104 snapshot.deref(),
12105 row,
12106 &prefix[..trimmed_prefix_len],
12107 &prefix[trimmed_prefix_len..],
12108 ignore_indent,
12109 )
12110 })
12111 .max_by_key(|range| range.end.column - range.start.column)
12112 .expect("prefixes is non-empty");
12113
12114 if prefix_range.is_empty() {
12115 all_selection_lines_are_comments = false;
12116 }
12117
12118 selection_edit_ranges.push(prefix_range);
12119 }
12120
12121 if all_selection_lines_are_comments {
12122 edits.extend(
12123 selection_edit_ranges
12124 .iter()
12125 .cloned()
12126 .map(|range| (range, empty_str.clone())),
12127 );
12128 } else {
12129 let min_column = selection_edit_ranges
12130 .iter()
12131 .map(|range| range.start.column)
12132 .min()
12133 .unwrap_or(0);
12134 edits.extend(selection_edit_ranges.iter().map(|range| {
12135 let position = Point::new(range.start.row, min_column);
12136 (position..position, first_prefix.clone())
12137 }));
12138 }
12139 } else if let Some((full_comment_prefix, comment_suffix)) =
12140 language.block_comment_delimiters()
12141 {
12142 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12143 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12144 let prefix_range = comment_prefix_range(
12145 snapshot.deref(),
12146 start_row,
12147 comment_prefix,
12148 comment_prefix_whitespace,
12149 ignore_indent,
12150 );
12151 let suffix_range = comment_suffix_range(
12152 snapshot.deref(),
12153 end_row,
12154 comment_suffix.trim_start_matches(' '),
12155 comment_suffix.starts_with(' '),
12156 );
12157
12158 if prefix_range.is_empty() || suffix_range.is_empty() {
12159 edits.push((
12160 prefix_range.start..prefix_range.start,
12161 full_comment_prefix.clone(),
12162 ));
12163 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12164 suffixes_inserted.push((end_row, comment_suffix.len()));
12165 } else {
12166 edits.push((prefix_range, empty_str.clone()));
12167 edits.push((suffix_range, empty_str.clone()));
12168 }
12169 } else {
12170 continue;
12171 }
12172 }
12173
12174 drop(snapshot);
12175 this.buffer.update(cx, |buffer, cx| {
12176 buffer.edit(edits, None, cx);
12177 });
12178
12179 // Adjust selections so that they end before any comment suffixes that
12180 // were inserted.
12181 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12182 let mut selections = this.selections.all::<Point>(cx);
12183 let snapshot = this.buffer.read(cx).read(cx);
12184 for selection in &mut selections {
12185 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12186 match row.cmp(&MultiBufferRow(selection.end.row)) {
12187 Ordering::Less => {
12188 suffixes_inserted.next();
12189 continue;
12190 }
12191 Ordering::Greater => break,
12192 Ordering::Equal => {
12193 if selection.end.column == snapshot.line_len(row) {
12194 if selection.is_empty() {
12195 selection.start.column -= suffix_len as u32;
12196 }
12197 selection.end.column -= suffix_len as u32;
12198 }
12199 break;
12200 }
12201 }
12202 }
12203 }
12204
12205 drop(snapshot);
12206 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12207 s.select(selections)
12208 });
12209
12210 let selections = this.selections.all::<Point>(cx);
12211 let selections_on_single_row = selections.windows(2).all(|selections| {
12212 selections[0].start.row == selections[1].start.row
12213 && selections[0].end.row == selections[1].end.row
12214 && selections[0].start.row == selections[0].end.row
12215 });
12216 let selections_selecting = selections
12217 .iter()
12218 .any(|selection| selection.start != selection.end);
12219 let advance_downwards = action.advance_downwards
12220 && selections_on_single_row
12221 && !selections_selecting
12222 && !matches!(this.mode, EditorMode::SingleLine { .. });
12223
12224 if advance_downwards {
12225 let snapshot = this.buffer.read(cx).snapshot(cx);
12226
12227 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12228 s.move_cursors_with(|display_snapshot, display_point, _| {
12229 let mut point = display_point.to_point(display_snapshot);
12230 point.row += 1;
12231 point = snapshot.clip_point(point, Bias::Left);
12232 let display_point = point.to_display_point(display_snapshot);
12233 let goal = SelectionGoal::HorizontalPosition(
12234 display_snapshot
12235 .x_for_display_point(display_point, text_layout_details)
12236 .into(),
12237 );
12238 (display_point, goal)
12239 })
12240 });
12241 }
12242 });
12243 }
12244
12245 pub fn select_enclosing_symbol(
12246 &mut self,
12247 _: &SelectEnclosingSymbol,
12248 window: &mut Window,
12249 cx: &mut Context<Self>,
12250 ) {
12251 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12252
12253 let buffer = self.buffer.read(cx).snapshot(cx);
12254 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12255
12256 fn update_selection(
12257 selection: &Selection<usize>,
12258 buffer_snap: &MultiBufferSnapshot,
12259 ) -> Option<Selection<usize>> {
12260 let cursor = selection.head();
12261 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12262 for symbol in symbols.iter().rev() {
12263 let start = symbol.range.start.to_offset(buffer_snap);
12264 let end = symbol.range.end.to_offset(buffer_snap);
12265 let new_range = start..end;
12266 if start < selection.start || end > selection.end {
12267 return Some(Selection {
12268 id: selection.id,
12269 start: new_range.start,
12270 end: new_range.end,
12271 goal: SelectionGoal::None,
12272 reversed: selection.reversed,
12273 });
12274 }
12275 }
12276 None
12277 }
12278
12279 let mut selected_larger_symbol = false;
12280 let new_selections = old_selections
12281 .iter()
12282 .map(|selection| match update_selection(selection, &buffer) {
12283 Some(new_selection) => {
12284 if new_selection.range() != selection.range() {
12285 selected_larger_symbol = true;
12286 }
12287 new_selection
12288 }
12289 None => selection.clone(),
12290 })
12291 .collect::<Vec<_>>();
12292
12293 if selected_larger_symbol {
12294 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12295 s.select(new_selections);
12296 });
12297 }
12298 }
12299
12300 pub fn select_larger_syntax_node(
12301 &mut self,
12302 _: &SelectLargerSyntaxNode,
12303 window: &mut Window,
12304 cx: &mut Context<Self>,
12305 ) {
12306 let Some(visible_row_count) = self.visible_row_count() else {
12307 return;
12308 };
12309 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12310 if old_selections.is_empty() {
12311 return;
12312 }
12313
12314 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12315
12316 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12317 let buffer = self.buffer.read(cx).snapshot(cx);
12318
12319 let mut selected_larger_node = false;
12320 let mut new_selections = old_selections
12321 .iter()
12322 .map(|selection| {
12323 let old_range = selection.start..selection.end;
12324 let mut new_range = old_range.clone();
12325 let mut new_node = None;
12326 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12327 {
12328 new_node = Some(node);
12329 new_range = match containing_range {
12330 MultiOrSingleBufferOffsetRange::Single(_) => break,
12331 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12332 };
12333 if !display_map.intersects_fold(new_range.start)
12334 && !display_map.intersects_fold(new_range.end)
12335 {
12336 break;
12337 }
12338 }
12339
12340 if let Some(node) = new_node {
12341 // Log the ancestor, to support using this action as a way to explore TreeSitter
12342 // nodes. Parent and grandparent are also logged because this operation will not
12343 // visit nodes that have the same range as their parent.
12344 log::info!("Node: {node:?}");
12345 let parent = node.parent();
12346 log::info!("Parent: {parent:?}");
12347 let grandparent = parent.and_then(|x| x.parent());
12348 log::info!("Grandparent: {grandparent:?}");
12349 }
12350
12351 selected_larger_node |= new_range != old_range;
12352 Selection {
12353 id: selection.id,
12354 start: new_range.start,
12355 end: new_range.end,
12356 goal: SelectionGoal::None,
12357 reversed: selection.reversed,
12358 }
12359 })
12360 .collect::<Vec<_>>();
12361
12362 if !selected_larger_node {
12363 return; // don't put this call in the history
12364 }
12365
12366 // scroll based on transformation done to the last selection created by the user
12367 let (last_old, last_new) = old_selections
12368 .last()
12369 .zip(new_selections.last().cloned())
12370 .expect("old_selections isn't empty");
12371
12372 // revert selection
12373 let is_selection_reversed = {
12374 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12375 new_selections.last_mut().expect("checked above").reversed =
12376 should_newest_selection_be_reversed;
12377 should_newest_selection_be_reversed
12378 };
12379
12380 if selected_larger_node {
12381 self.select_syntax_node_history.disable_clearing = true;
12382 self.change_selections(None, window, cx, |s| {
12383 s.select(new_selections.clone());
12384 });
12385 self.select_syntax_node_history.disable_clearing = false;
12386 }
12387
12388 let start_row = last_new.start.to_display_point(&display_map).row().0;
12389 let end_row = last_new.end.to_display_point(&display_map).row().0;
12390 let selection_height = end_row - start_row + 1;
12391 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12392
12393 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
12394 let scroll_behavior = if fits_on_the_screen {
12395 self.request_autoscroll(Autoscroll::fit(), cx);
12396 SelectSyntaxNodeScrollBehavior::FitSelection
12397 } else if is_selection_reversed {
12398 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12399 SelectSyntaxNodeScrollBehavior::CursorTop
12400 } else {
12401 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12402 SelectSyntaxNodeScrollBehavior::CursorBottom
12403 };
12404
12405 self.select_syntax_node_history.push((
12406 old_selections,
12407 scroll_behavior,
12408 is_selection_reversed,
12409 ));
12410 }
12411
12412 pub fn select_smaller_syntax_node(
12413 &mut self,
12414 _: &SelectSmallerSyntaxNode,
12415 window: &mut Window,
12416 cx: &mut Context<Self>,
12417 ) {
12418 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12419
12420 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12421 self.select_syntax_node_history.pop()
12422 {
12423 if let Some(selection) = selections.last_mut() {
12424 selection.reversed = is_selection_reversed;
12425 }
12426
12427 self.select_syntax_node_history.disable_clearing = true;
12428 self.change_selections(None, window, cx, |s| {
12429 s.select(selections.to_vec());
12430 });
12431 self.select_syntax_node_history.disable_clearing = false;
12432
12433 match scroll_behavior {
12434 SelectSyntaxNodeScrollBehavior::CursorTop => {
12435 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12436 }
12437 SelectSyntaxNodeScrollBehavior::FitSelection => {
12438 self.request_autoscroll(Autoscroll::fit(), cx);
12439 }
12440 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12441 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12442 }
12443 }
12444 }
12445 }
12446
12447 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12448 if !EditorSettings::get_global(cx).gutter.runnables {
12449 self.clear_tasks();
12450 return Task::ready(());
12451 }
12452 let project = self.project.as_ref().map(Entity::downgrade);
12453 let task_sources = self.lsp_task_sources(cx);
12454 cx.spawn_in(window, async move |editor, cx| {
12455 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12456 let Some(project) = project.and_then(|p| p.upgrade()) else {
12457 return;
12458 };
12459 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
12460 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12461 }) else {
12462 return;
12463 };
12464
12465 let hide_runnables = project
12466 .update(cx, |project, cx| {
12467 // Do not display any test indicators in non-dev server remote projects.
12468 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12469 })
12470 .unwrap_or(true);
12471 if hide_runnables {
12472 return;
12473 }
12474 let new_rows =
12475 cx.background_spawn({
12476 let snapshot = display_snapshot.clone();
12477 async move {
12478 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12479 }
12480 })
12481 .await;
12482 let Ok(lsp_tasks) =
12483 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
12484 else {
12485 return;
12486 };
12487 let lsp_tasks = lsp_tasks.await;
12488
12489 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
12490 lsp_tasks
12491 .into_iter()
12492 .flat_map(|(kind, tasks)| {
12493 tasks.into_iter().filter_map(move |(location, task)| {
12494 Some((kind.clone(), location?, task))
12495 })
12496 })
12497 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
12498 let buffer = location.target.buffer;
12499 let buffer_snapshot = buffer.read(cx).snapshot();
12500 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
12501 |(excerpt_id, snapshot, _)| {
12502 if snapshot.remote_id() == buffer_snapshot.remote_id() {
12503 display_snapshot
12504 .buffer_snapshot
12505 .anchor_in_excerpt(excerpt_id, location.target.range.start)
12506 } else {
12507 None
12508 }
12509 },
12510 );
12511 if let Some(offset) = offset {
12512 let task_buffer_range =
12513 location.target.range.to_point(&buffer_snapshot);
12514 let context_buffer_range =
12515 task_buffer_range.to_offset(&buffer_snapshot);
12516 let context_range = BufferOffset(context_buffer_range.start)
12517 ..BufferOffset(context_buffer_range.end);
12518
12519 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
12520 .or_insert_with(|| RunnableTasks {
12521 templates: Vec::new(),
12522 offset,
12523 column: task_buffer_range.start.column,
12524 extra_variables: HashMap::default(),
12525 context_range,
12526 })
12527 .templates
12528 .push((kind, task.original_task().clone()));
12529 }
12530
12531 acc
12532 })
12533 }) else {
12534 return;
12535 };
12536
12537 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12538 editor
12539 .update(cx, |editor, _| {
12540 editor.clear_tasks();
12541 for (key, mut value) in rows {
12542 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
12543 value.templates.extend(lsp_tasks.templates);
12544 }
12545
12546 editor.insert_tasks(key, value);
12547 }
12548 for (key, value) in lsp_tasks_by_rows {
12549 editor.insert_tasks(key, value);
12550 }
12551 })
12552 .ok();
12553 })
12554 }
12555 fn fetch_runnable_ranges(
12556 snapshot: &DisplaySnapshot,
12557 range: Range<Anchor>,
12558 ) -> Vec<language::RunnableRange> {
12559 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12560 }
12561
12562 fn runnable_rows(
12563 project: Entity<Project>,
12564 snapshot: DisplaySnapshot,
12565 runnable_ranges: Vec<RunnableRange>,
12566 mut cx: AsyncWindowContext,
12567 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
12568 runnable_ranges
12569 .into_iter()
12570 .filter_map(|mut runnable| {
12571 let tasks = cx
12572 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12573 .ok()?;
12574 if tasks.is_empty() {
12575 return None;
12576 }
12577
12578 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12579
12580 let row = snapshot
12581 .buffer_snapshot
12582 .buffer_line_for_row(MultiBufferRow(point.row))?
12583 .1
12584 .start
12585 .row;
12586
12587 let context_range =
12588 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12589 Some((
12590 (runnable.buffer_id, row),
12591 RunnableTasks {
12592 templates: tasks,
12593 offset: snapshot
12594 .buffer_snapshot
12595 .anchor_before(runnable.run_range.start),
12596 context_range,
12597 column: point.column,
12598 extra_variables: runnable.extra_captures,
12599 },
12600 ))
12601 })
12602 .collect()
12603 }
12604
12605 fn templates_with_tags(
12606 project: &Entity<Project>,
12607 runnable: &mut Runnable,
12608 cx: &mut App,
12609 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12610 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12611 let (worktree_id, file) = project
12612 .buffer_for_id(runnable.buffer, cx)
12613 .and_then(|buffer| buffer.read(cx).file())
12614 .map(|file| (file.worktree_id(cx), file.clone()))
12615 .unzip();
12616
12617 (
12618 project.task_store().read(cx).task_inventory().cloned(),
12619 worktree_id,
12620 file,
12621 )
12622 });
12623
12624 let mut templates_with_tags = mem::take(&mut runnable.tags)
12625 .into_iter()
12626 .flat_map(|RunnableTag(tag)| {
12627 inventory
12628 .as_ref()
12629 .into_iter()
12630 .flat_map(|inventory| {
12631 inventory.read(cx).list_tasks(
12632 file.clone(),
12633 Some(runnable.language.clone()),
12634 worktree_id,
12635 cx,
12636 )
12637 })
12638 .filter(move |(_, template)| {
12639 template.tags.iter().any(|source_tag| source_tag == &tag)
12640 })
12641 })
12642 .sorted_by_key(|(kind, _)| kind.to_owned())
12643 .collect::<Vec<_>>();
12644 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
12645 // Strongest source wins; if we have worktree tag binding, prefer that to
12646 // global and language bindings;
12647 // if we have a global binding, prefer that to language binding.
12648 let first_mismatch = templates_with_tags
12649 .iter()
12650 .position(|(tag_source, _)| tag_source != leading_tag_source);
12651 if let Some(index) = first_mismatch {
12652 templates_with_tags.truncate(index);
12653 }
12654 }
12655
12656 templates_with_tags
12657 }
12658
12659 pub fn move_to_enclosing_bracket(
12660 &mut self,
12661 _: &MoveToEnclosingBracket,
12662 window: &mut Window,
12663 cx: &mut Context<Self>,
12664 ) {
12665 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12666 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12667 s.move_offsets_with(|snapshot, selection| {
12668 let Some(enclosing_bracket_ranges) =
12669 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12670 else {
12671 return;
12672 };
12673
12674 let mut best_length = usize::MAX;
12675 let mut best_inside = false;
12676 let mut best_in_bracket_range = false;
12677 let mut best_destination = None;
12678 for (open, close) in enclosing_bracket_ranges {
12679 let close = close.to_inclusive();
12680 let length = close.end() - open.start;
12681 let inside = selection.start >= open.end && selection.end <= *close.start();
12682 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12683 || close.contains(&selection.head());
12684
12685 // If best is next to a bracket and current isn't, skip
12686 if !in_bracket_range && best_in_bracket_range {
12687 continue;
12688 }
12689
12690 // Prefer smaller lengths unless best is inside and current isn't
12691 if length > best_length && (best_inside || !inside) {
12692 continue;
12693 }
12694
12695 best_length = length;
12696 best_inside = inside;
12697 best_in_bracket_range = in_bracket_range;
12698 best_destination = Some(
12699 if close.contains(&selection.start) && close.contains(&selection.end) {
12700 if inside { open.end } else { open.start }
12701 } else if inside {
12702 *close.start()
12703 } else {
12704 *close.end()
12705 },
12706 );
12707 }
12708
12709 if let Some(destination) = best_destination {
12710 selection.collapse_to(destination, SelectionGoal::None);
12711 }
12712 })
12713 });
12714 }
12715
12716 pub fn undo_selection(
12717 &mut self,
12718 _: &UndoSelection,
12719 window: &mut Window,
12720 cx: &mut Context<Self>,
12721 ) {
12722 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12723 self.end_selection(window, cx);
12724 self.selection_history.mode = SelectionHistoryMode::Undoing;
12725 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12726 self.change_selections(None, window, cx, |s| {
12727 s.select_anchors(entry.selections.to_vec())
12728 });
12729 self.select_next_state = entry.select_next_state;
12730 self.select_prev_state = entry.select_prev_state;
12731 self.add_selections_state = entry.add_selections_state;
12732 self.request_autoscroll(Autoscroll::newest(), cx);
12733 }
12734 self.selection_history.mode = SelectionHistoryMode::Normal;
12735 }
12736
12737 pub fn redo_selection(
12738 &mut self,
12739 _: &RedoSelection,
12740 window: &mut Window,
12741 cx: &mut Context<Self>,
12742 ) {
12743 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12744 self.end_selection(window, cx);
12745 self.selection_history.mode = SelectionHistoryMode::Redoing;
12746 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12747 self.change_selections(None, window, cx, |s| {
12748 s.select_anchors(entry.selections.to_vec())
12749 });
12750 self.select_next_state = entry.select_next_state;
12751 self.select_prev_state = entry.select_prev_state;
12752 self.add_selections_state = entry.add_selections_state;
12753 self.request_autoscroll(Autoscroll::newest(), cx);
12754 }
12755 self.selection_history.mode = SelectionHistoryMode::Normal;
12756 }
12757
12758 pub fn expand_excerpts(
12759 &mut self,
12760 action: &ExpandExcerpts,
12761 _: &mut Window,
12762 cx: &mut Context<Self>,
12763 ) {
12764 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12765 }
12766
12767 pub fn expand_excerpts_down(
12768 &mut self,
12769 action: &ExpandExcerptsDown,
12770 _: &mut Window,
12771 cx: &mut Context<Self>,
12772 ) {
12773 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12774 }
12775
12776 pub fn expand_excerpts_up(
12777 &mut self,
12778 action: &ExpandExcerptsUp,
12779 _: &mut Window,
12780 cx: &mut Context<Self>,
12781 ) {
12782 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12783 }
12784
12785 pub fn expand_excerpts_for_direction(
12786 &mut self,
12787 lines: u32,
12788 direction: ExpandExcerptDirection,
12789
12790 cx: &mut Context<Self>,
12791 ) {
12792 let selections = self.selections.disjoint_anchors();
12793
12794 let lines = if lines == 0 {
12795 EditorSettings::get_global(cx).expand_excerpt_lines
12796 } else {
12797 lines
12798 };
12799
12800 self.buffer.update(cx, |buffer, cx| {
12801 let snapshot = buffer.snapshot(cx);
12802 let mut excerpt_ids = selections
12803 .iter()
12804 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12805 .collect::<Vec<_>>();
12806 excerpt_ids.sort();
12807 excerpt_ids.dedup();
12808 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12809 })
12810 }
12811
12812 pub fn expand_excerpt(
12813 &mut self,
12814 excerpt: ExcerptId,
12815 direction: ExpandExcerptDirection,
12816 window: &mut Window,
12817 cx: &mut Context<Self>,
12818 ) {
12819 let current_scroll_position = self.scroll_position(cx);
12820 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
12821 let mut should_scroll_up = false;
12822
12823 if direction == ExpandExcerptDirection::Down {
12824 let multi_buffer = self.buffer.read(cx);
12825 let snapshot = multi_buffer.snapshot(cx);
12826 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
12827 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
12828 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
12829 let buffer_snapshot = buffer.read(cx).snapshot();
12830 let excerpt_end_row =
12831 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
12832 let last_row = buffer_snapshot.max_point().row;
12833 let lines_below = last_row.saturating_sub(excerpt_end_row);
12834 should_scroll_up = lines_below >= lines_to_expand;
12835 }
12836 }
12837 }
12838 }
12839
12840 self.buffer.update(cx, |buffer, cx| {
12841 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
12842 });
12843
12844 if should_scroll_up {
12845 let new_scroll_position =
12846 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
12847 self.set_scroll_position(new_scroll_position, window, cx);
12848 }
12849 }
12850
12851 pub fn go_to_singleton_buffer_point(
12852 &mut self,
12853 point: Point,
12854 window: &mut Window,
12855 cx: &mut Context<Self>,
12856 ) {
12857 self.go_to_singleton_buffer_range(point..point, window, cx);
12858 }
12859
12860 pub fn go_to_singleton_buffer_range(
12861 &mut self,
12862 range: Range<Point>,
12863 window: &mut Window,
12864 cx: &mut Context<Self>,
12865 ) {
12866 let multibuffer = self.buffer().read(cx);
12867 let Some(buffer) = multibuffer.as_singleton() else {
12868 return;
12869 };
12870 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12871 return;
12872 };
12873 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12874 return;
12875 };
12876 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12877 s.select_anchor_ranges([start..end])
12878 });
12879 }
12880
12881 fn go_to_diagnostic(
12882 &mut self,
12883 _: &GoToDiagnostic,
12884 window: &mut Window,
12885 cx: &mut Context<Self>,
12886 ) {
12887 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12888 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12889 }
12890
12891 fn go_to_prev_diagnostic(
12892 &mut self,
12893 _: &GoToPreviousDiagnostic,
12894 window: &mut Window,
12895 cx: &mut Context<Self>,
12896 ) {
12897 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12898 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12899 }
12900
12901 pub fn go_to_diagnostic_impl(
12902 &mut self,
12903 direction: Direction,
12904 window: &mut Window,
12905 cx: &mut Context<Self>,
12906 ) {
12907 let buffer = self.buffer.read(cx).snapshot(cx);
12908 let selection = self.selections.newest::<usize>(cx);
12909 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12910 if direction == Direction::Next {
12911 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12912 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12913 return;
12914 };
12915 self.activate_diagnostics(
12916 buffer_id,
12917 popover.local_diagnostic.diagnostic.group_id,
12918 window,
12919 cx,
12920 );
12921 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12922 let primary_range_start = active_diagnostics.primary_range.start;
12923 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12924 let mut new_selection = s.newest_anchor().clone();
12925 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12926 s.select_anchors(vec![new_selection.clone()]);
12927 });
12928 self.refresh_inline_completion(false, true, window, cx);
12929 }
12930 return;
12931 }
12932 }
12933
12934 let active_group_id = self
12935 .active_diagnostics
12936 .as_ref()
12937 .map(|active_group| active_group.group_id);
12938 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12939 active_diagnostics
12940 .primary_range
12941 .to_offset(&buffer)
12942 .to_inclusive()
12943 });
12944 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12945 if active_primary_range.contains(&selection.head()) {
12946 *active_primary_range.start()
12947 } else {
12948 selection.head()
12949 }
12950 } else {
12951 selection.head()
12952 };
12953
12954 let snapshot = self.snapshot(window, cx);
12955 let primary_diagnostics_before = buffer
12956 .diagnostics_in_range::<usize>(0..search_start)
12957 .filter(|entry| entry.diagnostic.is_primary)
12958 .filter(|entry| entry.range.start != entry.range.end)
12959 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12960 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12961 .collect::<Vec<_>>();
12962 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12963 primary_diagnostics_before
12964 .iter()
12965 .position(|entry| entry.diagnostic.group_id == active_group_id)
12966 });
12967
12968 let primary_diagnostics_after = buffer
12969 .diagnostics_in_range::<usize>(search_start..buffer.len())
12970 .filter(|entry| entry.diagnostic.is_primary)
12971 .filter(|entry| entry.range.start != entry.range.end)
12972 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12973 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12974 .collect::<Vec<_>>();
12975 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12976 primary_diagnostics_after
12977 .iter()
12978 .enumerate()
12979 .rev()
12980 .find_map(|(i, entry)| {
12981 if entry.diagnostic.group_id == active_group_id {
12982 Some(i)
12983 } else {
12984 None
12985 }
12986 })
12987 });
12988
12989 let next_primary_diagnostic = match direction {
12990 Direction::Prev => primary_diagnostics_before
12991 .iter()
12992 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
12993 .rev()
12994 .next(),
12995 Direction::Next => primary_diagnostics_after
12996 .iter()
12997 .skip(
12998 last_same_group_diagnostic_after
12999 .map(|index| index + 1)
13000 .unwrap_or(0),
13001 )
13002 .next(),
13003 };
13004
13005 // Cycle around to the start of the buffer, potentially moving back to the start of
13006 // the currently active diagnostic.
13007 let cycle_around = || match direction {
13008 Direction::Prev => primary_diagnostics_after
13009 .iter()
13010 .rev()
13011 .chain(primary_diagnostics_before.iter().rev())
13012 .next(),
13013 Direction::Next => primary_diagnostics_before
13014 .iter()
13015 .chain(primary_diagnostics_after.iter())
13016 .next(),
13017 };
13018
13019 if let Some((primary_range, group_id)) = next_primary_diagnostic
13020 .or_else(cycle_around)
13021 .map(|entry| (&entry.range, entry.diagnostic.group_id))
13022 {
13023 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
13024 return;
13025 };
13026 self.activate_diagnostics(buffer_id, group_id, window, cx);
13027 if self.active_diagnostics.is_some() {
13028 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13029 s.select(vec![Selection {
13030 id: selection.id,
13031 start: primary_range.start,
13032 end: primary_range.start,
13033 reversed: false,
13034 goal: SelectionGoal::None,
13035 }]);
13036 });
13037 self.refresh_inline_completion(false, true, window, cx);
13038 }
13039 }
13040 }
13041
13042 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13043 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13044 let snapshot = self.snapshot(window, cx);
13045 let selection = self.selections.newest::<Point>(cx);
13046 self.go_to_hunk_before_or_after_position(
13047 &snapshot,
13048 selection.head(),
13049 Direction::Next,
13050 window,
13051 cx,
13052 );
13053 }
13054
13055 pub fn go_to_hunk_before_or_after_position(
13056 &mut self,
13057 snapshot: &EditorSnapshot,
13058 position: Point,
13059 direction: Direction,
13060 window: &mut Window,
13061 cx: &mut Context<Editor>,
13062 ) {
13063 let row = if direction == Direction::Next {
13064 self.hunk_after_position(snapshot, position)
13065 .map(|hunk| hunk.row_range.start)
13066 } else {
13067 self.hunk_before_position(snapshot, position)
13068 };
13069
13070 if let Some(row) = row {
13071 let destination = Point::new(row.0, 0);
13072 let autoscroll = Autoscroll::center();
13073
13074 self.unfold_ranges(&[destination..destination], false, false, cx);
13075 self.change_selections(Some(autoscroll), window, cx, |s| {
13076 s.select_ranges([destination..destination]);
13077 });
13078 }
13079 }
13080
13081 fn hunk_after_position(
13082 &mut self,
13083 snapshot: &EditorSnapshot,
13084 position: Point,
13085 ) -> Option<MultiBufferDiffHunk> {
13086 snapshot
13087 .buffer_snapshot
13088 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13089 .find(|hunk| hunk.row_range.start.0 > position.row)
13090 .or_else(|| {
13091 snapshot
13092 .buffer_snapshot
13093 .diff_hunks_in_range(Point::zero()..position)
13094 .find(|hunk| hunk.row_range.end.0 < position.row)
13095 })
13096 }
13097
13098 fn go_to_prev_hunk(
13099 &mut self,
13100 _: &GoToPreviousHunk,
13101 window: &mut Window,
13102 cx: &mut Context<Self>,
13103 ) {
13104 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13105 let snapshot = self.snapshot(window, cx);
13106 let selection = self.selections.newest::<Point>(cx);
13107 self.go_to_hunk_before_or_after_position(
13108 &snapshot,
13109 selection.head(),
13110 Direction::Prev,
13111 window,
13112 cx,
13113 );
13114 }
13115
13116 fn hunk_before_position(
13117 &mut self,
13118 snapshot: &EditorSnapshot,
13119 position: Point,
13120 ) -> Option<MultiBufferRow> {
13121 snapshot
13122 .buffer_snapshot
13123 .diff_hunk_before(position)
13124 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13125 }
13126
13127 fn go_to_line<T: 'static>(
13128 &mut self,
13129 position: Anchor,
13130 highlight_color: Option<Hsla>,
13131 window: &mut Window,
13132 cx: &mut Context<Self>,
13133 ) {
13134 let snapshot = self.snapshot(window, cx).display_snapshot;
13135 let position = position.to_point(&snapshot.buffer_snapshot);
13136 let start = snapshot
13137 .buffer_snapshot
13138 .clip_point(Point::new(position.row, 0), Bias::Left);
13139 let end = start + Point::new(1, 0);
13140 let start = snapshot.buffer_snapshot.anchor_before(start);
13141 let end = snapshot.buffer_snapshot.anchor_before(end);
13142
13143 self.highlight_rows::<T>(
13144 start..end,
13145 highlight_color
13146 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13147 false,
13148 cx,
13149 );
13150 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13151 }
13152
13153 pub fn go_to_definition(
13154 &mut self,
13155 _: &GoToDefinition,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) -> Task<Result<Navigated>> {
13159 let definition =
13160 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13161 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13162 cx.spawn_in(window, async move |editor, cx| {
13163 if definition.await? == Navigated::Yes {
13164 return Ok(Navigated::Yes);
13165 }
13166 match fallback_strategy {
13167 GoToDefinitionFallback::None => Ok(Navigated::No),
13168 GoToDefinitionFallback::FindAllReferences => {
13169 match editor.update_in(cx, |editor, window, cx| {
13170 editor.find_all_references(&FindAllReferences, window, cx)
13171 })? {
13172 Some(references) => references.await,
13173 None => Ok(Navigated::No),
13174 }
13175 }
13176 }
13177 })
13178 }
13179
13180 pub fn go_to_declaration(
13181 &mut self,
13182 _: &GoToDeclaration,
13183 window: &mut Window,
13184 cx: &mut Context<Self>,
13185 ) -> Task<Result<Navigated>> {
13186 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13187 }
13188
13189 pub fn go_to_declaration_split(
13190 &mut self,
13191 _: &GoToDeclaration,
13192 window: &mut Window,
13193 cx: &mut Context<Self>,
13194 ) -> Task<Result<Navigated>> {
13195 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13196 }
13197
13198 pub fn go_to_implementation(
13199 &mut self,
13200 _: &GoToImplementation,
13201 window: &mut Window,
13202 cx: &mut Context<Self>,
13203 ) -> Task<Result<Navigated>> {
13204 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13205 }
13206
13207 pub fn go_to_implementation_split(
13208 &mut self,
13209 _: &GoToImplementationSplit,
13210 window: &mut Window,
13211 cx: &mut Context<Self>,
13212 ) -> Task<Result<Navigated>> {
13213 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13214 }
13215
13216 pub fn go_to_type_definition(
13217 &mut self,
13218 _: &GoToTypeDefinition,
13219 window: &mut Window,
13220 cx: &mut Context<Self>,
13221 ) -> Task<Result<Navigated>> {
13222 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13223 }
13224
13225 pub fn go_to_definition_split(
13226 &mut self,
13227 _: &GoToDefinitionSplit,
13228 window: &mut Window,
13229 cx: &mut Context<Self>,
13230 ) -> Task<Result<Navigated>> {
13231 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13232 }
13233
13234 pub fn go_to_type_definition_split(
13235 &mut self,
13236 _: &GoToTypeDefinitionSplit,
13237 window: &mut Window,
13238 cx: &mut Context<Self>,
13239 ) -> Task<Result<Navigated>> {
13240 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13241 }
13242
13243 fn go_to_definition_of_kind(
13244 &mut self,
13245 kind: GotoDefinitionKind,
13246 split: bool,
13247 window: &mut Window,
13248 cx: &mut Context<Self>,
13249 ) -> Task<Result<Navigated>> {
13250 let Some(provider) = self.semantics_provider.clone() else {
13251 return Task::ready(Ok(Navigated::No));
13252 };
13253 let head = self.selections.newest::<usize>(cx).head();
13254 let buffer = self.buffer.read(cx);
13255 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13256 text_anchor
13257 } else {
13258 return Task::ready(Ok(Navigated::No));
13259 };
13260
13261 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13262 return Task::ready(Ok(Navigated::No));
13263 };
13264
13265 cx.spawn_in(window, async move |editor, cx| {
13266 let definitions = definitions.await?;
13267 let navigated = editor
13268 .update_in(cx, |editor, window, cx| {
13269 editor.navigate_to_hover_links(
13270 Some(kind),
13271 definitions
13272 .into_iter()
13273 .filter(|location| {
13274 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13275 })
13276 .map(HoverLink::Text)
13277 .collect::<Vec<_>>(),
13278 split,
13279 window,
13280 cx,
13281 )
13282 })?
13283 .await?;
13284 anyhow::Ok(navigated)
13285 })
13286 }
13287
13288 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13289 let selection = self.selections.newest_anchor();
13290 let head = selection.head();
13291 let tail = selection.tail();
13292
13293 let Some((buffer, start_position)) =
13294 self.buffer.read(cx).text_anchor_for_position(head, cx)
13295 else {
13296 return;
13297 };
13298
13299 let end_position = if head != tail {
13300 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13301 return;
13302 };
13303 Some(pos)
13304 } else {
13305 None
13306 };
13307
13308 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13309 let url = if let Some(end_pos) = end_position {
13310 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13311 } else {
13312 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13313 };
13314
13315 if let Some(url) = url {
13316 editor.update(cx, |_, cx| {
13317 cx.open_url(&url);
13318 })
13319 } else {
13320 Ok(())
13321 }
13322 });
13323
13324 url_finder.detach();
13325 }
13326
13327 pub fn open_selected_filename(
13328 &mut self,
13329 _: &OpenSelectedFilename,
13330 window: &mut Window,
13331 cx: &mut Context<Self>,
13332 ) {
13333 let Some(workspace) = self.workspace() else {
13334 return;
13335 };
13336
13337 let position = self.selections.newest_anchor().head();
13338
13339 let Some((buffer, buffer_position)) =
13340 self.buffer.read(cx).text_anchor_for_position(position, cx)
13341 else {
13342 return;
13343 };
13344
13345 let project = self.project.clone();
13346
13347 cx.spawn_in(window, async move |_, cx| {
13348 let result = find_file(&buffer, project, buffer_position, cx).await;
13349
13350 if let Some((_, path)) = result {
13351 workspace
13352 .update_in(cx, |workspace, window, cx| {
13353 workspace.open_resolved_path(path, window, cx)
13354 })?
13355 .await?;
13356 }
13357 anyhow::Ok(())
13358 })
13359 .detach();
13360 }
13361
13362 pub(crate) fn navigate_to_hover_links(
13363 &mut self,
13364 kind: Option<GotoDefinitionKind>,
13365 mut definitions: Vec<HoverLink>,
13366 split: bool,
13367 window: &mut Window,
13368 cx: &mut Context<Editor>,
13369 ) -> Task<Result<Navigated>> {
13370 // If there is one definition, just open it directly
13371 if definitions.len() == 1 {
13372 let definition = definitions.pop().unwrap();
13373
13374 enum TargetTaskResult {
13375 Location(Option<Location>),
13376 AlreadyNavigated,
13377 }
13378
13379 let target_task = match definition {
13380 HoverLink::Text(link) => {
13381 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13382 }
13383 HoverLink::InlayHint(lsp_location, server_id) => {
13384 let computation =
13385 self.compute_target_location(lsp_location, server_id, window, cx);
13386 cx.background_spawn(async move {
13387 let location = computation.await?;
13388 Ok(TargetTaskResult::Location(location))
13389 })
13390 }
13391 HoverLink::Url(url) => {
13392 cx.open_url(&url);
13393 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13394 }
13395 HoverLink::File(path) => {
13396 if let Some(workspace) = self.workspace() {
13397 cx.spawn_in(window, async move |_, cx| {
13398 workspace
13399 .update_in(cx, |workspace, window, cx| {
13400 workspace.open_resolved_path(path, window, cx)
13401 })?
13402 .await
13403 .map(|_| TargetTaskResult::AlreadyNavigated)
13404 })
13405 } else {
13406 Task::ready(Ok(TargetTaskResult::Location(None)))
13407 }
13408 }
13409 };
13410 cx.spawn_in(window, async move |editor, cx| {
13411 let target = match target_task.await.context("target resolution task")? {
13412 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13413 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13414 TargetTaskResult::Location(Some(target)) => target,
13415 };
13416
13417 editor.update_in(cx, |editor, window, cx| {
13418 let Some(workspace) = editor.workspace() else {
13419 return Navigated::No;
13420 };
13421 let pane = workspace.read(cx).active_pane().clone();
13422
13423 let range = target.range.to_point(target.buffer.read(cx));
13424 let range = editor.range_for_match(&range);
13425 let range = collapse_multiline_range(range);
13426
13427 if !split
13428 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13429 {
13430 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13431 } else {
13432 window.defer(cx, move |window, cx| {
13433 let target_editor: Entity<Self> =
13434 workspace.update(cx, |workspace, cx| {
13435 let pane = if split {
13436 workspace.adjacent_pane(window, cx)
13437 } else {
13438 workspace.active_pane().clone()
13439 };
13440
13441 workspace.open_project_item(
13442 pane,
13443 target.buffer.clone(),
13444 true,
13445 true,
13446 window,
13447 cx,
13448 )
13449 });
13450 target_editor.update(cx, |target_editor, cx| {
13451 // When selecting a definition in a different buffer, disable the nav history
13452 // to avoid creating a history entry at the previous cursor location.
13453 pane.update(cx, |pane, _| pane.disable_history());
13454 target_editor.go_to_singleton_buffer_range(range, window, cx);
13455 pane.update(cx, |pane, _| pane.enable_history());
13456 });
13457 });
13458 }
13459 Navigated::Yes
13460 })
13461 })
13462 } else if !definitions.is_empty() {
13463 cx.spawn_in(window, async move |editor, cx| {
13464 let (title, location_tasks, workspace) = editor
13465 .update_in(cx, |editor, window, cx| {
13466 let tab_kind = match kind {
13467 Some(GotoDefinitionKind::Implementation) => "Implementations",
13468 _ => "Definitions",
13469 };
13470 let title = definitions
13471 .iter()
13472 .find_map(|definition| match definition {
13473 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13474 let buffer = origin.buffer.read(cx);
13475 format!(
13476 "{} for {}",
13477 tab_kind,
13478 buffer
13479 .text_for_range(origin.range.clone())
13480 .collect::<String>()
13481 )
13482 }),
13483 HoverLink::InlayHint(_, _) => None,
13484 HoverLink::Url(_) => None,
13485 HoverLink::File(_) => None,
13486 })
13487 .unwrap_or(tab_kind.to_string());
13488 let location_tasks = definitions
13489 .into_iter()
13490 .map(|definition| match definition {
13491 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13492 HoverLink::InlayHint(lsp_location, server_id) => editor
13493 .compute_target_location(lsp_location, server_id, window, cx),
13494 HoverLink::Url(_) => Task::ready(Ok(None)),
13495 HoverLink::File(_) => Task::ready(Ok(None)),
13496 })
13497 .collect::<Vec<_>>();
13498 (title, location_tasks, editor.workspace().clone())
13499 })
13500 .context("location tasks preparation")?;
13501
13502 let locations = future::join_all(location_tasks)
13503 .await
13504 .into_iter()
13505 .filter_map(|location| location.transpose())
13506 .collect::<Result<_>>()
13507 .context("location tasks")?;
13508
13509 let Some(workspace) = workspace else {
13510 return Ok(Navigated::No);
13511 };
13512 let opened = workspace
13513 .update_in(cx, |workspace, window, cx| {
13514 Self::open_locations_in_multibuffer(
13515 workspace,
13516 locations,
13517 title,
13518 split,
13519 MultibufferSelectionMode::First,
13520 window,
13521 cx,
13522 )
13523 })
13524 .ok();
13525
13526 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13527 })
13528 } else {
13529 Task::ready(Ok(Navigated::No))
13530 }
13531 }
13532
13533 fn compute_target_location(
13534 &self,
13535 lsp_location: lsp::Location,
13536 server_id: LanguageServerId,
13537 window: &mut Window,
13538 cx: &mut Context<Self>,
13539 ) -> Task<anyhow::Result<Option<Location>>> {
13540 let Some(project) = self.project.clone() else {
13541 return Task::ready(Ok(None));
13542 };
13543
13544 cx.spawn_in(window, async move |editor, cx| {
13545 let location_task = editor.update(cx, |_, cx| {
13546 project.update(cx, |project, cx| {
13547 let language_server_name = project
13548 .language_server_statuses(cx)
13549 .find(|(id, _)| server_id == *id)
13550 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13551 language_server_name.map(|language_server_name| {
13552 project.open_local_buffer_via_lsp(
13553 lsp_location.uri.clone(),
13554 server_id,
13555 language_server_name,
13556 cx,
13557 )
13558 })
13559 })
13560 })?;
13561 let location = match location_task {
13562 Some(task) => Some({
13563 let target_buffer_handle = task.await.context("open local buffer")?;
13564 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13565 let target_start = target_buffer
13566 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13567 let target_end = target_buffer
13568 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13569 target_buffer.anchor_after(target_start)
13570 ..target_buffer.anchor_before(target_end)
13571 })?;
13572 Location {
13573 buffer: target_buffer_handle,
13574 range,
13575 }
13576 }),
13577 None => None,
13578 };
13579 Ok(location)
13580 })
13581 }
13582
13583 pub fn find_all_references(
13584 &mut self,
13585 _: &FindAllReferences,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) -> Option<Task<Result<Navigated>>> {
13589 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13590
13591 let selection = self.selections.newest::<usize>(cx);
13592 let multi_buffer = self.buffer.read(cx);
13593 let head = selection.head();
13594
13595 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13596 let head_anchor = multi_buffer_snapshot.anchor_at(
13597 head,
13598 if head < selection.tail() {
13599 Bias::Right
13600 } else {
13601 Bias::Left
13602 },
13603 );
13604
13605 match self
13606 .find_all_references_task_sources
13607 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13608 {
13609 Ok(_) => {
13610 log::info!(
13611 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13612 );
13613 return None;
13614 }
13615 Err(i) => {
13616 self.find_all_references_task_sources.insert(i, head_anchor);
13617 }
13618 }
13619
13620 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13621 let workspace = self.workspace()?;
13622 let project = workspace.read(cx).project().clone();
13623 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13624 Some(cx.spawn_in(window, async move |editor, cx| {
13625 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13626 if let Ok(i) = editor
13627 .find_all_references_task_sources
13628 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13629 {
13630 editor.find_all_references_task_sources.remove(i);
13631 }
13632 });
13633
13634 let locations = references.await?;
13635 if locations.is_empty() {
13636 return anyhow::Ok(Navigated::No);
13637 }
13638
13639 workspace.update_in(cx, |workspace, window, cx| {
13640 let title = locations
13641 .first()
13642 .as_ref()
13643 .map(|location| {
13644 let buffer = location.buffer.read(cx);
13645 format!(
13646 "References to `{}`",
13647 buffer
13648 .text_for_range(location.range.clone())
13649 .collect::<String>()
13650 )
13651 })
13652 .unwrap();
13653 Self::open_locations_in_multibuffer(
13654 workspace,
13655 locations,
13656 title,
13657 false,
13658 MultibufferSelectionMode::First,
13659 window,
13660 cx,
13661 );
13662 Navigated::Yes
13663 })
13664 }))
13665 }
13666
13667 /// Opens a multibuffer with the given project locations in it
13668 pub fn open_locations_in_multibuffer(
13669 workspace: &mut Workspace,
13670 mut locations: Vec<Location>,
13671 title: String,
13672 split: bool,
13673 multibuffer_selection_mode: MultibufferSelectionMode,
13674 window: &mut Window,
13675 cx: &mut Context<Workspace>,
13676 ) {
13677 // If there are multiple definitions, open them in a multibuffer
13678 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13679 let mut locations = locations.into_iter().peekable();
13680 let mut ranges: Vec<Range<Anchor>> = Vec::new();
13681 let capability = workspace.project().read(cx).capability();
13682
13683 let excerpt_buffer = cx.new(|cx| {
13684 let mut multibuffer = MultiBuffer::new(capability);
13685 while let Some(location) = locations.next() {
13686 let buffer = location.buffer.read(cx);
13687 let mut ranges_for_buffer = Vec::new();
13688 let range = location.range.to_point(buffer);
13689 ranges_for_buffer.push(range.clone());
13690
13691 while let Some(next_location) = locations.peek() {
13692 if next_location.buffer == location.buffer {
13693 ranges_for_buffer.push(next_location.range.to_point(buffer));
13694 locations.next();
13695 } else {
13696 break;
13697 }
13698 }
13699
13700 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13701 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
13702 PathKey::for_buffer(&location.buffer, cx),
13703 location.buffer.clone(),
13704 ranges_for_buffer,
13705 DEFAULT_MULTIBUFFER_CONTEXT,
13706 cx,
13707 );
13708 ranges.extend(new_ranges)
13709 }
13710
13711 multibuffer.with_title(title)
13712 });
13713
13714 let editor = cx.new(|cx| {
13715 Editor::for_multibuffer(
13716 excerpt_buffer,
13717 Some(workspace.project().clone()),
13718 window,
13719 cx,
13720 )
13721 });
13722 editor.update(cx, |editor, cx| {
13723 match multibuffer_selection_mode {
13724 MultibufferSelectionMode::First => {
13725 if let Some(first_range) = ranges.first() {
13726 editor.change_selections(None, window, cx, |selections| {
13727 selections.clear_disjoint();
13728 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13729 });
13730 }
13731 editor.highlight_background::<Self>(
13732 &ranges,
13733 |theme| theme.editor_highlighted_line_background,
13734 cx,
13735 );
13736 }
13737 MultibufferSelectionMode::All => {
13738 editor.change_selections(None, window, cx, |selections| {
13739 selections.clear_disjoint();
13740 selections.select_anchor_ranges(ranges);
13741 });
13742 }
13743 }
13744 editor.register_buffers_with_language_servers(cx);
13745 });
13746
13747 let item = Box::new(editor);
13748 let item_id = item.item_id();
13749
13750 if split {
13751 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13752 } else {
13753 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13754 let (preview_item_id, preview_item_idx) =
13755 workspace.active_pane().update(cx, |pane, _| {
13756 (pane.preview_item_id(), pane.preview_item_idx())
13757 });
13758
13759 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13760
13761 if let Some(preview_item_id) = preview_item_id {
13762 workspace.active_pane().update(cx, |pane, cx| {
13763 pane.remove_item(preview_item_id, false, false, window, cx);
13764 });
13765 }
13766 } else {
13767 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13768 }
13769 }
13770 workspace.active_pane().update(cx, |pane, cx| {
13771 pane.set_preview_item_id(Some(item_id), cx);
13772 });
13773 }
13774
13775 pub fn rename(
13776 &mut self,
13777 _: &Rename,
13778 window: &mut Window,
13779 cx: &mut Context<Self>,
13780 ) -> Option<Task<Result<()>>> {
13781 use language::ToOffset as _;
13782
13783 let provider = self.semantics_provider.clone()?;
13784 let selection = self.selections.newest_anchor().clone();
13785 let (cursor_buffer, cursor_buffer_position) = self
13786 .buffer
13787 .read(cx)
13788 .text_anchor_for_position(selection.head(), cx)?;
13789 let (tail_buffer, cursor_buffer_position_end) = self
13790 .buffer
13791 .read(cx)
13792 .text_anchor_for_position(selection.tail(), cx)?;
13793 if tail_buffer != cursor_buffer {
13794 return None;
13795 }
13796
13797 let snapshot = cursor_buffer.read(cx).snapshot();
13798 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13799 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13800 let prepare_rename = provider
13801 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13802 .unwrap_or_else(|| Task::ready(Ok(None)));
13803 drop(snapshot);
13804
13805 Some(cx.spawn_in(window, async move |this, cx| {
13806 let rename_range = if let Some(range) = prepare_rename.await? {
13807 Some(range)
13808 } else {
13809 this.update(cx, |this, cx| {
13810 let buffer = this.buffer.read(cx).snapshot(cx);
13811 let mut buffer_highlights = this
13812 .document_highlights_for_position(selection.head(), &buffer)
13813 .filter(|highlight| {
13814 highlight.start.excerpt_id == selection.head().excerpt_id
13815 && highlight.end.excerpt_id == selection.head().excerpt_id
13816 });
13817 buffer_highlights
13818 .next()
13819 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13820 })?
13821 };
13822 if let Some(rename_range) = rename_range {
13823 this.update_in(cx, |this, window, cx| {
13824 let snapshot = cursor_buffer.read(cx).snapshot();
13825 let rename_buffer_range = rename_range.to_offset(&snapshot);
13826 let cursor_offset_in_rename_range =
13827 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13828 let cursor_offset_in_rename_range_end =
13829 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13830
13831 this.take_rename(false, window, cx);
13832 let buffer = this.buffer.read(cx).read(cx);
13833 let cursor_offset = selection.head().to_offset(&buffer);
13834 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13835 let rename_end = rename_start + rename_buffer_range.len();
13836 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13837 let mut old_highlight_id = None;
13838 let old_name: Arc<str> = buffer
13839 .chunks(rename_start..rename_end, true)
13840 .map(|chunk| {
13841 if old_highlight_id.is_none() {
13842 old_highlight_id = chunk.syntax_highlight_id;
13843 }
13844 chunk.text
13845 })
13846 .collect::<String>()
13847 .into();
13848
13849 drop(buffer);
13850
13851 // Position the selection in the rename editor so that it matches the current selection.
13852 this.show_local_selections = false;
13853 let rename_editor = cx.new(|cx| {
13854 let mut editor = Editor::single_line(window, cx);
13855 editor.buffer.update(cx, |buffer, cx| {
13856 buffer.edit([(0..0, old_name.clone())], None, cx)
13857 });
13858 let rename_selection_range = match cursor_offset_in_rename_range
13859 .cmp(&cursor_offset_in_rename_range_end)
13860 {
13861 Ordering::Equal => {
13862 editor.select_all(&SelectAll, window, cx);
13863 return editor;
13864 }
13865 Ordering::Less => {
13866 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13867 }
13868 Ordering::Greater => {
13869 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13870 }
13871 };
13872 if rename_selection_range.end > old_name.len() {
13873 editor.select_all(&SelectAll, window, cx);
13874 } else {
13875 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13876 s.select_ranges([rename_selection_range]);
13877 });
13878 }
13879 editor
13880 });
13881 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13882 if e == &EditorEvent::Focused {
13883 cx.emit(EditorEvent::FocusedIn)
13884 }
13885 })
13886 .detach();
13887
13888 let write_highlights =
13889 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13890 let read_highlights =
13891 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13892 let ranges = write_highlights
13893 .iter()
13894 .flat_map(|(_, ranges)| ranges.iter())
13895 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13896 .cloned()
13897 .collect();
13898
13899 this.highlight_text::<Rename>(
13900 ranges,
13901 HighlightStyle {
13902 fade_out: Some(0.6),
13903 ..Default::default()
13904 },
13905 cx,
13906 );
13907 let rename_focus_handle = rename_editor.focus_handle(cx);
13908 window.focus(&rename_focus_handle);
13909 let block_id = this.insert_blocks(
13910 [BlockProperties {
13911 style: BlockStyle::Flex,
13912 placement: BlockPlacement::Below(range.start),
13913 height: Some(1),
13914 render: Arc::new({
13915 let rename_editor = rename_editor.clone();
13916 move |cx: &mut BlockContext| {
13917 let mut text_style = cx.editor_style.text.clone();
13918 if let Some(highlight_style) = old_highlight_id
13919 .and_then(|h| h.style(&cx.editor_style.syntax))
13920 {
13921 text_style = text_style.highlight(highlight_style);
13922 }
13923 div()
13924 .block_mouse_down()
13925 .pl(cx.anchor_x)
13926 .child(EditorElement::new(
13927 &rename_editor,
13928 EditorStyle {
13929 background: cx.theme().system().transparent,
13930 local_player: cx.editor_style.local_player,
13931 text: text_style,
13932 scrollbar_width: cx.editor_style.scrollbar_width,
13933 syntax: cx.editor_style.syntax.clone(),
13934 status: cx.editor_style.status.clone(),
13935 inlay_hints_style: HighlightStyle {
13936 font_weight: Some(FontWeight::BOLD),
13937 ..make_inlay_hints_style(cx.app)
13938 },
13939 inline_completion_styles: make_suggestion_styles(
13940 cx.app,
13941 ),
13942 ..EditorStyle::default()
13943 },
13944 ))
13945 .into_any_element()
13946 }
13947 }),
13948 priority: 0,
13949 }],
13950 Some(Autoscroll::fit()),
13951 cx,
13952 )[0];
13953 this.pending_rename = Some(RenameState {
13954 range,
13955 old_name,
13956 editor: rename_editor,
13957 block_id,
13958 });
13959 })?;
13960 }
13961
13962 Ok(())
13963 }))
13964 }
13965
13966 pub fn confirm_rename(
13967 &mut self,
13968 _: &ConfirmRename,
13969 window: &mut Window,
13970 cx: &mut Context<Self>,
13971 ) -> Option<Task<Result<()>>> {
13972 let rename = self.take_rename(false, window, cx)?;
13973 let workspace = self.workspace()?.downgrade();
13974 let (buffer, start) = self
13975 .buffer
13976 .read(cx)
13977 .text_anchor_for_position(rename.range.start, cx)?;
13978 let (end_buffer, _) = self
13979 .buffer
13980 .read(cx)
13981 .text_anchor_for_position(rename.range.end, cx)?;
13982 if buffer != end_buffer {
13983 return None;
13984 }
13985
13986 let old_name = rename.old_name;
13987 let new_name = rename.editor.read(cx).text(cx);
13988
13989 let rename = self.semantics_provider.as_ref()?.perform_rename(
13990 &buffer,
13991 start,
13992 new_name.clone(),
13993 cx,
13994 )?;
13995
13996 Some(cx.spawn_in(window, async move |editor, cx| {
13997 let project_transaction = rename.await?;
13998 Self::open_project_transaction(
13999 &editor,
14000 workspace,
14001 project_transaction,
14002 format!("Rename: {} → {}", old_name, new_name),
14003 cx,
14004 )
14005 .await?;
14006
14007 editor.update(cx, |editor, cx| {
14008 editor.refresh_document_highlights(cx);
14009 })?;
14010 Ok(())
14011 }))
14012 }
14013
14014 fn take_rename(
14015 &mut self,
14016 moving_cursor: bool,
14017 window: &mut Window,
14018 cx: &mut Context<Self>,
14019 ) -> Option<RenameState> {
14020 let rename = self.pending_rename.take()?;
14021 if rename.editor.focus_handle(cx).is_focused(window) {
14022 window.focus(&self.focus_handle);
14023 }
14024
14025 self.remove_blocks(
14026 [rename.block_id].into_iter().collect(),
14027 Some(Autoscroll::fit()),
14028 cx,
14029 );
14030 self.clear_highlights::<Rename>(cx);
14031 self.show_local_selections = true;
14032
14033 if moving_cursor {
14034 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14035 editor.selections.newest::<usize>(cx).head()
14036 });
14037
14038 // Update the selection to match the position of the selection inside
14039 // the rename editor.
14040 let snapshot = self.buffer.read(cx).read(cx);
14041 let rename_range = rename.range.to_offset(&snapshot);
14042 let cursor_in_editor = snapshot
14043 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14044 .min(rename_range.end);
14045 drop(snapshot);
14046
14047 self.change_selections(None, window, cx, |s| {
14048 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14049 });
14050 } else {
14051 self.refresh_document_highlights(cx);
14052 }
14053
14054 Some(rename)
14055 }
14056
14057 pub fn pending_rename(&self) -> Option<&RenameState> {
14058 self.pending_rename.as_ref()
14059 }
14060
14061 fn format(
14062 &mut self,
14063 _: &Format,
14064 window: &mut Window,
14065 cx: &mut Context<Self>,
14066 ) -> Option<Task<Result<()>>> {
14067 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14068
14069 let project = match &self.project {
14070 Some(project) => project.clone(),
14071 None => return None,
14072 };
14073
14074 Some(self.perform_format(
14075 project,
14076 FormatTrigger::Manual,
14077 FormatTarget::Buffers,
14078 window,
14079 cx,
14080 ))
14081 }
14082
14083 fn format_selections(
14084 &mut self,
14085 _: &FormatSelections,
14086 window: &mut Window,
14087 cx: &mut Context<Self>,
14088 ) -> Option<Task<Result<()>>> {
14089 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14090
14091 let project = match &self.project {
14092 Some(project) => project.clone(),
14093 None => return None,
14094 };
14095
14096 let ranges = self
14097 .selections
14098 .all_adjusted(cx)
14099 .into_iter()
14100 .map(|selection| selection.range())
14101 .collect_vec();
14102
14103 Some(self.perform_format(
14104 project,
14105 FormatTrigger::Manual,
14106 FormatTarget::Ranges(ranges),
14107 window,
14108 cx,
14109 ))
14110 }
14111
14112 fn perform_format(
14113 &mut self,
14114 project: Entity<Project>,
14115 trigger: FormatTrigger,
14116 target: FormatTarget,
14117 window: &mut Window,
14118 cx: &mut Context<Self>,
14119 ) -> Task<Result<()>> {
14120 let buffer = self.buffer.clone();
14121 let (buffers, target) = match target {
14122 FormatTarget::Buffers => {
14123 let mut buffers = buffer.read(cx).all_buffers();
14124 if trigger == FormatTrigger::Save {
14125 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14126 }
14127 (buffers, LspFormatTarget::Buffers)
14128 }
14129 FormatTarget::Ranges(selection_ranges) => {
14130 let multi_buffer = buffer.read(cx);
14131 let snapshot = multi_buffer.read(cx);
14132 let mut buffers = HashSet::default();
14133 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14134 BTreeMap::new();
14135 for selection_range in selection_ranges {
14136 for (buffer, buffer_range, _) in
14137 snapshot.range_to_buffer_ranges(selection_range)
14138 {
14139 let buffer_id = buffer.remote_id();
14140 let start = buffer.anchor_before(buffer_range.start);
14141 let end = buffer.anchor_after(buffer_range.end);
14142 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14143 buffer_id_to_ranges
14144 .entry(buffer_id)
14145 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14146 .or_insert_with(|| vec![start..end]);
14147 }
14148 }
14149 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14150 }
14151 };
14152
14153 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14154 let format = project.update(cx, |project, cx| {
14155 project.format(buffers, target, true, trigger, cx)
14156 });
14157
14158 cx.spawn_in(window, async move |_, cx| {
14159 let transaction = futures::select_biased! {
14160 transaction = format.log_err().fuse() => transaction,
14161 () = timeout => {
14162 log::warn!("timed out waiting for formatting");
14163 None
14164 }
14165 };
14166
14167 buffer
14168 .update(cx, |buffer, cx| {
14169 if let Some(transaction) = transaction {
14170 if !buffer.is_singleton() {
14171 buffer.push_transaction(&transaction.0, cx);
14172 }
14173 }
14174 cx.notify();
14175 })
14176 .ok();
14177
14178 Ok(())
14179 })
14180 }
14181
14182 fn organize_imports(
14183 &mut self,
14184 _: &OrganizeImports,
14185 window: &mut Window,
14186 cx: &mut Context<Self>,
14187 ) -> Option<Task<Result<()>>> {
14188 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14189 let project = match &self.project {
14190 Some(project) => project.clone(),
14191 None => return None,
14192 };
14193 Some(self.perform_code_action_kind(
14194 project,
14195 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14196 window,
14197 cx,
14198 ))
14199 }
14200
14201 fn perform_code_action_kind(
14202 &mut self,
14203 project: Entity<Project>,
14204 kind: CodeActionKind,
14205 window: &mut Window,
14206 cx: &mut Context<Self>,
14207 ) -> Task<Result<()>> {
14208 let buffer = self.buffer.clone();
14209 let buffers = buffer.read(cx).all_buffers();
14210 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14211 let apply_action = project.update(cx, |project, cx| {
14212 project.apply_code_action_kind(buffers, kind, true, cx)
14213 });
14214 cx.spawn_in(window, async move |_, cx| {
14215 let transaction = futures::select_biased! {
14216 () = timeout => {
14217 log::warn!("timed out waiting for executing code action");
14218 None
14219 }
14220 transaction = apply_action.log_err().fuse() => transaction,
14221 };
14222 buffer
14223 .update(cx, |buffer, cx| {
14224 // check if we need this
14225 if let Some(transaction) = transaction {
14226 if !buffer.is_singleton() {
14227 buffer.push_transaction(&transaction.0, cx);
14228 }
14229 }
14230 cx.notify();
14231 })
14232 .ok();
14233 Ok(())
14234 })
14235 }
14236
14237 fn restart_language_server(
14238 &mut self,
14239 _: &RestartLanguageServer,
14240 _: &mut Window,
14241 cx: &mut Context<Self>,
14242 ) {
14243 if let Some(project) = self.project.clone() {
14244 self.buffer.update(cx, |multi_buffer, cx| {
14245 project.update(cx, |project, cx| {
14246 project.restart_language_servers_for_buffers(
14247 multi_buffer.all_buffers().into_iter().collect(),
14248 cx,
14249 );
14250 });
14251 })
14252 }
14253 }
14254
14255 fn stop_language_server(
14256 &mut self,
14257 _: &StopLanguageServer,
14258 _: &mut Window,
14259 cx: &mut Context<Self>,
14260 ) {
14261 if let Some(project) = self.project.clone() {
14262 self.buffer.update(cx, |multi_buffer, cx| {
14263 project.update(cx, |project, cx| {
14264 project.stop_language_servers_for_buffers(
14265 multi_buffer.all_buffers().into_iter().collect(),
14266 cx,
14267 );
14268 cx.emit(project::Event::RefreshInlayHints);
14269 });
14270 });
14271 }
14272 }
14273
14274 fn cancel_language_server_work(
14275 workspace: &mut Workspace,
14276 _: &actions::CancelLanguageServerWork,
14277 _: &mut Window,
14278 cx: &mut Context<Workspace>,
14279 ) {
14280 let project = workspace.project();
14281 let buffers = workspace
14282 .active_item(cx)
14283 .and_then(|item| item.act_as::<Editor>(cx))
14284 .map_or(HashSet::default(), |editor| {
14285 editor.read(cx).buffer.read(cx).all_buffers()
14286 });
14287 project.update(cx, |project, cx| {
14288 project.cancel_language_server_work_for_buffers(buffers, cx);
14289 });
14290 }
14291
14292 fn show_character_palette(
14293 &mut self,
14294 _: &ShowCharacterPalette,
14295 window: &mut Window,
14296 _: &mut Context<Self>,
14297 ) {
14298 window.show_character_palette();
14299 }
14300
14301 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14302 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
14303 let buffer = self.buffer.read(cx).snapshot(cx);
14304 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
14305 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
14306 let is_valid = buffer
14307 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14308 .any(|entry| {
14309 entry.diagnostic.is_primary
14310 && !entry.range.is_empty()
14311 && entry.range.start == primary_range_start
14312 && entry.diagnostic.message == active_diagnostics.primary_message
14313 });
14314
14315 if is_valid != active_diagnostics.is_valid {
14316 active_diagnostics.is_valid = is_valid;
14317 if is_valid {
14318 let mut new_styles = HashMap::default();
14319 for (block_id, diagnostic) in &active_diagnostics.blocks {
14320 new_styles.insert(
14321 *block_id,
14322 diagnostic_block_renderer(diagnostic.clone(), None, true),
14323 );
14324 }
14325 self.display_map.update(cx, |display_map, _cx| {
14326 display_map.replace_blocks(new_styles);
14327 });
14328 } else {
14329 self.dismiss_diagnostics(cx);
14330 }
14331 }
14332 }
14333 }
14334
14335 fn activate_diagnostics(
14336 &mut self,
14337 buffer_id: BufferId,
14338 group_id: usize,
14339 window: &mut Window,
14340 cx: &mut Context<Self>,
14341 ) {
14342 self.dismiss_diagnostics(cx);
14343 let snapshot = self.snapshot(window, cx);
14344 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
14345 let buffer = self.buffer.read(cx).snapshot(cx);
14346
14347 let mut primary_range = None;
14348 let mut primary_message = None;
14349 let diagnostic_group = buffer
14350 .diagnostic_group(buffer_id, group_id)
14351 .filter_map(|entry| {
14352 let start = entry.range.start;
14353 let end = entry.range.end;
14354 if snapshot.is_line_folded(MultiBufferRow(start.row))
14355 && (start.row == end.row
14356 || snapshot.is_line_folded(MultiBufferRow(end.row)))
14357 {
14358 return None;
14359 }
14360 if entry.diagnostic.is_primary {
14361 primary_range = Some(entry.range.clone());
14362 primary_message = Some(entry.diagnostic.message.clone());
14363 }
14364 Some(entry)
14365 })
14366 .collect::<Vec<_>>();
14367 let primary_range = primary_range?;
14368 let primary_message = primary_message?;
14369
14370 let blocks = display_map
14371 .insert_blocks(
14372 diagnostic_group.iter().map(|entry| {
14373 let diagnostic = entry.diagnostic.clone();
14374 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
14375 BlockProperties {
14376 style: BlockStyle::Fixed,
14377 placement: BlockPlacement::Below(
14378 buffer.anchor_after(entry.range.start),
14379 ),
14380 height: Some(message_height),
14381 render: diagnostic_block_renderer(diagnostic, None, true),
14382 priority: 0,
14383 }
14384 }),
14385 cx,
14386 )
14387 .into_iter()
14388 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
14389 .collect();
14390
14391 Some(ActiveDiagnosticGroup {
14392 primary_range: buffer.anchor_before(primary_range.start)
14393 ..buffer.anchor_after(primary_range.end),
14394 primary_message,
14395 group_id,
14396 blocks,
14397 is_valid: true,
14398 })
14399 });
14400 }
14401
14402 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14403 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
14404 self.display_map.update(cx, |display_map, cx| {
14405 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
14406 });
14407 cx.notify();
14408 }
14409 }
14410
14411 /// Disable inline diagnostics rendering for this editor.
14412 pub fn disable_inline_diagnostics(&mut self) {
14413 self.inline_diagnostics_enabled = false;
14414 self.inline_diagnostics_update = Task::ready(());
14415 self.inline_diagnostics.clear();
14416 }
14417
14418 pub fn inline_diagnostics_enabled(&self) -> bool {
14419 self.inline_diagnostics_enabled
14420 }
14421
14422 pub fn show_inline_diagnostics(&self) -> bool {
14423 self.show_inline_diagnostics
14424 }
14425
14426 pub fn toggle_inline_diagnostics(
14427 &mut self,
14428 _: &ToggleInlineDiagnostics,
14429 window: &mut Window,
14430 cx: &mut Context<Editor>,
14431 ) {
14432 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14433 self.refresh_inline_diagnostics(false, window, cx);
14434 }
14435
14436 fn refresh_inline_diagnostics(
14437 &mut self,
14438 debounce: bool,
14439 window: &mut Window,
14440 cx: &mut Context<Self>,
14441 ) {
14442 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14443 self.inline_diagnostics_update = Task::ready(());
14444 self.inline_diagnostics.clear();
14445 return;
14446 }
14447
14448 let debounce_ms = ProjectSettings::get_global(cx)
14449 .diagnostics
14450 .inline
14451 .update_debounce_ms;
14452 let debounce = if debounce && debounce_ms > 0 {
14453 Some(Duration::from_millis(debounce_ms))
14454 } else {
14455 None
14456 };
14457 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14458 if let Some(debounce) = debounce {
14459 cx.background_executor().timer(debounce).await;
14460 }
14461 let Some(snapshot) = editor
14462 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14463 .ok()
14464 else {
14465 return;
14466 };
14467
14468 let new_inline_diagnostics = cx
14469 .background_spawn(async move {
14470 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14471 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14472 let message = diagnostic_entry
14473 .diagnostic
14474 .message
14475 .split_once('\n')
14476 .map(|(line, _)| line)
14477 .map(SharedString::new)
14478 .unwrap_or_else(|| {
14479 SharedString::from(diagnostic_entry.diagnostic.message)
14480 });
14481 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14482 let (Ok(i) | Err(i)) = inline_diagnostics
14483 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14484 inline_diagnostics.insert(
14485 i,
14486 (
14487 start_anchor,
14488 InlineDiagnostic {
14489 message,
14490 group_id: diagnostic_entry.diagnostic.group_id,
14491 start: diagnostic_entry.range.start.to_point(&snapshot),
14492 is_primary: diagnostic_entry.diagnostic.is_primary,
14493 severity: diagnostic_entry.diagnostic.severity,
14494 },
14495 ),
14496 );
14497 }
14498 inline_diagnostics
14499 })
14500 .await;
14501
14502 editor
14503 .update(cx, |editor, cx| {
14504 editor.inline_diagnostics = new_inline_diagnostics;
14505 cx.notify();
14506 })
14507 .ok();
14508 });
14509 }
14510
14511 pub fn set_selections_from_remote(
14512 &mut self,
14513 selections: Vec<Selection<Anchor>>,
14514 pending_selection: Option<Selection<Anchor>>,
14515 window: &mut Window,
14516 cx: &mut Context<Self>,
14517 ) {
14518 let old_cursor_position = self.selections.newest_anchor().head();
14519 self.selections.change_with(cx, |s| {
14520 s.select_anchors(selections);
14521 if let Some(pending_selection) = pending_selection {
14522 s.set_pending(pending_selection, SelectMode::Character);
14523 } else {
14524 s.clear_pending();
14525 }
14526 });
14527 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14528 }
14529
14530 fn push_to_selection_history(&mut self) {
14531 self.selection_history.push(SelectionHistoryEntry {
14532 selections: self.selections.disjoint_anchors(),
14533 select_next_state: self.select_next_state.clone(),
14534 select_prev_state: self.select_prev_state.clone(),
14535 add_selections_state: self.add_selections_state.clone(),
14536 });
14537 }
14538
14539 pub fn transact(
14540 &mut self,
14541 window: &mut Window,
14542 cx: &mut Context<Self>,
14543 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14544 ) -> Option<TransactionId> {
14545 self.start_transaction_at(Instant::now(), window, cx);
14546 update(self, window, cx);
14547 self.end_transaction_at(Instant::now(), cx)
14548 }
14549
14550 pub fn start_transaction_at(
14551 &mut self,
14552 now: Instant,
14553 window: &mut Window,
14554 cx: &mut Context<Self>,
14555 ) {
14556 self.end_selection(window, cx);
14557 if let Some(tx_id) = self
14558 .buffer
14559 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14560 {
14561 self.selection_history
14562 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14563 cx.emit(EditorEvent::TransactionBegun {
14564 transaction_id: tx_id,
14565 })
14566 }
14567 }
14568
14569 pub fn end_transaction_at(
14570 &mut self,
14571 now: Instant,
14572 cx: &mut Context<Self>,
14573 ) -> Option<TransactionId> {
14574 if let Some(transaction_id) = self
14575 .buffer
14576 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14577 {
14578 if let Some((_, end_selections)) =
14579 self.selection_history.transaction_mut(transaction_id)
14580 {
14581 *end_selections = Some(self.selections.disjoint_anchors());
14582 } else {
14583 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14584 }
14585
14586 cx.emit(EditorEvent::Edited { transaction_id });
14587 Some(transaction_id)
14588 } else {
14589 None
14590 }
14591 }
14592
14593 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14594 if self.selection_mark_mode {
14595 self.change_selections(None, window, cx, |s| {
14596 s.move_with(|_, sel| {
14597 sel.collapse_to(sel.head(), SelectionGoal::None);
14598 });
14599 })
14600 }
14601 self.selection_mark_mode = true;
14602 cx.notify();
14603 }
14604
14605 pub fn swap_selection_ends(
14606 &mut self,
14607 _: &actions::SwapSelectionEnds,
14608 window: &mut Window,
14609 cx: &mut Context<Self>,
14610 ) {
14611 self.change_selections(None, window, cx, |s| {
14612 s.move_with(|_, sel| {
14613 if sel.start != sel.end {
14614 sel.reversed = !sel.reversed
14615 }
14616 });
14617 });
14618 self.request_autoscroll(Autoscroll::newest(), cx);
14619 cx.notify();
14620 }
14621
14622 pub fn toggle_fold(
14623 &mut self,
14624 _: &actions::ToggleFold,
14625 window: &mut Window,
14626 cx: &mut Context<Self>,
14627 ) {
14628 if self.is_singleton(cx) {
14629 let selection = self.selections.newest::<Point>(cx);
14630
14631 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14632 let range = if selection.is_empty() {
14633 let point = selection.head().to_display_point(&display_map);
14634 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14635 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14636 .to_point(&display_map);
14637 start..end
14638 } else {
14639 selection.range()
14640 };
14641 if display_map.folds_in_range(range).next().is_some() {
14642 self.unfold_lines(&Default::default(), window, cx)
14643 } else {
14644 self.fold(&Default::default(), window, cx)
14645 }
14646 } else {
14647 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14648 let buffer_ids: HashSet<_> = self
14649 .selections
14650 .disjoint_anchor_ranges()
14651 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14652 .collect();
14653
14654 let should_unfold = buffer_ids
14655 .iter()
14656 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14657
14658 for buffer_id in buffer_ids {
14659 if should_unfold {
14660 self.unfold_buffer(buffer_id, cx);
14661 } else {
14662 self.fold_buffer(buffer_id, cx);
14663 }
14664 }
14665 }
14666 }
14667
14668 pub fn toggle_fold_recursive(
14669 &mut self,
14670 _: &actions::ToggleFoldRecursive,
14671 window: &mut Window,
14672 cx: &mut Context<Self>,
14673 ) {
14674 let selection = self.selections.newest::<Point>(cx);
14675
14676 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14677 let range = if selection.is_empty() {
14678 let point = selection.head().to_display_point(&display_map);
14679 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14680 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14681 .to_point(&display_map);
14682 start..end
14683 } else {
14684 selection.range()
14685 };
14686 if display_map.folds_in_range(range).next().is_some() {
14687 self.unfold_recursive(&Default::default(), window, cx)
14688 } else {
14689 self.fold_recursive(&Default::default(), window, cx)
14690 }
14691 }
14692
14693 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14694 if self.is_singleton(cx) {
14695 let mut to_fold = Vec::new();
14696 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14697 let selections = self.selections.all_adjusted(cx);
14698
14699 for selection in selections {
14700 let range = selection.range().sorted();
14701 let buffer_start_row = range.start.row;
14702
14703 if range.start.row != range.end.row {
14704 let mut found = false;
14705 let mut row = range.start.row;
14706 while row <= range.end.row {
14707 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14708 {
14709 found = true;
14710 row = crease.range().end.row + 1;
14711 to_fold.push(crease);
14712 } else {
14713 row += 1
14714 }
14715 }
14716 if found {
14717 continue;
14718 }
14719 }
14720
14721 for row in (0..=range.start.row).rev() {
14722 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14723 if crease.range().end.row >= buffer_start_row {
14724 to_fold.push(crease);
14725 if row <= range.start.row {
14726 break;
14727 }
14728 }
14729 }
14730 }
14731 }
14732
14733 self.fold_creases(to_fold, true, window, cx);
14734 } else {
14735 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14736 let buffer_ids = self
14737 .selections
14738 .disjoint_anchor_ranges()
14739 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14740 .collect::<HashSet<_>>();
14741 for buffer_id in buffer_ids {
14742 self.fold_buffer(buffer_id, cx);
14743 }
14744 }
14745 }
14746
14747 fn fold_at_level(
14748 &mut self,
14749 fold_at: &FoldAtLevel,
14750 window: &mut Window,
14751 cx: &mut Context<Self>,
14752 ) {
14753 if !self.buffer.read(cx).is_singleton() {
14754 return;
14755 }
14756
14757 let fold_at_level = fold_at.0;
14758 let snapshot = self.buffer.read(cx).snapshot(cx);
14759 let mut to_fold = Vec::new();
14760 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14761
14762 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14763 while start_row < end_row {
14764 match self
14765 .snapshot(window, cx)
14766 .crease_for_buffer_row(MultiBufferRow(start_row))
14767 {
14768 Some(crease) => {
14769 let nested_start_row = crease.range().start.row + 1;
14770 let nested_end_row = crease.range().end.row;
14771
14772 if current_level < fold_at_level {
14773 stack.push((nested_start_row, nested_end_row, current_level + 1));
14774 } else if current_level == fold_at_level {
14775 to_fold.push(crease);
14776 }
14777
14778 start_row = nested_end_row + 1;
14779 }
14780 None => start_row += 1,
14781 }
14782 }
14783 }
14784
14785 self.fold_creases(to_fold, true, window, cx);
14786 }
14787
14788 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14789 if self.buffer.read(cx).is_singleton() {
14790 let mut fold_ranges = Vec::new();
14791 let snapshot = self.buffer.read(cx).snapshot(cx);
14792
14793 for row in 0..snapshot.max_row().0 {
14794 if let Some(foldable_range) = self
14795 .snapshot(window, cx)
14796 .crease_for_buffer_row(MultiBufferRow(row))
14797 {
14798 fold_ranges.push(foldable_range);
14799 }
14800 }
14801
14802 self.fold_creases(fold_ranges, true, window, cx);
14803 } else {
14804 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14805 editor
14806 .update_in(cx, |editor, _, cx| {
14807 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14808 editor.fold_buffer(buffer_id, cx);
14809 }
14810 })
14811 .ok();
14812 });
14813 }
14814 }
14815
14816 pub fn fold_function_bodies(
14817 &mut self,
14818 _: &actions::FoldFunctionBodies,
14819 window: &mut Window,
14820 cx: &mut Context<Self>,
14821 ) {
14822 let snapshot = self.buffer.read(cx).snapshot(cx);
14823
14824 let ranges = snapshot
14825 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14826 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14827 .collect::<Vec<_>>();
14828
14829 let creases = ranges
14830 .into_iter()
14831 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14832 .collect();
14833
14834 self.fold_creases(creases, true, window, cx);
14835 }
14836
14837 pub fn fold_recursive(
14838 &mut self,
14839 _: &actions::FoldRecursive,
14840 window: &mut Window,
14841 cx: &mut Context<Self>,
14842 ) {
14843 let mut to_fold = Vec::new();
14844 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14845 let selections = self.selections.all_adjusted(cx);
14846
14847 for selection in selections {
14848 let range = selection.range().sorted();
14849 let buffer_start_row = range.start.row;
14850
14851 if range.start.row != range.end.row {
14852 let mut found = false;
14853 for row in range.start.row..=range.end.row {
14854 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14855 found = true;
14856 to_fold.push(crease);
14857 }
14858 }
14859 if found {
14860 continue;
14861 }
14862 }
14863
14864 for row in (0..=range.start.row).rev() {
14865 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14866 if crease.range().end.row >= buffer_start_row {
14867 to_fold.push(crease);
14868 } else {
14869 break;
14870 }
14871 }
14872 }
14873 }
14874
14875 self.fold_creases(to_fold, true, window, cx);
14876 }
14877
14878 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14879 let buffer_row = fold_at.buffer_row;
14880 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14881
14882 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14883 let autoscroll = self
14884 .selections
14885 .all::<Point>(cx)
14886 .iter()
14887 .any(|selection| crease.range().overlaps(&selection.range()));
14888
14889 self.fold_creases(vec![crease], autoscroll, window, cx);
14890 }
14891 }
14892
14893 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14894 if self.is_singleton(cx) {
14895 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14896 let buffer = &display_map.buffer_snapshot;
14897 let selections = self.selections.all::<Point>(cx);
14898 let ranges = selections
14899 .iter()
14900 .map(|s| {
14901 let range = s.display_range(&display_map).sorted();
14902 let mut start = range.start.to_point(&display_map);
14903 let mut end = range.end.to_point(&display_map);
14904 start.column = 0;
14905 end.column = buffer.line_len(MultiBufferRow(end.row));
14906 start..end
14907 })
14908 .collect::<Vec<_>>();
14909
14910 self.unfold_ranges(&ranges, true, true, cx);
14911 } else {
14912 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14913 let buffer_ids = self
14914 .selections
14915 .disjoint_anchor_ranges()
14916 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14917 .collect::<HashSet<_>>();
14918 for buffer_id in buffer_ids {
14919 self.unfold_buffer(buffer_id, cx);
14920 }
14921 }
14922 }
14923
14924 pub fn unfold_recursive(
14925 &mut self,
14926 _: &UnfoldRecursive,
14927 _window: &mut Window,
14928 cx: &mut Context<Self>,
14929 ) {
14930 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14931 let selections = self.selections.all::<Point>(cx);
14932 let ranges = selections
14933 .iter()
14934 .map(|s| {
14935 let mut range = s.display_range(&display_map).sorted();
14936 *range.start.column_mut() = 0;
14937 *range.end.column_mut() = display_map.line_len(range.end.row());
14938 let start = range.start.to_point(&display_map);
14939 let end = range.end.to_point(&display_map);
14940 start..end
14941 })
14942 .collect::<Vec<_>>();
14943
14944 self.unfold_ranges(&ranges, true, true, cx);
14945 }
14946
14947 pub fn unfold_at(
14948 &mut self,
14949 unfold_at: &UnfoldAt,
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
14955 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14956 ..Point::new(
14957 unfold_at.buffer_row.0,
14958 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14959 );
14960
14961 let autoscroll = self
14962 .selections
14963 .all::<Point>(cx)
14964 .iter()
14965 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14966
14967 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14968 }
14969
14970 pub fn unfold_all(
14971 &mut self,
14972 _: &actions::UnfoldAll,
14973 _window: &mut Window,
14974 cx: &mut Context<Self>,
14975 ) {
14976 if self.buffer.read(cx).is_singleton() {
14977 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14978 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
14979 } else {
14980 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
14981 editor
14982 .update(cx, |editor, cx| {
14983 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14984 editor.unfold_buffer(buffer_id, cx);
14985 }
14986 })
14987 .ok();
14988 });
14989 }
14990 }
14991
14992 pub fn fold_selected_ranges(
14993 &mut self,
14994 _: &FoldSelectedRanges,
14995 window: &mut Window,
14996 cx: &mut Context<Self>,
14997 ) {
14998 let selections = self.selections.all_adjusted(cx);
14999 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15000 let ranges = selections
15001 .into_iter()
15002 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15003 .collect::<Vec<_>>();
15004 self.fold_creases(ranges, true, window, cx);
15005 }
15006
15007 pub fn fold_ranges<T: ToOffset + Clone>(
15008 &mut self,
15009 ranges: Vec<Range<T>>,
15010 auto_scroll: bool,
15011 window: &mut Window,
15012 cx: &mut Context<Self>,
15013 ) {
15014 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15015 let ranges = ranges
15016 .into_iter()
15017 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15018 .collect::<Vec<_>>();
15019 self.fold_creases(ranges, auto_scroll, window, cx);
15020 }
15021
15022 pub fn fold_creases<T: ToOffset + Clone>(
15023 &mut self,
15024 creases: Vec<Crease<T>>,
15025 auto_scroll: bool,
15026 window: &mut Window,
15027 cx: &mut Context<Self>,
15028 ) {
15029 if creases.is_empty() {
15030 return;
15031 }
15032
15033 let mut buffers_affected = HashSet::default();
15034 let multi_buffer = self.buffer().read(cx);
15035 for crease in &creases {
15036 if let Some((_, buffer, _)) =
15037 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15038 {
15039 buffers_affected.insert(buffer.read(cx).remote_id());
15040 };
15041 }
15042
15043 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15044
15045 if auto_scroll {
15046 self.request_autoscroll(Autoscroll::fit(), cx);
15047 }
15048
15049 cx.notify();
15050
15051 if let Some(active_diagnostics) = self.active_diagnostics.take() {
15052 // Clear diagnostics block when folding a range that contains it.
15053 let snapshot = self.snapshot(window, cx);
15054 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
15055 drop(snapshot);
15056 self.active_diagnostics = Some(active_diagnostics);
15057 self.dismiss_diagnostics(cx);
15058 } else {
15059 self.active_diagnostics = Some(active_diagnostics);
15060 }
15061 }
15062
15063 self.scrollbar_marker_state.dirty = true;
15064 self.folds_did_change(cx);
15065 }
15066
15067 /// Removes any folds whose ranges intersect any of the given ranges.
15068 pub fn unfold_ranges<T: ToOffset + Clone>(
15069 &mut self,
15070 ranges: &[Range<T>],
15071 inclusive: bool,
15072 auto_scroll: bool,
15073 cx: &mut Context<Self>,
15074 ) {
15075 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15076 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15077 });
15078 self.folds_did_change(cx);
15079 }
15080
15081 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15082 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15083 return;
15084 }
15085 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15086 self.display_map.update(cx, |display_map, cx| {
15087 display_map.fold_buffers([buffer_id], cx)
15088 });
15089 cx.emit(EditorEvent::BufferFoldToggled {
15090 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15091 folded: true,
15092 });
15093 cx.notify();
15094 }
15095
15096 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15097 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15098 return;
15099 }
15100 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15101 self.display_map.update(cx, |display_map, cx| {
15102 display_map.unfold_buffers([buffer_id], cx);
15103 });
15104 cx.emit(EditorEvent::BufferFoldToggled {
15105 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15106 folded: false,
15107 });
15108 cx.notify();
15109 }
15110
15111 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15112 self.display_map.read(cx).is_buffer_folded(buffer)
15113 }
15114
15115 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15116 self.display_map.read(cx).folded_buffers()
15117 }
15118
15119 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15120 self.display_map.update(cx, |display_map, cx| {
15121 display_map.disable_header_for_buffer(buffer_id, cx);
15122 });
15123 cx.notify();
15124 }
15125
15126 /// Removes any folds with the given ranges.
15127 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15128 &mut self,
15129 ranges: &[Range<T>],
15130 type_id: TypeId,
15131 auto_scroll: bool,
15132 cx: &mut Context<Self>,
15133 ) {
15134 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15135 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15136 });
15137 self.folds_did_change(cx);
15138 }
15139
15140 fn remove_folds_with<T: ToOffset + Clone>(
15141 &mut self,
15142 ranges: &[Range<T>],
15143 auto_scroll: bool,
15144 cx: &mut Context<Self>,
15145 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15146 ) {
15147 if ranges.is_empty() {
15148 return;
15149 }
15150
15151 let mut buffers_affected = HashSet::default();
15152 let multi_buffer = self.buffer().read(cx);
15153 for range in ranges {
15154 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15155 buffers_affected.insert(buffer.read(cx).remote_id());
15156 };
15157 }
15158
15159 self.display_map.update(cx, update);
15160
15161 if auto_scroll {
15162 self.request_autoscroll(Autoscroll::fit(), cx);
15163 }
15164
15165 cx.notify();
15166 self.scrollbar_marker_state.dirty = true;
15167 self.active_indent_guides_state.dirty = true;
15168 }
15169
15170 pub fn update_fold_widths(
15171 &mut self,
15172 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15173 cx: &mut Context<Self>,
15174 ) -> bool {
15175 self.display_map
15176 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15177 }
15178
15179 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15180 self.display_map.read(cx).fold_placeholder.clone()
15181 }
15182
15183 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15184 self.buffer.update(cx, |buffer, cx| {
15185 buffer.set_all_diff_hunks_expanded(cx);
15186 });
15187 }
15188
15189 pub fn expand_all_diff_hunks(
15190 &mut self,
15191 _: &ExpandAllDiffHunks,
15192 _window: &mut Window,
15193 cx: &mut Context<Self>,
15194 ) {
15195 self.buffer.update(cx, |buffer, cx| {
15196 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15197 });
15198 }
15199
15200 pub fn toggle_selected_diff_hunks(
15201 &mut self,
15202 _: &ToggleSelectedDiffHunks,
15203 _window: &mut Window,
15204 cx: &mut Context<Self>,
15205 ) {
15206 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15207 self.toggle_diff_hunks_in_ranges(ranges, cx);
15208 }
15209
15210 pub fn diff_hunks_in_ranges<'a>(
15211 &'a self,
15212 ranges: &'a [Range<Anchor>],
15213 buffer: &'a MultiBufferSnapshot,
15214 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15215 ranges.iter().flat_map(move |range| {
15216 let end_excerpt_id = range.end.excerpt_id;
15217 let range = range.to_point(buffer);
15218 let mut peek_end = range.end;
15219 if range.end.row < buffer.max_row().0 {
15220 peek_end = Point::new(range.end.row + 1, 0);
15221 }
15222 buffer
15223 .diff_hunks_in_range(range.start..peek_end)
15224 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15225 })
15226 }
15227
15228 pub fn has_stageable_diff_hunks_in_ranges(
15229 &self,
15230 ranges: &[Range<Anchor>],
15231 snapshot: &MultiBufferSnapshot,
15232 ) -> bool {
15233 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15234 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15235 }
15236
15237 pub fn toggle_staged_selected_diff_hunks(
15238 &mut self,
15239 _: &::git::ToggleStaged,
15240 _: &mut Window,
15241 cx: &mut Context<Self>,
15242 ) {
15243 let snapshot = self.buffer.read(cx).snapshot(cx);
15244 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15245 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15246 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15247 }
15248
15249 pub fn set_render_diff_hunk_controls(
15250 &mut self,
15251 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15252 cx: &mut Context<Self>,
15253 ) {
15254 self.render_diff_hunk_controls = render_diff_hunk_controls;
15255 cx.notify();
15256 }
15257
15258 pub fn stage_and_next(
15259 &mut self,
15260 _: &::git::StageAndNext,
15261 window: &mut Window,
15262 cx: &mut Context<Self>,
15263 ) {
15264 self.do_stage_or_unstage_and_next(true, window, cx);
15265 }
15266
15267 pub fn unstage_and_next(
15268 &mut self,
15269 _: &::git::UnstageAndNext,
15270 window: &mut Window,
15271 cx: &mut Context<Self>,
15272 ) {
15273 self.do_stage_or_unstage_and_next(false, window, cx);
15274 }
15275
15276 pub fn stage_or_unstage_diff_hunks(
15277 &mut self,
15278 stage: bool,
15279 ranges: Vec<Range<Anchor>>,
15280 cx: &mut Context<Self>,
15281 ) {
15282 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15283 cx.spawn(async move |this, cx| {
15284 task.await?;
15285 this.update(cx, |this, cx| {
15286 let snapshot = this.buffer.read(cx).snapshot(cx);
15287 let chunk_by = this
15288 .diff_hunks_in_ranges(&ranges, &snapshot)
15289 .chunk_by(|hunk| hunk.buffer_id);
15290 for (buffer_id, hunks) in &chunk_by {
15291 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15292 }
15293 })
15294 })
15295 .detach_and_log_err(cx);
15296 }
15297
15298 fn save_buffers_for_ranges_if_needed(
15299 &mut self,
15300 ranges: &[Range<Anchor>],
15301 cx: &mut Context<Editor>,
15302 ) -> Task<Result<()>> {
15303 let multibuffer = self.buffer.read(cx);
15304 let snapshot = multibuffer.read(cx);
15305 let buffer_ids: HashSet<_> = ranges
15306 .iter()
15307 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15308 .collect();
15309 drop(snapshot);
15310
15311 let mut buffers = HashSet::default();
15312 for buffer_id in buffer_ids {
15313 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15314 let buffer = buffer_entity.read(cx);
15315 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15316 {
15317 buffers.insert(buffer_entity);
15318 }
15319 }
15320 }
15321
15322 if let Some(project) = &self.project {
15323 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15324 } else {
15325 Task::ready(Ok(()))
15326 }
15327 }
15328
15329 fn do_stage_or_unstage_and_next(
15330 &mut self,
15331 stage: bool,
15332 window: &mut Window,
15333 cx: &mut Context<Self>,
15334 ) {
15335 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15336
15337 if ranges.iter().any(|range| range.start != range.end) {
15338 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15339 return;
15340 }
15341
15342 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15343 let snapshot = self.snapshot(window, cx);
15344 let position = self.selections.newest::<Point>(cx).head();
15345 let mut row = snapshot
15346 .buffer_snapshot
15347 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15348 .find(|hunk| hunk.row_range.start.0 > position.row)
15349 .map(|hunk| hunk.row_range.start);
15350
15351 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15352 // Outside of the project diff editor, wrap around to the beginning.
15353 if !all_diff_hunks_expanded {
15354 row = row.or_else(|| {
15355 snapshot
15356 .buffer_snapshot
15357 .diff_hunks_in_range(Point::zero()..position)
15358 .find(|hunk| hunk.row_range.end.0 < position.row)
15359 .map(|hunk| hunk.row_range.start)
15360 });
15361 }
15362
15363 if let Some(row) = row {
15364 let destination = Point::new(row.0, 0);
15365 let autoscroll = Autoscroll::center();
15366
15367 self.unfold_ranges(&[destination..destination], false, false, cx);
15368 self.change_selections(Some(autoscroll), window, cx, |s| {
15369 s.select_ranges([destination..destination]);
15370 });
15371 }
15372 }
15373
15374 fn do_stage_or_unstage(
15375 &self,
15376 stage: bool,
15377 buffer_id: BufferId,
15378 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15379 cx: &mut App,
15380 ) -> Option<()> {
15381 let project = self.project.as_ref()?;
15382 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15383 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15384 let buffer_snapshot = buffer.read(cx).snapshot();
15385 let file_exists = buffer_snapshot
15386 .file()
15387 .is_some_and(|file| file.disk_state().exists());
15388 diff.update(cx, |diff, cx| {
15389 diff.stage_or_unstage_hunks(
15390 stage,
15391 &hunks
15392 .map(|hunk| buffer_diff::DiffHunk {
15393 buffer_range: hunk.buffer_range,
15394 diff_base_byte_range: hunk.diff_base_byte_range,
15395 secondary_status: hunk.secondary_status,
15396 range: Point::zero()..Point::zero(), // unused
15397 })
15398 .collect::<Vec<_>>(),
15399 &buffer_snapshot,
15400 file_exists,
15401 cx,
15402 )
15403 });
15404 None
15405 }
15406
15407 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15408 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15409 self.buffer
15410 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15411 }
15412
15413 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15414 self.buffer.update(cx, |buffer, cx| {
15415 let ranges = vec![Anchor::min()..Anchor::max()];
15416 if !buffer.all_diff_hunks_expanded()
15417 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15418 {
15419 buffer.collapse_diff_hunks(ranges, cx);
15420 true
15421 } else {
15422 false
15423 }
15424 })
15425 }
15426
15427 fn toggle_diff_hunks_in_ranges(
15428 &mut self,
15429 ranges: Vec<Range<Anchor>>,
15430 cx: &mut Context<Editor>,
15431 ) {
15432 self.buffer.update(cx, |buffer, cx| {
15433 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15434 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15435 })
15436 }
15437
15438 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15439 self.buffer.update(cx, |buffer, cx| {
15440 let snapshot = buffer.snapshot(cx);
15441 let excerpt_id = range.end.excerpt_id;
15442 let point_range = range.to_point(&snapshot);
15443 let expand = !buffer.single_hunk_is_expanded(range, cx);
15444 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15445 })
15446 }
15447
15448 pub(crate) fn apply_all_diff_hunks(
15449 &mut self,
15450 _: &ApplyAllDiffHunks,
15451 window: &mut Window,
15452 cx: &mut Context<Self>,
15453 ) {
15454 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15455
15456 let buffers = self.buffer.read(cx).all_buffers();
15457 for branch_buffer in buffers {
15458 branch_buffer.update(cx, |branch_buffer, cx| {
15459 branch_buffer.merge_into_base(Vec::new(), cx);
15460 });
15461 }
15462
15463 if let Some(project) = self.project.clone() {
15464 self.save(true, project, window, cx).detach_and_log_err(cx);
15465 }
15466 }
15467
15468 pub(crate) fn apply_selected_diff_hunks(
15469 &mut self,
15470 _: &ApplyDiffHunk,
15471 window: &mut Window,
15472 cx: &mut Context<Self>,
15473 ) {
15474 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15475 let snapshot = self.snapshot(window, cx);
15476 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15477 let mut ranges_by_buffer = HashMap::default();
15478 self.transact(window, cx, |editor, _window, cx| {
15479 for hunk in hunks {
15480 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15481 ranges_by_buffer
15482 .entry(buffer.clone())
15483 .or_insert_with(Vec::new)
15484 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15485 }
15486 }
15487
15488 for (buffer, ranges) in ranges_by_buffer {
15489 buffer.update(cx, |buffer, cx| {
15490 buffer.merge_into_base(ranges, cx);
15491 });
15492 }
15493 });
15494
15495 if let Some(project) = self.project.clone() {
15496 self.save(true, project, window, cx).detach_and_log_err(cx);
15497 }
15498 }
15499
15500 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15501 if hovered != self.gutter_hovered {
15502 self.gutter_hovered = hovered;
15503 cx.notify();
15504 }
15505 }
15506
15507 pub fn insert_blocks(
15508 &mut self,
15509 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15510 autoscroll: Option<Autoscroll>,
15511 cx: &mut Context<Self>,
15512 ) -> Vec<CustomBlockId> {
15513 let blocks = self
15514 .display_map
15515 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15516 if let Some(autoscroll) = autoscroll {
15517 self.request_autoscroll(autoscroll, cx);
15518 }
15519 cx.notify();
15520 blocks
15521 }
15522
15523 pub fn resize_blocks(
15524 &mut self,
15525 heights: HashMap<CustomBlockId, u32>,
15526 autoscroll: Option<Autoscroll>,
15527 cx: &mut Context<Self>,
15528 ) {
15529 self.display_map
15530 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15531 if let Some(autoscroll) = autoscroll {
15532 self.request_autoscroll(autoscroll, cx);
15533 }
15534 cx.notify();
15535 }
15536
15537 pub fn replace_blocks(
15538 &mut self,
15539 renderers: HashMap<CustomBlockId, RenderBlock>,
15540 autoscroll: Option<Autoscroll>,
15541 cx: &mut Context<Self>,
15542 ) {
15543 self.display_map
15544 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15545 if let Some(autoscroll) = autoscroll {
15546 self.request_autoscroll(autoscroll, cx);
15547 }
15548 cx.notify();
15549 }
15550
15551 pub fn remove_blocks(
15552 &mut self,
15553 block_ids: HashSet<CustomBlockId>,
15554 autoscroll: Option<Autoscroll>,
15555 cx: &mut Context<Self>,
15556 ) {
15557 self.display_map.update(cx, |display_map, cx| {
15558 display_map.remove_blocks(block_ids, cx)
15559 });
15560 if let Some(autoscroll) = autoscroll {
15561 self.request_autoscroll(autoscroll, cx);
15562 }
15563 cx.notify();
15564 }
15565
15566 pub fn row_for_block(
15567 &self,
15568 block_id: CustomBlockId,
15569 cx: &mut Context<Self>,
15570 ) -> Option<DisplayRow> {
15571 self.display_map
15572 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15573 }
15574
15575 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15576 self.focused_block = Some(focused_block);
15577 }
15578
15579 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15580 self.focused_block.take()
15581 }
15582
15583 pub fn insert_creases(
15584 &mut self,
15585 creases: impl IntoIterator<Item = Crease<Anchor>>,
15586 cx: &mut Context<Self>,
15587 ) -> Vec<CreaseId> {
15588 self.display_map
15589 .update(cx, |map, cx| map.insert_creases(creases, cx))
15590 }
15591
15592 pub fn remove_creases(
15593 &mut self,
15594 ids: impl IntoIterator<Item = CreaseId>,
15595 cx: &mut Context<Self>,
15596 ) {
15597 self.display_map
15598 .update(cx, |map, cx| map.remove_creases(ids, cx));
15599 }
15600
15601 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15602 self.display_map
15603 .update(cx, |map, cx| map.snapshot(cx))
15604 .longest_row()
15605 }
15606
15607 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15608 self.display_map
15609 .update(cx, |map, cx| map.snapshot(cx))
15610 .max_point()
15611 }
15612
15613 pub fn text(&self, cx: &App) -> String {
15614 self.buffer.read(cx).read(cx).text()
15615 }
15616
15617 pub fn is_empty(&self, cx: &App) -> bool {
15618 self.buffer.read(cx).read(cx).is_empty()
15619 }
15620
15621 pub fn text_option(&self, cx: &App) -> Option<String> {
15622 let text = self.text(cx);
15623 let text = text.trim();
15624
15625 if text.is_empty() {
15626 return None;
15627 }
15628
15629 Some(text.to_string())
15630 }
15631
15632 pub fn set_text(
15633 &mut self,
15634 text: impl Into<Arc<str>>,
15635 window: &mut Window,
15636 cx: &mut Context<Self>,
15637 ) {
15638 self.transact(window, cx, |this, _, cx| {
15639 this.buffer
15640 .read(cx)
15641 .as_singleton()
15642 .expect("you can only call set_text on editors for singleton buffers")
15643 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15644 });
15645 }
15646
15647 pub fn display_text(&self, cx: &mut App) -> String {
15648 self.display_map
15649 .update(cx, |map, cx| map.snapshot(cx))
15650 .text()
15651 }
15652
15653 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15654 let mut wrap_guides = smallvec::smallvec![];
15655
15656 if self.show_wrap_guides == Some(false) {
15657 return wrap_guides;
15658 }
15659
15660 let settings = self.buffer.read(cx).language_settings(cx);
15661 if settings.show_wrap_guides {
15662 match self.soft_wrap_mode(cx) {
15663 SoftWrap::Column(soft_wrap) => {
15664 wrap_guides.push((soft_wrap as usize, true));
15665 }
15666 SoftWrap::Bounded(soft_wrap) => {
15667 wrap_guides.push((soft_wrap as usize, true));
15668 }
15669 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15670 }
15671 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15672 }
15673
15674 wrap_guides
15675 }
15676
15677 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15678 let settings = self.buffer.read(cx).language_settings(cx);
15679 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15680 match mode {
15681 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15682 SoftWrap::None
15683 }
15684 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15685 language_settings::SoftWrap::PreferredLineLength => {
15686 SoftWrap::Column(settings.preferred_line_length)
15687 }
15688 language_settings::SoftWrap::Bounded => {
15689 SoftWrap::Bounded(settings.preferred_line_length)
15690 }
15691 }
15692 }
15693
15694 pub fn set_soft_wrap_mode(
15695 &mut self,
15696 mode: language_settings::SoftWrap,
15697
15698 cx: &mut Context<Self>,
15699 ) {
15700 self.soft_wrap_mode_override = Some(mode);
15701 cx.notify();
15702 }
15703
15704 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15705 self.hard_wrap = hard_wrap;
15706 cx.notify();
15707 }
15708
15709 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15710 self.text_style_refinement = Some(style);
15711 }
15712
15713 /// called by the Element so we know what style we were most recently rendered with.
15714 pub(crate) fn set_style(
15715 &mut self,
15716 style: EditorStyle,
15717 window: &mut Window,
15718 cx: &mut Context<Self>,
15719 ) {
15720 let rem_size = window.rem_size();
15721 self.display_map.update(cx, |map, cx| {
15722 map.set_font(
15723 style.text.font(),
15724 style.text.font_size.to_pixels(rem_size),
15725 cx,
15726 )
15727 });
15728 self.style = Some(style);
15729 }
15730
15731 pub fn style(&self) -> Option<&EditorStyle> {
15732 self.style.as_ref()
15733 }
15734
15735 // Called by the element. This method is not designed to be called outside of the editor
15736 // element's layout code because it does not notify when rewrapping is computed synchronously.
15737 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15738 self.display_map
15739 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15740 }
15741
15742 pub fn set_soft_wrap(&mut self) {
15743 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15744 }
15745
15746 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15747 if self.soft_wrap_mode_override.is_some() {
15748 self.soft_wrap_mode_override.take();
15749 } else {
15750 let soft_wrap = match self.soft_wrap_mode(cx) {
15751 SoftWrap::GitDiff => return,
15752 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15753 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15754 language_settings::SoftWrap::None
15755 }
15756 };
15757 self.soft_wrap_mode_override = Some(soft_wrap);
15758 }
15759 cx.notify();
15760 }
15761
15762 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15763 let Some(workspace) = self.workspace() else {
15764 return;
15765 };
15766 let fs = workspace.read(cx).app_state().fs.clone();
15767 let current_show = TabBarSettings::get_global(cx).show;
15768 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15769 setting.show = Some(!current_show);
15770 });
15771 }
15772
15773 pub fn toggle_indent_guides(
15774 &mut self,
15775 _: &ToggleIndentGuides,
15776 _: &mut Window,
15777 cx: &mut Context<Self>,
15778 ) {
15779 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15780 self.buffer
15781 .read(cx)
15782 .language_settings(cx)
15783 .indent_guides
15784 .enabled
15785 });
15786 self.show_indent_guides = Some(!currently_enabled);
15787 cx.notify();
15788 }
15789
15790 fn should_show_indent_guides(&self) -> Option<bool> {
15791 self.show_indent_guides
15792 }
15793
15794 pub fn toggle_line_numbers(
15795 &mut self,
15796 _: &ToggleLineNumbers,
15797 _: &mut Window,
15798 cx: &mut Context<Self>,
15799 ) {
15800 let mut editor_settings = EditorSettings::get_global(cx).clone();
15801 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15802 EditorSettings::override_global(editor_settings, cx);
15803 }
15804
15805 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15806 if let Some(show_line_numbers) = self.show_line_numbers {
15807 return show_line_numbers;
15808 }
15809 EditorSettings::get_global(cx).gutter.line_numbers
15810 }
15811
15812 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15813 self.use_relative_line_numbers
15814 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15815 }
15816
15817 pub fn toggle_relative_line_numbers(
15818 &mut self,
15819 _: &ToggleRelativeLineNumbers,
15820 _: &mut Window,
15821 cx: &mut Context<Self>,
15822 ) {
15823 let is_relative = self.should_use_relative_line_numbers(cx);
15824 self.set_relative_line_number(Some(!is_relative), cx)
15825 }
15826
15827 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15828 self.use_relative_line_numbers = is_relative;
15829 cx.notify();
15830 }
15831
15832 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15833 self.show_gutter = show_gutter;
15834 cx.notify();
15835 }
15836
15837 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15838 self.show_scrollbars = show_scrollbars;
15839 cx.notify();
15840 }
15841
15842 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15843 self.show_line_numbers = Some(show_line_numbers);
15844 cx.notify();
15845 }
15846
15847 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15848 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15849 cx.notify();
15850 }
15851
15852 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15853 self.show_code_actions = Some(show_code_actions);
15854 cx.notify();
15855 }
15856
15857 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15858 self.show_runnables = Some(show_runnables);
15859 cx.notify();
15860 }
15861
15862 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15863 self.show_breakpoints = Some(show_breakpoints);
15864 cx.notify();
15865 }
15866
15867 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15868 if self.display_map.read(cx).masked != masked {
15869 self.display_map.update(cx, |map, _| map.masked = masked);
15870 }
15871 cx.notify()
15872 }
15873
15874 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15875 self.show_wrap_guides = Some(show_wrap_guides);
15876 cx.notify();
15877 }
15878
15879 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15880 self.show_indent_guides = Some(show_indent_guides);
15881 cx.notify();
15882 }
15883
15884 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15885 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15886 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15887 if let Some(dir) = file.abs_path(cx).parent() {
15888 return Some(dir.to_owned());
15889 }
15890 }
15891
15892 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15893 return Some(project_path.path.to_path_buf());
15894 }
15895 }
15896
15897 None
15898 }
15899
15900 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15901 self.active_excerpt(cx)?
15902 .1
15903 .read(cx)
15904 .file()
15905 .and_then(|f| f.as_local())
15906 }
15907
15908 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15909 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15910 let buffer = buffer.read(cx);
15911 if let Some(project_path) = buffer.project_path(cx) {
15912 let project = self.project.as_ref()?.read(cx);
15913 project.absolute_path(&project_path, cx)
15914 } else {
15915 buffer
15916 .file()
15917 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15918 }
15919 })
15920 }
15921
15922 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15923 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15924 let project_path = buffer.read(cx).project_path(cx)?;
15925 let project = self.project.as_ref()?.read(cx);
15926 let entry = project.entry_for_path(&project_path, cx)?;
15927 let path = entry.path.to_path_buf();
15928 Some(path)
15929 })
15930 }
15931
15932 pub fn reveal_in_finder(
15933 &mut self,
15934 _: &RevealInFileManager,
15935 _window: &mut Window,
15936 cx: &mut Context<Self>,
15937 ) {
15938 if let Some(target) = self.target_file(cx) {
15939 cx.reveal_path(&target.abs_path(cx));
15940 }
15941 }
15942
15943 pub fn copy_path(
15944 &mut self,
15945 _: &zed_actions::workspace::CopyPath,
15946 _window: &mut Window,
15947 cx: &mut Context<Self>,
15948 ) {
15949 if let Some(path) = self.target_file_abs_path(cx) {
15950 if let Some(path) = path.to_str() {
15951 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15952 }
15953 }
15954 }
15955
15956 pub fn copy_relative_path(
15957 &mut self,
15958 _: &zed_actions::workspace::CopyRelativePath,
15959 _window: &mut Window,
15960 cx: &mut Context<Self>,
15961 ) {
15962 if let Some(path) = self.target_file_path(cx) {
15963 if let Some(path) = path.to_str() {
15964 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15965 }
15966 }
15967 }
15968
15969 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
15970 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15971 buffer.read(cx).project_path(cx)
15972 } else {
15973 None
15974 }
15975 }
15976
15977 // Returns true if the editor handled a go-to-line request
15978 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
15979 maybe!({
15980 let breakpoint_store = self.breakpoint_store.as_ref()?;
15981
15982 let Some((_, _, active_position)) =
15983 breakpoint_store.read(cx).active_position().cloned()
15984 else {
15985 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15986 return None;
15987 };
15988
15989 let snapshot = self
15990 .project
15991 .as_ref()?
15992 .read(cx)
15993 .buffer_for_id(active_position.buffer_id?, cx)?
15994 .read(cx)
15995 .snapshot();
15996
15997 let mut handled = false;
15998 for (id, ExcerptRange { context, .. }) in self
15999 .buffer
16000 .read(cx)
16001 .excerpts_for_buffer(active_position.buffer_id?, cx)
16002 {
16003 if context.start.cmp(&active_position, &snapshot).is_ge()
16004 || context.end.cmp(&active_position, &snapshot).is_lt()
16005 {
16006 continue;
16007 }
16008 let snapshot = self.buffer.read(cx).snapshot(cx);
16009 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
16010
16011 handled = true;
16012 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16013 self.go_to_line::<DebugCurrentRowHighlight>(
16014 multibuffer_anchor,
16015 Some(cx.theme().colors().editor_debugger_active_line_background),
16016 window,
16017 cx,
16018 );
16019
16020 cx.notify();
16021 }
16022 handled.then_some(())
16023 })
16024 .is_some()
16025 }
16026
16027 pub fn copy_file_name_without_extension(
16028 &mut self,
16029 _: &CopyFileNameWithoutExtension,
16030 _: &mut Window,
16031 cx: &mut Context<Self>,
16032 ) {
16033 if let Some(file) = self.target_file(cx) {
16034 if let Some(file_stem) = file.path().file_stem() {
16035 if let Some(name) = file_stem.to_str() {
16036 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16037 }
16038 }
16039 }
16040 }
16041
16042 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16043 if let Some(file) = self.target_file(cx) {
16044 if let Some(file_name) = file.path().file_name() {
16045 if let Some(name) = file_name.to_str() {
16046 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16047 }
16048 }
16049 }
16050 }
16051
16052 pub fn toggle_git_blame(
16053 &mut self,
16054 _: &::git::Blame,
16055 window: &mut Window,
16056 cx: &mut Context<Self>,
16057 ) {
16058 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16059
16060 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16061 self.start_git_blame(true, window, cx);
16062 }
16063
16064 cx.notify();
16065 }
16066
16067 pub fn toggle_git_blame_inline(
16068 &mut self,
16069 _: &ToggleGitBlameInline,
16070 window: &mut Window,
16071 cx: &mut Context<Self>,
16072 ) {
16073 self.toggle_git_blame_inline_internal(true, window, cx);
16074 cx.notify();
16075 }
16076
16077 pub fn open_git_blame_commit(
16078 &mut self,
16079 _: &OpenGitBlameCommit,
16080 window: &mut Window,
16081 cx: &mut Context<Self>,
16082 ) {
16083 self.open_git_blame_commit_internal(window, cx);
16084 }
16085
16086 fn open_git_blame_commit_internal(
16087 &mut self,
16088 window: &mut Window,
16089 cx: &mut Context<Self>,
16090 ) -> Option<()> {
16091 let blame = self.blame.as_ref()?;
16092 let snapshot = self.snapshot(window, cx);
16093 let cursor = self.selections.newest::<Point>(cx).head();
16094 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16095 let blame_entry = blame
16096 .update(cx, |blame, cx| {
16097 blame
16098 .blame_for_rows(
16099 &[RowInfo {
16100 buffer_id: Some(buffer.remote_id()),
16101 buffer_row: Some(point.row),
16102 ..Default::default()
16103 }],
16104 cx,
16105 )
16106 .next()
16107 })
16108 .flatten()?;
16109 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16110 let repo = blame.read(cx).repository(cx)?;
16111 let workspace = self.workspace()?.downgrade();
16112 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16113 None
16114 }
16115
16116 pub fn git_blame_inline_enabled(&self) -> bool {
16117 self.git_blame_inline_enabled
16118 }
16119
16120 pub fn toggle_selection_menu(
16121 &mut self,
16122 _: &ToggleSelectionMenu,
16123 _: &mut Window,
16124 cx: &mut Context<Self>,
16125 ) {
16126 self.show_selection_menu = self
16127 .show_selection_menu
16128 .map(|show_selections_menu| !show_selections_menu)
16129 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16130
16131 cx.notify();
16132 }
16133
16134 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16135 self.show_selection_menu
16136 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16137 }
16138
16139 fn start_git_blame(
16140 &mut self,
16141 user_triggered: bool,
16142 window: &mut Window,
16143 cx: &mut Context<Self>,
16144 ) {
16145 if let Some(project) = self.project.as_ref() {
16146 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16147 return;
16148 };
16149
16150 if buffer.read(cx).file().is_none() {
16151 return;
16152 }
16153
16154 let focused = self.focus_handle(cx).contains_focused(window, cx);
16155
16156 let project = project.clone();
16157 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16158 self.blame_subscription =
16159 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16160 self.blame = Some(blame);
16161 }
16162 }
16163
16164 fn toggle_git_blame_inline_internal(
16165 &mut self,
16166 user_triggered: bool,
16167 window: &mut Window,
16168 cx: &mut Context<Self>,
16169 ) {
16170 if self.git_blame_inline_enabled {
16171 self.git_blame_inline_enabled = false;
16172 self.show_git_blame_inline = false;
16173 self.show_git_blame_inline_delay_task.take();
16174 } else {
16175 self.git_blame_inline_enabled = true;
16176 self.start_git_blame_inline(user_triggered, window, cx);
16177 }
16178
16179 cx.notify();
16180 }
16181
16182 fn start_git_blame_inline(
16183 &mut self,
16184 user_triggered: bool,
16185 window: &mut Window,
16186 cx: &mut Context<Self>,
16187 ) {
16188 self.start_git_blame(user_triggered, window, cx);
16189
16190 if ProjectSettings::get_global(cx)
16191 .git
16192 .inline_blame_delay()
16193 .is_some()
16194 {
16195 self.start_inline_blame_timer(window, cx);
16196 } else {
16197 self.show_git_blame_inline = true
16198 }
16199 }
16200
16201 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16202 self.blame.as_ref()
16203 }
16204
16205 pub fn show_git_blame_gutter(&self) -> bool {
16206 self.show_git_blame_gutter
16207 }
16208
16209 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16210 self.show_git_blame_gutter && self.has_blame_entries(cx)
16211 }
16212
16213 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16214 self.show_git_blame_inline
16215 && (self.focus_handle.is_focused(window)
16216 || self
16217 .git_blame_inline_tooltip
16218 .as_ref()
16219 .and_then(|t| t.upgrade())
16220 .is_some())
16221 && !self.newest_selection_head_on_empty_line(cx)
16222 && self.has_blame_entries(cx)
16223 }
16224
16225 fn has_blame_entries(&self, cx: &App) -> bool {
16226 self.blame()
16227 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16228 }
16229
16230 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16231 let cursor_anchor = self.selections.newest_anchor().head();
16232
16233 let snapshot = self.buffer.read(cx).snapshot(cx);
16234 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16235
16236 snapshot.line_len(buffer_row) == 0
16237 }
16238
16239 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16240 let buffer_and_selection = maybe!({
16241 let selection = self.selections.newest::<Point>(cx);
16242 let selection_range = selection.range();
16243
16244 let multi_buffer = self.buffer().read(cx);
16245 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16246 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16247
16248 let (buffer, range, _) = if selection.reversed {
16249 buffer_ranges.first()
16250 } else {
16251 buffer_ranges.last()
16252 }?;
16253
16254 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16255 ..text::ToPoint::to_point(&range.end, &buffer).row;
16256 Some((
16257 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16258 selection,
16259 ))
16260 });
16261
16262 let Some((buffer, selection)) = buffer_and_selection else {
16263 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16264 };
16265
16266 let Some(project) = self.project.as_ref() else {
16267 return Task::ready(Err(anyhow!("editor does not have project")));
16268 };
16269
16270 project.update(cx, |project, cx| {
16271 project.get_permalink_to_line(&buffer, selection, cx)
16272 })
16273 }
16274
16275 pub fn copy_permalink_to_line(
16276 &mut self,
16277 _: &CopyPermalinkToLine,
16278 window: &mut Window,
16279 cx: &mut Context<Self>,
16280 ) {
16281 let permalink_task = self.get_permalink_to_line(cx);
16282 let workspace = self.workspace();
16283
16284 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16285 Ok(permalink) => {
16286 cx.update(|_, cx| {
16287 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16288 })
16289 .ok();
16290 }
16291 Err(err) => {
16292 let message = format!("Failed to copy permalink: {err}");
16293
16294 Err::<(), anyhow::Error>(err).log_err();
16295
16296 if let Some(workspace) = workspace {
16297 workspace
16298 .update_in(cx, |workspace, _, cx| {
16299 struct CopyPermalinkToLine;
16300
16301 workspace.show_toast(
16302 Toast::new(
16303 NotificationId::unique::<CopyPermalinkToLine>(),
16304 message,
16305 ),
16306 cx,
16307 )
16308 })
16309 .ok();
16310 }
16311 }
16312 })
16313 .detach();
16314 }
16315
16316 pub fn copy_file_location(
16317 &mut self,
16318 _: &CopyFileLocation,
16319 _: &mut Window,
16320 cx: &mut Context<Self>,
16321 ) {
16322 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16323 if let Some(file) = self.target_file(cx) {
16324 if let Some(path) = file.path().to_str() {
16325 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16326 }
16327 }
16328 }
16329
16330 pub fn open_permalink_to_line(
16331 &mut self,
16332 _: &OpenPermalinkToLine,
16333 window: &mut Window,
16334 cx: &mut Context<Self>,
16335 ) {
16336 let permalink_task = self.get_permalink_to_line(cx);
16337 let workspace = self.workspace();
16338
16339 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16340 Ok(permalink) => {
16341 cx.update(|_, cx| {
16342 cx.open_url(permalink.as_ref());
16343 })
16344 .ok();
16345 }
16346 Err(err) => {
16347 let message = format!("Failed to open permalink: {err}");
16348
16349 Err::<(), anyhow::Error>(err).log_err();
16350
16351 if let Some(workspace) = workspace {
16352 workspace
16353 .update(cx, |workspace, cx| {
16354 struct OpenPermalinkToLine;
16355
16356 workspace.show_toast(
16357 Toast::new(
16358 NotificationId::unique::<OpenPermalinkToLine>(),
16359 message,
16360 ),
16361 cx,
16362 )
16363 })
16364 .ok();
16365 }
16366 }
16367 })
16368 .detach();
16369 }
16370
16371 pub fn insert_uuid_v4(
16372 &mut self,
16373 _: &InsertUuidV4,
16374 window: &mut Window,
16375 cx: &mut Context<Self>,
16376 ) {
16377 self.insert_uuid(UuidVersion::V4, window, cx);
16378 }
16379
16380 pub fn insert_uuid_v7(
16381 &mut self,
16382 _: &InsertUuidV7,
16383 window: &mut Window,
16384 cx: &mut Context<Self>,
16385 ) {
16386 self.insert_uuid(UuidVersion::V7, window, cx);
16387 }
16388
16389 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16390 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16391 self.transact(window, cx, |this, window, cx| {
16392 let edits = this
16393 .selections
16394 .all::<Point>(cx)
16395 .into_iter()
16396 .map(|selection| {
16397 let uuid = match version {
16398 UuidVersion::V4 => uuid::Uuid::new_v4(),
16399 UuidVersion::V7 => uuid::Uuid::now_v7(),
16400 };
16401
16402 (selection.range(), uuid.to_string())
16403 });
16404 this.edit(edits, cx);
16405 this.refresh_inline_completion(true, false, window, cx);
16406 });
16407 }
16408
16409 pub fn open_selections_in_multibuffer(
16410 &mut self,
16411 _: &OpenSelectionsInMultibuffer,
16412 window: &mut Window,
16413 cx: &mut Context<Self>,
16414 ) {
16415 let multibuffer = self.buffer.read(cx);
16416
16417 let Some(buffer) = multibuffer.as_singleton() else {
16418 return;
16419 };
16420
16421 let Some(workspace) = self.workspace() else {
16422 return;
16423 };
16424
16425 let locations = self
16426 .selections
16427 .disjoint_anchors()
16428 .iter()
16429 .map(|range| Location {
16430 buffer: buffer.clone(),
16431 range: range.start.text_anchor..range.end.text_anchor,
16432 })
16433 .collect::<Vec<_>>();
16434
16435 let title = multibuffer.title(cx).to_string();
16436
16437 cx.spawn_in(window, async move |_, cx| {
16438 workspace.update_in(cx, |workspace, window, cx| {
16439 Self::open_locations_in_multibuffer(
16440 workspace,
16441 locations,
16442 format!("Selections for '{title}'"),
16443 false,
16444 MultibufferSelectionMode::All,
16445 window,
16446 cx,
16447 );
16448 })
16449 })
16450 .detach();
16451 }
16452
16453 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16454 /// last highlight added will be used.
16455 ///
16456 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16457 pub fn highlight_rows<T: 'static>(
16458 &mut self,
16459 range: Range<Anchor>,
16460 color: Hsla,
16461 should_autoscroll: bool,
16462 cx: &mut Context<Self>,
16463 ) {
16464 let snapshot = self.buffer().read(cx).snapshot(cx);
16465 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16466 let ix = row_highlights.binary_search_by(|highlight| {
16467 Ordering::Equal
16468 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16469 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16470 });
16471
16472 if let Err(mut ix) = ix {
16473 let index = post_inc(&mut self.highlight_order);
16474
16475 // If this range intersects with the preceding highlight, then merge it with
16476 // the preceding highlight. Otherwise insert a new highlight.
16477 let mut merged = false;
16478 if ix > 0 {
16479 let prev_highlight = &mut row_highlights[ix - 1];
16480 if prev_highlight
16481 .range
16482 .end
16483 .cmp(&range.start, &snapshot)
16484 .is_ge()
16485 {
16486 ix -= 1;
16487 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16488 prev_highlight.range.end = range.end;
16489 }
16490 merged = true;
16491 prev_highlight.index = index;
16492 prev_highlight.color = color;
16493 prev_highlight.should_autoscroll = should_autoscroll;
16494 }
16495 }
16496
16497 if !merged {
16498 row_highlights.insert(
16499 ix,
16500 RowHighlight {
16501 range: range.clone(),
16502 index,
16503 color,
16504 should_autoscroll,
16505 },
16506 );
16507 }
16508
16509 // If any of the following highlights intersect with this one, merge them.
16510 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16511 let highlight = &row_highlights[ix];
16512 if next_highlight
16513 .range
16514 .start
16515 .cmp(&highlight.range.end, &snapshot)
16516 .is_le()
16517 {
16518 if next_highlight
16519 .range
16520 .end
16521 .cmp(&highlight.range.end, &snapshot)
16522 .is_gt()
16523 {
16524 row_highlights[ix].range.end = next_highlight.range.end;
16525 }
16526 row_highlights.remove(ix + 1);
16527 } else {
16528 break;
16529 }
16530 }
16531 }
16532 }
16533
16534 /// Remove any highlighted row ranges of the given type that intersect the
16535 /// given ranges.
16536 pub fn remove_highlighted_rows<T: 'static>(
16537 &mut self,
16538 ranges_to_remove: Vec<Range<Anchor>>,
16539 cx: &mut Context<Self>,
16540 ) {
16541 let snapshot = self.buffer().read(cx).snapshot(cx);
16542 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16543 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16544 row_highlights.retain(|highlight| {
16545 while let Some(range_to_remove) = ranges_to_remove.peek() {
16546 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16547 Ordering::Less | Ordering::Equal => {
16548 ranges_to_remove.next();
16549 }
16550 Ordering::Greater => {
16551 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16552 Ordering::Less | Ordering::Equal => {
16553 return false;
16554 }
16555 Ordering::Greater => break,
16556 }
16557 }
16558 }
16559 }
16560
16561 true
16562 })
16563 }
16564
16565 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16566 pub fn clear_row_highlights<T: 'static>(&mut self) {
16567 self.highlighted_rows.remove(&TypeId::of::<T>());
16568 }
16569
16570 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16571 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16572 self.highlighted_rows
16573 .get(&TypeId::of::<T>())
16574 .map_or(&[] as &[_], |vec| vec.as_slice())
16575 .iter()
16576 .map(|highlight| (highlight.range.clone(), highlight.color))
16577 }
16578
16579 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16580 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16581 /// Allows to ignore certain kinds of highlights.
16582 pub fn highlighted_display_rows(
16583 &self,
16584 window: &mut Window,
16585 cx: &mut App,
16586 ) -> BTreeMap<DisplayRow, LineHighlight> {
16587 let snapshot = self.snapshot(window, cx);
16588 let mut used_highlight_orders = HashMap::default();
16589 self.highlighted_rows
16590 .iter()
16591 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16592 .fold(
16593 BTreeMap::<DisplayRow, LineHighlight>::new(),
16594 |mut unique_rows, highlight| {
16595 let start = highlight.range.start.to_display_point(&snapshot);
16596 let end = highlight.range.end.to_display_point(&snapshot);
16597 let start_row = start.row().0;
16598 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16599 && end.column() == 0
16600 {
16601 end.row().0.saturating_sub(1)
16602 } else {
16603 end.row().0
16604 };
16605 for row in start_row..=end_row {
16606 let used_index =
16607 used_highlight_orders.entry(row).or_insert(highlight.index);
16608 if highlight.index >= *used_index {
16609 *used_index = highlight.index;
16610 unique_rows.insert(DisplayRow(row), highlight.color.into());
16611 }
16612 }
16613 unique_rows
16614 },
16615 )
16616 }
16617
16618 pub fn highlighted_display_row_for_autoscroll(
16619 &self,
16620 snapshot: &DisplaySnapshot,
16621 ) -> Option<DisplayRow> {
16622 self.highlighted_rows
16623 .values()
16624 .flat_map(|highlighted_rows| highlighted_rows.iter())
16625 .filter_map(|highlight| {
16626 if highlight.should_autoscroll {
16627 Some(highlight.range.start.to_display_point(snapshot).row())
16628 } else {
16629 None
16630 }
16631 })
16632 .min()
16633 }
16634
16635 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16636 self.highlight_background::<SearchWithinRange>(
16637 ranges,
16638 |colors| colors.editor_document_highlight_read_background,
16639 cx,
16640 )
16641 }
16642
16643 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16644 self.breadcrumb_header = Some(new_header);
16645 }
16646
16647 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16648 self.clear_background_highlights::<SearchWithinRange>(cx);
16649 }
16650
16651 pub fn highlight_background<T: 'static>(
16652 &mut self,
16653 ranges: &[Range<Anchor>],
16654 color_fetcher: fn(&ThemeColors) -> Hsla,
16655 cx: &mut Context<Self>,
16656 ) {
16657 self.background_highlights
16658 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16659 self.scrollbar_marker_state.dirty = true;
16660 cx.notify();
16661 }
16662
16663 pub fn clear_background_highlights<T: 'static>(
16664 &mut self,
16665 cx: &mut Context<Self>,
16666 ) -> Option<BackgroundHighlight> {
16667 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16668 if !text_highlights.1.is_empty() {
16669 self.scrollbar_marker_state.dirty = true;
16670 cx.notify();
16671 }
16672 Some(text_highlights)
16673 }
16674
16675 pub fn highlight_gutter<T: 'static>(
16676 &mut self,
16677 ranges: &[Range<Anchor>],
16678 color_fetcher: fn(&App) -> Hsla,
16679 cx: &mut Context<Self>,
16680 ) {
16681 self.gutter_highlights
16682 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16683 cx.notify();
16684 }
16685
16686 pub fn clear_gutter_highlights<T: 'static>(
16687 &mut self,
16688 cx: &mut Context<Self>,
16689 ) -> Option<GutterHighlight> {
16690 cx.notify();
16691 self.gutter_highlights.remove(&TypeId::of::<T>())
16692 }
16693
16694 #[cfg(feature = "test-support")]
16695 pub fn all_text_background_highlights(
16696 &self,
16697 window: &mut Window,
16698 cx: &mut Context<Self>,
16699 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16700 let snapshot = self.snapshot(window, cx);
16701 let buffer = &snapshot.buffer_snapshot;
16702 let start = buffer.anchor_before(0);
16703 let end = buffer.anchor_after(buffer.len());
16704 let theme = cx.theme().colors();
16705 self.background_highlights_in_range(start..end, &snapshot, theme)
16706 }
16707
16708 #[cfg(feature = "test-support")]
16709 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16710 let snapshot = self.buffer().read(cx).snapshot(cx);
16711
16712 let highlights = self
16713 .background_highlights
16714 .get(&TypeId::of::<items::BufferSearchHighlights>());
16715
16716 if let Some((_color, ranges)) = highlights {
16717 ranges
16718 .iter()
16719 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16720 .collect_vec()
16721 } else {
16722 vec![]
16723 }
16724 }
16725
16726 fn document_highlights_for_position<'a>(
16727 &'a self,
16728 position: Anchor,
16729 buffer: &'a MultiBufferSnapshot,
16730 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16731 let read_highlights = self
16732 .background_highlights
16733 .get(&TypeId::of::<DocumentHighlightRead>())
16734 .map(|h| &h.1);
16735 let write_highlights = self
16736 .background_highlights
16737 .get(&TypeId::of::<DocumentHighlightWrite>())
16738 .map(|h| &h.1);
16739 let left_position = position.bias_left(buffer);
16740 let right_position = position.bias_right(buffer);
16741 read_highlights
16742 .into_iter()
16743 .chain(write_highlights)
16744 .flat_map(move |ranges| {
16745 let start_ix = match ranges.binary_search_by(|probe| {
16746 let cmp = probe.end.cmp(&left_position, buffer);
16747 if cmp.is_ge() {
16748 Ordering::Greater
16749 } else {
16750 Ordering::Less
16751 }
16752 }) {
16753 Ok(i) | Err(i) => i,
16754 };
16755
16756 ranges[start_ix..]
16757 .iter()
16758 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16759 })
16760 }
16761
16762 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16763 self.background_highlights
16764 .get(&TypeId::of::<T>())
16765 .map_or(false, |(_, highlights)| !highlights.is_empty())
16766 }
16767
16768 pub fn background_highlights_in_range(
16769 &self,
16770 search_range: Range<Anchor>,
16771 display_snapshot: &DisplaySnapshot,
16772 theme: &ThemeColors,
16773 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16774 let mut results = Vec::new();
16775 for (color_fetcher, ranges) in self.background_highlights.values() {
16776 let color = color_fetcher(theme);
16777 let start_ix = match ranges.binary_search_by(|probe| {
16778 let cmp = probe
16779 .end
16780 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16781 if cmp.is_gt() {
16782 Ordering::Greater
16783 } else {
16784 Ordering::Less
16785 }
16786 }) {
16787 Ok(i) | Err(i) => i,
16788 };
16789 for range in &ranges[start_ix..] {
16790 if range
16791 .start
16792 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16793 .is_ge()
16794 {
16795 break;
16796 }
16797
16798 let start = range.start.to_display_point(display_snapshot);
16799 let end = range.end.to_display_point(display_snapshot);
16800 results.push((start..end, color))
16801 }
16802 }
16803 results
16804 }
16805
16806 pub fn background_highlight_row_ranges<T: 'static>(
16807 &self,
16808 search_range: Range<Anchor>,
16809 display_snapshot: &DisplaySnapshot,
16810 count: usize,
16811 ) -> Vec<RangeInclusive<DisplayPoint>> {
16812 let mut results = Vec::new();
16813 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16814 return vec![];
16815 };
16816
16817 let start_ix = match ranges.binary_search_by(|probe| {
16818 let cmp = probe
16819 .end
16820 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16821 if cmp.is_gt() {
16822 Ordering::Greater
16823 } else {
16824 Ordering::Less
16825 }
16826 }) {
16827 Ok(i) | Err(i) => i,
16828 };
16829 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16830 if let (Some(start_display), Some(end_display)) = (start, end) {
16831 results.push(
16832 start_display.to_display_point(display_snapshot)
16833 ..=end_display.to_display_point(display_snapshot),
16834 );
16835 }
16836 };
16837 let mut start_row: Option<Point> = None;
16838 let mut end_row: Option<Point> = None;
16839 if ranges.len() > count {
16840 return Vec::new();
16841 }
16842 for range in &ranges[start_ix..] {
16843 if range
16844 .start
16845 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16846 .is_ge()
16847 {
16848 break;
16849 }
16850 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16851 if let Some(current_row) = &end_row {
16852 if end.row == current_row.row {
16853 continue;
16854 }
16855 }
16856 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16857 if start_row.is_none() {
16858 assert_eq!(end_row, None);
16859 start_row = Some(start);
16860 end_row = Some(end);
16861 continue;
16862 }
16863 if let Some(current_end) = end_row.as_mut() {
16864 if start.row > current_end.row + 1 {
16865 push_region(start_row, end_row);
16866 start_row = Some(start);
16867 end_row = Some(end);
16868 } else {
16869 // Merge two hunks.
16870 *current_end = end;
16871 }
16872 } else {
16873 unreachable!();
16874 }
16875 }
16876 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16877 push_region(start_row, end_row);
16878 results
16879 }
16880
16881 pub fn gutter_highlights_in_range(
16882 &self,
16883 search_range: Range<Anchor>,
16884 display_snapshot: &DisplaySnapshot,
16885 cx: &App,
16886 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16887 let mut results = Vec::new();
16888 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16889 let color = color_fetcher(cx);
16890 let start_ix = match ranges.binary_search_by(|probe| {
16891 let cmp = probe
16892 .end
16893 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16894 if cmp.is_gt() {
16895 Ordering::Greater
16896 } else {
16897 Ordering::Less
16898 }
16899 }) {
16900 Ok(i) | Err(i) => i,
16901 };
16902 for range in &ranges[start_ix..] {
16903 if range
16904 .start
16905 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16906 .is_ge()
16907 {
16908 break;
16909 }
16910
16911 let start = range.start.to_display_point(display_snapshot);
16912 let end = range.end.to_display_point(display_snapshot);
16913 results.push((start..end, color))
16914 }
16915 }
16916 results
16917 }
16918
16919 /// Get the text ranges corresponding to the redaction query
16920 pub fn redacted_ranges(
16921 &self,
16922 search_range: Range<Anchor>,
16923 display_snapshot: &DisplaySnapshot,
16924 cx: &App,
16925 ) -> Vec<Range<DisplayPoint>> {
16926 display_snapshot
16927 .buffer_snapshot
16928 .redacted_ranges(search_range, |file| {
16929 if let Some(file) = file {
16930 file.is_private()
16931 && EditorSettings::get(
16932 Some(SettingsLocation {
16933 worktree_id: file.worktree_id(cx),
16934 path: file.path().as_ref(),
16935 }),
16936 cx,
16937 )
16938 .redact_private_values
16939 } else {
16940 false
16941 }
16942 })
16943 .map(|range| {
16944 range.start.to_display_point(display_snapshot)
16945 ..range.end.to_display_point(display_snapshot)
16946 })
16947 .collect()
16948 }
16949
16950 pub fn highlight_text<T: 'static>(
16951 &mut self,
16952 ranges: Vec<Range<Anchor>>,
16953 style: HighlightStyle,
16954 cx: &mut Context<Self>,
16955 ) {
16956 self.display_map.update(cx, |map, _| {
16957 map.highlight_text(TypeId::of::<T>(), ranges, style)
16958 });
16959 cx.notify();
16960 }
16961
16962 pub(crate) fn highlight_inlays<T: 'static>(
16963 &mut self,
16964 highlights: Vec<InlayHighlight>,
16965 style: HighlightStyle,
16966 cx: &mut Context<Self>,
16967 ) {
16968 self.display_map.update(cx, |map, _| {
16969 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16970 });
16971 cx.notify();
16972 }
16973
16974 pub fn text_highlights<'a, T: 'static>(
16975 &'a self,
16976 cx: &'a App,
16977 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
16978 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
16979 }
16980
16981 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
16982 let cleared = self
16983 .display_map
16984 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
16985 if cleared {
16986 cx.notify();
16987 }
16988 }
16989
16990 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
16991 (self.read_only(cx) || self.blink_manager.read(cx).visible())
16992 && self.focus_handle.is_focused(window)
16993 }
16994
16995 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
16996 self.show_cursor_when_unfocused = is_enabled;
16997 cx.notify();
16998 }
16999
17000 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17001 cx.notify();
17002 }
17003
17004 fn on_buffer_event(
17005 &mut self,
17006 multibuffer: &Entity<MultiBuffer>,
17007 event: &multi_buffer::Event,
17008 window: &mut Window,
17009 cx: &mut Context<Self>,
17010 ) {
17011 match event {
17012 multi_buffer::Event::Edited {
17013 singleton_buffer_edited,
17014 edited_buffer: buffer_edited,
17015 } => {
17016 self.scrollbar_marker_state.dirty = true;
17017 self.active_indent_guides_state.dirty = true;
17018 self.refresh_active_diagnostics(cx);
17019 self.refresh_code_actions(window, cx);
17020 if self.has_active_inline_completion() {
17021 self.update_visible_inline_completion(window, cx);
17022 }
17023 if let Some(buffer) = buffer_edited {
17024 let buffer_id = buffer.read(cx).remote_id();
17025 if !self.registered_buffers.contains_key(&buffer_id) {
17026 if let Some(project) = self.project.as_ref() {
17027 project.update(cx, |project, cx| {
17028 self.registered_buffers.insert(
17029 buffer_id,
17030 project.register_buffer_with_language_servers(&buffer, cx),
17031 );
17032 })
17033 }
17034 }
17035 }
17036 cx.emit(EditorEvent::BufferEdited);
17037 cx.emit(SearchEvent::MatchesInvalidated);
17038 if *singleton_buffer_edited {
17039 if let Some(project) = &self.project {
17040 #[allow(clippy::mutable_key_type)]
17041 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17042 multibuffer
17043 .all_buffers()
17044 .into_iter()
17045 .filter_map(|buffer| {
17046 buffer.update(cx, |buffer, cx| {
17047 let language = buffer.language()?;
17048 let should_discard = project.update(cx, |project, cx| {
17049 project.is_local()
17050 && !project.has_language_servers_for(buffer, cx)
17051 });
17052 should_discard.not().then_some(language.clone())
17053 })
17054 })
17055 .collect::<HashSet<_>>()
17056 });
17057 if !languages_affected.is_empty() {
17058 self.refresh_inlay_hints(
17059 InlayHintRefreshReason::BufferEdited(languages_affected),
17060 cx,
17061 );
17062 }
17063 }
17064 }
17065
17066 let Some(project) = &self.project else { return };
17067 let (telemetry, is_via_ssh) = {
17068 let project = project.read(cx);
17069 let telemetry = project.client().telemetry().clone();
17070 let is_via_ssh = project.is_via_ssh();
17071 (telemetry, is_via_ssh)
17072 };
17073 refresh_linked_ranges(self, window, cx);
17074 telemetry.log_edit_event("editor", is_via_ssh);
17075 }
17076 multi_buffer::Event::ExcerptsAdded {
17077 buffer,
17078 predecessor,
17079 excerpts,
17080 } => {
17081 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17082 let buffer_id = buffer.read(cx).remote_id();
17083 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17084 if let Some(project) = &self.project {
17085 get_uncommitted_diff_for_buffer(
17086 project,
17087 [buffer.clone()],
17088 self.buffer.clone(),
17089 cx,
17090 )
17091 .detach();
17092 }
17093 }
17094 cx.emit(EditorEvent::ExcerptsAdded {
17095 buffer: buffer.clone(),
17096 predecessor: *predecessor,
17097 excerpts: excerpts.clone(),
17098 });
17099 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17100 }
17101 multi_buffer::Event::ExcerptsRemoved { ids } => {
17102 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17103 let buffer = self.buffer.read(cx);
17104 self.registered_buffers
17105 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17106 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17107 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
17108 }
17109 multi_buffer::Event::ExcerptsEdited {
17110 excerpt_ids,
17111 buffer_ids,
17112 } => {
17113 self.display_map.update(cx, |map, cx| {
17114 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17115 });
17116 cx.emit(EditorEvent::ExcerptsEdited {
17117 ids: excerpt_ids.clone(),
17118 })
17119 }
17120 multi_buffer::Event::ExcerptsExpanded { ids } => {
17121 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17122 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17123 }
17124 multi_buffer::Event::Reparsed(buffer_id) => {
17125 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17126 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17127
17128 cx.emit(EditorEvent::Reparsed(*buffer_id));
17129 }
17130 multi_buffer::Event::DiffHunksToggled => {
17131 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17132 }
17133 multi_buffer::Event::LanguageChanged(buffer_id) => {
17134 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17135 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17136 cx.emit(EditorEvent::Reparsed(*buffer_id));
17137 cx.notify();
17138 }
17139 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17140 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17141 multi_buffer::Event::FileHandleChanged
17142 | multi_buffer::Event::Reloaded
17143 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17144 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17145 multi_buffer::Event::DiagnosticsUpdated => {
17146 self.refresh_active_diagnostics(cx);
17147 self.refresh_inline_diagnostics(true, window, cx);
17148 self.scrollbar_marker_state.dirty = true;
17149 cx.notify();
17150 }
17151 _ => {}
17152 };
17153 }
17154
17155 fn on_display_map_changed(
17156 &mut self,
17157 _: Entity<DisplayMap>,
17158 _: &mut Window,
17159 cx: &mut Context<Self>,
17160 ) {
17161 cx.notify();
17162 }
17163
17164 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17165 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17166 self.update_edit_prediction_settings(cx);
17167 self.refresh_inline_completion(true, false, window, cx);
17168 self.refresh_inlay_hints(
17169 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17170 self.selections.newest_anchor().head(),
17171 &self.buffer.read(cx).snapshot(cx),
17172 cx,
17173 )),
17174 cx,
17175 );
17176
17177 let old_cursor_shape = self.cursor_shape;
17178
17179 {
17180 let editor_settings = EditorSettings::get_global(cx);
17181 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17182 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17183 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17184 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17185 }
17186
17187 if old_cursor_shape != self.cursor_shape {
17188 cx.emit(EditorEvent::CursorShapeChanged);
17189 }
17190
17191 let project_settings = ProjectSettings::get_global(cx);
17192 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17193
17194 if self.mode == EditorMode::Full {
17195 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17196 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17197 if self.show_inline_diagnostics != show_inline_diagnostics {
17198 self.show_inline_diagnostics = show_inline_diagnostics;
17199 self.refresh_inline_diagnostics(false, window, cx);
17200 }
17201
17202 if self.git_blame_inline_enabled != inline_blame_enabled {
17203 self.toggle_git_blame_inline_internal(false, window, cx);
17204 }
17205 }
17206
17207 cx.notify();
17208 }
17209
17210 pub fn set_searchable(&mut self, searchable: bool) {
17211 self.searchable = searchable;
17212 }
17213
17214 pub fn searchable(&self) -> bool {
17215 self.searchable
17216 }
17217
17218 fn open_proposed_changes_editor(
17219 &mut self,
17220 _: &OpenProposedChangesEditor,
17221 window: &mut Window,
17222 cx: &mut Context<Self>,
17223 ) {
17224 let Some(workspace) = self.workspace() else {
17225 cx.propagate();
17226 return;
17227 };
17228
17229 let selections = self.selections.all::<usize>(cx);
17230 let multi_buffer = self.buffer.read(cx);
17231 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17232 let mut new_selections_by_buffer = HashMap::default();
17233 for selection in selections {
17234 for (buffer, range, _) in
17235 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17236 {
17237 let mut range = range.to_point(buffer);
17238 range.start.column = 0;
17239 range.end.column = buffer.line_len(range.end.row);
17240 new_selections_by_buffer
17241 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17242 .or_insert(Vec::new())
17243 .push(range)
17244 }
17245 }
17246
17247 let proposed_changes_buffers = new_selections_by_buffer
17248 .into_iter()
17249 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17250 .collect::<Vec<_>>();
17251 let proposed_changes_editor = cx.new(|cx| {
17252 ProposedChangesEditor::new(
17253 "Proposed changes",
17254 proposed_changes_buffers,
17255 self.project.clone(),
17256 window,
17257 cx,
17258 )
17259 });
17260
17261 window.defer(cx, move |window, cx| {
17262 workspace.update(cx, |workspace, cx| {
17263 workspace.active_pane().update(cx, |pane, cx| {
17264 pane.add_item(
17265 Box::new(proposed_changes_editor),
17266 true,
17267 true,
17268 None,
17269 window,
17270 cx,
17271 );
17272 });
17273 });
17274 });
17275 }
17276
17277 pub fn open_excerpts_in_split(
17278 &mut self,
17279 _: &OpenExcerptsSplit,
17280 window: &mut Window,
17281 cx: &mut Context<Self>,
17282 ) {
17283 self.open_excerpts_common(None, true, window, cx)
17284 }
17285
17286 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17287 self.open_excerpts_common(None, false, window, cx)
17288 }
17289
17290 fn open_excerpts_common(
17291 &mut self,
17292 jump_data: Option<JumpData>,
17293 split: bool,
17294 window: &mut Window,
17295 cx: &mut Context<Self>,
17296 ) {
17297 let Some(workspace) = self.workspace() else {
17298 cx.propagate();
17299 return;
17300 };
17301
17302 if self.buffer.read(cx).is_singleton() {
17303 cx.propagate();
17304 return;
17305 }
17306
17307 let mut new_selections_by_buffer = HashMap::default();
17308 match &jump_data {
17309 Some(JumpData::MultiBufferPoint {
17310 excerpt_id,
17311 position,
17312 anchor,
17313 line_offset_from_top,
17314 }) => {
17315 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17316 if let Some(buffer) = multi_buffer_snapshot
17317 .buffer_id_for_excerpt(*excerpt_id)
17318 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17319 {
17320 let buffer_snapshot = buffer.read(cx).snapshot();
17321 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17322 language::ToPoint::to_point(anchor, &buffer_snapshot)
17323 } else {
17324 buffer_snapshot.clip_point(*position, Bias::Left)
17325 };
17326 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17327 new_selections_by_buffer.insert(
17328 buffer,
17329 (
17330 vec![jump_to_offset..jump_to_offset],
17331 Some(*line_offset_from_top),
17332 ),
17333 );
17334 }
17335 }
17336 Some(JumpData::MultiBufferRow {
17337 row,
17338 line_offset_from_top,
17339 }) => {
17340 let point = MultiBufferPoint::new(row.0, 0);
17341 if let Some((buffer, buffer_point, _)) =
17342 self.buffer.read(cx).point_to_buffer_point(point, cx)
17343 {
17344 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17345 new_selections_by_buffer
17346 .entry(buffer)
17347 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17348 .0
17349 .push(buffer_offset..buffer_offset)
17350 }
17351 }
17352 None => {
17353 let selections = self.selections.all::<usize>(cx);
17354 let multi_buffer = self.buffer.read(cx);
17355 for selection in selections {
17356 for (snapshot, range, _, anchor) in multi_buffer
17357 .snapshot(cx)
17358 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17359 {
17360 if let Some(anchor) = anchor {
17361 // selection is in a deleted hunk
17362 let Some(buffer_id) = anchor.buffer_id else {
17363 continue;
17364 };
17365 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17366 continue;
17367 };
17368 let offset = text::ToOffset::to_offset(
17369 &anchor.text_anchor,
17370 &buffer_handle.read(cx).snapshot(),
17371 );
17372 let range = offset..offset;
17373 new_selections_by_buffer
17374 .entry(buffer_handle)
17375 .or_insert((Vec::new(), None))
17376 .0
17377 .push(range)
17378 } else {
17379 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17380 else {
17381 continue;
17382 };
17383 new_selections_by_buffer
17384 .entry(buffer_handle)
17385 .or_insert((Vec::new(), None))
17386 .0
17387 .push(range)
17388 }
17389 }
17390 }
17391 }
17392 }
17393
17394 new_selections_by_buffer
17395 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
17396
17397 if new_selections_by_buffer.is_empty() {
17398 return;
17399 }
17400
17401 // We defer the pane interaction because we ourselves are a workspace item
17402 // and activating a new item causes the pane to call a method on us reentrantly,
17403 // which panics if we're on the stack.
17404 window.defer(cx, move |window, cx| {
17405 workspace.update(cx, |workspace, cx| {
17406 let pane = if split {
17407 workspace.adjacent_pane(window, cx)
17408 } else {
17409 workspace.active_pane().clone()
17410 };
17411
17412 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17413 let editor = buffer
17414 .read(cx)
17415 .file()
17416 .is_none()
17417 .then(|| {
17418 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17419 // so `workspace.open_project_item` will never find them, always opening a new editor.
17420 // Instead, we try to activate the existing editor in the pane first.
17421 let (editor, pane_item_index) =
17422 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17423 let editor = item.downcast::<Editor>()?;
17424 let singleton_buffer =
17425 editor.read(cx).buffer().read(cx).as_singleton()?;
17426 if singleton_buffer == buffer {
17427 Some((editor, i))
17428 } else {
17429 None
17430 }
17431 })?;
17432 pane.update(cx, |pane, cx| {
17433 pane.activate_item(pane_item_index, true, true, window, cx)
17434 });
17435 Some(editor)
17436 })
17437 .flatten()
17438 .unwrap_or_else(|| {
17439 workspace.open_project_item::<Self>(
17440 pane.clone(),
17441 buffer,
17442 true,
17443 true,
17444 window,
17445 cx,
17446 )
17447 });
17448
17449 editor.update(cx, |editor, cx| {
17450 let autoscroll = match scroll_offset {
17451 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17452 None => Autoscroll::newest(),
17453 };
17454 let nav_history = editor.nav_history.take();
17455 editor.change_selections(Some(autoscroll), window, cx, |s| {
17456 s.select_ranges(ranges);
17457 });
17458 editor.nav_history = nav_history;
17459 });
17460 }
17461 })
17462 });
17463 }
17464
17465 // For now, don't allow opening excerpts in buffers that aren't backed by
17466 // regular project files.
17467 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
17468 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
17469 }
17470
17471 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17472 let snapshot = self.buffer.read(cx).read(cx);
17473 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17474 Some(
17475 ranges
17476 .iter()
17477 .map(move |range| {
17478 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17479 })
17480 .collect(),
17481 )
17482 }
17483
17484 fn selection_replacement_ranges(
17485 &self,
17486 range: Range<OffsetUtf16>,
17487 cx: &mut App,
17488 ) -> Vec<Range<OffsetUtf16>> {
17489 let selections = self.selections.all::<OffsetUtf16>(cx);
17490 let newest_selection = selections
17491 .iter()
17492 .max_by_key(|selection| selection.id)
17493 .unwrap();
17494 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17495 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17496 let snapshot = self.buffer.read(cx).read(cx);
17497 selections
17498 .into_iter()
17499 .map(|mut selection| {
17500 selection.start.0 =
17501 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17502 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17503 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17504 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17505 })
17506 .collect()
17507 }
17508
17509 fn report_editor_event(
17510 &self,
17511 event_type: &'static str,
17512 file_extension: Option<String>,
17513 cx: &App,
17514 ) {
17515 if cfg!(any(test, feature = "test-support")) {
17516 return;
17517 }
17518
17519 let Some(project) = &self.project else { return };
17520
17521 // If None, we are in a file without an extension
17522 let file = self
17523 .buffer
17524 .read(cx)
17525 .as_singleton()
17526 .and_then(|b| b.read(cx).file());
17527 let file_extension = file_extension.or(file
17528 .as_ref()
17529 .and_then(|file| Path::new(file.file_name(cx)).extension())
17530 .and_then(|e| e.to_str())
17531 .map(|a| a.to_string()));
17532
17533 let vim_mode = cx
17534 .global::<SettingsStore>()
17535 .raw_user_settings()
17536 .get("vim_mode")
17537 == Some(&serde_json::Value::Bool(true));
17538
17539 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17540 let copilot_enabled = edit_predictions_provider
17541 == language::language_settings::EditPredictionProvider::Copilot;
17542 let copilot_enabled_for_language = self
17543 .buffer
17544 .read(cx)
17545 .language_settings(cx)
17546 .show_edit_predictions;
17547
17548 let project = project.read(cx);
17549 telemetry::event!(
17550 event_type,
17551 file_extension,
17552 vim_mode,
17553 copilot_enabled,
17554 copilot_enabled_for_language,
17555 edit_predictions_provider,
17556 is_via_ssh = project.is_via_ssh(),
17557 );
17558 }
17559
17560 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17561 /// with each line being an array of {text, highlight} objects.
17562 fn copy_highlight_json(
17563 &mut self,
17564 _: &CopyHighlightJson,
17565 window: &mut Window,
17566 cx: &mut Context<Self>,
17567 ) {
17568 #[derive(Serialize)]
17569 struct Chunk<'a> {
17570 text: String,
17571 highlight: Option<&'a str>,
17572 }
17573
17574 let snapshot = self.buffer.read(cx).snapshot(cx);
17575 let range = self
17576 .selected_text_range(false, window, cx)
17577 .and_then(|selection| {
17578 if selection.range.is_empty() {
17579 None
17580 } else {
17581 Some(selection.range)
17582 }
17583 })
17584 .unwrap_or_else(|| 0..snapshot.len());
17585
17586 let chunks = snapshot.chunks(range, true);
17587 let mut lines = Vec::new();
17588 let mut line: VecDeque<Chunk> = VecDeque::new();
17589
17590 let Some(style) = self.style.as_ref() else {
17591 return;
17592 };
17593
17594 for chunk in chunks {
17595 let highlight = chunk
17596 .syntax_highlight_id
17597 .and_then(|id| id.name(&style.syntax));
17598 let mut chunk_lines = chunk.text.split('\n').peekable();
17599 while let Some(text) = chunk_lines.next() {
17600 let mut merged_with_last_token = false;
17601 if let Some(last_token) = line.back_mut() {
17602 if last_token.highlight == highlight {
17603 last_token.text.push_str(text);
17604 merged_with_last_token = true;
17605 }
17606 }
17607
17608 if !merged_with_last_token {
17609 line.push_back(Chunk {
17610 text: text.into(),
17611 highlight,
17612 });
17613 }
17614
17615 if chunk_lines.peek().is_some() {
17616 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17617 line.pop_front();
17618 }
17619 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17620 line.pop_back();
17621 }
17622
17623 lines.push(mem::take(&mut line));
17624 }
17625 }
17626 }
17627
17628 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17629 return;
17630 };
17631 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17632 }
17633
17634 pub fn open_context_menu(
17635 &mut self,
17636 _: &OpenContextMenu,
17637 window: &mut Window,
17638 cx: &mut Context<Self>,
17639 ) {
17640 self.request_autoscroll(Autoscroll::newest(), cx);
17641 let position = self.selections.newest_display(cx).start;
17642 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17643 }
17644
17645 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17646 &self.inlay_hint_cache
17647 }
17648
17649 pub fn replay_insert_event(
17650 &mut self,
17651 text: &str,
17652 relative_utf16_range: Option<Range<isize>>,
17653 window: &mut Window,
17654 cx: &mut Context<Self>,
17655 ) {
17656 if !self.input_enabled {
17657 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17658 return;
17659 }
17660 if let Some(relative_utf16_range) = relative_utf16_range {
17661 let selections = self.selections.all::<OffsetUtf16>(cx);
17662 self.change_selections(None, window, cx, |s| {
17663 let new_ranges = selections.into_iter().map(|range| {
17664 let start = OffsetUtf16(
17665 range
17666 .head()
17667 .0
17668 .saturating_add_signed(relative_utf16_range.start),
17669 );
17670 let end = OffsetUtf16(
17671 range
17672 .head()
17673 .0
17674 .saturating_add_signed(relative_utf16_range.end),
17675 );
17676 start..end
17677 });
17678 s.select_ranges(new_ranges);
17679 });
17680 }
17681
17682 self.handle_input(text, window, cx);
17683 }
17684
17685 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17686 let Some(provider) = self.semantics_provider.as_ref() else {
17687 return false;
17688 };
17689
17690 let mut supports = false;
17691 self.buffer().update(cx, |this, cx| {
17692 this.for_each_buffer(|buffer| {
17693 supports |= provider.supports_inlay_hints(buffer, cx);
17694 });
17695 });
17696
17697 supports
17698 }
17699
17700 pub fn is_focused(&self, window: &Window) -> bool {
17701 self.focus_handle.is_focused(window)
17702 }
17703
17704 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17705 cx.emit(EditorEvent::Focused);
17706
17707 if let Some(descendant) = self
17708 .last_focused_descendant
17709 .take()
17710 .and_then(|descendant| descendant.upgrade())
17711 {
17712 window.focus(&descendant);
17713 } else {
17714 if let Some(blame) = self.blame.as_ref() {
17715 blame.update(cx, GitBlame::focus)
17716 }
17717
17718 self.blink_manager.update(cx, BlinkManager::enable);
17719 self.show_cursor_names(window, cx);
17720 self.buffer.update(cx, |buffer, cx| {
17721 buffer.finalize_last_transaction(cx);
17722 if self.leader_peer_id.is_none() {
17723 buffer.set_active_selections(
17724 &self.selections.disjoint_anchors(),
17725 self.selections.line_mode,
17726 self.cursor_shape,
17727 cx,
17728 );
17729 }
17730 });
17731 }
17732 }
17733
17734 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17735 cx.emit(EditorEvent::FocusedIn)
17736 }
17737
17738 fn handle_focus_out(
17739 &mut self,
17740 event: FocusOutEvent,
17741 _window: &mut Window,
17742 cx: &mut Context<Self>,
17743 ) {
17744 if event.blurred != self.focus_handle {
17745 self.last_focused_descendant = Some(event.blurred);
17746 }
17747 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17748 }
17749
17750 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17751 self.blink_manager.update(cx, BlinkManager::disable);
17752 self.buffer
17753 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17754
17755 if let Some(blame) = self.blame.as_ref() {
17756 blame.update(cx, GitBlame::blur)
17757 }
17758 if !self.hover_state.focused(window, cx) {
17759 hide_hover(self, cx);
17760 }
17761 if !self
17762 .context_menu
17763 .borrow()
17764 .as_ref()
17765 .is_some_and(|context_menu| context_menu.focused(window, cx))
17766 {
17767 self.hide_context_menu(window, cx);
17768 }
17769 self.discard_inline_completion(false, cx);
17770 cx.emit(EditorEvent::Blurred);
17771 cx.notify();
17772 }
17773
17774 pub fn register_action<A: Action>(
17775 &mut self,
17776 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17777 ) -> Subscription {
17778 let id = self.next_editor_action_id.post_inc();
17779 let listener = Arc::new(listener);
17780 self.editor_actions.borrow_mut().insert(
17781 id,
17782 Box::new(move |window, _| {
17783 let listener = listener.clone();
17784 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17785 let action = action.downcast_ref().unwrap();
17786 if phase == DispatchPhase::Bubble {
17787 listener(action, window, cx)
17788 }
17789 })
17790 }),
17791 );
17792
17793 let editor_actions = self.editor_actions.clone();
17794 Subscription::new(move || {
17795 editor_actions.borrow_mut().remove(&id);
17796 })
17797 }
17798
17799 pub fn file_header_size(&self) -> u32 {
17800 FILE_HEADER_HEIGHT
17801 }
17802
17803 pub fn restore(
17804 &mut self,
17805 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17806 window: &mut Window,
17807 cx: &mut Context<Self>,
17808 ) {
17809 let workspace = self.workspace();
17810 let project = self.project.as_ref();
17811 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17812 let mut tasks = Vec::new();
17813 for (buffer_id, changes) in revert_changes {
17814 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17815 buffer.update(cx, |buffer, cx| {
17816 buffer.edit(
17817 changes
17818 .into_iter()
17819 .map(|(range, text)| (range, text.to_string())),
17820 None,
17821 cx,
17822 );
17823 });
17824
17825 if let Some(project) =
17826 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17827 {
17828 project.update(cx, |project, cx| {
17829 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17830 })
17831 }
17832 }
17833 }
17834 tasks
17835 });
17836 cx.spawn_in(window, async move |_, cx| {
17837 for (buffer, task) in save_tasks {
17838 let result = task.await;
17839 if result.is_err() {
17840 let Some(path) = buffer
17841 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17842 .ok()
17843 else {
17844 continue;
17845 };
17846 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17847 let Some(task) = cx
17848 .update_window_entity(&workspace, |workspace, window, cx| {
17849 workspace
17850 .open_path_preview(path, None, false, false, false, window, cx)
17851 })
17852 .ok()
17853 else {
17854 continue;
17855 };
17856 task.await.log_err();
17857 }
17858 }
17859 }
17860 })
17861 .detach();
17862 self.change_selections(None, window, cx, |selections| selections.refresh());
17863 }
17864
17865 pub fn to_pixel_point(
17866 &self,
17867 source: multi_buffer::Anchor,
17868 editor_snapshot: &EditorSnapshot,
17869 window: &mut Window,
17870 ) -> Option<gpui::Point<Pixels>> {
17871 let source_point = source.to_display_point(editor_snapshot);
17872 self.display_to_pixel_point(source_point, editor_snapshot, window)
17873 }
17874
17875 pub fn display_to_pixel_point(
17876 &self,
17877 source: DisplayPoint,
17878 editor_snapshot: &EditorSnapshot,
17879 window: &mut Window,
17880 ) -> Option<gpui::Point<Pixels>> {
17881 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17882 let text_layout_details = self.text_layout_details(window);
17883 let scroll_top = text_layout_details
17884 .scroll_anchor
17885 .scroll_position(editor_snapshot)
17886 .y;
17887
17888 if source.row().as_f32() < scroll_top.floor() {
17889 return None;
17890 }
17891 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17892 let source_y = line_height * (source.row().as_f32() - scroll_top);
17893 Some(gpui::Point::new(source_x, source_y))
17894 }
17895
17896 pub fn has_visible_completions_menu(&self) -> bool {
17897 !self.edit_prediction_preview_is_active()
17898 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17899 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17900 })
17901 }
17902
17903 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17904 self.addons
17905 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17906 }
17907
17908 pub fn unregister_addon<T: Addon>(&mut self) {
17909 self.addons.remove(&std::any::TypeId::of::<T>());
17910 }
17911
17912 pub fn addon<T: Addon>(&self) -> Option<&T> {
17913 let type_id = std::any::TypeId::of::<T>();
17914 self.addons
17915 .get(&type_id)
17916 .and_then(|item| item.to_any().downcast_ref::<T>())
17917 }
17918
17919 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17920 let text_layout_details = self.text_layout_details(window);
17921 let style = &text_layout_details.editor_style;
17922 let font_id = window.text_system().resolve_font(&style.text.font());
17923 let font_size = style.text.font_size.to_pixels(window.rem_size());
17924 let line_height = style.text.line_height_in_pixels(window.rem_size());
17925 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17926
17927 gpui::Size::new(em_width, line_height)
17928 }
17929
17930 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17931 self.load_diff_task.clone()
17932 }
17933
17934 fn read_metadata_from_db(
17935 &mut self,
17936 item_id: u64,
17937 workspace_id: WorkspaceId,
17938 window: &mut Window,
17939 cx: &mut Context<Editor>,
17940 ) {
17941 if self.is_singleton(cx)
17942 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
17943 {
17944 let buffer_snapshot = OnceCell::new();
17945
17946 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
17947 if !folds.is_empty() {
17948 let snapshot =
17949 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17950 self.fold_ranges(
17951 folds
17952 .into_iter()
17953 .map(|(start, end)| {
17954 snapshot.clip_offset(start, Bias::Left)
17955 ..snapshot.clip_offset(end, Bias::Right)
17956 })
17957 .collect(),
17958 false,
17959 window,
17960 cx,
17961 );
17962 }
17963 }
17964
17965 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
17966 if !selections.is_empty() {
17967 let snapshot =
17968 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17969 self.change_selections(None, window, cx, |s| {
17970 s.select_ranges(selections.into_iter().map(|(start, end)| {
17971 snapshot.clip_offset(start, Bias::Left)
17972 ..snapshot.clip_offset(end, Bias::Right)
17973 }));
17974 });
17975 }
17976 };
17977 }
17978
17979 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
17980 }
17981}
17982
17983fn insert_extra_newline_brackets(
17984 buffer: &MultiBufferSnapshot,
17985 range: Range<usize>,
17986 language: &language::LanguageScope,
17987) -> bool {
17988 let leading_whitespace_len = buffer
17989 .reversed_chars_at(range.start)
17990 .take_while(|c| c.is_whitespace() && *c != '\n')
17991 .map(|c| c.len_utf8())
17992 .sum::<usize>();
17993 let trailing_whitespace_len = buffer
17994 .chars_at(range.end)
17995 .take_while(|c| c.is_whitespace() && *c != '\n')
17996 .map(|c| c.len_utf8())
17997 .sum::<usize>();
17998 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
17999
18000 language.brackets().any(|(pair, enabled)| {
18001 let pair_start = pair.start.trim_end();
18002 let pair_end = pair.end.trim_start();
18003
18004 enabled
18005 && pair.newline
18006 && buffer.contains_str_at(range.end, pair_end)
18007 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18008 })
18009}
18010
18011fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18012 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18013 [(buffer, range, _)] => (*buffer, range.clone()),
18014 _ => return false,
18015 };
18016 let pair = {
18017 let mut result: Option<BracketMatch> = None;
18018
18019 for pair in buffer
18020 .all_bracket_ranges(range.clone())
18021 .filter(move |pair| {
18022 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18023 })
18024 {
18025 let len = pair.close_range.end - pair.open_range.start;
18026
18027 if let Some(existing) = &result {
18028 let existing_len = existing.close_range.end - existing.open_range.start;
18029 if len > existing_len {
18030 continue;
18031 }
18032 }
18033
18034 result = Some(pair);
18035 }
18036
18037 result
18038 };
18039 let Some(pair) = pair else {
18040 return false;
18041 };
18042 pair.newline_only
18043 && buffer
18044 .chars_for_range(pair.open_range.end..range.start)
18045 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18046 .all(|c| c.is_whitespace() && c != '\n')
18047}
18048
18049fn get_uncommitted_diff_for_buffer(
18050 project: &Entity<Project>,
18051 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18052 buffer: Entity<MultiBuffer>,
18053 cx: &mut App,
18054) -> Task<()> {
18055 let mut tasks = Vec::new();
18056 project.update(cx, |project, cx| {
18057 for buffer in buffers {
18058 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18059 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18060 }
18061 }
18062 });
18063 cx.spawn(async move |cx| {
18064 let diffs = future::join_all(tasks).await;
18065 buffer
18066 .update(cx, |buffer, cx| {
18067 for diff in diffs.into_iter().flatten() {
18068 buffer.add_diff(diff, cx);
18069 }
18070 })
18071 .ok();
18072 })
18073}
18074
18075fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18076 let tab_size = tab_size.get() as usize;
18077 let mut width = offset;
18078
18079 for ch in text.chars() {
18080 width += if ch == '\t' {
18081 tab_size - (width % tab_size)
18082 } else {
18083 1
18084 };
18085 }
18086
18087 width - offset
18088}
18089
18090#[cfg(test)]
18091mod tests {
18092 use super::*;
18093
18094 #[test]
18095 fn test_string_size_with_expanded_tabs() {
18096 let nz = |val| NonZeroU32::new(val).unwrap();
18097 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18098 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18099 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18100 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18101 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18102 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18103 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18104 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18105 }
18106}
18107
18108/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18109struct WordBreakingTokenizer<'a> {
18110 input: &'a str,
18111}
18112
18113impl<'a> WordBreakingTokenizer<'a> {
18114 fn new(input: &'a str) -> Self {
18115 Self { input }
18116 }
18117}
18118
18119fn is_char_ideographic(ch: char) -> bool {
18120 use unicode_script::Script::*;
18121 use unicode_script::UnicodeScript;
18122 matches!(ch.script(), Han | Tangut | Yi)
18123}
18124
18125fn is_grapheme_ideographic(text: &str) -> bool {
18126 text.chars().any(is_char_ideographic)
18127}
18128
18129fn is_grapheme_whitespace(text: &str) -> bool {
18130 text.chars().any(|x| x.is_whitespace())
18131}
18132
18133fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18134 text.chars().next().map_or(false, |ch| {
18135 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18136 })
18137}
18138
18139#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18140enum WordBreakToken<'a> {
18141 Word { token: &'a str, grapheme_len: usize },
18142 InlineWhitespace { token: &'a str, grapheme_len: usize },
18143 Newline,
18144}
18145
18146impl<'a> Iterator for WordBreakingTokenizer<'a> {
18147 /// Yields a span, the count of graphemes in the token, and whether it was
18148 /// whitespace. Note that it also breaks at word boundaries.
18149 type Item = WordBreakToken<'a>;
18150
18151 fn next(&mut self) -> Option<Self::Item> {
18152 use unicode_segmentation::UnicodeSegmentation;
18153 if self.input.is_empty() {
18154 return None;
18155 }
18156
18157 let mut iter = self.input.graphemes(true).peekable();
18158 let mut offset = 0;
18159 let mut grapheme_len = 0;
18160 if let Some(first_grapheme) = iter.next() {
18161 let is_newline = first_grapheme == "\n";
18162 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18163 offset += first_grapheme.len();
18164 grapheme_len += 1;
18165 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18166 if let Some(grapheme) = iter.peek().copied() {
18167 if should_stay_with_preceding_ideograph(grapheme) {
18168 offset += grapheme.len();
18169 grapheme_len += 1;
18170 }
18171 }
18172 } else {
18173 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18174 let mut next_word_bound = words.peek().copied();
18175 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18176 next_word_bound = words.next();
18177 }
18178 while let Some(grapheme) = iter.peek().copied() {
18179 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18180 break;
18181 };
18182 if is_grapheme_whitespace(grapheme) != is_whitespace
18183 || (grapheme == "\n") != is_newline
18184 {
18185 break;
18186 };
18187 offset += grapheme.len();
18188 grapheme_len += 1;
18189 iter.next();
18190 }
18191 }
18192 let token = &self.input[..offset];
18193 self.input = &self.input[offset..];
18194 if token == "\n" {
18195 Some(WordBreakToken::Newline)
18196 } else if is_whitespace {
18197 Some(WordBreakToken::InlineWhitespace {
18198 token,
18199 grapheme_len,
18200 })
18201 } else {
18202 Some(WordBreakToken::Word {
18203 token,
18204 grapheme_len,
18205 })
18206 }
18207 } else {
18208 None
18209 }
18210 }
18211}
18212
18213#[test]
18214fn test_word_breaking_tokenizer() {
18215 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18216 ("", &[]),
18217 (" ", &[whitespace(" ", 2)]),
18218 ("Ʒ", &[word("Ʒ", 1)]),
18219 ("Ǽ", &[word("Ǽ", 1)]),
18220 ("⋑", &[word("⋑", 1)]),
18221 ("⋑⋑", &[word("⋑⋑", 2)]),
18222 (
18223 "原理,进而",
18224 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18225 ),
18226 (
18227 "hello world",
18228 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18229 ),
18230 (
18231 "hello, world",
18232 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18233 ),
18234 (
18235 " hello world",
18236 &[
18237 whitespace(" ", 2),
18238 word("hello", 5),
18239 whitespace(" ", 1),
18240 word("world", 5),
18241 ],
18242 ),
18243 (
18244 "这是什么 \n 钢笔",
18245 &[
18246 word("这", 1),
18247 word("是", 1),
18248 word("什", 1),
18249 word("么", 1),
18250 whitespace(" ", 1),
18251 newline(),
18252 whitespace(" ", 1),
18253 word("钢", 1),
18254 word("笔", 1),
18255 ],
18256 ),
18257 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
18258 ];
18259
18260 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18261 WordBreakToken::Word {
18262 token,
18263 grapheme_len,
18264 }
18265 }
18266
18267 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18268 WordBreakToken::InlineWhitespace {
18269 token,
18270 grapheme_len,
18271 }
18272 }
18273
18274 fn newline() -> WordBreakToken<'static> {
18275 WordBreakToken::Newline
18276 }
18277
18278 for (input, result) in tests {
18279 assert_eq!(
18280 WordBreakingTokenizer::new(input)
18281 .collect::<Vec<_>>()
18282 .as_slice(),
18283 *result,
18284 );
18285 }
18286}
18287
18288fn wrap_with_prefix(
18289 line_prefix: String,
18290 unwrapped_text: String,
18291 wrap_column: usize,
18292 tab_size: NonZeroU32,
18293 preserve_existing_whitespace: bool,
18294) -> String {
18295 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18296 let mut wrapped_text = String::new();
18297 let mut current_line = line_prefix.clone();
18298
18299 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18300 let mut current_line_len = line_prefix_len;
18301 let mut in_whitespace = false;
18302 for token in tokenizer {
18303 let have_preceding_whitespace = in_whitespace;
18304 match token {
18305 WordBreakToken::Word {
18306 token,
18307 grapheme_len,
18308 } => {
18309 in_whitespace = false;
18310 if current_line_len + grapheme_len > wrap_column
18311 && current_line_len != line_prefix_len
18312 {
18313 wrapped_text.push_str(current_line.trim_end());
18314 wrapped_text.push('\n');
18315 current_line.truncate(line_prefix.len());
18316 current_line_len = line_prefix_len;
18317 }
18318 current_line.push_str(token);
18319 current_line_len += grapheme_len;
18320 }
18321 WordBreakToken::InlineWhitespace {
18322 mut token,
18323 mut grapheme_len,
18324 } => {
18325 in_whitespace = true;
18326 if have_preceding_whitespace && !preserve_existing_whitespace {
18327 continue;
18328 }
18329 if !preserve_existing_whitespace {
18330 token = " ";
18331 grapheme_len = 1;
18332 }
18333 if current_line_len + grapheme_len > wrap_column {
18334 wrapped_text.push_str(current_line.trim_end());
18335 wrapped_text.push('\n');
18336 current_line.truncate(line_prefix.len());
18337 current_line_len = line_prefix_len;
18338 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18339 current_line.push_str(token);
18340 current_line_len += grapheme_len;
18341 }
18342 }
18343 WordBreakToken::Newline => {
18344 in_whitespace = true;
18345 if preserve_existing_whitespace {
18346 wrapped_text.push_str(current_line.trim_end());
18347 wrapped_text.push('\n');
18348 current_line.truncate(line_prefix.len());
18349 current_line_len = line_prefix_len;
18350 } else if have_preceding_whitespace {
18351 continue;
18352 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18353 {
18354 wrapped_text.push_str(current_line.trim_end());
18355 wrapped_text.push('\n');
18356 current_line.truncate(line_prefix.len());
18357 current_line_len = line_prefix_len;
18358 } else if current_line_len != line_prefix_len {
18359 current_line.push(' ');
18360 current_line_len += 1;
18361 }
18362 }
18363 }
18364 }
18365
18366 if !current_line.is_empty() {
18367 wrapped_text.push_str(¤t_line);
18368 }
18369 wrapped_text
18370}
18371
18372#[test]
18373fn test_wrap_with_prefix() {
18374 assert_eq!(
18375 wrap_with_prefix(
18376 "# ".to_string(),
18377 "abcdefg".to_string(),
18378 4,
18379 NonZeroU32::new(4).unwrap(),
18380 false,
18381 ),
18382 "# abcdefg"
18383 );
18384 assert_eq!(
18385 wrap_with_prefix(
18386 "".to_string(),
18387 "\thello world".to_string(),
18388 8,
18389 NonZeroU32::new(4).unwrap(),
18390 false,
18391 ),
18392 "hello\nworld"
18393 );
18394 assert_eq!(
18395 wrap_with_prefix(
18396 "// ".to_string(),
18397 "xx \nyy zz aa bb cc".to_string(),
18398 12,
18399 NonZeroU32::new(4).unwrap(),
18400 false,
18401 ),
18402 "// xx yy zz\n// aa bb cc"
18403 );
18404 assert_eq!(
18405 wrap_with_prefix(
18406 String::new(),
18407 "这是什么 \n 钢笔".to_string(),
18408 3,
18409 NonZeroU32::new(4).unwrap(),
18410 false,
18411 ),
18412 "这是什\n么 钢\n笔"
18413 );
18414}
18415
18416pub trait CollaborationHub {
18417 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18418 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18419 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18420}
18421
18422impl CollaborationHub for Entity<Project> {
18423 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18424 self.read(cx).collaborators()
18425 }
18426
18427 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18428 self.read(cx).user_store().read(cx).participant_indices()
18429 }
18430
18431 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18432 let this = self.read(cx);
18433 let user_ids = this.collaborators().values().map(|c| c.user_id);
18434 this.user_store().read_with(cx, |user_store, cx| {
18435 user_store.participant_names(user_ids, cx)
18436 })
18437 }
18438}
18439
18440pub trait SemanticsProvider {
18441 fn hover(
18442 &self,
18443 buffer: &Entity<Buffer>,
18444 position: text::Anchor,
18445 cx: &mut App,
18446 ) -> Option<Task<Vec<project::Hover>>>;
18447
18448 fn inlay_hints(
18449 &self,
18450 buffer_handle: Entity<Buffer>,
18451 range: Range<text::Anchor>,
18452 cx: &mut App,
18453 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18454
18455 fn resolve_inlay_hint(
18456 &self,
18457 hint: InlayHint,
18458 buffer_handle: Entity<Buffer>,
18459 server_id: LanguageServerId,
18460 cx: &mut App,
18461 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18462
18463 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18464
18465 fn document_highlights(
18466 &self,
18467 buffer: &Entity<Buffer>,
18468 position: text::Anchor,
18469 cx: &mut App,
18470 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18471
18472 fn definitions(
18473 &self,
18474 buffer: &Entity<Buffer>,
18475 position: text::Anchor,
18476 kind: GotoDefinitionKind,
18477 cx: &mut App,
18478 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18479
18480 fn range_for_rename(
18481 &self,
18482 buffer: &Entity<Buffer>,
18483 position: text::Anchor,
18484 cx: &mut App,
18485 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18486
18487 fn perform_rename(
18488 &self,
18489 buffer: &Entity<Buffer>,
18490 position: text::Anchor,
18491 new_name: String,
18492 cx: &mut App,
18493 ) -> Option<Task<Result<ProjectTransaction>>>;
18494}
18495
18496pub trait CompletionProvider {
18497 fn completions(
18498 &self,
18499 excerpt_id: ExcerptId,
18500 buffer: &Entity<Buffer>,
18501 buffer_position: text::Anchor,
18502 trigger: CompletionContext,
18503 window: &mut Window,
18504 cx: &mut Context<Editor>,
18505 ) -> Task<Result<Option<Vec<Completion>>>>;
18506
18507 fn resolve_completions(
18508 &self,
18509 buffer: Entity<Buffer>,
18510 completion_indices: Vec<usize>,
18511 completions: Rc<RefCell<Box<[Completion]>>>,
18512 cx: &mut Context<Editor>,
18513 ) -> Task<Result<bool>>;
18514
18515 fn apply_additional_edits_for_completion(
18516 &self,
18517 _buffer: Entity<Buffer>,
18518 _completions: Rc<RefCell<Box<[Completion]>>>,
18519 _completion_index: usize,
18520 _push_to_history: bool,
18521 _cx: &mut Context<Editor>,
18522 ) -> Task<Result<Option<language::Transaction>>> {
18523 Task::ready(Ok(None))
18524 }
18525
18526 fn is_completion_trigger(
18527 &self,
18528 buffer: &Entity<Buffer>,
18529 position: language::Anchor,
18530 text: &str,
18531 trigger_in_words: bool,
18532 cx: &mut Context<Editor>,
18533 ) -> bool;
18534
18535 fn sort_completions(&self) -> bool {
18536 true
18537 }
18538
18539 fn filter_completions(&self) -> bool {
18540 true
18541 }
18542}
18543
18544pub trait CodeActionProvider {
18545 fn id(&self) -> Arc<str>;
18546
18547 fn code_actions(
18548 &self,
18549 buffer: &Entity<Buffer>,
18550 range: Range<text::Anchor>,
18551 window: &mut Window,
18552 cx: &mut App,
18553 ) -> Task<Result<Vec<CodeAction>>>;
18554
18555 fn apply_code_action(
18556 &self,
18557 buffer_handle: Entity<Buffer>,
18558 action: CodeAction,
18559 excerpt_id: ExcerptId,
18560 push_to_history: bool,
18561 window: &mut Window,
18562 cx: &mut App,
18563 ) -> Task<Result<ProjectTransaction>>;
18564}
18565
18566impl CodeActionProvider for Entity<Project> {
18567 fn id(&self) -> Arc<str> {
18568 "project".into()
18569 }
18570
18571 fn code_actions(
18572 &self,
18573 buffer: &Entity<Buffer>,
18574 range: Range<text::Anchor>,
18575 _window: &mut Window,
18576 cx: &mut App,
18577 ) -> Task<Result<Vec<CodeAction>>> {
18578 self.update(cx, |project, cx| {
18579 let code_lens = project.code_lens(buffer, range.clone(), cx);
18580 let code_actions = project.code_actions(buffer, range, None, cx);
18581 cx.background_spawn(async move {
18582 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18583 Ok(code_lens
18584 .context("code lens fetch")?
18585 .into_iter()
18586 .chain(code_actions.context("code action fetch")?)
18587 .collect())
18588 })
18589 })
18590 }
18591
18592 fn apply_code_action(
18593 &self,
18594 buffer_handle: Entity<Buffer>,
18595 action: CodeAction,
18596 _excerpt_id: ExcerptId,
18597 push_to_history: bool,
18598 _window: &mut Window,
18599 cx: &mut App,
18600 ) -> Task<Result<ProjectTransaction>> {
18601 self.update(cx, |project, cx| {
18602 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18603 })
18604 }
18605}
18606
18607fn snippet_completions(
18608 project: &Project,
18609 buffer: &Entity<Buffer>,
18610 buffer_position: text::Anchor,
18611 cx: &mut App,
18612) -> Task<Result<Vec<Completion>>> {
18613 let language = buffer.read(cx).language_at(buffer_position);
18614 let language_name = language.as_ref().map(|language| language.lsp_id());
18615 let snippet_store = project.snippets().read(cx);
18616 let snippets = snippet_store.snippets_for(language_name, cx);
18617
18618 if snippets.is_empty() {
18619 return Task::ready(Ok(vec![]));
18620 }
18621 let snapshot = buffer.read(cx).text_snapshot();
18622 let chars: String = snapshot
18623 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18624 .collect();
18625
18626 let scope = language.map(|language| language.default_scope());
18627 let executor = cx.background_executor().clone();
18628
18629 cx.background_spawn(async move {
18630 let classifier = CharClassifier::new(scope).for_completion(true);
18631 let mut last_word = chars
18632 .chars()
18633 .take_while(|c| classifier.is_word(*c))
18634 .collect::<String>();
18635 last_word = last_word.chars().rev().collect();
18636
18637 if last_word.is_empty() {
18638 return Ok(vec![]);
18639 }
18640
18641 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18642 let to_lsp = |point: &text::Anchor| {
18643 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18644 point_to_lsp(end)
18645 };
18646 let lsp_end = to_lsp(&buffer_position);
18647
18648 let candidates = snippets
18649 .iter()
18650 .enumerate()
18651 .flat_map(|(ix, snippet)| {
18652 snippet
18653 .prefix
18654 .iter()
18655 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18656 })
18657 .collect::<Vec<StringMatchCandidate>>();
18658
18659 let mut matches = fuzzy::match_strings(
18660 &candidates,
18661 &last_word,
18662 last_word.chars().any(|c| c.is_uppercase()),
18663 100,
18664 &Default::default(),
18665 executor,
18666 )
18667 .await;
18668
18669 // Remove all candidates where the query's start does not match the start of any word in the candidate
18670 if let Some(query_start) = last_word.chars().next() {
18671 matches.retain(|string_match| {
18672 split_words(&string_match.string).any(|word| {
18673 // Check that the first codepoint of the word as lowercase matches the first
18674 // codepoint of the query as lowercase
18675 word.chars()
18676 .flat_map(|codepoint| codepoint.to_lowercase())
18677 .zip(query_start.to_lowercase())
18678 .all(|(word_cp, query_cp)| word_cp == query_cp)
18679 })
18680 });
18681 }
18682
18683 let matched_strings = matches
18684 .into_iter()
18685 .map(|m| m.string)
18686 .collect::<HashSet<_>>();
18687
18688 let result: Vec<Completion> = snippets
18689 .into_iter()
18690 .filter_map(|snippet| {
18691 let matching_prefix = snippet
18692 .prefix
18693 .iter()
18694 .find(|prefix| matched_strings.contains(*prefix))?;
18695 let start = as_offset - last_word.len();
18696 let start = snapshot.anchor_before(start);
18697 let range = start..buffer_position;
18698 let lsp_start = to_lsp(&start);
18699 let lsp_range = lsp::Range {
18700 start: lsp_start,
18701 end: lsp_end,
18702 };
18703 Some(Completion {
18704 old_range: range,
18705 new_text: snippet.body.clone(),
18706 source: CompletionSource::Lsp {
18707 server_id: LanguageServerId(usize::MAX),
18708 resolved: true,
18709 lsp_completion: Box::new(lsp::CompletionItem {
18710 label: snippet.prefix.first().unwrap().clone(),
18711 kind: Some(CompletionItemKind::SNIPPET),
18712 label_details: snippet.description.as_ref().map(|description| {
18713 lsp::CompletionItemLabelDetails {
18714 detail: Some(description.clone()),
18715 description: None,
18716 }
18717 }),
18718 insert_text_format: Some(InsertTextFormat::SNIPPET),
18719 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18720 lsp::InsertReplaceEdit {
18721 new_text: snippet.body.clone(),
18722 insert: lsp_range,
18723 replace: lsp_range,
18724 },
18725 )),
18726 filter_text: Some(snippet.body.clone()),
18727 sort_text: Some(char::MAX.to_string()),
18728 ..lsp::CompletionItem::default()
18729 }),
18730 lsp_defaults: None,
18731 },
18732 label: CodeLabel {
18733 text: matching_prefix.clone(),
18734 runs: Vec::new(),
18735 filter_range: 0..matching_prefix.len(),
18736 },
18737 icon_path: None,
18738 documentation: snippet
18739 .description
18740 .clone()
18741 .map(|description| CompletionDocumentation::SingleLine(description.into())),
18742 insert_text_mode: None,
18743 confirm: None,
18744 })
18745 })
18746 .collect();
18747
18748 Ok(result)
18749 })
18750}
18751
18752impl CompletionProvider for Entity<Project> {
18753 fn completions(
18754 &self,
18755 _excerpt_id: ExcerptId,
18756 buffer: &Entity<Buffer>,
18757 buffer_position: text::Anchor,
18758 options: CompletionContext,
18759 _window: &mut Window,
18760 cx: &mut Context<Editor>,
18761 ) -> Task<Result<Option<Vec<Completion>>>> {
18762 self.update(cx, |project, cx| {
18763 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18764 let project_completions = project.completions(buffer, buffer_position, options, cx);
18765 cx.background_spawn(async move {
18766 let snippets_completions = snippets.await?;
18767 match project_completions.await? {
18768 Some(mut completions) => {
18769 completions.extend(snippets_completions);
18770 Ok(Some(completions))
18771 }
18772 None => {
18773 if snippets_completions.is_empty() {
18774 Ok(None)
18775 } else {
18776 Ok(Some(snippets_completions))
18777 }
18778 }
18779 }
18780 })
18781 })
18782 }
18783
18784 fn resolve_completions(
18785 &self,
18786 buffer: Entity<Buffer>,
18787 completion_indices: Vec<usize>,
18788 completions: Rc<RefCell<Box<[Completion]>>>,
18789 cx: &mut Context<Editor>,
18790 ) -> Task<Result<bool>> {
18791 self.update(cx, |project, cx| {
18792 project.lsp_store().update(cx, |lsp_store, cx| {
18793 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18794 })
18795 })
18796 }
18797
18798 fn apply_additional_edits_for_completion(
18799 &self,
18800 buffer: Entity<Buffer>,
18801 completions: Rc<RefCell<Box<[Completion]>>>,
18802 completion_index: usize,
18803 push_to_history: bool,
18804 cx: &mut Context<Editor>,
18805 ) -> Task<Result<Option<language::Transaction>>> {
18806 self.update(cx, |project, cx| {
18807 project.lsp_store().update(cx, |lsp_store, cx| {
18808 lsp_store.apply_additional_edits_for_completion(
18809 buffer,
18810 completions,
18811 completion_index,
18812 push_to_history,
18813 cx,
18814 )
18815 })
18816 })
18817 }
18818
18819 fn is_completion_trigger(
18820 &self,
18821 buffer: &Entity<Buffer>,
18822 position: language::Anchor,
18823 text: &str,
18824 trigger_in_words: bool,
18825 cx: &mut Context<Editor>,
18826 ) -> bool {
18827 let mut chars = text.chars();
18828 let char = if let Some(char) = chars.next() {
18829 char
18830 } else {
18831 return false;
18832 };
18833 if chars.next().is_some() {
18834 return false;
18835 }
18836
18837 let buffer = buffer.read(cx);
18838 let snapshot = buffer.snapshot();
18839 if !snapshot.settings_at(position, cx).show_completions_on_input {
18840 return false;
18841 }
18842 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18843 if trigger_in_words && classifier.is_word(char) {
18844 return true;
18845 }
18846
18847 buffer.completion_triggers().contains(text)
18848 }
18849}
18850
18851impl SemanticsProvider for Entity<Project> {
18852 fn hover(
18853 &self,
18854 buffer: &Entity<Buffer>,
18855 position: text::Anchor,
18856 cx: &mut App,
18857 ) -> Option<Task<Vec<project::Hover>>> {
18858 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18859 }
18860
18861 fn document_highlights(
18862 &self,
18863 buffer: &Entity<Buffer>,
18864 position: text::Anchor,
18865 cx: &mut App,
18866 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18867 Some(self.update(cx, |project, cx| {
18868 project.document_highlights(buffer, position, cx)
18869 }))
18870 }
18871
18872 fn definitions(
18873 &self,
18874 buffer: &Entity<Buffer>,
18875 position: text::Anchor,
18876 kind: GotoDefinitionKind,
18877 cx: &mut App,
18878 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18879 Some(self.update(cx, |project, cx| match kind {
18880 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18881 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18882 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18883 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18884 }))
18885 }
18886
18887 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18888 // TODO: make this work for remote projects
18889 self.update(cx, |this, cx| {
18890 buffer.update(cx, |buffer, cx| {
18891 this.any_language_server_supports_inlay_hints(buffer, cx)
18892 })
18893 })
18894 }
18895
18896 fn inlay_hints(
18897 &self,
18898 buffer_handle: Entity<Buffer>,
18899 range: Range<text::Anchor>,
18900 cx: &mut App,
18901 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
18902 Some(self.update(cx, |project, cx| {
18903 project.inlay_hints(buffer_handle, range, cx)
18904 }))
18905 }
18906
18907 fn resolve_inlay_hint(
18908 &self,
18909 hint: InlayHint,
18910 buffer_handle: Entity<Buffer>,
18911 server_id: LanguageServerId,
18912 cx: &mut App,
18913 ) -> Option<Task<anyhow::Result<InlayHint>>> {
18914 Some(self.update(cx, |project, cx| {
18915 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
18916 }))
18917 }
18918
18919 fn range_for_rename(
18920 &self,
18921 buffer: &Entity<Buffer>,
18922 position: text::Anchor,
18923 cx: &mut App,
18924 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
18925 Some(self.update(cx, |project, cx| {
18926 let buffer = buffer.clone();
18927 let task = project.prepare_rename(buffer.clone(), position, cx);
18928 cx.spawn(async move |_, cx| {
18929 Ok(match task.await? {
18930 PrepareRenameResponse::Success(range) => Some(range),
18931 PrepareRenameResponse::InvalidPosition => None,
18932 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
18933 // Fallback on using TreeSitter info to determine identifier range
18934 buffer.update(cx, |buffer, _| {
18935 let snapshot = buffer.snapshot();
18936 let (range, kind) = snapshot.surrounding_word(position);
18937 if kind != Some(CharKind::Word) {
18938 return None;
18939 }
18940 Some(
18941 snapshot.anchor_before(range.start)
18942 ..snapshot.anchor_after(range.end),
18943 )
18944 })?
18945 }
18946 })
18947 })
18948 }))
18949 }
18950
18951 fn perform_rename(
18952 &self,
18953 buffer: &Entity<Buffer>,
18954 position: text::Anchor,
18955 new_name: String,
18956 cx: &mut App,
18957 ) -> Option<Task<Result<ProjectTransaction>>> {
18958 Some(self.update(cx, |project, cx| {
18959 project.perform_rename(buffer.clone(), position, new_name, cx)
18960 }))
18961 }
18962}
18963
18964fn inlay_hint_settings(
18965 location: Anchor,
18966 snapshot: &MultiBufferSnapshot,
18967 cx: &mut Context<Editor>,
18968) -> InlayHintSettings {
18969 let file = snapshot.file_at(location);
18970 let language = snapshot.language_at(location).map(|l| l.name());
18971 language_settings(language, file, cx).inlay_hints
18972}
18973
18974fn consume_contiguous_rows(
18975 contiguous_row_selections: &mut Vec<Selection<Point>>,
18976 selection: &Selection<Point>,
18977 display_map: &DisplaySnapshot,
18978 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
18979) -> (MultiBufferRow, MultiBufferRow) {
18980 contiguous_row_selections.push(selection.clone());
18981 let start_row = MultiBufferRow(selection.start.row);
18982 let mut end_row = ending_row(selection, display_map);
18983
18984 while let Some(next_selection) = selections.peek() {
18985 if next_selection.start.row <= end_row.0 {
18986 end_row = ending_row(next_selection, display_map);
18987 contiguous_row_selections.push(selections.next().unwrap().clone());
18988 } else {
18989 break;
18990 }
18991 }
18992 (start_row, end_row)
18993}
18994
18995fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
18996 if next_selection.end.column > 0 || next_selection.is_empty() {
18997 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
18998 } else {
18999 MultiBufferRow(next_selection.end.row)
19000 }
19001}
19002
19003impl EditorSnapshot {
19004 pub fn remote_selections_in_range<'a>(
19005 &'a self,
19006 range: &'a Range<Anchor>,
19007 collaboration_hub: &dyn CollaborationHub,
19008 cx: &'a App,
19009 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19010 let participant_names = collaboration_hub.user_names(cx);
19011 let participant_indices = collaboration_hub.user_participant_indices(cx);
19012 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19013 let collaborators_by_replica_id = collaborators_by_peer_id
19014 .iter()
19015 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19016 .collect::<HashMap<_, _>>();
19017 self.buffer_snapshot
19018 .selections_in_range(range, false)
19019 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19020 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19021 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19022 let user_name = participant_names.get(&collaborator.user_id).cloned();
19023 Some(RemoteSelection {
19024 replica_id,
19025 selection,
19026 cursor_shape,
19027 line_mode,
19028 participant_index,
19029 peer_id: collaborator.peer_id,
19030 user_name,
19031 })
19032 })
19033 }
19034
19035 pub fn hunks_for_ranges(
19036 &self,
19037 ranges: impl IntoIterator<Item = Range<Point>>,
19038 ) -> Vec<MultiBufferDiffHunk> {
19039 let mut hunks = Vec::new();
19040 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19041 HashMap::default();
19042 for query_range in ranges {
19043 let query_rows =
19044 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19045 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19046 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19047 ) {
19048 // Include deleted hunks that are adjacent to the query range, because
19049 // otherwise they would be missed.
19050 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19051 if hunk.status().is_deleted() {
19052 intersects_range |= hunk.row_range.start == query_rows.end;
19053 intersects_range |= hunk.row_range.end == query_rows.start;
19054 }
19055 if intersects_range {
19056 if !processed_buffer_rows
19057 .entry(hunk.buffer_id)
19058 .or_default()
19059 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19060 {
19061 continue;
19062 }
19063 hunks.push(hunk);
19064 }
19065 }
19066 }
19067
19068 hunks
19069 }
19070
19071 fn display_diff_hunks_for_rows<'a>(
19072 &'a self,
19073 display_rows: Range<DisplayRow>,
19074 folded_buffers: &'a HashSet<BufferId>,
19075 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19076 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19077 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19078
19079 self.buffer_snapshot
19080 .diff_hunks_in_range(buffer_start..buffer_end)
19081 .filter_map(|hunk| {
19082 if folded_buffers.contains(&hunk.buffer_id) {
19083 return None;
19084 }
19085
19086 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19087 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19088
19089 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19090 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19091
19092 let display_hunk = if hunk_display_start.column() != 0 {
19093 DisplayDiffHunk::Folded {
19094 display_row: hunk_display_start.row(),
19095 }
19096 } else {
19097 let mut end_row = hunk_display_end.row();
19098 if hunk_display_end.column() > 0 {
19099 end_row.0 += 1;
19100 }
19101 let is_created_file = hunk.is_created_file();
19102 DisplayDiffHunk::Unfolded {
19103 status: hunk.status(),
19104 diff_base_byte_range: hunk.diff_base_byte_range,
19105 display_row_range: hunk_display_start.row()..end_row,
19106 multi_buffer_range: Anchor::range_in_buffer(
19107 hunk.excerpt_id,
19108 hunk.buffer_id,
19109 hunk.buffer_range,
19110 ),
19111 is_created_file,
19112 }
19113 };
19114
19115 Some(display_hunk)
19116 })
19117 }
19118
19119 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19120 self.display_snapshot.buffer_snapshot.language_at(position)
19121 }
19122
19123 pub fn is_focused(&self) -> bool {
19124 self.is_focused
19125 }
19126
19127 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19128 self.placeholder_text.as_ref()
19129 }
19130
19131 pub fn scroll_position(&self) -> gpui::Point<f32> {
19132 self.scroll_anchor.scroll_position(&self.display_snapshot)
19133 }
19134
19135 fn gutter_dimensions(
19136 &self,
19137 font_id: FontId,
19138 font_size: Pixels,
19139 max_line_number_width: Pixels,
19140 cx: &App,
19141 ) -> Option<GutterDimensions> {
19142 if !self.show_gutter {
19143 return None;
19144 }
19145
19146 let descent = cx.text_system().descent(font_id, font_size);
19147 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19148 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19149
19150 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19151 matches!(
19152 ProjectSettings::get_global(cx).git.git_gutter,
19153 Some(GitGutterSetting::TrackedFiles)
19154 )
19155 });
19156 let gutter_settings = EditorSettings::get_global(cx).gutter;
19157 let show_line_numbers = self
19158 .show_line_numbers
19159 .unwrap_or(gutter_settings.line_numbers);
19160 let line_gutter_width = if show_line_numbers {
19161 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19162 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19163 max_line_number_width.max(min_width_for_number_on_gutter)
19164 } else {
19165 0.0.into()
19166 };
19167
19168 let show_code_actions = self
19169 .show_code_actions
19170 .unwrap_or(gutter_settings.code_actions);
19171
19172 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19173 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19174
19175 let git_blame_entries_width =
19176 self.git_blame_gutter_max_author_length
19177 .map(|max_author_length| {
19178 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19179 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19180
19181 /// The number of characters to dedicate to gaps and margins.
19182 const SPACING_WIDTH: usize = 4;
19183
19184 let max_char_count = max_author_length.min(renderer.max_author_length())
19185 + ::git::SHORT_SHA_LENGTH
19186 + MAX_RELATIVE_TIMESTAMP.len()
19187 + SPACING_WIDTH;
19188
19189 em_advance * max_char_count
19190 });
19191
19192 let is_singleton = self.buffer_snapshot.is_singleton();
19193
19194 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19195 left_padding += if !is_singleton {
19196 em_width * 4.0
19197 } else if show_code_actions || show_runnables || show_breakpoints {
19198 em_width * 3.0
19199 } else if show_git_gutter && show_line_numbers {
19200 em_width * 2.0
19201 } else if show_git_gutter || show_line_numbers {
19202 em_width
19203 } else {
19204 px(0.)
19205 };
19206
19207 let shows_folds = is_singleton && gutter_settings.folds;
19208
19209 let right_padding = if shows_folds && show_line_numbers {
19210 em_width * 4.0
19211 } else if shows_folds || (!is_singleton && show_line_numbers) {
19212 em_width * 3.0
19213 } else if show_line_numbers {
19214 em_width
19215 } else {
19216 px(0.)
19217 };
19218
19219 Some(GutterDimensions {
19220 left_padding,
19221 right_padding,
19222 width: line_gutter_width + left_padding + right_padding,
19223 margin: -descent,
19224 git_blame_entries_width,
19225 })
19226 }
19227
19228 pub fn render_crease_toggle(
19229 &self,
19230 buffer_row: MultiBufferRow,
19231 row_contains_cursor: bool,
19232 editor: Entity<Editor>,
19233 window: &mut Window,
19234 cx: &mut App,
19235 ) -> Option<AnyElement> {
19236 let folded = self.is_line_folded(buffer_row);
19237 let mut is_foldable = false;
19238
19239 if let Some(crease) = self
19240 .crease_snapshot
19241 .query_row(buffer_row, &self.buffer_snapshot)
19242 {
19243 is_foldable = true;
19244 match crease {
19245 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
19246 if let Some(render_toggle) = render_toggle {
19247 let toggle_callback =
19248 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
19249 if folded {
19250 editor.update(cx, |editor, cx| {
19251 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
19252 });
19253 } else {
19254 editor.update(cx, |editor, cx| {
19255 editor.unfold_at(
19256 &crate::UnfoldAt { buffer_row },
19257 window,
19258 cx,
19259 )
19260 });
19261 }
19262 });
19263 return Some((render_toggle)(
19264 buffer_row,
19265 folded,
19266 toggle_callback,
19267 window,
19268 cx,
19269 ));
19270 }
19271 }
19272 }
19273 }
19274
19275 is_foldable |= self.starts_indent(buffer_row);
19276
19277 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19278 Some(
19279 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19280 .toggle_state(folded)
19281 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19282 if folded {
19283 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
19284 } else {
19285 this.fold_at(&FoldAt { buffer_row }, window, cx);
19286 }
19287 }))
19288 .into_any_element(),
19289 )
19290 } else {
19291 None
19292 }
19293 }
19294
19295 pub fn render_crease_trailer(
19296 &self,
19297 buffer_row: MultiBufferRow,
19298 window: &mut Window,
19299 cx: &mut App,
19300 ) -> Option<AnyElement> {
19301 let folded = self.is_line_folded(buffer_row);
19302 if let Crease::Inline { render_trailer, .. } = self
19303 .crease_snapshot
19304 .query_row(buffer_row, &self.buffer_snapshot)?
19305 {
19306 let render_trailer = render_trailer.as_ref()?;
19307 Some(render_trailer(buffer_row, folded, window, cx))
19308 } else {
19309 None
19310 }
19311 }
19312}
19313
19314impl Deref for EditorSnapshot {
19315 type Target = DisplaySnapshot;
19316
19317 fn deref(&self) -> &Self::Target {
19318 &self.display_snapshot
19319 }
19320}
19321
19322#[derive(Clone, Debug, PartialEq, Eq)]
19323pub enum EditorEvent {
19324 InputIgnored {
19325 text: Arc<str>,
19326 },
19327 InputHandled {
19328 utf16_range_to_replace: Option<Range<isize>>,
19329 text: Arc<str>,
19330 },
19331 ExcerptsAdded {
19332 buffer: Entity<Buffer>,
19333 predecessor: ExcerptId,
19334 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19335 },
19336 ExcerptsRemoved {
19337 ids: Vec<ExcerptId>,
19338 },
19339 BufferFoldToggled {
19340 ids: Vec<ExcerptId>,
19341 folded: bool,
19342 },
19343 ExcerptsEdited {
19344 ids: Vec<ExcerptId>,
19345 },
19346 ExcerptsExpanded {
19347 ids: Vec<ExcerptId>,
19348 },
19349 BufferEdited,
19350 Edited {
19351 transaction_id: clock::Lamport,
19352 },
19353 Reparsed(BufferId),
19354 Focused,
19355 FocusedIn,
19356 Blurred,
19357 DirtyChanged,
19358 Saved,
19359 TitleChanged,
19360 DiffBaseChanged,
19361 SelectionsChanged {
19362 local: bool,
19363 },
19364 ScrollPositionChanged {
19365 local: bool,
19366 autoscroll: bool,
19367 },
19368 Closed,
19369 TransactionUndone {
19370 transaction_id: clock::Lamport,
19371 },
19372 TransactionBegun {
19373 transaction_id: clock::Lamport,
19374 },
19375 Reloaded,
19376 CursorShapeChanged,
19377 PushedToNavHistory {
19378 anchor: Anchor,
19379 is_deactivate: bool,
19380 },
19381}
19382
19383impl EventEmitter<EditorEvent> for Editor {}
19384
19385impl Focusable for Editor {
19386 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19387 self.focus_handle.clone()
19388 }
19389}
19390
19391impl Render for Editor {
19392 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19393 let settings = ThemeSettings::get_global(cx);
19394
19395 let mut text_style = match self.mode {
19396 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19397 color: cx.theme().colors().editor_foreground,
19398 font_family: settings.ui_font.family.clone(),
19399 font_features: settings.ui_font.features.clone(),
19400 font_fallbacks: settings.ui_font.fallbacks.clone(),
19401 font_size: rems(0.875).into(),
19402 font_weight: settings.ui_font.weight,
19403 line_height: relative(settings.buffer_line_height.value()),
19404 ..Default::default()
19405 },
19406 EditorMode::Full => TextStyle {
19407 color: cx.theme().colors().editor_foreground,
19408 font_family: settings.buffer_font.family.clone(),
19409 font_features: settings.buffer_font.features.clone(),
19410 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19411 font_size: settings.buffer_font_size(cx).into(),
19412 font_weight: settings.buffer_font.weight,
19413 line_height: relative(settings.buffer_line_height.value()),
19414 ..Default::default()
19415 },
19416 };
19417 if let Some(text_style_refinement) = &self.text_style_refinement {
19418 text_style.refine(text_style_refinement)
19419 }
19420
19421 let background = match self.mode {
19422 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19423 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19424 EditorMode::Full => cx.theme().colors().editor_background,
19425 };
19426
19427 EditorElement::new(
19428 &cx.entity(),
19429 EditorStyle {
19430 background,
19431 local_player: cx.theme().players().local(),
19432 text: text_style,
19433 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19434 syntax: cx.theme().syntax().clone(),
19435 status: cx.theme().status().clone(),
19436 inlay_hints_style: make_inlay_hints_style(cx),
19437 inline_completion_styles: make_suggestion_styles(cx),
19438 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19439 },
19440 )
19441 }
19442}
19443
19444impl EntityInputHandler for Editor {
19445 fn text_for_range(
19446 &mut self,
19447 range_utf16: Range<usize>,
19448 adjusted_range: &mut Option<Range<usize>>,
19449 _: &mut Window,
19450 cx: &mut Context<Self>,
19451 ) -> Option<String> {
19452 let snapshot = self.buffer.read(cx).read(cx);
19453 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19454 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19455 if (start.0..end.0) != range_utf16 {
19456 adjusted_range.replace(start.0..end.0);
19457 }
19458 Some(snapshot.text_for_range(start..end).collect())
19459 }
19460
19461 fn selected_text_range(
19462 &mut self,
19463 ignore_disabled_input: bool,
19464 _: &mut Window,
19465 cx: &mut Context<Self>,
19466 ) -> Option<UTF16Selection> {
19467 // Prevent the IME menu from appearing when holding down an alphabetic key
19468 // while input is disabled.
19469 if !ignore_disabled_input && !self.input_enabled {
19470 return None;
19471 }
19472
19473 let selection = self.selections.newest::<OffsetUtf16>(cx);
19474 let range = selection.range();
19475
19476 Some(UTF16Selection {
19477 range: range.start.0..range.end.0,
19478 reversed: selection.reversed,
19479 })
19480 }
19481
19482 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19483 let snapshot = self.buffer.read(cx).read(cx);
19484 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19485 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19486 }
19487
19488 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19489 self.clear_highlights::<InputComposition>(cx);
19490 self.ime_transaction.take();
19491 }
19492
19493 fn replace_text_in_range(
19494 &mut self,
19495 range_utf16: Option<Range<usize>>,
19496 text: &str,
19497 window: &mut Window,
19498 cx: &mut Context<Self>,
19499 ) {
19500 if !self.input_enabled {
19501 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19502 return;
19503 }
19504
19505 self.transact(window, cx, |this, window, cx| {
19506 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19507 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19508 Some(this.selection_replacement_ranges(range_utf16, cx))
19509 } else {
19510 this.marked_text_ranges(cx)
19511 };
19512
19513 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19514 let newest_selection_id = this.selections.newest_anchor().id;
19515 this.selections
19516 .all::<OffsetUtf16>(cx)
19517 .iter()
19518 .zip(ranges_to_replace.iter())
19519 .find_map(|(selection, range)| {
19520 if selection.id == newest_selection_id {
19521 Some(
19522 (range.start.0 as isize - selection.head().0 as isize)
19523 ..(range.end.0 as isize - selection.head().0 as isize),
19524 )
19525 } else {
19526 None
19527 }
19528 })
19529 });
19530
19531 cx.emit(EditorEvent::InputHandled {
19532 utf16_range_to_replace: range_to_replace,
19533 text: text.into(),
19534 });
19535
19536 if let Some(new_selected_ranges) = new_selected_ranges {
19537 this.change_selections(None, window, cx, |selections| {
19538 selections.select_ranges(new_selected_ranges)
19539 });
19540 this.backspace(&Default::default(), window, cx);
19541 }
19542
19543 this.handle_input(text, window, cx);
19544 });
19545
19546 if let Some(transaction) = self.ime_transaction {
19547 self.buffer.update(cx, |buffer, cx| {
19548 buffer.group_until_transaction(transaction, cx);
19549 });
19550 }
19551
19552 self.unmark_text(window, cx);
19553 }
19554
19555 fn replace_and_mark_text_in_range(
19556 &mut self,
19557 range_utf16: Option<Range<usize>>,
19558 text: &str,
19559 new_selected_range_utf16: Option<Range<usize>>,
19560 window: &mut Window,
19561 cx: &mut Context<Self>,
19562 ) {
19563 if !self.input_enabled {
19564 return;
19565 }
19566
19567 let transaction = self.transact(window, cx, |this, window, cx| {
19568 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19569 let snapshot = this.buffer.read(cx).read(cx);
19570 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19571 for marked_range in &mut marked_ranges {
19572 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19573 marked_range.start.0 += relative_range_utf16.start;
19574 marked_range.start =
19575 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19576 marked_range.end =
19577 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19578 }
19579 }
19580 Some(marked_ranges)
19581 } else if let Some(range_utf16) = range_utf16 {
19582 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19583 Some(this.selection_replacement_ranges(range_utf16, cx))
19584 } else {
19585 None
19586 };
19587
19588 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19589 let newest_selection_id = this.selections.newest_anchor().id;
19590 this.selections
19591 .all::<OffsetUtf16>(cx)
19592 .iter()
19593 .zip(ranges_to_replace.iter())
19594 .find_map(|(selection, range)| {
19595 if selection.id == newest_selection_id {
19596 Some(
19597 (range.start.0 as isize - selection.head().0 as isize)
19598 ..(range.end.0 as isize - selection.head().0 as isize),
19599 )
19600 } else {
19601 None
19602 }
19603 })
19604 });
19605
19606 cx.emit(EditorEvent::InputHandled {
19607 utf16_range_to_replace: range_to_replace,
19608 text: text.into(),
19609 });
19610
19611 if let Some(ranges) = ranges_to_replace {
19612 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19613 }
19614
19615 let marked_ranges = {
19616 let snapshot = this.buffer.read(cx).read(cx);
19617 this.selections
19618 .disjoint_anchors()
19619 .iter()
19620 .map(|selection| {
19621 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19622 })
19623 .collect::<Vec<_>>()
19624 };
19625
19626 if text.is_empty() {
19627 this.unmark_text(window, cx);
19628 } else {
19629 this.highlight_text::<InputComposition>(
19630 marked_ranges.clone(),
19631 HighlightStyle {
19632 underline: Some(UnderlineStyle {
19633 thickness: px(1.),
19634 color: None,
19635 wavy: false,
19636 }),
19637 ..Default::default()
19638 },
19639 cx,
19640 );
19641 }
19642
19643 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19644 let use_autoclose = this.use_autoclose;
19645 let use_auto_surround = this.use_auto_surround;
19646 this.set_use_autoclose(false);
19647 this.set_use_auto_surround(false);
19648 this.handle_input(text, window, cx);
19649 this.set_use_autoclose(use_autoclose);
19650 this.set_use_auto_surround(use_auto_surround);
19651
19652 if let Some(new_selected_range) = new_selected_range_utf16 {
19653 let snapshot = this.buffer.read(cx).read(cx);
19654 let new_selected_ranges = marked_ranges
19655 .into_iter()
19656 .map(|marked_range| {
19657 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19658 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19659 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19660 snapshot.clip_offset_utf16(new_start, Bias::Left)
19661 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19662 })
19663 .collect::<Vec<_>>();
19664
19665 drop(snapshot);
19666 this.change_selections(None, window, cx, |selections| {
19667 selections.select_ranges(new_selected_ranges)
19668 });
19669 }
19670 });
19671
19672 self.ime_transaction = self.ime_transaction.or(transaction);
19673 if let Some(transaction) = self.ime_transaction {
19674 self.buffer.update(cx, |buffer, cx| {
19675 buffer.group_until_transaction(transaction, cx);
19676 });
19677 }
19678
19679 if self.text_highlights::<InputComposition>(cx).is_none() {
19680 self.ime_transaction.take();
19681 }
19682 }
19683
19684 fn bounds_for_range(
19685 &mut self,
19686 range_utf16: Range<usize>,
19687 element_bounds: gpui::Bounds<Pixels>,
19688 window: &mut Window,
19689 cx: &mut Context<Self>,
19690 ) -> Option<gpui::Bounds<Pixels>> {
19691 let text_layout_details = self.text_layout_details(window);
19692 let gpui::Size {
19693 width: em_width,
19694 height: line_height,
19695 } = self.character_size(window);
19696
19697 let snapshot = self.snapshot(window, cx);
19698 let scroll_position = snapshot.scroll_position();
19699 let scroll_left = scroll_position.x * em_width;
19700
19701 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19702 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19703 + self.gutter_dimensions.width
19704 + self.gutter_dimensions.margin;
19705 let y = line_height * (start.row().as_f32() - scroll_position.y);
19706
19707 Some(Bounds {
19708 origin: element_bounds.origin + point(x, y),
19709 size: size(em_width, line_height),
19710 })
19711 }
19712
19713 fn character_index_for_point(
19714 &mut self,
19715 point: gpui::Point<Pixels>,
19716 _window: &mut Window,
19717 _cx: &mut Context<Self>,
19718 ) -> Option<usize> {
19719 let position_map = self.last_position_map.as_ref()?;
19720 if !position_map.text_hitbox.contains(&point) {
19721 return None;
19722 }
19723 let display_point = position_map.point_for_position(point).previous_valid;
19724 let anchor = position_map
19725 .snapshot
19726 .display_point_to_anchor(display_point, Bias::Left);
19727 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19728 Some(utf16_offset.0)
19729 }
19730}
19731
19732trait SelectionExt {
19733 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19734 fn spanned_rows(
19735 &self,
19736 include_end_if_at_line_start: bool,
19737 map: &DisplaySnapshot,
19738 ) -> Range<MultiBufferRow>;
19739}
19740
19741impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19742 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19743 let start = self
19744 .start
19745 .to_point(&map.buffer_snapshot)
19746 .to_display_point(map);
19747 let end = self
19748 .end
19749 .to_point(&map.buffer_snapshot)
19750 .to_display_point(map);
19751 if self.reversed {
19752 end..start
19753 } else {
19754 start..end
19755 }
19756 }
19757
19758 fn spanned_rows(
19759 &self,
19760 include_end_if_at_line_start: bool,
19761 map: &DisplaySnapshot,
19762 ) -> Range<MultiBufferRow> {
19763 let start = self.start.to_point(&map.buffer_snapshot);
19764 let mut end = self.end.to_point(&map.buffer_snapshot);
19765 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19766 end.row -= 1;
19767 }
19768
19769 let buffer_start = map.prev_line_boundary(start).0;
19770 let buffer_end = map.next_line_boundary(end).0;
19771 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19772 }
19773}
19774
19775impl<T: InvalidationRegion> InvalidationStack<T> {
19776 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19777 where
19778 S: Clone + ToOffset,
19779 {
19780 while let Some(region) = self.last() {
19781 let all_selections_inside_invalidation_ranges =
19782 if selections.len() == region.ranges().len() {
19783 selections
19784 .iter()
19785 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19786 .all(|(selection, invalidation_range)| {
19787 let head = selection.head().to_offset(buffer);
19788 invalidation_range.start <= head && invalidation_range.end >= head
19789 })
19790 } else {
19791 false
19792 };
19793
19794 if all_selections_inside_invalidation_ranges {
19795 break;
19796 } else {
19797 self.pop();
19798 }
19799 }
19800 }
19801}
19802
19803impl<T> Default for InvalidationStack<T> {
19804 fn default() -> Self {
19805 Self(Default::default())
19806 }
19807}
19808
19809impl<T> Deref for InvalidationStack<T> {
19810 type Target = Vec<T>;
19811
19812 fn deref(&self) -> &Self::Target {
19813 &self.0
19814 }
19815}
19816
19817impl<T> DerefMut for InvalidationStack<T> {
19818 fn deref_mut(&mut self) -> &mut Self::Target {
19819 &mut self.0
19820 }
19821}
19822
19823impl InvalidationRegion for SnippetState {
19824 fn ranges(&self) -> &[Range<Anchor>] {
19825 &self.ranges[self.active_index]
19826 }
19827}
19828
19829pub fn diagnostic_block_renderer(
19830 diagnostic: Diagnostic,
19831 max_message_rows: Option<u8>,
19832 allow_closing: bool,
19833) -> RenderBlock {
19834 let (text_without_backticks, code_ranges) =
19835 highlight_diagnostic_message(&diagnostic, max_message_rows);
19836
19837 Arc::new(move |cx: &mut BlockContext| {
19838 let group_id: SharedString = cx.block_id.to_string().into();
19839
19840 let mut text_style = cx.window.text_style().clone();
19841 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19842 let theme_settings = ThemeSettings::get_global(cx);
19843 text_style.font_family = theme_settings.buffer_font.family.clone();
19844 text_style.font_style = theme_settings.buffer_font.style;
19845 text_style.font_features = theme_settings.buffer_font.features.clone();
19846 text_style.font_weight = theme_settings.buffer_font.weight;
19847
19848 let multi_line_diagnostic = diagnostic.message.contains('\n');
19849
19850 let buttons = |diagnostic: &Diagnostic| {
19851 if multi_line_diagnostic {
19852 v_flex()
19853 } else {
19854 h_flex()
19855 }
19856 .when(allow_closing, |div| {
19857 div.children(diagnostic.is_primary.then(|| {
19858 IconButton::new("close-block", IconName::XCircle)
19859 .icon_color(Color::Muted)
19860 .size(ButtonSize::Compact)
19861 .style(ButtonStyle::Transparent)
19862 .visible_on_hover(group_id.clone())
19863 .on_click(move |_click, window, cx| {
19864 window.dispatch_action(Box::new(Cancel), cx)
19865 })
19866 .tooltip(|window, cx| {
19867 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19868 })
19869 }))
19870 })
19871 .child(
19872 IconButton::new("copy-block", IconName::Copy)
19873 .icon_color(Color::Muted)
19874 .size(ButtonSize::Compact)
19875 .style(ButtonStyle::Transparent)
19876 .visible_on_hover(group_id.clone())
19877 .on_click({
19878 let message = diagnostic.message.clone();
19879 move |_click, _, cx| {
19880 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19881 }
19882 })
19883 .tooltip(Tooltip::text("Copy diagnostic message")),
19884 )
19885 };
19886
19887 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19888 AvailableSpace::min_size(),
19889 cx.window,
19890 cx.app,
19891 );
19892
19893 h_flex()
19894 .id(cx.block_id)
19895 .group(group_id.clone())
19896 .relative()
19897 .size_full()
19898 .block_mouse_down()
19899 .pl(cx.gutter_dimensions.width)
19900 .w(cx.max_width - cx.gutter_dimensions.full_width())
19901 .child(
19902 div()
19903 .flex()
19904 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
19905 .flex_shrink(),
19906 )
19907 .child(buttons(&diagnostic))
19908 .child(div().flex().flex_shrink_0().child(
19909 StyledText::new(text_without_backticks.clone()).with_default_highlights(
19910 &text_style,
19911 code_ranges.iter().map(|range| {
19912 (
19913 range.clone(),
19914 HighlightStyle {
19915 font_weight: Some(FontWeight::BOLD),
19916 ..Default::default()
19917 },
19918 )
19919 }),
19920 ),
19921 ))
19922 .into_any_element()
19923 })
19924}
19925
19926fn inline_completion_edit_text(
19927 current_snapshot: &BufferSnapshot,
19928 edits: &[(Range<Anchor>, String)],
19929 edit_preview: &EditPreview,
19930 include_deletions: bool,
19931 cx: &App,
19932) -> HighlightedText {
19933 let edits = edits
19934 .iter()
19935 .map(|(anchor, text)| {
19936 (
19937 anchor.start.text_anchor..anchor.end.text_anchor,
19938 text.clone(),
19939 )
19940 })
19941 .collect::<Vec<_>>();
19942
19943 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
19944}
19945
19946pub fn highlight_diagnostic_message(
19947 diagnostic: &Diagnostic,
19948 mut max_message_rows: Option<u8>,
19949) -> (SharedString, Vec<Range<usize>>) {
19950 let mut text_without_backticks = String::new();
19951 let mut code_ranges = Vec::new();
19952
19953 if let Some(source) = &diagnostic.source {
19954 text_without_backticks.push_str(source);
19955 code_ranges.push(0..source.len());
19956 text_without_backticks.push_str(": ");
19957 }
19958
19959 let mut prev_offset = 0;
19960 let mut in_code_block = false;
19961 let has_row_limit = max_message_rows.is_some();
19962 let mut newline_indices = diagnostic
19963 .message
19964 .match_indices('\n')
19965 .filter(|_| has_row_limit)
19966 .map(|(ix, _)| ix)
19967 .fuse()
19968 .peekable();
19969
19970 for (quote_ix, _) in diagnostic
19971 .message
19972 .match_indices('`')
19973 .chain([(diagnostic.message.len(), "")])
19974 {
19975 let mut first_newline_ix = None;
19976 let mut last_newline_ix = None;
19977 while let Some(newline_ix) = newline_indices.peek() {
19978 if *newline_ix < quote_ix {
19979 if first_newline_ix.is_none() {
19980 first_newline_ix = Some(*newline_ix);
19981 }
19982 last_newline_ix = Some(*newline_ix);
19983
19984 if let Some(rows_left) = &mut max_message_rows {
19985 if *rows_left == 0 {
19986 break;
19987 } else {
19988 *rows_left -= 1;
19989 }
19990 }
19991 let _ = newline_indices.next();
19992 } else {
19993 break;
19994 }
19995 }
19996 let prev_len = text_without_backticks.len();
19997 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
19998 text_without_backticks.push_str(new_text);
19999 if in_code_block {
20000 code_ranges.push(prev_len..text_without_backticks.len());
20001 }
20002 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
20003 in_code_block = !in_code_block;
20004 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
20005 text_without_backticks.push_str("...");
20006 break;
20007 }
20008 }
20009
20010 (text_without_backticks.into(), code_ranges)
20011}
20012
20013fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20014 match severity {
20015 DiagnosticSeverity::ERROR => colors.error,
20016 DiagnosticSeverity::WARNING => colors.warning,
20017 DiagnosticSeverity::INFORMATION => colors.info,
20018 DiagnosticSeverity::HINT => colors.info,
20019 _ => colors.ignored,
20020 }
20021}
20022
20023pub fn styled_runs_for_code_label<'a>(
20024 label: &'a CodeLabel,
20025 syntax_theme: &'a theme::SyntaxTheme,
20026) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20027 let fade_out = HighlightStyle {
20028 fade_out: Some(0.35),
20029 ..Default::default()
20030 };
20031
20032 let mut prev_end = label.filter_range.end;
20033 label
20034 .runs
20035 .iter()
20036 .enumerate()
20037 .flat_map(move |(ix, (range, highlight_id))| {
20038 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20039 style
20040 } else {
20041 return Default::default();
20042 };
20043 let mut muted_style = style;
20044 muted_style.highlight(fade_out);
20045
20046 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20047 if range.start >= label.filter_range.end {
20048 if range.start > prev_end {
20049 runs.push((prev_end..range.start, fade_out));
20050 }
20051 runs.push((range.clone(), muted_style));
20052 } else if range.end <= label.filter_range.end {
20053 runs.push((range.clone(), style));
20054 } else {
20055 runs.push((range.start..label.filter_range.end, style));
20056 runs.push((label.filter_range.end..range.end, muted_style));
20057 }
20058 prev_end = cmp::max(prev_end, range.end);
20059
20060 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20061 runs.push((prev_end..label.text.len(), fade_out));
20062 }
20063
20064 runs
20065 })
20066}
20067
20068pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20069 let mut prev_index = 0;
20070 let mut prev_codepoint: Option<char> = None;
20071 text.char_indices()
20072 .chain([(text.len(), '\0')])
20073 .filter_map(move |(index, codepoint)| {
20074 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20075 let is_boundary = index == text.len()
20076 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20077 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20078 if is_boundary {
20079 let chunk = &text[prev_index..index];
20080 prev_index = index;
20081 Some(chunk)
20082 } else {
20083 None
20084 }
20085 })
20086}
20087
20088pub trait RangeToAnchorExt: Sized {
20089 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20090
20091 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20092 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20093 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20094 }
20095}
20096
20097impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20098 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20099 let start_offset = self.start.to_offset(snapshot);
20100 let end_offset = self.end.to_offset(snapshot);
20101 if start_offset == end_offset {
20102 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20103 } else {
20104 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20105 }
20106 }
20107}
20108
20109pub trait RowExt {
20110 fn as_f32(&self) -> f32;
20111
20112 fn next_row(&self) -> Self;
20113
20114 fn previous_row(&self) -> Self;
20115
20116 fn minus(&self, other: Self) -> u32;
20117}
20118
20119impl RowExt for DisplayRow {
20120 fn as_f32(&self) -> f32 {
20121 self.0 as f32
20122 }
20123
20124 fn next_row(&self) -> Self {
20125 Self(self.0 + 1)
20126 }
20127
20128 fn previous_row(&self) -> Self {
20129 Self(self.0.saturating_sub(1))
20130 }
20131
20132 fn minus(&self, other: Self) -> u32 {
20133 self.0 - other.0
20134 }
20135}
20136
20137impl RowExt for MultiBufferRow {
20138 fn as_f32(&self) -> f32 {
20139 self.0 as f32
20140 }
20141
20142 fn next_row(&self) -> Self {
20143 Self(self.0 + 1)
20144 }
20145
20146 fn previous_row(&self) -> Self {
20147 Self(self.0.saturating_sub(1))
20148 }
20149
20150 fn minus(&self, other: Self) -> u32 {
20151 self.0 - other.0
20152 }
20153}
20154
20155trait RowRangeExt {
20156 type Row;
20157
20158 fn len(&self) -> usize;
20159
20160 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20161}
20162
20163impl RowRangeExt for Range<MultiBufferRow> {
20164 type Row = MultiBufferRow;
20165
20166 fn len(&self) -> usize {
20167 (self.end.0 - self.start.0) as usize
20168 }
20169
20170 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20171 (self.start.0..self.end.0).map(MultiBufferRow)
20172 }
20173}
20174
20175impl RowRangeExt for Range<DisplayRow> {
20176 type Row = DisplayRow;
20177
20178 fn len(&self) -> usize {
20179 (self.end.0 - self.start.0) as usize
20180 }
20181
20182 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20183 (self.start.0..self.end.0).map(DisplayRow)
20184 }
20185}
20186
20187/// If select range has more than one line, we
20188/// just point the cursor to range.start.
20189fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20190 if range.start.row == range.end.row {
20191 range
20192 } else {
20193 range.start..range.start
20194 }
20195}
20196pub struct KillRing(ClipboardItem);
20197impl Global for KillRing {}
20198
20199const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20200
20201enum BreakpointPromptEditAction {
20202 Log,
20203 Condition,
20204 HitCondition,
20205}
20206
20207struct BreakpointPromptEditor {
20208 pub(crate) prompt: Entity<Editor>,
20209 editor: WeakEntity<Editor>,
20210 breakpoint_anchor: Anchor,
20211 breakpoint: Breakpoint,
20212 edit_action: BreakpointPromptEditAction,
20213 block_ids: HashSet<CustomBlockId>,
20214 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20215 _subscriptions: Vec<Subscription>,
20216}
20217
20218impl BreakpointPromptEditor {
20219 const MAX_LINES: u8 = 4;
20220
20221 fn new(
20222 editor: WeakEntity<Editor>,
20223 breakpoint_anchor: Anchor,
20224 breakpoint: Breakpoint,
20225 edit_action: BreakpointPromptEditAction,
20226 window: &mut Window,
20227 cx: &mut Context<Self>,
20228 ) -> Self {
20229 let base_text = match edit_action {
20230 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20231 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20232 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20233 }
20234 .map(|msg| msg.to_string())
20235 .unwrap_or_default();
20236
20237 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20238 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20239
20240 let prompt = cx.new(|cx| {
20241 let mut prompt = Editor::new(
20242 EditorMode::AutoHeight {
20243 max_lines: Self::MAX_LINES as usize,
20244 },
20245 buffer,
20246 None,
20247 window,
20248 cx,
20249 );
20250 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20251 prompt.set_show_cursor_when_unfocused(false, cx);
20252 prompt.set_placeholder_text(
20253 match edit_action {
20254 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20255 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20256 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20257 },
20258 cx,
20259 );
20260
20261 prompt
20262 });
20263
20264 Self {
20265 prompt,
20266 editor,
20267 breakpoint_anchor,
20268 breakpoint,
20269 edit_action,
20270 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20271 block_ids: Default::default(),
20272 _subscriptions: vec![],
20273 }
20274 }
20275
20276 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20277 self.block_ids.extend(block_ids)
20278 }
20279
20280 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20281 if let Some(editor) = self.editor.upgrade() {
20282 let message = self
20283 .prompt
20284 .read(cx)
20285 .buffer
20286 .read(cx)
20287 .as_singleton()
20288 .expect("A multi buffer in breakpoint prompt isn't possible")
20289 .read(cx)
20290 .as_rope()
20291 .to_string();
20292
20293 editor.update(cx, |editor, cx| {
20294 editor.edit_breakpoint_at_anchor(
20295 self.breakpoint_anchor,
20296 self.breakpoint.clone(),
20297 match self.edit_action {
20298 BreakpointPromptEditAction::Log => {
20299 BreakpointEditAction::EditLogMessage(message.into())
20300 }
20301 BreakpointPromptEditAction::Condition => {
20302 BreakpointEditAction::EditCondition(message.into())
20303 }
20304 BreakpointPromptEditAction::HitCondition => {
20305 BreakpointEditAction::EditHitCondition(message.into())
20306 }
20307 },
20308 cx,
20309 );
20310
20311 editor.remove_blocks(self.block_ids.clone(), None, cx);
20312 cx.focus_self(window);
20313 });
20314 }
20315 }
20316
20317 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20318 self.editor
20319 .update(cx, |editor, cx| {
20320 editor.remove_blocks(self.block_ids.clone(), None, cx);
20321 window.focus(&editor.focus_handle);
20322 })
20323 .log_err();
20324 }
20325
20326 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20327 let settings = ThemeSettings::get_global(cx);
20328 let text_style = TextStyle {
20329 color: if self.prompt.read(cx).read_only(cx) {
20330 cx.theme().colors().text_disabled
20331 } else {
20332 cx.theme().colors().text
20333 },
20334 font_family: settings.buffer_font.family.clone(),
20335 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20336 font_size: settings.buffer_font_size(cx).into(),
20337 font_weight: settings.buffer_font.weight,
20338 line_height: relative(settings.buffer_line_height.value()),
20339 ..Default::default()
20340 };
20341 EditorElement::new(
20342 &self.prompt,
20343 EditorStyle {
20344 background: cx.theme().colors().editor_background,
20345 local_player: cx.theme().players().local(),
20346 text: text_style,
20347 ..Default::default()
20348 },
20349 )
20350 }
20351}
20352
20353impl Render for BreakpointPromptEditor {
20354 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20355 let gutter_dimensions = *self.gutter_dimensions.lock();
20356 h_flex()
20357 .key_context("Editor")
20358 .bg(cx.theme().colors().editor_background)
20359 .border_y_1()
20360 .border_color(cx.theme().status().info_border)
20361 .size_full()
20362 .py(window.line_height() / 2.5)
20363 .on_action(cx.listener(Self::confirm))
20364 .on_action(cx.listener(Self::cancel))
20365 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20366 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20367 }
20368}
20369
20370impl Focusable for BreakpointPromptEditor {
20371 fn focus_handle(&self, cx: &App) -> FocusHandle {
20372 self.prompt.focus_handle(cx)
20373 }
20374}
20375
20376fn all_edits_insertions_or_deletions(
20377 edits: &Vec<(Range<Anchor>, String)>,
20378 snapshot: &MultiBufferSnapshot,
20379) -> bool {
20380 let mut all_insertions = true;
20381 let mut all_deletions = true;
20382
20383 for (range, new_text) in edits.iter() {
20384 let range_is_empty = range.to_offset(&snapshot).is_empty();
20385 let text_is_empty = new_text.is_empty();
20386
20387 if range_is_empty != text_is_empty {
20388 if range_is_empty {
20389 all_deletions = false;
20390 } else {
20391 all_insertions = false;
20392 }
20393 } else {
20394 return false;
20395 }
20396
20397 if !all_insertions && !all_deletions {
20398 return false;
20399 }
20400 }
20401 all_insertions || all_deletions
20402}
20403
20404struct MissingEditPredictionKeybindingTooltip;
20405
20406impl Render for MissingEditPredictionKeybindingTooltip {
20407 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20408 ui::tooltip_container(window, cx, |container, _, cx| {
20409 container
20410 .flex_shrink_0()
20411 .max_w_80()
20412 .min_h(rems_from_px(124.))
20413 .justify_between()
20414 .child(
20415 v_flex()
20416 .flex_1()
20417 .text_ui_sm(cx)
20418 .child(Label::new("Conflict with Accept Keybinding"))
20419 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20420 )
20421 .child(
20422 h_flex()
20423 .pb_1()
20424 .gap_1()
20425 .items_end()
20426 .w_full()
20427 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20428 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20429 }))
20430 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20431 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20432 })),
20433 )
20434 })
20435 }
20436}
20437
20438#[derive(Debug, Clone, Copy, PartialEq)]
20439pub struct LineHighlight {
20440 pub background: Background,
20441 pub border: Option<gpui::Hsla>,
20442}
20443
20444impl From<Hsla> for LineHighlight {
20445 fn from(hsla: Hsla) -> Self {
20446 Self {
20447 background: hsla.into(),
20448 border: None,
20449 }
20450 }
20451}
20452
20453impl From<Background> for LineHighlight {
20454 fn from(background: Background) -> Self {
20455 Self {
20456 background,
20457 border: None,
20458 }
20459 }
20460}
20461
20462fn render_diff_hunk_controls(
20463 row: u32,
20464 status: &DiffHunkStatus,
20465 hunk_range: Range<Anchor>,
20466 is_created_file: bool,
20467 line_height: Pixels,
20468 editor: &Entity<Editor>,
20469 _window: &mut Window,
20470 cx: &mut App,
20471) -> AnyElement {
20472 h_flex()
20473 .h(line_height)
20474 .mr_1()
20475 .gap_1()
20476 .px_0p5()
20477 .pb_1()
20478 .border_x_1()
20479 .border_b_1()
20480 .border_color(cx.theme().colors().border_variant)
20481 .rounded_b_lg()
20482 .bg(cx.theme().colors().editor_background)
20483 .gap_1()
20484 .occlude()
20485 .shadow_md()
20486 .child(if status.has_secondary_hunk() {
20487 Button::new(("stage", row as u64), "Stage")
20488 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20489 .tooltip({
20490 let focus_handle = editor.focus_handle(cx);
20491 move |window, cx| {
20492 Tooltip::for_action_in(
20493 "Stage Hunk",
20494 &::git::ToggleStaged,
20495 &focus_handle,
20496 window,
20497 cx,
20498 )
20499 }
20500 })
20501 .on_click({
20502 let editor = editor.clone();
20503 move |_event, _window, cx| {
20504 editor.update(cx, |editor, cx| {
20505 editor.stage_or_unstage_diff_hunks(
20506 true,
20507 vec![hunk_range.start..hunk_range.start],
20508 cx,
20509 );
20510 });
20511 }
20512 })
20513 } else {
20514 Button::new(("unstage", row as u64), "Unstage")
20515 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20516 .tooltip({
20517 let focus_handle = editor.focus_handle(cx);
20518 move |window, cx| {
20519 Tooltip::for_action_in(
20520 "Unstage Hunk",
20521 &::git::ToggleStaged,
20522 &focus_handle,
20523 window,
20524 cx,
20525 )
20526 }
20527 })
20528 .on_click({
20529 let editor = editor.clone();
20530 move |_event, _window, cx| {
20531 editor.update(cx, |editor, cx| {
20532 editor.stage_or_unstage_diff_hunks(
20533 false,
20534 vec![hunk_range.start..hunk_range.start],
20535 cx,
20536 );
20537 });
20538 }
20539 })
20540 })
20541 .child(
20542 Button::new(("restore", row as u64), "Restore")
20543 .tooltip({
20544 let focus_handle = editor.focus_handle(cx);
20545 move |window, cx| {
20546 Tooltip::for_action_in(
20547 "Restore Hunk",
20548 &::git::Restore,
20549 &focus_handle,
20550 window,
20551 cx,
20552 )
20553 }
20554 })
20555 .on_click({
20556 let editor = editor.clone();
20557 move |_event, window, cx| {
20558 editor.update(cx, |editor, cx| {
20559 let snapshot = editor.snapshot(window, cx);
20560 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20561 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20562 });
20563 }
20564 })
20565 .disabled(is_created_file),
20566 )
20567 .when(
20568 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20569 |el| {
20570 el.child(
20571 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20572 .shape(IconButtonShape::Square)
20573 .icon_size(IconSize::Small)
20574 // .disabled(!has_multiple_hunks)
20575 .tooltip({
20576 let focus_handle = editor.focus_handle(cx);
20577 move |window, cx| {
20578 Tooltip::for_action_in(
20579 "Next Hunk",
20580 &GoToHunk,
20581 &focus_handle,
20582 window,
20583 cx,
20584 )
20585 }
20586 })
20587 .on_click({
20588 let editor = editor.clone();
20589 move |_event, window, cx| {
20590 editor.update(cx, |editor, cx| {
20591 let snapshot = editor.snapshot(window, cx);
20592 let position =
20593 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20594 editor.go_to_hunk_before_or_after_position(
20595 &snapshot,
20596 position,
20597 Direction::Next,
20598 window,
20599 cx,
20600 );
20601 editor.expand_selected_diff_hunks(cx);
20602 });
20603 }
20604 }),
20605 )
20606 .child(
20607 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20608 .shape(IconButtonShape::Square)
20609 .icon_size(IconSize::Small)
20610 // .disabled(!has_multiple_hunks)
20611 .tooltip({
20612 let focus_handle = editor.focus_handle(cx);
20613 move |window, cx| {
20614 Tooltip::for_action_in(
20615 "Previous Hunk",
20616 &GoToPreviousHunk,
20617 &focus_handle,
20618 window,
20619 cx,
20620 )
20621 }
20622 })
20623 .on_click({
20624 let editor = editor.clone();
20625 move |_event, window, cx| {
20626 editor.update(cx, |editor, cx| {
20627 let snapshot = editor.snapshot(window, cx);
20628 let point =
20629 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20630 editor.go_to_hunk_before_or_after_position(
20631 &snapshot,
20632 point,
20633 Direction::Prev,
20634 window,
20635 cx,
20636 );
20637 editor.expand_selected_diff_hunks(cx);
20638 });
20639 }
20640 }),
20641 )
20642 },
20643 )
20644 .into_any_element()
20645}