1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod editor_tests;
44#[cfg(test)]
45mod inline_completion_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
52use aho_corasick::AhoCorasick;
53use anyhow::{Context as _, Result, anyhow};
54use blink_manager::BlinkManager;
55use buffer_diff::DiffHunkStatus;
56use client::{Collaborator, ParticipantIndex};
57use clock::ReplicaId;
58use collections::{BTreeMap, HashMap, HashSet, VecDeque};
59use convert_case::{Case, Casing};
60use display_map::*;
61pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
62use editor_settings::GoToDefinitionFallback;
63pub use editor_settings::{
64 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
65 ShowScrollbar,
66};
67pub use editor_settings_controls::*;
68use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
69pub use element::{
70 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
71};
72use feature_flags::{Debugger, FeatureFlagAppExt};
73use futures::{
74 FutureExt,
75 future::{self, Shared, join},
76};
77use fuzzy::StringMatchCandidate;
78
79use ::git::Restore;
80use code_context_menus::{
81 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
82 CompletionsMenu, ContextMenuOrigin,
83};
84use git::blame::{GitBlame, GlobalBlameRenderer};
85use gpui::{
86 Action, Animation, AnimationExt, AnyElement, AnyWeakEntity, App, AppContext,
87 AsyncWindowContext, AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry,
88 ClipboardItem, Context, DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter,
89 FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla,
90 KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render,
91 SharedString, Size, Stateful, Styled, StyledText, Subscription, Task, TextStyle,
92 TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity,
93 WeakFocusHandle, Window, div, impl_actions, point, prelude::*, pulsating_between, px, relative,
94 size,
95};
96use highlight_matching_bracket::refresh_matching_bracket_highlights;
97use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
98pub use hover_popover::hover_markdown_style;
99use hover_popover::{HoverState, hide_hover};
100use indent_guides::ActiveIndentGuidesState;
101use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
102pub use inline_completion::Direction;
103use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
104pub use items::MAX_TAB_TITLE_LEN;
105use itertools::Itertools;
106use language::{
107 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
108 CursorShape, Diagnostic, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
109 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
110 TransactionId, TreeSitterOptions, WordsQuery,
111 language_settings::{
112 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
113 all_language_settings, language_settings,
114 },
115 point_from_lsp, text_diff_with_options,
116};
117use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
118use linked_editing_ranges::refresh_linked_ranges;
119use mouse_context_menu::MouseContextMenu;
120use persistence::DB;
121use project::{
122 ProjectPath,
123 debugger::breakpoint_store::{
124 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
125 },
126};
127
128pub use git::blame::BlameRenderer;
129pub use proposed_changes_editor::{
130 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
131};
132use smallvec::smallvec;
133use std::{cell::OnceCell, iter::Peekable};
134use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
135
136pub use lsp::CompletionContext;
137use lsp::{
138 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
139 InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
140};
141
142use language::BufferSnapshot;
143pub use lsp_ext::lsp_tasks;
144use movement::TextLayoutDetails;
145pub use multi_buffer::{
146 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
147 ToOffset, ToPoint,
148};
149use multi_buffer::{
150 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
151 MultiOrSingleBufferOffsetRange, PathKey, ToOffsetUtf16,
152};
153use parking_lot::Mutex;
154use project::{
155 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
156 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
157 TaskSourceKind,
158 debugger::breakpoint_store::Breakpoint,
159 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
160 project_settings::{GitGutterSetting, ProjectSettings},
161};
162use rand::prelude::*;
163use rpc::{ErrorExt, proto::*};
164use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
165use selections_collection::{
166 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
167};
168use serde::{Deserialize, Serialize};
169use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
170use smallvec::SmallVec;
171use snippet::Snippet;
172use std::sync::Arc;
173use std::{
174 any::TypeId,
175 borrow::Cow,
176 cell::RefCell,
177 cmp::{self, Ordering, Reverse},
178 mem,
179 num::NonZeroU32,
180 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
181 path::{Path, PathBuf},
182 rc::Rc,
183 time::{Duration, Instant},
184};
185pub use sum_tree::Bias;
186use sum_tree::TreeMap;
187use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
188use theme::{
189 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
190 observe_buffer_font_size_adjustment,
191};
192use ui::{
193 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
194 IconSize, Key, Tooltip, h_flex, prelude::*,
195};
196use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
197use workspace::{
198 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
199 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
200 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
201 item::{ItemHandle, PreviewTabsSettings},
202 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
203 searchable::SearchEvent,
204};
205
206use crate::hover_links::{find_url, find_url_from_range};
207use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
208
209pub const FILE_HEADER_HEIGHT: u32 = 2;
210pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
211pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
212const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
213const MAX_LINE_LEN: usize = 1024;
214const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
215const MAX_SELECTION_HISTORY_LEN: usize = 1024;
216pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
217#[doc(hidden)]
218pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
219
220pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
221pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
222pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
223
224pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
225pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
226pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
227
228pub type RenderDiffHunkControlsFn = Arc<
229 dyn Fn(
230 u32,
231 &DiffHunkStatus,
232 Range<Anchor>,
233 bool,
234 Pixels,
235 &Entity<Editor>,
236 &mut Window,
237 &mut App,
238 ) -> AnyElement,
239>;
240
241const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
242 alt: true,
243 shift: true,
244 control: false,
245 platform: false,
246 function: false,
247};
248
249#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
250pub enum InlayId {
251 InlineCompletion(usize),
252 Hint(usize),
253}
254
255impl InlayId {
256 fn id(&self) -> usize {
257 match self {
258 Self::InlineCompletion(id) => *id,
259 Self::Hint(id) => *id,
260 }
261 }
262}
263
264pub enum DebugCurrentRowHighlight {}
265enum DocumentHighlightRead {}
266enum DocumentHighlightWrite {}
267enum InputComposition {}
268enum SelectedTextHighlight {}
269
270#[derive(Debug, Copy, Clone, PartialEq, Eq)]
271pub enum Navigated {
272 Yes,
273 No,
274}
275
276impl Navigated {
277 pub fn from_bool(yes: bool) -> Navigated {
278 if yes { Navigated::Yes } else { Navigated::No }
279 }
280}
281
282#[derive(Debug, Clone, PartialEq, Eq)]
283enum DisplayDiffHunk {
284 Folded {
285 display_row: DisplayRow,
286 },
287 Unfolded {
288 is_created_file: bool,
289 diff_base_byte_range: Range<usize>,
290 display_row_range: Range<DisplayRow>,
291 multi_buffer_range: Range<Anchor>,
292 status: DiffHunkStatus,
293 },
294}
295
296pub enum HideMouseCursorOrigin {
297 TypingAction,
298 MovementAction,
299}
300
301pub fn init_settings(cx: &mut App) {
302 EditorSettings::register(cx);
303}
304
305pub fn init(cx: &mut App) {
306 init_settings(cx);
307
308 cx.set_global(GlobalBlameRenderer(Arc::new(())));
309
310 workspace::register_project_item::<Editor>(cx);
311 workspace::FollowableViewRegistry::register::<Editor>(cx);
312 workspace::register_serializable_item::<Editor>(cx);
313
314 cx.observe_new(
315 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
316 workspace.register_action(Editor::new_file);
317 workspace.register_action(Editor::new_file_vertical);
318 workspace.register_action(Editor::new_file_horizontal);
319 workspace.register_action(Editor::cancel_language_server_work);
320 },
321 )
322 .detach();
323
324 cx.on_action(move |_: &workspace::NewFile, cx| {
325 let app_state = workspace::AppState::global(cx);
326 if let Some(app_state) = app_state.upgrade() {
327 workspace::open_new(
328 Default::default(),
329 app_state,
330 cx,
331 |workspace, window, cx| {
332 Editor::new_file(workspace, &Default::default(), window, cx)
333 },
334 )
335 .detach();
336 }
337 });
338 cx.on_action(move |_: &workspace::NewWindow, cx| {
339 let app_state = workspace::AppState::global(cx);
340 if let Some(app_state) = app_state.upgrade() {
341 workspace::open_new(
342 Default::default(),
343 app_state,
344 cx,
345 |workspace, window, cx| {
346 cx.activate(true);
347 Editor::new_file(workspace, &Default::default(), window, cx)
348 },
349 )
350 .detach();
351 }
352 });
353}
354
355pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
356 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
357}
358
359pub struct SearchWithinRange;
360
361trait InvalidationRegion {
362 fn ranges(&self) -> &[Range<Anchor>];
363}
364
365#[derive(Clone, Debug, PartialEq)]
366pub enum SelectPhase {
367 Begin {
368 position: DisplayPoint,
369 add: bool,
370 click_count: usize,
371 },
372 BeginColumnar {
373 position: DisplayPoint,
374 reset: bool,
375 goal_column: u32,
376 },
377 Extend {
378 position: DisplayPoint,
379 click_count: usize,
380 },
381 Update {
382 position: DisplayPoint,
383 goal_column: u32,
384 scroll_delta: gpui::Point<f32>,
385 },
386 End,
387}
388
389#[derive(Clone, Debug)]
390pub enum SelectMode {
391 Character,
392 Word(Range<Anchor>),
393 Line(Range<Anchor>),
394 All,
395}
396
397#[derive(Copy, Clone, PartialEq, Eq, Debug)]
398pub enum EditorMode {
399 SingleLine { auto_width: bool },
400 AutoHeight { max_lines: usize },
401 Full,
402}
403
404#[derive(Copy, Clone, Debug)]
405pub enum SoftWrap {
406 /// Prefer not to wrap at all.
407 ///
408 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
409 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
410 GitDiff,
411 /// Prefer a single line generally, unless an overly long line is encountered.
412 None,
413 /// Soft wrap lines that exceed the editor width.
414 EditorWidth,
415 /// Soft wrap lines at the preferred line length.
416 Column(u32),
417 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
418 Bounded(u32),
419}
420
421#[derive(Clone)]
422pub struct EditorStyle {
423 pub background: Hsla,
424 pub local_player: PlayerColor,
425 pub text: TextStyle,
426 pub scrollbar_width: Pixels,
427 pub syntax: Arc<SyntaxTheme>,
428 pub status: StatusColors,
429 pub inlay_hints_style: HighlightStyle,
430 pub inline_completion_styles: InlineCompletionStyles,
431 pub unnecessary_code_fade: f32,
432}
433
434impl Default for EditorStyle {
435 fn default() -> Self {
436 Self {
437 background: Hsla::default(),
438 local_player: PlayerColor::default(),
439 text: TextStyle::default(),
440 scrollbar_width: Pixels::default(),
441 syntax: Default::default(),
442 // HACK: Status colors don't have a real default.
443 // We should look into removing the status colors from the editor
444 // style and retrieve them directly from the theme.
445 status: StatusColors::dark(),
446 inlay_hints_style: HighlightStyle::default(),
447 inline_completion_styles: InlineCompletionStyles {
448 insertion: HighlightStyle::default(),
449 whitespace: HighlightStyle::default(),
450 },
451 unnecessary_code_fade: Default::default(),
452 }
453 }
454}
455
456pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
457 let show_background = language_settings::language_settings(None, None, cx)
458 .inlay_hints
459 .show_background;
460
461 HighlightStyle {
462 color: Some(cx.theme().status().hint),
463 background_color: show_background.then(|| cx.theme().status().hint_background),
464 ..HighlightStyle::default()
465 }
466}
467
468pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
469 InlineCompletionStyles {
470 insertion: HighlightStyle {
471 color: Some(cx.theme().status().predictive),
472 ..HighlightStyle::default()
473 },
474 whitespace: HighlightStyle {
475 background_color: Some(cx.theme().status().created_background),
476 ..HighlightStyle::default()
477 },
478 }
479}
480
481type CompletionId = usize;
482
483pub(crate) enum EditDisplayMode {
484 TabAccept,
485 DiffPopover,
486 Inline,
487}
488
489enum InlineCompletion {
490 Edit {
491 edits: Vec<(Range<Anchor>, String)>,
492 edit_preview: Option<EditPreview>,
493 display_mode: EditDisplayMode,
494 snapshot: BufferSnapshot,
495 },
496 Move {
497 target: Anchor,
498 snapshot: BufferSnapshot,
499 },
500}
501
502struct InlineCompletionState {
503 inlay_ids: Vec<InlayId>,
504 completion: InlineCompletion,
505 completion_id: Option<SharedString>,
506 invalidation_range: Range<Anchor>,
507}
508
509enum EditPredictionSettings {
510 Disabled,
511 Enabled {
512 show_in_menu: bool,
513 preview_requires_modifier: bool,
514 },
515}
516
517enum InlineCompletionHighlight {}
518
519#[derive(Debug, Clone)]
520struct InlineDiagnostic {
521 message: SharedString,
522 group_id: usize,
523 is_primary: bool,
524 start: Point,
525 severity: DiagnosticSeverity,
526}
527
528pub enum MenuInlineCompletionsPolicy {
529 Never,
530 ByProvider,
531}
532
533pub enum EditPredictionPreview {
534 /// Modifier is not pressed
535 Inactive { released_too_fast: bool },
536 /// Modifier pressed
537 Active {
538 since: Instant,
539 previous_scroll_position: Option<ScrollAnchor>,
540 },
541}
542
543impl EditPredictionPreview {
544 pub fn released_too_fast(&self) -> bool {
545 match self {
546 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
547 EditPredictionPreview::Active { .. } => false,
548 }
549 }
550
551 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
552 if let EditPredictionPreview::Active {
553 previous_scroll_position,
554 ..
555 } = self
556 {
557 *previous_scroll_position = scroll_position;
558 }
559 }
560}
561
562pub struct ContextMenuOptions {
563 pub min_entries_visible: usize,
564 pub max_entries_visible: usize,
565 pub placement: Option<ContextMenuPlacement>,
566}
567
568#[derive(Debug, Clone, PartialEq, Eq)]
569pub enum ContextMenuPlacement {
570 Above,
571 Below,
572}
573
574#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
575struct EditorActionId(usize);
576
577impl EditorActionId {
578 pub fn post_inc(&mut self) -> Self {
579 let answer = self.0;
580
581 *self = Self(answer + 1);
582
583 Self(answer)
584 }
585}
586
587// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
588// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
589
590type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
591type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
592
593#[derive(Default)]
594struct ScrollbarMarkerState {
595 scrollbar_size: Size<Pixels>,
596 dirty: bool,
597 markers: Arc<[PaintQuad]>,
598 pending_refresh: Option<Task<Result<()>>>,
599}
600
601impl ScrollbarMarkerState {
602 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
603 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
604 }
605}
606
607#[derive(Clone, Debug)]
608struct RunnableTasks {
609 templates: Vec<(TaskSourceKind, TaskTemplate)>,
610 offset: multi_buffer::Anchor,
611 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
612 column: u32,
613 // Values of all named captures, including those starting with '_'
614 extra_variables: HashMap<String, String>,
615 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
616 context_range: Range<BufferOffset>,
617}
618
619impl RunnableTasks {
620 fn resolve<'a>(
621 &'a self,
622 cx: &'a task::TaskContext,
623 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
624 self.templates.iter().filter_map(|(kind, template)| {
625 template
626 .resolve_task(&kind.to_id_base(), cx)
627 .map(|task| (kind.clone(), task))
628 })
629 }
630}
631
632#[derive(Clone)]
633struct ResolvedTasks {
634 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
635 position: Anchor,
636}
637
638#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
639struct BufferOffset(usize);
640
641// Addons allow storing per-editor state in other crates (e.g. Vim)
642pub trait Addon: 'static {
643 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
644
645 fn render_buffer_header_controls(
646 &self,
647 _: &ExcerptInfo,
648 _: &Window,
649 _: &App,
650 ) -> Option<AnyElement> {
651 None
652 }
653
654 fn to_any(&self) -> &dyn std::any::Any;
655}
656
657/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
658///
659/// See the [module level documentation](self) for more information.
660pub struct Editor {
661 focus_handle: FocusHandle,
662 last_focused_descendant: Option<WeakFocusHandle>,
663 /// The text buffer being edited
664 buffer: Entity<MultiBuffer>,
665 /// Map of how text in the buffer should be displayed.
666 /// Handles soft wraps, folds, fake inlay text insertions, etc.
667 pub display_map: Entity<DisplayMap>,
668 pub selections: SelectionsCollection,
669 pub scroll_manager: ScrollManager,
670 /// When inline assist editors are linked, they all render cursors because
671 /// typing enters text into each of them, even the ones that aren't focused.
672 pub(crate) show_cursor_when_unfocused: bool,
673 columnar_selection_tail: Option<Anchor>,
674 add_selections_state: Option<AddSelectionsState>,
675 select_next_state: Option<SelectNextState>,
676 select_prev_state: Option<SelectNextState>,
677 selection_history: SelectionHistory,
678 autoclose_regions: Vec<AutocloseRegion>,
679 snippet_stack: InvalidationStack<SnippetState>,
680 select_syntax_node_history: SelectSyntaxNodeHistory,
681 ime_transaction: Option<TransactionId>,
682 active_diagnostics: Option<ActiveDiagnosticGroup>,
683 show_inline_diagnostics: bool,
684 inline_diagnostics_update: Task<()>,
685 inline_diagnostics_enabled: bool,
686 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
687 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
688 hard_wrap: Option<usize>,
689
690 // TODO: make this a access method
691 pub project: Option<Entity<Project>>,
692 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
693 completion_provider: Option<Box<dyn CompletionProvider>>,
694 collaboration_hub: Option<Box<dyn CollaborationHub>>,
695 blink_manager: Entity<BlinkManager>,
696 show_cursor_names: bool,
697 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
698 pub show_local_selections: bool,
699 mode: EditorMode,
700 show_breadcrumbs: bool,
701 show_gutter: bool,
702 show_scrollbars: bool,
703 show_line_numbers: Option<bool>,
704 use_relative_line_numbers: Option<bool>,
705 show_git_diff_gutter: Option<bool>,
706 show_code_actions: Option<bool>,
707 show_runnables: Option<bool>,
708 show_breakpoints: Option<bool>,
709 show_wrap_guides: Option<bool>,
710 show_indent_guides: Option<bool>,
711 placeholder_text: Option<Arc<str>>,
712 highlight_order: usize,
713 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
714 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
715 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
716 scrollbar_marker_state: ScrollbarMarkerState,
717 active_indent_guides_state: ActiveIndentGuidesState,
718 nav_history: Option<ItemNavHistory>,
719 context_menu: RefCell<Option<CodeContextMenu>>,
720 context_menu_options: Option<ContextMenuOptions>,
721 mouse_context_menu: Option<MouseContextMenu>,
722 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
723 signature_help_state: SignatureHelpState,
724 auto_signature_help: Option<bool>,
725 find_all_references_task_sources: Vec<Anchor>,
726 next_completion_id: CompletionId,
727 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
728 code_actions_task: Option<Task<Result<()>>>,
729 selection_highlight_task: Option<Task<()>>,
730 document_highlights_task: Option<Task<()>>,
731 linked_editing_range_task: Option<Task<Option<()>>>,
732 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
733 pending_rename: Option<RenameState>,
734 searchable: bool,
735 cursor_shape: CursorShape,
736 current_line_highlight: Option<CurrentLineHighlight>,
737 collapse_matches: bool,
738 autoindent_mode: Option<AutoindentMode>,
739 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
740 input_enabled: bool,
741 use_modal_editing: bool,
742 read_only: bool,
743 leader_peer_id: Option<PeerId>,
744 remote_id: Option<ViewId>,
745 hover_state: HoverState,
746 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
747 gutter_hovered: bool,
748 hovered_link_state: Option<HoveredLinkState>,
749 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
750 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
751 active_inline_completion: Option<InlineCompletionState>,
752 /// Used to prevent flickering as the user types while the menu is open
753 stale_inline_completion_in_menu: Option<InlineCompletionState>,
754 edit_prediction_settings: EditPredictionSettings,
755 inline_completions_hidden_for_vim_mode: bool,
756 show_inline_completions_override: Option<bool>,
757 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
758 edit_prediction_preview: EditPredictionPreview,
759 edit_prediction_indent_conflict: bool,
760 edit_prediction_requires_modifier_in_indent_conflict: bool,
761 inlay_hint_cache: InlayHintCache,
762 next_inlay_id: usize,
763 _subscriptions: Vec<Subscription>,
764 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
765 gutter_dimensions: GutterDimensions,
766 style: Option<EditorStyle>,
767 text_style_refinement: Option<TextStyleRefinement>,
768 next_editor_action_id: EditorActionId,
769 editor_actions:
770 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
771 use_autoclose: bool,
772 use_auto_surround: bool,
773 auto_replace_emoji_shortcode: bool,
774 jsx_tag_auto_close_enabled_in_any_buffer: bool,
775 show_git_blame_gutter: bool,
776 show_git_blame_inline: bool,
777 show_git_blame_inline_delay_task: Option<Task<()>>,
778 pub git_blame_inline_tooltip: Option<AnyWeakEntity>,
779 git_blame_inline_enabled: bool,
780 render_diff_hunk_controls: RenderDiffHunkControlsFn,
781 serialize_dirty_buffers: bool,
782 show_selection_menu: Option<bool>,
783 blame: Option<Entity<GitBlame>>,
784 blame_subscription: Option<Subscription>,
785 custom_context_menu: Option<
786 Box<
787 dyn 'static
788 + Fn(
789 &mut Self,
790 DisplayPoint,
791 &mut Window,
792 &mut Context<Self>,
793 ) -> Option<Entity<ui::ContextMenu>>,
794 >,
795 >,
796 last_bounds: Option<Bounds<Pixels>>,
797 last_position_map: Option<Rc<PositionMap>>,
798 expect_bounds_change: Option<Bounds<Pixels>>,
799 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
800 tasks_update_task: Option<Task<()>>,
801 breakpoint_store: Option<Entity<BreakpointStore>>,
802 /// Allow's a user to create a breakpoint by selecting this indicator
803 /// It should be None while a user is not hovering over the gutter
804 /// Otherwise it represents the point that the breakpoint will be shown
805 gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
806 in_project_search: bool,
807 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
808 breadcrumb_header: Option<String>,
809 focused_block: Option<FocusedBlock>,
810 next_scroll_position: NextScrollCursorCenterTopBottom,
811 addons: HashMap<TypeId, Box<dyn Addon>>,
812 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
813 load_diff_task: Option<Shared<Task<()>>>,
814 selection_mark_mode: bool,
815 toggle_fold_multiple_buffers: Task<()>,
816 _scroll_cursor_center_top_bottom_task: Task<()>,
817 serialize_selections: Task<()>,
818 serialize_folds: Task<()>,
819 mouse_cursor_hidden: bool,
820 hide_mouse_mode: HideMouseMode,
821}
822
823#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
824enum NextScrollCursorCenterTopBottom {
825 #[default]
826 Center,
827 Top,
828 Bottom,
829}
830
831impl NextScrollCursorCenterTopBottom {
832 fn next(&self) -> Self {
833 match self {
834 Self::Center => Self::Top,
835 Self::Top => Self::Bottom,
836 Self::Bottom => Self::Center,
837 }
838 }
839}
840
841#[derive(Clone)]
842pub struct EditorSnapshot {
843 pub mode: EditorMode,
844 show_gutter: bool,
845 show_line_numbers: Option<bool>,
846 show_git_diff_gutter: Option<bool>,
847 show_code_actions: Option<bool>,
848 show_runnables: Option<bool>,
849 show_breakpoints: Option<bool>,
850 git_blame_gutter_max_author_length: Option<usize>,
851 pub display_snapshot: DisplaySnapshot,
852 pub placeholder_text: Option<Arc<str>>,
853 is_focused: bool,
854 scroll_anchor: ScrollAnchor,
855 ongoing_scroll: OngoingScroll,
856 current_line_highlight: CurrentLineHighlight,
857 gutter_hovered: bool,
858}
859
860#[derive(Default, Debug, Clone, Copy)]
861pub struct GutterDimensions {
862 pub left_padding: Pixels,
863 pub right_padding: Pixels,
864 pub width: Pixels,
865 pub margin: Pixels,
866 pub git_blame_entries_width: Option<Pixels>,
867}
868
869impl GutterDimensions {
870 /// The full width of the space taken up by the gutter.
871 pub fn full_width(&self) -> Pixels {
872 self.margin + self.width
873 }
874
875 /// The width of the space reserved for the fold indicators,
876 /// use alongside 'justify_end' and `gutter_width` to
877 /// right align content with the line numbers
878 pub fn fold_area_width(&self) -> Pixels {
879 self.margin + self.right_padding
880 }
881}
882
883#[derive(Debug)]
884pub struct RemoteSelection {
885 pub replica_id: ReplicaId,
886 pub selection: Selection<Anchor>,
887 pub cursor_shape: CursorShape,
888 pub peer_id: PeerId,
889 pub line_mode: bool,
890 pub participant_index: Option<ParticipantIndex>,
891 pub user_name: Option<SharedString>,
892}
893
894#[derive(Clone, Debug)]
895struct SelectionHistoryEntry {
896 selections: Arc<[Selection<Anchor>]>,
897 select_next_state: Option<SelectNextState>,
898 select_prev_state: Option<SelectNextState>,
899 add_selections_state: Option<AddSelectionsState>,
900}
901
902enum SelectionHistoryMode {
903 Normal,
904 Undoing,
905 Redoing,
906}
907
908#[derive(Clone, PartialEq, Eq, Hash)]
909struct HoveredCursor {
910 replica_id: u16,
911 selection_id: usize,
912}
913
914impl Default for SelectionHistoryMode {
915 fn default() -> Self {
916 Self::Normal
917 }
918}
919
920#[derive(Default)]
921struct SelectionHistory {
922 #[allow(clippy::type_complexity)]
923 selections_by_transaction:
924 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
925 mode: SelectionHistoryMode,
926 undo_stack: VecDeque<SelectionHistoryEntry>,
927 redo_stack: VecDeque<SelectionHistoryEntry>,
928}
929
930impl SelectionHistory {
931 fn insert_transaction(
932 &mut self,
933 transaction_id: TransactionId,
934 selections: Arc<[Selection<Anchor>]>,
935 ) {
936 self.selections_by_transaction
937 .insert(transaction_id, (selections, None));
938 }
939
940 #[allow(clippy::type_complexity)]
941 fn transaction(
942 &self,
943 transaction_id: TransactionId,
944 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
945 self.selections_by_transaction.get(&transaction_id)
946 }
947
948 #[allow(clippy::type_complexity)]
949 fn transaction_mut(
950 &mut self,
951 transaction_id: TransactionId,
952 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
953 self.selections_by_transaction.get_mut(&transaction_id)
954 }
955
956 fn push(&mut self, entry: SelectionHistoryEntry) {
957 if !entry.selections.is_empty() {
958 match self.mode {
959 SelectionHistoryMode::Normal => {
960 self.push_undo(entry);
961 self.redo_stack.clear();
962 }
963 SelectionHistoryMode::Undoing => self.push_redo(entry),
964 SelectionHistoryMode::Redoing => self.push_undo(entry),
965 }
966 }
967 }
968
969 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
970 if self
971 .undo_stack
972 .back()
973 .map_or(true, |e| e.selections != entry.selections)
974 {
975 self.undo_stack.push_back(entry);
976 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
977 self.undo_stack.pop_front();
978 }
979 }
980 }
981
982 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
983 if self
984 .redo_stack
985 .back()
986 .map_or(true, |e| e.selections != entry.selections)
987 {
988 self.redo_stack.push_back(entry);
989 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
990 self.redo_stack.pop_front();
991 }
992 }
993 }
994}
995
996struct RowHighlight {
997 index: usize,
998 range: Range<Anchor>,
999 color: Hsla,
1000 should_autoscroll: bool,
1001}
1002
1003#[derive(Clone, Debug)]
1004struct AddSelectionsState {
1005 above: bool,
1006 stack: Vec<usize>,
1007}
1008
1009#[derive(Clone)]
1010struct SelectNextState {
1011 query: AhoCorasick,
1012 wordwise: bool,
1013 done: bool,
1014}
1015
1016impl std::fmt::Debug for SelectNextState {
1017 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1018 f.debug_struct(std::any::type_name::<Self>())
1019 .field("wordwise", &self.wordwise)
1020 .field("done", &self.done)
1021 .finish()
1022 }
1023}
1024
1025#[derive(Debug)]
1026struct AutocloseRegion {
1027 selection_id: usize,
1028 range: Range<Anchor>,
1029 pair: BracketPair,
1030}
1031
1032#[derive(Debug)]
1033struct SnippetState {
1034 ranges: Vec<Vec<Range<Anchor>>>,
1035 active_index: usize,
1036 choices: Vec<Option<Vec<String>>>,
1037}
1038
1039#[doc(hidden)]
1040pub struct RenameState {
1041 pub range: Range<Anchor>,
1042 pub old_name: Arc<str>,
1043 pub editor: Entity<Editor>,
1044 block_id: CustomBlockId,
1045}
1046
1047struct InvalidationStack<T>(Vec<T>);
1048
1049struct RegisteredInlineCompletionProvider {
1050 provider: Arc<dyn InlineCompletionProviderHandle>,
1051 _subscription: Subscription,
1052}
1053
1054#[derive(Debug, PartialEq, Eq)]
1055struct ActiveDiagnosticGroup {
1056 primary_range: Range<Anchor>,
1057 primary_message: String,
1058 group_id: usize,
1059 blocks: HashMap<CustomBlockId, Diagnostic>,
1060 is_valid: bool,
1061}
1062
1063#[derive(Serialize, Deserialize, Clone, Debug)]
1064pub struct ClipboardSelection {
1065 /// The number of bytes in this selection.
1066 pub len: usize,
1067 /// Whether this was a full-line selection.
1068 pub is_entire_line: bool,
1069 /// The indentation of the first line when this content was originally copied.
1070 pub first_line_indent: u32,
1071}
1072
1073// selections, scroll behavior, was newest selection reversed
1074type SelectSyntaxNodeHistoryState = (
1075 Box<[Selection<usize>]>,
1076 SelectSyntaxNodeScrollBehavior,
1077 bool,
1078);
1079
1080#[derive(Default)]
1081struct SelectSyntaxNodeHistory {
1082 stack: Vec<SelectSyntaxNodeHistoryState>,
1083 // disable temporarily to allow changing selections without losing the stack
1084 pub disable_clearing: bool,
1085}
1086
1087impl SelectSyntaxNodeHistory {
1088 pub fn try_clear(&mut self) {
1089 if !self.disable_clearing {
1090 self.stack.clear();
1091 }
1092 }
1093
1094 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1095 self.stack.push(selection);
1096 }
1097
1098 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1099 self.stack.pop()
1100 }
1101}
1102
1103enum SelectSyntaxNodeScrollBehavior {
1104 CursorTop,
1105 FitSelection,
1106 CursorBottom,
1107}
1108
1109#[derive(Debug)]
1110pub(crate) struct NavigationData {
1111 cursor_anchor: Anchor,
1112 cursor_position: Point,
1113 scroll_anchor: ScrollAnchor,
1114 scroll_top_row: u32,
1115}
1116
1117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1118pub enum GotoDefinitionKind {
1119 Symbol,
1120 Declaration,
1121 Type,
1122 Implementation,
1123}
1124
1125#[derive(Debug, Clone)]
1126enum InlayHintRefreshReason {
1127 ModifiersChanged(bool),
1128 Toggle(bool),
1129 SettingsChange(InlayHintSettings),
1130 NewLinesShown,
1131 BufferEdited(HashSet<Arc<Language>>),
1132 RefreshRequested,
1133 ExcerptsRemoved(Vec<ExcerptId>),
1134}
1135
1136impl InlayHintRefreshReason {
1137 fn description(&self) -> &'static str {
1138 match self {
1139 Self::ModifiersChanged(_) => "modifiers changed",
1140 Self::Toggle(_) => "toggle",
1141 Self::SettingsChange(_) => "settings change",
1142 Self::NewLinesShown => "new lines shown",
1143 Self::BufferEdited(_) => "buffer edited",
1144 Self::RefreshRequested => "refresh requested",
1145 Self::ExcerptsRemoved(_) => "excerpts removed",
1146 }
1147 }
1148}
1149
1150pub enum FormatTarget {
1151 Buffers,
1152 Ranges(Vec<Range<MultiBufferPoint>>),
1153}
1154
1155pub(crate) struct FocusedBlock {
1156 id: BlockId,
1157 focus_handle: WeakFocusHandle,
1158}
1159
1160#[derive(Clone)]
1161enum JumpData {
1162 MultiBufferRow {
1163 row: MultiBufferRow,
1164 line_offset_from_top: u32,
1165 },
1166 MultiBufferPoint {
1167 excerpt_id: ExcerptId,
1168 position: Point,
1169 anchor: text::Anchor,
1170 line_offset_from_top: u32,
1171 },
1172}
1173
1174pub enum MultibufferSelectionMode {
1175 First,
1176 All,
1177}
1178
1179#[derive(Clone, Copy, Debug, Default)]
1180pub struct RewrapOptions {
1181 pub override_language_settings: bool,
1182 pub preserve_existing_whitespace: bool,
1183}
1184
1185impl Editor {
1186 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1187 let buffer = cx.new(|cx| Buffer::local("", cx));
1188 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1189 Self::new(
1190 EditorMode::SingleLine { auto_width: false },
1191 buffer,
1192 None,
1193 window,
1194 cx,
1195 )
1196 }
1197
1198 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1199 let buffer = cx.new(|cx| Buffer::local("", cx));
1200 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1201 Self::new(EditorMode::Full, buffer, None, window, cx)
1202 }
1203
1204 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1205 let buffer = cx.new(|cx| Buffer::local("", cx));
1206 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1207 Self::new(
1208 EditorMode::SingleLine { auto_width: true },
1209 buffer,
1210 None,
1211 window,
1212 cx,
1213 )
1214 }
1215
1216 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1217 let buffer = cx.new(|cx| Buffer::local("", cx));
1218 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1219 Self::new(
1220 EditorMode::AutoHeight { max_lines },
1221 buffer,
1222 None,
1223 window,
1224 cx,
1225 )
1226 }
1227
1228 pub fn for_buffer(
1229 buffer: Entity<Buffer>,
1230 project: Option<Entity<Project>>,
1231 window: &mut Window,
1232 cx: &mut Context<Self>,
1233 ) -> Self {
1234 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1235 Self::new(EditorMode::Full, buffer, project, window, cx)
1236 }
1237
1238 pub fn for_multibuffer(
1239 buffer: Entity<MultiBuffer>,
1240 project: Option<Entity<Project>>,
1241 window: &mut Window,
1242 cx: &mut Context<Self>,
1243 ) -> Self {
1244 Self::new(EditorMode::Full, buffer, project, window, cx)
1245 }
1246
1247 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1248 let mut clone = Self::new(
1249 self.mode,
1250 self.buffer.clone(),
1251 self.project.clone(),
1252 window,
1253 cx,
1254 );
1255 self.display_map.update(cx, |display_map, cx| {
1256 let snapshot = display_map.snapshot(cx);
1257 clone.display_map.update(cx, |display_map, cx| {
1258 display_map.set_state(&snapshot, cx);
1259 });
1260 });
1261 clone.folds_did_change(cx);
1262 clone.selections.clone_state(&self.selections);
1263 clone.scroll_manager.clone_state(&self.scroll_manager);
1264 clone.searchable = self.searchable;
1265 clone.read_only = self.read_only;
1266 clone
1267 }
1268
1269 pub fn new(
1270 mode: EditorMode,
1271 buffer: Entity<MultiBuffer>,
1272 project: Option<Entity<Project>>,
1273 window: &mut Window,
1274 cx: &mut Context<Self>,
1275 ) -> Self {
1276 let style = window.text_style();
1277 let font_size = style.font_size.to_pixels(window.rem_size());
1278 let editor = cx.entity().downgrade();
1279 let fold_placeholder = FoldPlaceholder {
1280 constrain_width: true,
1281 render: Arc::new(move |fold_id, fold_range, cx| {
1282 let editor = editor.clone();
1283 div()
1284 .id(fold_id)
1285 .bg(cx.theme().colors().ghost_element_background)
1286 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1287 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1288 .rounded_xs()
1289 .size_full()
1290 .cursor_pointer()
1291 .child("⋯")
1292 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1293 .on_click(move |_, _window, cx| {
1294 editor
1295 .update(cx, |editor, cx| {
1296 editor.unfold_ranges(
1297 &[fold_range.start..fold_range.end],
1298 true,
1299 false,
1300 cx,
1301 );
1302 cx.stop_propagation();
1303 })
1304 .ok();
1305 })
1306 .into_any()
1307 }),
1308 merge_adjacent: true,
1309 ..Default::default()
1310 };
1311 let display_map = cx.new(|cx| {
1312 DisplayMap::new(
1313 buffer.clone(),
1314 style.font(),
1315 font_size,
1316 None,
1317 FILE_HEADER_HEIGHT,
1318 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1319 fold_placeholder,
1320 cx,
1321 )
1322 });
1323
1324 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1325
1326 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1327
1328 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1329 .then(|| language_settings::SoftWrap::None);
1330
1331 let mut project_subscriptions = Vec::new();
1332 if mode == EditorMode::Full {
1333 if let Some(project) = project.as_ref() {
1334 project_subscriptions.push(cx.subscribe_in(
1335 project,
1336 window,
1337 |editor, _, event, window, cx| match event {
1338 project::Event::RefreshCodeLens => {
1339 // we always query lens with actions, without storing them, always refreshing them
1340 }
1341 project::Event::RefreshInlayHints => {
1342 editor
1343 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1344 }
1345 project::Event::SnippetEdit(id, snippet_edits) => {
1346 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1347 let focus_handle = editor.focus_handle(cx);
1348 if focus_handle.is_focused(window) {
1349 let snapshot = buffer.read(cx).snapshot();
1350 for (range, snippet) in snippet_edits {
1351 let editor_range =
1352 language::range_from_lsp(*range).to_offset(&snapshot);
1353 editor
1354 .insert_snippet(
1355 &[editor_range],
1356 snippet.clone(),
1357 window,
1358 cx,
1359 )
1360 .ok();
1361 }
1362 }
1363 }
1364 }
1365 _ => {}
1366 },
1367 ));
1368 if let Some(task_inventory) = project
1369 .read(cx)
1370 .task_store()
1371 .read(cx)
1372 .task_inventory()
1373 .cloned()
1374 {
1375 project_subscriptions.push(cx.observe_in(
1376 &task_inventory,
1377 window,
1378 |editor, _, window, cx| {
1379 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1380 },
1381 ));
1382 };
1383
1384 project_subscriptions.push(cx.subscribe_in(
1385 &project.read(cx).breakpoint_store(),
1386 window,
1387 |editor, _, event, window, cx| match event {
1388 BreakpointStoreEvent::ActiveDebugLineChanged => {
1389 if editor.go_to_active_debug_line(window, cx) {
1390 cx.stop_propagation();
1391 }
1392 }
1393 _ => {}
1394 },
1395 ));
1396 }
1397 }
1398
1399 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1400
1401 let inlay_hint_settings =
1402 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1403 let focus_handle = cx.focus_handle();
1404 cx.on_focus(&focus_handle, window, Self::handle_focus)
1405 .detach();
1406 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1407 .detach();
1408 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1409 .detach();
1410 cx.on_blur(&focus_handle, window, Self::handle_blur)
1411 .detach();
1412
1413 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1414 Some(false)
1415 } else {
1416 None
1417 };
1418
1419 let breakpoint_store = match (mode, project.as_ref()) {
1420 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1421 _ => None,
1422 };
1423
1424 let mut code_action_providers = Vec::new();
1425 let mut load_uncommitted_diff = None;
1426 if let Some(project) = project.clone() {
1427 load_uncommitted_diff = Some(
1428 get_uncommitted_diff_for_buffer(
1429 &project,
1430 buffer.read(cx).all_buffers(),
1431 buffer.clone(),
1432 cx,
1433 )
1434 .shared(),
1435 );
1436 code_action_providers.push(Rc::new(project) as Rc<_>);
1437 }
1438
1439 let mut this = Self {
1440 focus_handle,
1441 show_cursor_when_unfocused: false,
1442 last_focused_descendant: None,
1443 buffer: buffer.clone(),
1444 display_map: display_map.clone(),
1445 selections,
1446 scroll_manager: ScrollManager::new(cx),
1447 columnar_selection_tail: None,
1448 add_selections_state: None,
1449 select_next_state: None,
1450 select_prev_state: None,
1451 selection_history: Default::default(),
1452 autoclose_regions: Default::default(),
1453 snippet_stack: Default::default(),
1454 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1455 ime_transaction: Default::default(),
1456 active_diagnostics: None,
1457 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1458 inline_diagnostics_update: Task::ready(()),
1459 inline_diagnostics: Vec::new(),
1460 soft_wrap_mode_override,
1461 hard_wrap: None,
1462 completion_provider: project.clone().map(|project| Box::new(project) as _),
1463 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1464 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1465 project,
1466 blink_manager: blink_manager.clone(),
1467 show_local_selections: true,
1468 show_scrollbars: true,
1469 mode,
1470 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1471 show_gutter: mode == EditorMode::Full,
1472 show_line_numbers: None,
1473 use_relative_line_numbers: None,
1474 show_git_diff_gutter: None,
1475 show_code_actions: None,
1476 show_runnables: None,
1477 show_breakpoints: None,
1478 show_wrap_guides: None,
1479 show_indent_guides,
1480 placeholder_text: None,
1481 highlight_order: 0,
1482 highlighted_rows: HashMap::default(),
1483 background_highlights: Default::default(),
1484 gutter_highlights: TreeMap::default(),
1485 scrollbar_marker_state: ScrollbarMarkerState::default(),
1486 active_indent_guides_state: ActiveIndentGuidesState::default(),
1487 nav_history: None,
1488 context_menu: RefCell::new(None),
1489 context_menu_options: None,
1490 mouse_context_menu: None,
1491 completion_tasks: Default::default(),
1492 signature_help_state: SignatureHelpState::default(),
1493 auto_signature_help: None,
1494 find_all_references_task_sources: Vec::new(),
1495 next_completion_id: 0,
1496 next_inlay_id: 0,
1497 code_action_providers,
1498 available_code_actions: Default::default(),
1499 code_actions_task: Default::default(),
1500 selection_highlight_task: Default::default(),
1501 document_highlights_task: Default::default(),
1502 linked_editing_range_task: Default::default(),
1503 pending_rename: Default::default(),
1504 searchable: true,
1505 cursor_shape: EditorSettings::get_global(cx)
1506 .cursor_shape
1507 .unwrap_or_default(),
1508 current_line_highlight: None,
1509 autoindent_mode: Some(AutoindentMode::EachLine),
1510 collapse_matches: false,
1511 workspace: None,
1512 input_enabled: true,
1513 use_modal_editing: mode == EditorMode::Full,
1514 read_only: false,
1515 use_autoclose: true,
1516 use_auto_surround: true,
1517 auto_replace_emoji_shortcode: false,
1518 jsx_tag_auto_close_enabled_in_any_buffer: false,
1519 leader_peer_id: None,
1520 remote_id: None,
1521 hover_state: Default::default(),
1522 pending_mouse_down: None,
1523 hovered_link_state: Default::default(),
1524 edit_prediction_provider: None,
1525 active_inline_completion: None,
1526 stale_inline_completion_in_menu: None,
1527 edit_prediction_preview: EditPredictionPreview::Inactive {
1528 released_too_fast: false,
1529 },
1530 inline_diagnostics_enabled: mode == EditorMode::Full,
1531 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1532
1533 gutter_hovered: false,
1534 pixel_position_of_newest_cursor: None,
1535 last_bounds: None,
1536 last_position_map: None,
1537 expect_bounds_change: None,
1538 gutter_dimensions: GutterDimensions::default(),
1539 style: None,
1540 show_cursor_names: false,
1541 hovered_cursors: Default::default(),
1542 next_editor_action_id: EditorActionId::default(),
1543 editor_actions: Rc::default(),
1544 inline_completions_hidden_for_vim_mode: false,
1545 show_inline_completions_override: None,
1546 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1547 edit_prediction_settings: EditPredictionSettings::Disabled,
1548 edit_prediction_indent_conflict: false,
1549 edit_prediction_requires_modifier_in_indent_conflict: true,
1550 custom_context_menu: None,
1551 show_git_blame_gutter: false,
1552 show_git_blame_inline: false,
1553 show_selection_menu: None,
1554 show_git_blame_inline_delay_task: None,
1555 git_blame_inline_tooltip: None,
1556 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1557 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1558 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1559 .session
1560 .restore_unsaved_buffers,
1561 blame: None,
1562 blame_subscription: None,
1563 tasks: Default::default(),
1564
1565 breakpoint_store,
1566 gutter_breakpoint_indicator: (None, None),
1567 _subscriptions: vec![
1568 cx.observe(&buffer, Self::on_buffer_changed),
1569 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1570 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1571 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1572 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1573 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1574 cx.observe_window_activation(window, |editor, window, cx| {
1575 let active = window.is_window_active();
1576 editor.blink_manager.update(cx, |blink_manager, cx| {
1577 if active {
1578 blink_manager.enable(cx);
1579 } else {
1580 blink_manager.disable(cx);
1581 }
1582 });
1583 }),
1584 ],
1585 tasks_update_task: None,
1586 linked_edit_ranges: Default::default(),
1587 in_project_search: false,
1588 previous_search_ranges: None,
1589 breadcrumb_header: None,
1590 focused_block: None,
1591 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1592 addons: HashMap::default(),
1593 registered_buffers: HashMap::default(),
1594 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1595 selection_mark_mode: false,
1596 toggle_fold_multiple_buffers: Task::ready(()),
1597 serialize_selections: Task::ready(()),
1598 serialize_folds: Task::ready(()),
1599 text_style_refinement: None,
1600 load_diff_task: load_uncommitted_diff,
1601 mouse_cursor_hidden: false,
1602 hide_mouse_mode: EditorSettings::get_global(cx)
1603 .hide_mouse
1604 .unwrap_or_default(),
1605 };
1606 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1607 this._subscriptions
1608 .push(cx.observe(breakpoints, |_, _, cx| {
1609 cx.notify();
1610 }));
1611 }
1612 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1613 this._subscriptions.extend(project_subscriptions);
1614
1615 this._subscriptions.push(cx.subscribe_in(
1616 &cx.entity(),
1617 window,
1618 |editor, _, e: &EditorEvent, window, cx| {
1619 if let EditorEvent::SelectionsChanged { local } = e {
1620 if *local {
1621 let new_anchor = editor.scroll_manager.anchor();
1622 let snapshot = editor.snapshot(window, cx);
1623 editor.update_restoration_data(cx, move |data| {
1624 data.scroll_position = (
1625 new_anchor.top_row(&snapshot.buffer_snapshot),
1626 new_anchor.offset,
1627 );
1628 });
1629 }
1630 }
1631 },
1632 ));
1633
1634 this.end_selection(window, cx);
1635 this.scroll_manager.show_scrollbars(window, cx);
1636 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1637
1638 if mode == EditorMode::Full {
1639 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1640 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1641
1642 if this.git_blame_inline_enabled {
1643 this.git_blame_inline_enabled = true;
1644 this.start_git_blame_inline(false, window, cx);
1645 }
1646
1647 this.go_to_active_debug_line(window, cx);
1648
1649 if let Some(buffer) = buffer.read(cx).as_singleton() {
1650 if let Some(project) = this.project.as_ref() {
1651 let handle = project.update(cx, |project, cx| {
1652 project.register_buffer_with_language_servers(&buffer, cx)
1653 });
1654 this.registered_buffers
1655 .insert(buffer.read(cx).remote_id(), handle);
1656 }
1657 }
1658 }
1659
1660 this.report_editor_event("Editor Opened", None, cx);
1661 this
1662 }
1663
1664 pub fn deploy_mouse_context_menu(
1665 &mut self,
1666 position: gpui::Point<Pixels>,
1667 context_menu: Entity<ContextMenu>,
1668 window: &mut Window,
1669 cx: &mut Context<Self>,
1670 ) {
1671 self.mouse_context_menu = Some(MouseContextMenu::new(
1672 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1673 context_menu,
1674 window,
1675 cx,
1676 ));
1677 }
1678
1679 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1680 self.mouse_context_menu
1681 .as_ref()
1682 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1683 }
1684
1685 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1686 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1687 }
1688
1689 fn key_context_internal(
1690 &self,
1691 has_active_edit_prediction: bool,
1692 window: &Window,
1693 cx: &App,
1694 ) -> KeyContext {
1695 let mut key_context = KeyContext::new_with_defaults();
1696 key_context.add("Editor");
1697 let mode = match self.mode {
1698 EditorMode::SingleLine { .. } => "single_line",
1699 EditorMode::AutoHeight { .. } => "auto_height",
1700 EditorMode::Full => "full",
1701 };
1702
1703 if EditorSettings::jupyter_enabled(cx) {
1704 key_context.add("jupyter");
1705 }
1706
1707 key_context.set("mode", mode);
1708 if self.pending_rename.is_some() {
1709 key_context.add("renaming");
1710 }
1711
1712 match self.context_menu.borrow().as_ref() {
1713 Some(CodeContextMenu::Completions(_)) => {
1714 key_context.add("menu");
1715 key_context.add("showing_completions");
1716 }
1717 Some(CodeContextMenu::CodeActions(_)) => {
1718 key_context.add("menu");
1719 key_context.add("showing_code_actions")
1720 }
1721 None => {}
1722 }
1723
1724 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1725 if !self.focus_handle(cx).contains_focused(window, cx)
1726 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1727 {
1728 for addon in self.addons.values() {
1729 addon.extend_key_context(&mut key_context, cx)
1730 }
1731 }
1732
1733 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1734 if let Some(extension) = singleton_buffer
1735 .read(cx)
1736 .file()
1737 .and_then(|file| file.path().extension()?.to_str())
1738 {
1739 key_context.set("extension", extension.to_string());
1740 }
1741 } else {
1742 key_context.add("multibuffer");
1743 }
1744
1745 if has_active_edit_prediction {
1746 if self.edit_prediction_in_conflict() {
1747 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1748 } else {
1749 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1750 key_context.add("copilot_suggestion");
1751 }
1752 }
1753
1754 if self.selection_mark_mode {
1755 key_context.add("selection_mode");
1756 }
1757
1758 key_context
1759 }
1760
1761 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1762 self.mouse_cursor_hidden = match origin {
1763 HideMouseCursorOrigin::TypingAction => {
1764 matches!(
1765 self.hide_mouse_mode,
1766 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1767 )
1768 }
1769 HideMouseCursorOrigin::MovementAction => {
1770 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
1771 }
1772 };
1773 }
1774
1775 pub fn edit_prediction_in_conflict(&self) -> bool {
1776 if !self.show_edit_predictions_in_menu() {
1777 return false;
1778 }
1779
1780 let showing_completions = self
1781 .context_menu
1782 .borrow()
1783 .as_ref()
1784 .map_or(false, |context| {
1785 matches!(context, CodeContextMenu::Completions(_))
1786 });
1787
1788 showing_completions
1789 || self.edit_prediction_requires_modifier()
1790 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1791 // bindings to insert tab characters.
1792 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1793 }
1794
1795 pub fn accept_edit_prediction_keybind(
1796 &self,
1797 window: &Window,
1798 cx: &App,
1799 ) -> AcceptEditPredictionBinding {
1800 let key_context = self.key_context_internal(true, window, cx);
1801 let in_conflict = self.edit_prediction_in_conflict();
1802
1803 AcceptEditPredictionBinding(
1804 window
1805 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1806 .into_iter()
1807 .filter(|binding| {
1808 !in_conflict
1809 || binding
1810 .keystrokes()
1811 .first()
1812 .map_or(false, |keystroke| keystroke.modifiers.modified())
1813 })
1814 .rev()
1815 .min_by_key(|binding| {
1816 binding
1817 .keystrokes()
1818 .first()
1819 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1820 }),
1821 )
1822 }
1823
1824 pub fn new_file(
1825 workspace: &mut Workspace,
1826 _: &workspace::NewFile,
1827 window: &mut Window,
1828 cx: &mut Context<Workspace>,
1829 ) {
1830 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1831 "Failed to create buffer",
1832 window,
1833 cx,
1834 |e, _, _| match e.error_code() {
1835 ErrorCode::RemoteUpgradeRequired => Some(format!(
1836 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1837 e.error_tag("required").unwrap_or("the latest version")
1838 )),
1839 _ => None,
1840 },
1841 );
1842 }
1843
1844 pub fn new_in_workspace(
1845 workspace: &mut Workspace,
1846 window: &mut Window,
1847 cx: &mut Context<Workspace>,
1848 ) -> Task<Result<Entity<Editor>>> {
1849 let project = workspace.project().clone();
1850 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1851
1852 cx.spawn_in(window, async move |workspace, cx| {
1853 let buffer = create.await?;
1854 workspace.update_in(cx, |workspace, window, cx| {
1855 let editor =
1856 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1857 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1858 editor
1859 })
1860 })
1861 }
1862
1863 fn new_file_vertical(
1864 workspace: &mut Workspace,
1865 _: &workspace::NewFileSplitVertical,
1866 window: &mut Window,
1867 cx: &mut Context<Workspace>,
1868 ) {
1869 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1870 }
1871
1872 fn new_file_horizontal(
1873 workspace: &mut Workspace,
1874 _: &workspace::NewFileSplitHorizontal,
1875 window: &mut Window,
1876 cx: &mut Context<Workspace>,
1877 ) {
1878 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1879 }
1880
1881 fn new_file_in_direction(
1882 workspace: &mut Workspace,
1883 direction: SplitDirection,
1884 window: &mut Window,
1885 cx: &mut Context<Workspace>,
1886 ) {
1887 let project = workspace.project().clone();
1888 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1889
1890 cx.spawn_in(window, async move |workspace, cx| {
1891 let buffer = create.await?;
1892 workspace.update_in(cx, move |workspace, window, cx| {
1893 workspace.split_item(
1894 direction,
1895 Box::new(
1896 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1897 ),
1898 window,
1899 cx,
1900 )
1901 })?;
1902 anyhow::Ok(())
1903 })
1904 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1905 match e.error_code() {
1906 ErrorCode::RemoteUpgradeRequired => Some(format!(
1907 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1908 e.error_tag("required").unwrap_or("the latest version")
1909 )),
1910 _ => None,
1911 }
1912 });
1913 }
1914
1915 pub fn leader_peer_id(&self) -> Option<PeerId> {
1916 self.leader_peer_id
1917 }
1918
1919 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1920 &self.buffer
1921 }
1922
1923 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1924 self.workspace.as_ref()?.0.upgrade()
1925 }
1926
1927 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1928 self.buffer().read(cx).title(cx)
1929 }
1930
1931 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1932 let git_blame_gutter_max_author_length = self
1933 .render_git_blame_gutter(cx)
1934 .then(|| {
1935 if let Some(blame) = self.blame.as_ref() {
1936 let max_author_length =
1937 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1938 Some(max_author_length)
1939 } else {
1940 None
1941 }
1942 })
1943 .flatten();
1944
1945 EditorSnapshot {
1946 mode: self.mode,
1947 show_gutter: self.show_gutter,
1948 show_line_numbers: self.show_line_numbers,
1949 show_git_diff_gutter: self.show_git_diff_gutter,
1950 show_code_actions: self.show_code_actions,
1951 show_runnables: self.show_runnables,
1952 show_breakpoints: self.show_breakpoints,
1953 git_blame_gutter_max_author_length,
1954 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1955 scroll_anchor: self.scroll_manager.anchor(),
1956 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1957 placeholder_text: self.placeholder_text.clone(),
1958 is_focused: self.focus_handle.is_focused(window),
1959 current_line_highlight: self
1960 .current_line_highlight
1961 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1962 gutter_hovered: self.gutter_hovered,
1963 }
1964 }
1965
1966 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1967 self.buffer.read(cx).language_at(point, cx)
1968 }
1969
1970 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1971 self.buffer.read(cx).read(cx).file_at(point).cloned()
1972 }
1973
1974 pub fn active_excerpt(
1975 &self,
1976 cx: &App,
1977 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1978 self.buffer
1979 .read(cx)
1980 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1981 }
1982
1983 pub fn mode(&self) -> EditorMode {
1984 self.mode
1985 }
1986
1987 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1988 self.collaboration_hub.as_deref()
1989 }
1990
1991 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1992 self.collaboration_hub = Some(hub);
1993 }
1994
1995 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1996 self.in_project_search = in_project_search;
1997 }
1998
1999 pub fn set_custom_context_menu(
2000 &mut self,
2001 f: impl 'static
2002 + Fn(
2003 &mut Self,
2004 DisplayPoint,
2005 &mut Window,
2006 &mut Context<Self>,
2007 ) -> Option<Entity<ui::ContextMenu>>,
2008 ) {
2009 self.custom_context_menu = Some(Box::new(f))
2010 }
2011
2012 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2013 self.completion_provider = provider;
2014 }
2015
2016 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2017 self.semantics_provider.clone()
2018 }
2019
2020 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2021 self.semantics_provider = provider;
2022 }
2023
2024 pub fn set_edit_prediction_provider<T>(
2025 &mut self,
2026 provider: Option<Entity<T>>,
2027 window: &mut Window,
2028 cx: &mut Context<Self>,
2029 ) where
2030 T: EditPredictionProvider,
2031 {
2032 self.edit_prediction_provider =
2033 provider.map(|provider| RegisteredInlineCompletionProvider {
2034 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2035 if this.focus_handle.is_focused(window) {
2036 this.update_visible_inline_completion(window, cx);
2037 }
2038 }),
2039 provider: Arc::new(provider),
2040 });
2041 self.update_edit_prediction_settings(cx);
2042 self.refresh_inline_completion(false, false, window, cx);
2043 }
2044
2045 pub fn placeholder_text(&self) -> Option<&str> {
2046 self.placeholder_text.as_deref()
2047 }
2048
2049 pub fn set_placeholder_text(
2050 &mut self,
2051 placeholder_text: impl Into<Arc<str>>,
2052 cx: &mut Context<Self>,
2053 ) {
2054 let placeholder_text = Some(placeholder_text.into());
2055 if self.placeholder_text != placeholder_text {
2056 self.placeholder_text = placeholder_text;
2057 cx.notify();
2058 }
2059 }
2060
2061 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2062 self.cursor_shape = cursor_shape;
2063
2064 // Disrupt blink for immediate user feedback that the cursor shape has changed
2065 self.blink_manager.update(cx, BlinkManager::show_cursor);
2066
2067 cx.notify();
2068 }
2069
2070 pub fn set_current_line_highlight(
2071 &mut self,
2072 current_line_highlight: Option<CurrentLineHighlight>,
2073 ) {
2074 self.current_line_highlight = current_line_highlight;
2075 }
2076
2077 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2078 self.collapse_matches = collapse_matches;
2079 }
2080
2081 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2082 let buffers = self.buffer.read(cx).all_buffers();
2083 let Some(project) = self.project.as_ref() else {
2084 return;
2085 };
2086 project.update(cx, |project, cx| {
2087 for buffer in buffers {
2088 self.registered_buffers
2089 .entry(buffer.read(cx).remote_id())
2090 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2091 }
2092 })
2093 }
2094
2095 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2096 if self.collapse_matches {
2097 return range.start..range.start;
2098 }
2099 range.clone()
2100 }
2101
2102 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2103 if self.display_map.read(cx).clip_at_line_ends != clip {
2104 self.display_map
2105 .update(cx, |map, _| map.clip_at_line_ends = clip);
2106 }
2107 }
2108
2109 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2110 self.input_enabled = input_enabled;
2111 }
2112
2113 pub fn set_inline_completions_hidden_for_vim_mode(
2114 &mut self,
2115 hidden: bool,
2116 window: &mut Window,
2117 cx: &mut Context<Self>,
2118 ) {
2119 if hidden != self.inline_completions_hidden_for_vim_mode {
2120 self.inline_completions_hidden_for_vim_mode = hidden;
2121 if hidden {
2122 self.update_visible_inline_completion(window, cx);
2123 } else {
2124 self.refresh_inline_completion(true, false, window, cx);
2125 }
2126 }
2127 }
2128
2129 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2130 self.menu_inline_completions_policy = value;
2131 }
2132
2133 pub fn set_autoindent(&mut self, autoindent: bool) {
2134 if autoindent {
2135 self.autoindent_mode = Some(AutoindentMode::EachLine);
2136 } else {
2137 self.autoindent_mode = None;
2138 }
2139 }
2140
2141 pub fn read_only(&self, cx: &App) -> bool {
2142 self.read_only || self.buffer.read(cx).read_only()
2143 }
2144
2145 pub fn set_read_only(&mut self, read_only: bool) {
2146 self.read_only = read_only;
2147 }
2148
2149 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2150 self.use_autoclose = autoclose;
2151 }
2152
2153 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2154 self.use_auto_surround = auto_surround;
2155 }
2156
2157 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2158 self.auto_replace_emoji_shortcode = auto_replace;
2159 }
2160
2161 pub fn toggle_edit_predictions(
2162 &mut self,
2163 _: &ToggleEditPrediction,
2164 window: &mut Window,
2165 cx: &mut Context<Self>,
2166 ) {
2167 if self.show_inline_completions_override.is_some() {
2168 self.set_show_edit_predictions(None, window, cx);
2169 } else {
2170 let show_edit_predictions = !self.edit_predictions_enabled();
2171 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2172 }
2173 }
2174
2175 pub fn set_show_edit_predictions(
2176 &mut self,
2177 show_edit_predictions: Option<bool>,
2178 window: &mut Window,
2179 cx: &mut Context<Self>,
2180 ) {
2181 self.show_inline_completions_override = show_edit_predictions;
2182 self.update_edit_prediction_settings(cx);
2183
2184 if let Some(false) = show_edit_predictions {
2185 self.discard_inline_completion(false, cx);
2186 } else {
2187 self.refresh_inline_completion(false, true, window, cx);
2188 }
2189 }
2190
2191 fn inline_completions_disabled_in_scope(
2192 &self,
2193 buffer: &Entity<Buffer>,
2194 buffer_position: language::Anchor,
2195 cx: &App,
2196 ) -> bool {
2197 let snapshot = buffer.read(cx).snapshot();
2198 let settings = snapshot.settings_at(buffer_position, cx);
2199
2200 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2201 return false;
2202 };
2203
2204 scope.override_name().map_or(false, |scope_name| {
2205 settings
2206 .edit_predictions_disabled_in
2207 .iter()
2208 .any(|s| s == scope_name)
2209 })
2210 }
2211
2212 pub fn set_use_modal_editing(&mut self, to: bool) {
2213 self.use_modal_editing = to;
2214 }
2215
2216 pub fn use_modal_editing(&self) -> bool {
2217 self.use_modal_editing
2218 }
2219
2220 fn selections_did_change(
2221 &mut self,
2222 local: bool,
2223 old_cursor_position: &Anchor,
2224 show_completions: bool,
2225 window: &mut Window,
2226 cx: &mut Context<Self>,
2227 ) {
2228 window.invalidate_character_coordinates();
2229
2230 // Copy selections to primary selection buffer
2231 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2232 if local {
2233 let selections = self.selections.all::<usize>(cx);
2234 let buffer_handle = self.buffer.read(cx).read(cx);
2235
2236 let mut text = String::new();
2237 for (index, selection) in selections.iter().enumerate() {
2238 let text_for_selection = buffer_handle
2239 .text_for_range(selection.start..selection.end)
2240 .collect::<String>();
2241
2242 text.push_str(&text_for_selection);
2243 if index != selections.len() - 1 {
2244 text.push('\n');
2245 }
2246 }
2247
2248 if !text.is_empty() {
2249 cx.write_to_primary(ClipboardItem::new_string(text));
2250 }
2251 }
2252
2253 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2254 self.buffer.update(cx, |buffer, cx| {
2255 buffer.set_active_selections(
2256 &self.selections.disjoint_anchors(),
2257 self.selections.line_mode,
2258 self.cursor_shape,
2259 cx,
2260 )
2261 });
2262 }
2263 let display_map = self
2264 .display_map
2265 .update(cx, |display_map, cx| display_map.snapshot(cx));
2266 let buffer = &display_map.buffer_snapshot;
2267 self.add_selections_state = None;
2268 self.select_next_state = None;
2269 self.select_prev_state = None;
2270 self.select_syntax_node_history.try_clear();
2271 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2272 self.snippet_stack
2273 .invalidate(&self.selections.disjoint_anchors(), buffer);
2274 self.take_rename(false, window, cx);
2275
2276 let new_cursor_position = self.selections.newest_anchor().head();
2277
2278 self.push_to_nav_history(
2279 *old_cursor_position,
2280 Some(new_cursor_position.to_point(buffer)),
2281 false,
2282 cx,
2283 );
2284
2285 if local {
2286 let new_cursor_position = self.selections.newest_anchor().head();
2287 let mut context_menu = self.context_menu.borrow_mut();
2288 let completion_menu = match context_menu.as_ref() {
2289 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2290 _ => {
2291 *context_menu = None;
2292 None
2293 }
2294 };
2295 if let Some(buffer_id) = new_cursor_position.buffer_id {
2296 if !self.registered_buffers.contains_key(&buffer_id) {
2297 if let Some(project) = self.project.as_ref() {
2298 project.update(cx, |project, cx| {
2299 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2300 return;
2301 };
2302 self.registered_buffers.insert(
2303 buffer_id,
2304 project.register_buffer_with_language_servers(&buffer, cx),
2305 );
2306 })
2307 }
2308 }
2309 }
2310
2311 if let Some(completion_menu) = completion_menu {
2312 let cursor_position = new_cursor_position.to_offset(buffer);
2313 let (word_range, kind) =
2314 buffer.surrounding_word(completion_menu.initial_position, true);
2315 if kind == Some(CharKind::Word)
2316 && word_range.to_inclusive().contains(&cursor_position)
2317 {
2318 let mut completion_menu = completion_menu.clone();
2319 drop(context_menu);
2320
2321 let query = Self::completion_query(buffer, cursor_position);
2322 cx.spawn(async move |this, cx| {
2323 completion_menu
2324 .filter(query.as_deref(), cx.background_executor().clone())
2325 .await;
2326
2327 this.update(cx, |this, cx| {
2328 let mut context_menu = this.context_menu.borrow_mut();
2329 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2330 else {
2331 return;
2332 };
2333
2334 if menu.id > completion_menu.id {
2335 return;
2336 }
2337
2338 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2339 drop(context_menu);
2340 cx.notify();
2341 })
2342 })
2343 .detach();
2344
2345 if show_completions {
2346 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2347 }
2348 } else {
2349 drop(context_menu);
2350 self.hide_context_menu(window, cx);
2351 }
2352 } else {
2353 drop(context_menu);
2354 }
2355
2356 hide_hover(self, cx);
2357
2358 if old_cursor_position.to_display_point(&display_map).row()
2359 != new_cursor_position.to_display_point(&display_map).row()
2360 {
2361 self.available_code_actions.take();
2362 }
2363 self.refresh_code_actions(window, cx);
2364 self.refresh_document_highlights(cx);
2365 self.refresh_selected_text_highlights(window, cx);
2366 refresh_matching_bracket_highlights(self, window, cx);
2367 self.update_visible_inline_completion(window, cx);
2368 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2369 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2370 if self.git_blame_inline_enabled {
2371 self.start_inline_blame_timer(window, cx);
2372 }
2373 }
2374
2375 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2376 cx.emit(EditorEvent::SelectionsChanged { local });
2377
2378 let selections = &self.selections.disjoint;
2379 if selections.len() == 1 {
2380 cx.emit(SearchEvent::ActiveMatchChanged)
2381 }
2382 if local {
2383 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2384 let inmemory_selections = selections
2385 .iter()
2386 .map(|s| {
2387 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2388 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2389 })
2390 .collect();
2391 self.update_restoration_data(cx, |data| {
2392 data.selections = inmemory_selections;
2393 });
2394
2395 if WorkspaceSettings::get(None, cx).restore_on_startup
2396 != RestoreOnStartupBehavior::None
2397 {
2398 if let Some(workspace_id) =
2399 self.workspace.as_ref().and_then(|workspace| workspace.1)
2400 {
2401 let snapshot = self.buffer().read(cx).snapshot(cx);
2402 let selections = selections.clone();
2403 let background_executor = cx.background_executor().clone();
2404 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2405 self.serialize_selections = cx.background_spawn(async move {
2406 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2407 let db_selections = selections
2408 .iter()
2409 .map(|selection| {
2410 (
2411 selection.start.to_offset(&snapshot),
2412 selection.end.to_offset(&snapshot),
2413 )
2414 })
2415 .collect();
2416
2417 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2418 .await
2419 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2420 .log_err();
2421 });
2422 }
2423 }
2424 }
2425 }
2426
2427 cx.notify();
2428 }
2429
2430 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2431 use text::ToOffset as _;
2432 use text::ToPoint as _;
2433
2434 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2435 return;
2436 }
2437
2438 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2439 return;
2440 };
2441
2442 let snapshot = singleton.read(cx).snapshot();
2443 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2444 let display_snapshot = display_map.snapshot(cx);
2445
2446 display_snapshot
2447 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2448 .map(|fold| {
2449 fold.range.start.text_anchor.to_point(&snapshot)
2450 ..fold.range.end.text_anchor.to_point(&snapshot)
2451 })
2452 .collect()
2453 });
2454 self.update_restoration_data(cx, |data| {
2455 data.folds = inmemory_folds;
2456 });
2457
2458 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2459 return;
2460 };
2461 let background_executor = cx.background_executor().clone();
2462 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2463 let db_folds = self.display_map.update(cx, |display_map, cx| {
2464 display_map
2465 .snapshot(cx)
2466 .folds_in_range(0..snapshot.len())
2467 .map(|fold| {
2468 (
2469 fold.range.start.text_anchor.to_offset(&snapshot),
2470 fold.range.end.text_anchor.to_offset(&snapshot),
2471 )
2472 })
2473 .collect()
2474 });
2475 self.serialize_folds = cx.background_spawn(async move {
2476 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2477 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2478 .await
2479 .with_context(|| {
2480 format!(
2481 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2482 )
2483 })
2484 .log_err();
2485 });
2486 }
2487
2488 pub fn sync_selections(
2489 &mut self,
2490 other: Entity<Editor>,
2491 cx: &mut Context<Self>,
2492 ) -> gpui::Subscription {
2493 let other_selections = other.read(cx).selections.disjoint.to_vec();
2494 self.selections.change_with(cx, |selections| {
2495 selections.select_anchors(other_selections);
2496 });
2497
2498 let other_subscription =
2499 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2500 EditorEvent::SelectionsChanged { local: true } => {
2501 let other_selections = other.read(cx).selections.disjoint.to_vec();
2502 if other_selections.is_empty() {
2503 return;
2504 }
2505 this.selections.change_with(cx, |selections| {
2506 selections.select_anchors(other_selections);
2507 });
2508 }
2509 _ => {}
2510 });
2511
2512 let this_subscription =
2513 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2514 EditorEvent::SelectionsChanged { local: true } => {
2515 let these_selections = this.selections.disjoint.to_vec();
2516 if these_selections.is_empty() {
2517 return;
2518 }
2519 other.update(cx, |other_editor, cx| {
2520 other_editor.selections.change_with(cx, |selections| {
2521 selections.select_anchors(these_selections);
2522 })
2523 });
2524 }
2525 _ => {}
2526 });
2527
2528 Subscription::join(other_subscription, this_subscription)
2529 }
2530
2531 pub fn change_selections<R>(
2532 &mut self,
2533 autoscroll: Option<Autoscroll>,
2534 window: &mut Window,
2535 cx: &mut Context<Self>,
2536 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2537 ) -> R {
2538 self.change_selections_inner(autoscroll, true, window, cx, change)
2539 }
2540
2541 fn change_selections_inner<R>(
2542 &mut self,
2543 autoscroll: Option<Autoscroll>,
2544 request_completions: bool,
2545 window: &mut Window,
2546 cx: &mut Context<Self>,
2547 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2548 ) -> R {
2549 let old_cursor_position = self.selections.newest_anchor().head();
2550 self.push_to_selection_history();
2551
2552 let (changed, result) = self.selections.change_with(cx, change);
2553
2554 if changed {
2555 if let Some(autoscroll) = autoscroll {
2556 self.request_autoscroll(autoscroll, cx);
2557 }
2558 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2559
2560 if self.should_open_signature_help_automatically(
2561 &old_cursor_position,
2562 self.signature_help_state.backspace_pressed(),
2563 cx,
2564 ) {
2565 self.show_signature_help(&ShowSignatureHelp, window, cx);
2566 }
2567 self.signature_help_state.set_backspace_pressed(false);
2568 }
2569
2570 result
2571 }
2572
2573 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2574 where
2575 I: IntoIterator<Item = (Range<S>, T)>,
2576 S: ToOffset,
2577 T: Into<Arc<str>>,
2578 {
2579 if self.read_only(cx) {
2580 return;
2581 }
2582
2583 self.buffer
2584 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2585 }
2586
2587 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2588 where
2589 I: IntoIterator<Item = (Range<S>, T)>,
2590 S: ToOffset,
2591 T: Into<Arc<str>>,
2592 {
2593 if self.read_only(cx) {
2594 return;
2595 }
2596
2597 self.buffer.update(cx, |buffer, cx| {
2598 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2599 });
2600 }
2601
2602 pub fn edit_with_block_indent<I, S, T>(
2603 &mut self,
2604 edits: I,
2605 original_indent_columns: Vec<Option<u32>>,
2606 cx: &mut Context<Self>,
2607 ) where
2608 I: IntoIterator<Item = (Range<S>, T)>,
2609 S: ToOffset,
2610 T: Into<Arc<str>>,
2611 {
2612 if self.read_only(cx) {
2613 return;
2614 }
2615
2616 self.buffer.update(cx, |buffer, cx| {
2617 buffer.edit(
2618 edits,
2619 Some(AutoindentMode::Block {
2620 original_indent_columns,
2621 }),
2622 cx,
2623 )
2624 });
2625 }
2626
2627 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2628 self.hide_context_menu(window, cx);
2629
2630 match phase {
2631 SelectPhase::Begin {
2632 position,
2633 add,
2634 click_count,
2635 } => self.begin_selection(position, add, click_count, window, cx),
2636 SelectPhase::BeginColumnar {
2637 position,
2638 goal_column,
2639 reset,
2640 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2641 SelectPhase::Extend {
2642 position,
2643 click_count,
2644 } => self.extend_selection(position, click_count, window, cx),
2645 SelectPhase::Update {
2646 position,
2647 goal_column,
2648 scroll_delta,
2649 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2650 SelectPhase::End => self.end_selection(window, cx),
2651 }
2652 }
2653
2654 fn extend_selection(
2655 &mut self,
2656 position: DisplayPoint,
2657 click_count: usize,
2658 window: &mut Window,
2659 cx: &mut Context<Self>,
2660 ) {
2661 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2662 let tail = self.selections.newest::<usize>(cx).tail();
2663 self.begin_selection(position, false, click_count, window, cx);
2664
2665 let position = position.to_offset(&display_map, Bias::Left);
2666 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2667
2668 let mut pending_selection = self
2669 .selections
2670 .pending_anchor()
2671 .expect("extend_selection not called with pending selection");
2672 if position >= tail {
2673 pending_selection.start = tail_anchor;
2674 } else {
2675 pending_selection.end = tail_anchor;
2676 pending_selection.reversed = true;
2677 }
2678
2679 let mut pending_mode = self.selections.pending_mode().unwrap();
2680 match &mut pending_mode {
2681 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2682 _ => {}
2683 }
2684
2685 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2686 s.set_pending(pending_selection, pending_mode)
2687 });
2688 }
2689
2690 fn begin_selection(
2691 &mut self,
2692 position: DisplayPoint,
2693 add: bool,
2694 click_count: usize,
2695 window: &mut Window,
2696 cx: &mut Context<Self>,
2697 ) {
2698 if !self.focus_handle.is_focused(window) {
2699 self.last_focused_descendant = None;
2700 window.focus(&self.focus_handle);
2701 }
2702
2703 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2704 let buffer = &display_map.buffer_snapshot;
2705 let newest_selection = self.selections.newest_anchor().clone();
2706 let position = display_map.clip_point(position, Bias::Left);
2707
2708 let start;
2709 let end;
2710 let mode;
2711 let mut auto_scroll;
2712 match click_count {
2713 1 => {
2714 start = buffer.anchor_before(position.to_point(&display_map));
2715 end = start;
2716 mode = SelectMode::Character;
2717 auto_scroll = true;
2718 }
2719 2 => {
2720 let range = movement::surrounding_word(&display_map, position);
2721 start = buffer.anchor_before(range.start.to_point(&display_map));
2722 end = buffer.anchor_before(range.end.to_point(&display_map));
2723 mode = SelectMode::Word(start..end);
2724 auto_scroll = true;
2725 }
2726 3 => {
2727 let position = display_map
2728 .clip_point(position, Bias::Left)
2729 .to_point(&display_map);
2730 let line_start = display_map.prev_line_boundary(position).0;
2731 let next_line_start = buffer.clip_point(
2732 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2733 Bias::Left,
2734 );
2735 start = buffer.anchor_before(line_start);
2736 end = buffer.anchor_before(next_line_start);
2737 mode = SelectMode::Line(start..end);
2738 auto_scroll = true;
2739 }
2740 _ => {
2741 start = buffer.anchor_before(0);
2742 end = buffer.anchor_before(buffer.len());
2743 mode = SelectMode::All;
2744 auto_scroll = false;
2745 }
2746 }
2747 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2748
2749 let point_to_delete: Option<usize> = {
2750 let selected_points: Vec<Selection<Point>> =
2751 self.selections.disjoint_in_range(start..end, cx);
2752
2753 if !add || click_count > 1 {
2754 None
2755 } else if !selected_points.is_empty() {
2756 Some(selected_points[0].id)
2757 } else {
2758 let clicked_point_already_selected =
2759 self.selections.disjoint.iter().find(|selection| {
2760 selection.start.to_point(buffer) == start.to_point(buffer)
2761 || selection.end.to_point(buffer) == end.to_point(buffer)
2762 });
2763
2764 clicked_point_already_selected.map(|selection| selection.id)
2765 }
2766 };
2767
2768 let selections_count = self.selections.count();
2769
2770 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2771 if let Some(point_to_delete) = point_to_delete {
2772 s.delete(point_to_delete);
2773
2774 if selections_count == 1 {
2775 s.set_pending_anchor_range(start..end, mode);
2776 }
2777 } else {
2778 if !add {
2779 s.clear_disjoint();
2780 } else if click_count > 1 {
2781 s.delete(newest_selection.id)
2782 }
2783
2784 s.set_pending_anchor_range(start..end, mode);
2785 }
2786 });
2787 }
2788
2789 fn begin_columnar_selection(
2790 &mut self,
2791 position: DisplayPoint,
2792 goal_column: u32,
2793 reset: bool,
2794 window: &mut Window,
2795 cx: &mut Context<Self>,
2796 ) {
2797 if !self.focus_handle.is_focused(window) {
2798 self.last_focused_descendant = None;
2799 window.focus(&self.focus_handle);
2800 }
2801
2802 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2803
2804 if reset {
2805 let pointer_position = display_map
2806 .buffer_snapshot
2807 .anchor_before(position.to_point(&display_map));
2808
2809 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2810 s.clear_disjoint();
2811 s.set_pending_anchor_range(
2812 pointer_position..pointer_position,
2813 SelectMode::Character,
2814 );
2815 });
2816 }
2817
2818 let tail = self.selections.newest::<Point>(cx).tail();
2819 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2820
2821 if !reset {
2822 self.select_columns(
2823 tail.to_display_point(&display_map),
2824 position,
2825 goal_column,
2826 &display_map,
2827 window,
2828 cx,
2829 );
2830 }
2831 }
2832
2833 fn update_selection(
2834 &mut self,
2835 position: DisplayPoint,
2836 goal_column: u32,
2837 scroll_delta: gpui::Point<f32>,
2838 window: &mut Window,
2839 cx: &mut Context<Self>,
2840 ) {
2841 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2842
2843 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2844 let tail = tail.to_display_point(&display_map);
2845 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2846 } else if let Some(mut pending) = self.selections.pending_anchor() {
2847 let buffer = self.buffer.read(cx).snapshot(cx);
2848 let head;
2849 let tail;
2850 let mode = self.selections.pending_mode().unwrap();
2851 match &mode {
2852 SelectMode::Character => {
2853 head = position.to_point(&display_map);
2854 tail = pending.tail().to_point(&buffer);
2855 }
2856 SelectMode::Word(original_range) => {
2857 let original_display_range = original_range.start.to_display_point(&display_map)
2858 ..original_range.end.to_display_point(&display_map);
2859 let original_buffer_range = original_display_range.start.to_point(&display_map)
2860 ..original_display_range.end.to_point(&display_map);
2861 if movement::is_inside_word(&display_map, position)
2862 || original_display_range.contains(&position)
2863 {
2864 let word_range = movement::surrounding_word(&display_map, position);
2865 if word_range.start < original_display_range.start {
2866 head = word_range.start.to_point(&display_map);
2867 } else {
2868 head = word_range.end.to_point(&display_map);
2869 }
2870 } else {
2871 head = position.to_point(&display_map);
2872 }
2873
2874 if head <= original_buffer_range.start {
2875 tail = original_buffer_range.end;
2876 } else {
2877 tail = original_buffer_range.start;
2878 }
2879 }
2880 SelectMode::Line(original_range) => {
2881 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2882
2883 let position = display_map
2884 .clip_point(position, Bias::Left)
2885 .to_point(&display_map);
2886 let line_start = display_map.prev_line_boundary(position).0;
2887 let next_line_start = buffer.clip_point(
2888 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2889 Bias::Left,
2890 );
2891
2892 if line_start < original_range.start {
2893 head = line_start
2894 } else {
2895 head = next_line_start
2896 }
2897
2898 if head <= original_range.start {
2899 tail = original_range.end;
2900 } else {
2901 tail = original_range.start;
2902 }
2903 }
2904 SelectMode::All => {
2905 return;
2906 }
2907 };
2908
2909 if head < tail {
2910 pending.start = buffer.anchor_before(head);
2911 pending.end = buffer.anchor_before(tail);
2912 pending.reversed = true;
2913 } else {
2914 pending.start = buffer.anchor_before(tail);
2915 pending.end = buffer.anchor_before(head);
2916 pending.reversed = false;
2917 }
2918
2919 self.change_selections(None, window, cx, |s| {
2920 s.set_pending(pending, mode);
2921 });
2922 } else {
2923 log::error!("update_selection dispatched with no pending selection");
2924 return;
2925 }
2926
2927 self.apply_scroll_delta(scroll_delta, window, cx);
2928 cx.notify();
2929 }
2930
2931 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2932 self.columnar_selection_tail.take();
2933 if self.selections.pending_anchor().is_some() {
2934 let selections = self.selections.all::<usize>(cx);
2935 self.change_selections(None, window, cx, |s| {
2936 s.select(selections);
2937 s.clear_pending();
2938 });
2939 }
2940 }
2941
2942 fn select_columns(
2943 &mut self,
2944 tail: DisplayPoint,
2945 head: DisplayPoint,
2946 goal_column: u32,
2947 display_map: &DisplaySnapshot,
2948 window: &mut Window,
2949 cx: &mut Context<Self>,
2950 ) {
2951 let start_row = cmp::min(tail.row(), head.row());
2952 let end_row = cmp::max(tail.row(), head.row());
2953 let start_column = cmp::min(tail.column(), goal_column);
2954 let end_column = cmp::max(tail.column(), goal_column);
2955 let reversed = start_column < tail.column();
2956
2957 let selection_ranges = (start_row.0..=end_row.0)
2958 .map(DisplayRow)
2959 .filter_map(|row| {
2960 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2961 let start = display_map
2962 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2963 .to_point(display_map);
2964 let end = display_map
2965 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2966 .to_point(display_map);
2967 if reversed {
2968 Some(end..start)
2969 } else {
2970 Some(start..end)
2971 }
2972 } else {
2973 None
2974 }
2975 })
2976 .collect::<Vec<_>>();
2977
2978 self.change_selections(None, window, cx, |s| {
2979 s.select_ranges(selection_ranges);
2980 });
2981 cx.notify();
2982 }
2983
2984 pub fn has_pending_nonempty_selection(&self) -> bool {
2985 let pending_nonempty_selection = match self.selections.pending_anchor() {
2986 Some(Selection { start, end, .. }) => start != end,
2987 None => false,
2988 };
2989
2990 pending_nonempty_selection
2991 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2992 }
2993
2994 pub fn has_pending_selection(&self) -> bool {
2995 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2996 }
2997
2998 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2999 self.selection_mark_mode = false;
3000
3001 if self.clear_expanded_diff_hunks(cx) {
3002 cx.notify();
3003 return;
3004 }
3005 if self.dismiss_menus_and_popups(true, window, cx) {
3006 return;
3007 }
3008
3009 if self.mode == EditorMode::Full
3010 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3011 {
3012 return;
3013 }
3014
3015 cx.propagate();
3016 }
3017
3018 pub fn dismiss_menus_and_popups(
3019 &mut self,
3020 is_user_requested: bool,
3021 window: &mut Window,
3022 cx: &mut Context<Self>,
3023 ) -> bool {
3024 if self.take_rename(false, window, cx).is_some() {
3025 return true;
3026 }
3027
3028 if hide_hover(self, cx) {
3029 return true;
3030 }
3031
3032 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3033 return true;
3034 }
3035
3036 if self.hide_context_menu(window, cx).is_some() {
3037 return true;
3038 }
3039
3040 if self.mouse_context_menu.take().is_some() {
3041 return true;
3042 }
3043
3044 if is_user_requested && self.discard_inline_completion(true, cx) {
3045 return true;
3046 }
3047
3048 if self.snippet_stack.pop().is_some() {
3049 return true;
3050 }
3051
3052 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
3053 self.dismiss_diagnostics(cx);
3054 return true;
3055 }
3056
3057 false
3058 }
3059
3060 fn linked_editing_ranges_for(
3061 &self,
3062 selection: Range<text::Anchor>,
3063 cx: &App,
3064 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3065 if self.linked_edit_ranges.is_empty() {
3066 return None;
3067 }
3068 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3069 selection.end.buffer_id.and_then(|end_buffer_id| {
3070 if selection.start.buffer_id != Some(end_buffer_id) {
3071 return None;
3072 }
3073 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3074 let snapshot = buffer.read(cx).snapshot();
3075 self.linked_edit_ranges
3076 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3077 .map(|ranges| (ranges, snapshot, buffer))
3078 })?;
3079 use text::ToOffset as TO;
3080 // find offset from the start of current range to current cursor position
3081 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3082
3083 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3084 let start_difference = start_offset - start_byte_offset;
3085 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3086 let end_difference = end_offset - start_byte_offset;
3087 // Current range has associated linked ranges.
3088 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3089 for range in linked_ranges.iter() {
3090 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3091 let end_offset = start_offset + end_difference;
3092 let start_offset = start_offset + start_difference;
3093 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3094 continue;
3095 }
3096 if self.selections.disjoint_anchor_ranges().any(|s| {
3097 if s.start.buffer_id != selection.start.buffer_id
3098 || s.end.buffer_id != selection.end.buffer_id
3099 {
3100 return false;
3101 }
3102 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3103 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3104 }) {
3105 continue;
3106 }
3107 let start = buffer_snapshot.anchor_after(start_offset);
3108 let end = buffer_snapshot.anchor_after(end_offset);
3109 linked_edits
3110 .entry(buffer.clone())
3111 .or_default()
3112 .push(start..end);
3113 }
3114 Some(linked_edits)
3115 }
3116
3117 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3118 let text: Arc<str> = text.into();
3119
3120 if self.read_only(cx) {
3121 return;
3122 }
3123
3124 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3125
3126 let selections = self.selections.all_adjusted(cx);
3127 let mut bracket_inserted = false;
3128 let mut edits = Vec::new();
3129 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3130 let mut new_selections = Vec::with_capacity(selections.len());
3131 let mut new_autoclose_regions = Vec::new();
3132 let snapshot = self.buffer.read(cx).read(cx);
3133 let mut clear_linked_edit_ranges = false;
3134
3135 for (selection, autoclose_region) in
3136 self.selections_with_autoclose_regions(selections, &snapshot)
3137 {
3138 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3139 // Determine if the inserted text matches the opening or closing
3140 // bracket of any of this language's bracket pairs.
3141 let mut bracket_pair = None;
3142 let mut is_bracket_pair_start = false;
3143 let mut is_bracket_pair_end = false;
3144 if !text.is_empty() {
3145 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3146 // and they are removing the character that triggered IME popup.
3147 for (pair, enabled) in scope.brackets() {
3148 if !pair.close && !pair.surround {
3149 continue;
3150 }
3151
3152 if enabled && pair.start.ends_with(text.as_ref()) {
3153 let prefix_len = pair.start.len() - text.len();
3154 let preceding_text_matches_prefix = prefix_len == 0
3155 || (selection.start.column >= (prefix_len as u32)
3156 && snapshot.contains_str_at(
3157 Point::new(
3158 selection.start.row,
3159 selection.start.column - (prefix_len as u32),
3160 ),
3161 &pair.start[..prefix_len],
3162 ));
3163 if preceding_text_matches_prefix {
3164 bracket_pair = Some(pair.clone());
3165 is_bracket_pair_start = true;
3166 break;
3167 }
3168 }
3169 if pair.end.as_str() == text.as_ref() {
3170 bracket_pair = Some(pair.clone());
3171 is_bracket_pair_end = true;
3172 break;
3173 }
3174 }
3175 }
3176
3177 if let Some(bracket_pair) = bracket_pair {
3178 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3179 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3180 let auto_surround =
3181 self.use_auto_surround && snapshot_settings.use_auto_surround;
3182 if selection.is_empty() {
3183 if is_bracket_pair_start {
3184 // If the inserted text is a suffix of an opening bracket and the
3185 // selection is preceded by the rest of the opening bracket, then
3186 // insert the closing bracket.
3187 let following_text_allows_autoclose = snapshot
3188 .chars_at(selection.start)
3189 .next()
3190 .map_or(true, |c| scope.should_autoclose_before(c));
3191
3192 let preceding_text_allows_autoclose = selection.start.column == 0
3193 || snapshot.reversed_chars_at(selection.start).next().map_or(
3194 true,
3195 |c| {
3196 bracket_pair.start != bracket_pair.end
3197 || !snapshot
3198 .char_classifier_at(selection.start)
3199 .is_word(c)
3200 },
3201 );
3202
3203 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3204 && bracket_pair.start.len() == 1
3205 {
3206 let target = bracket_pair.start.chars().next().unwrap();
3207 let current_line_count = snapshot
3208 .reversed_chars_at(selection.start)
3209 .take_while(|&c| c != '\n')
3210 .filter(|&c| c == target)
3211 .count();
3212 current_line_count % 2 == 1
3213 } else {
3214 false
3215 };
3216
3217 if autoclose
3218 && bracket_pair.close
3219 && following_text_allows_autoclose
3220 && preceding_text_allows_autoclose
3221 && !is_closing_quote
3222 {
3223 let anchor = snapshot.anchor_before(selection.end);
3224 new_selections.push((selection.map(|_| anchor), text.len()));
3225 new_autoclose_regions.push((
3226 anchor,
3227 text.len(),
3228 selection.id,
3229 bracket_pair.clone(),
3230 ));
3231 edits.push((
3232 selection.range(),
3233 format!("{}{}", text, bracket_pair.end).into(),
3234 ));
3235 bracket_inserted = true;
3236 continue;
3237 }
3238 }
3239
3240 if let Some(region) = autoclose_region {
3241 // If the selection is followed by an auto-inserted closing bracket,
3242 // then don't insert that closing bracket again; just move the selection
3243 // past the closing bracket.
3244 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3245 && text.as_ref() == region.pair.end.as_str();
3246 if should_skip {
3247 let anchor = snapshot.anchor_after(selection.end);
3248 new_selections
3249 .push((selection.map(|_| anchor), region.pair.end.len()));
3250 continue;
3251 }
3252 }
3253
3254 let always_treat_brackets_as_autoclosed = snapshot
3255 .language_settings_at(selection.start, cx)
3256 .always_treat_brackets_as_autoclosed;
3257 if always_treat_brackets_as_autoclosed
3258 && is_bracket_pair_end
3259 && snapshot.contains_str_at(selection.end, text.as_ref())
3260 {
3261 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3262 // and the inserted text is a closing bracket and the selection is followed
3263 // by the closing bracket then move the selection past the closing bracket.
3264 let anchor = snapshot.anchor_after(selection.end);
3265 new_selections.push((selection.map(|_| anchor), text.len()));
3266 continue;
3267 }
3268 }
3269 // If an opening bracket is 1 character long and is typed while
3270 // text is selected, then surround that text with the bracket pair.
3271 else if auto_surround
3272 && bracket_pair.surround
3273 && is_bracket_pair_start
3274 && bracket_pair.start.chars().count() == 1
3275 {
3276 edits.push((selection.start..selection.start, text.clone()));
3277 edits.push((
3278 selection.end..selection.end,
3279 bracket_pair.end.as_str().into(),
3280 ));
3281 bracket_inserted = true;
3282 new_selections.push((
3283 Selection {
3284 id: selection.id,
3285 start: snapshot.anchor_after(selection.start),
3286 end: snapshot.anchor_before(selection.end),
3287 reversed: selection.reversed,
3288 goal: selection.goal,
3289 },
3290 0,
3291 ));
3292 continue;
3293 }
3294 }
3295 }
3296
3297 if self.auto_replace_emoji_shortcode
3298 && selection.is_empty()
3299 && text.as_ref().ends_with(':')
3300 {
3301 if let Some(possible_emoji_short_code) =
3302 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3303 {
3304 if !possible_emoji_short_code.is_empty() {
3305 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3306 let emoji_shortcode_start = Point::new(
3307 selection.start.row,
3308 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3309 );
3310
3311 // Remove shortcode from buffer
3312 edits.push((
3313 emoji_shortcode_start..selection.start,
3314 "".to_string().into(),
3315 ));
3316 new_selections.push((
3317 Selection {
3318 id: selection.id,
3319 start: snapshot.anchor_after(emoji_shortcode_start),
3320 end: snapshot.anchor_before(selection.start),
3321 reversed: selection.reversed,
3322 goal: selection.goal,
3323 },
3324 0,
3325 ));
3326
3327 // Insert emoji
3328 let selection_start_anchor = snapshot.anchor_after(selection.start);
3329 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3330 edits.push((selection.start..selection.end, emoji.to_string().into()));
3331
3332 continue;
3333 }
3334 }
3335 }
3336 }
3337
3338 // If not handling any auto-close operation, then just replace the selected
3339 // text with the given input and move the selection to the end of the
3340 // newly inserted text.
3341 let anchor = snapshot.anchor_after(selection.end);
3342 if !self.linked_edit_ranges.is_empty() {
3343 let start_anchor = snapshot.anchor_before(selection.start);
3344
3345 let is_word_char = text.chars().next().map_or(true, |char| {
3346 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3347 classifier.is_word(char)
3348 });
3349
3350 if is_word_char {
3351 if let Some(ranges) = self
3352 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3353 {
3354 for (buffer, edits) in ranges {
3355 linked_edits
3356 .entry(buffer.clone())
3357 .or_default()
3358 .extend(edits.into_iter().map(|range| (range, text.clone())));
3359 }
3360 }
3361 } else {
3362 clear_linked_edit_ranges = true;
3363 }
3364 }
3365
3366 new_selections.push((selection.map(|_| anchor), 0));
3367 edits.push((selection.start..selection.end, text.clone()));
3368 }
3369
3370 drop(snapshot);
3371
3372 self.transact(window, cx, |this, window, cx| {
3373 if clear_linked_edit_ranges {
3374 this.linked_edit_ranges.clear();
3375 }
3376 let initial_buffer_versions =
3377 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3378
3379 this.buffer.update(cx, |buffer, cx| {
3380 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3381 });
3382 for (buffer, edits) in linked_edits {
3383 buffer.update(cx, |buffer, cx| {
3384 let snapshot = buffer.snapshot();
3385 let edits = edits
3386 .into_iter()
3387 .map(|(range, text)| {
3388 use text::ToPoint as TP;
3389 let end_point = TP::to_point(&range.end, &snapshot);
3390 let start_point = TP::to_point(&range.start, &snapshot);
3391 (start_point..end_point, text)
3392 })
3393 .sorted_by_key(|(range, _)| range.start);
3394 buffer.edit(edits, None, cx);
3395 })
3396 }
3397 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3398 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3399 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3400 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3401 .zip(new_selection_deltas)
3402 .map(|(selection, delta)| Selection {
3403 id: selection.id,
3404 start: selection.start + delta,
3405 end: selection.end + delta,
3406 reversed: selection.reversed,
3407 goal: SelectionGoal::None,
3408 })
3409 .collect::<Vec<_>>();
3410
3411 let mut i = 0;
3412 for (position, delta, selection_id, pair) in new_autoclose_regions {
3413 let position = position.to_offset(&map.buffer_snapshot) + delta;
3414 let start = map.buffer_snapshot.anchor_before(position);
3415 let end = map.buffer_snapshot.anchor_after(position);
3416 while let Some(existing_state) = this.autoclose_regions.get(i) {
3417 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3418 Ordering::Less => i += 1,
3419 Ordering::Greater => break,
3420 Ordering::Equal => {
3421 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3422 Ordering::Less => i += 1,
3423 Ordering::Equal => break,
3424 Ordering::Greater => break,
3425 }
3426 }
3427 }
3428 }
3429 this.autoclose_regions.insert(
3430 i,
3431 AutocloseRegion {
3432 selection_id,
3433 range: start..end,
3434 pair,
3435 },
3436 );
3437 }
3438
3439 let had_active_inline_completion = this.has_active_inline_completion();
3440 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3441 s.select(new_selections)
3442 });
3443
3444 if !bracket_inserted {
3445 if let Some(on_type_format_task) =
3446 this.trigger_on_type_formatting(text.to_string(), window, cx)
3447 {
3448 on_type_format_task.detach_and_log_err(cx);
3449 }
3450 }
3451
3452 let editor_settings = EditorSettings::get_global(cx);
3453 if bracket_inserted
3454 && (editor_settings.auto_signature_help
3455 || editor_settings.show_signature_help_after_edits)
3456 {
3457 this.show_signature_help(&ShowSignatureHelp, window, cx);
3458 }
3459
3460 let trigger_in_words =
3461 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3462 if this.hard_wrap.is_some() {
3463 let latest: Range<Point> = this.selections.newest(cx).range();
3464 if latest.is_empty()
3465 && this
3466 .buffer()
3467 .read(cx)
3468 .snapshot(cx)
3469 .line_len(MultiBufferRow(latest.start.row))
3470 == latest.start.column
3471 {
3472 this.rewrap_impl(
3473 RewrapOptions {
3474 override_language_settings: true,
3475 preserve_existing_whitespace: true,
3476 },
3477 cx,
3478 )
3479 }
3480 }
3481 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3482 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3483 this.refresh_inline_completion(true, false, window, cx);
3484 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3485 });
3486 }
3487
3488 fn find_possible_emoji_shortcode_at_position(
3489 snapshot: &MultiBufferSnapshot,
3490 position: Point,
3491 ) -> Option<String> {
3492 let mut chars = Vec::new();
3493 let mut found_colon = false;
3494 for char in snapshot.reversed_chars_at(position).take(100) {
3495 // Found a possible emoji shortcode in the middle of the buffer
3496 if found_colon {
3497 if char.is_whitespace() {
3498 chars.reverse();
3499 return Some(chars.iter().collect());
3500 }
3501 // If the previous character is not a whitespace, we are in the middle of a word
3502 // and we only want to complete the shortcode if the word is made up of other emojis
3503 let mut containing_word = String::new();
3504 for ch in snapshot
3505 .reversed_chars_at(position)
3506 .skip(chars.len() + 1)
3507 .take(100)
3508 {
3509 if ch.is_whitespace() {
3510 break;
3511 }
3512 containing_word.push(ch);
3513 }
3514 let containing_word = containing_word.chars().rev().collect::<String>();
3515 if util::word_consists_of_emojis(containing_word.as_str()) {
3516 chars.reverse();
3517 return Some(chars.iter().collect());
3518 }
3519 }
3520
3521 if char.is_whitespace() || !char.is_ascii() {
3522 return None;
3523 }
3524 if char == ':' {
3525 found_colon = true;
3526 } else {
3527 chars.push(char);
3528 }
3529 }
3530 // Found a possible emoji shortcode at the beginning of the buffer
3531 chars.reverse();
3532 Some(chars.iter().collect())
3533 }
3534
3535 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3536 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3537 self.transact(window, cx, |this, window, cx| {
3538 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3539 let selections = this.selections.all::<usize>(cx);
3540 let multi_buffer = this.buffer.read(cx);
3541 let buffer = multi_buffer.snapshot(cx);
3542 selections
3543 .iter()
3544 .map(|selection| {
3545 let start_point = selection.start.to_point(&buffer);
3546 let mut indent =
3547 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3548 indent.len = cmp::min(indent.len, start_point.column);
3549 let start = selection.start;
3550 let end = selection.end;
3551 let selection_is_empty = start == end;
3552 let language_scope = buffer.language_scope_at(start);
3553 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3554 &language_scope
3555 {
3556 let insert_extra_newline =
3557 insert_extra_newline_brackets(&buffer, start..end, language)
3558 || insert_extra_newline_tree_sitter(&buffer, start..end);
3559
3560 // Comment extension on newline is allowed only for cursor selections
3561 let comment_delimiter = maybe!({
3562 if !selection_is_empty {
3563 return None;
3564 }
3565
3566 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3567 return None;
3568 }
3569
3570 let delimiters = language.line_comment_prefixes();
3571 let max_len_of_delimiter =
3572 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3573 let (snapshot, range) =
3574 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3575
3576 let mut index_of_first_non_whitespace = 0;
3577 let comment_candidate = snapshot
3578 .chars_for_range(range)
3579 .skip_while(|c| {
3580 let should_skip = c.is_whitespace();
3581 if should_skip {
3582 index_of_first_non_whitespace += 1;
3583 }
3584 should_skip
3585 })
3586 .take(max_len_of_delimiter)
3587 .collect::<String>();
3588 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3589 comment_candidate.starts_with(comment_prefix.as_ref())
3590 })?;
3591 let cursor_is_placed_after_comment_marker =
3592 index_of_first_non_whitespace + comment_prefix.len()
3593 <= start_point.column as usize;
3594 if cursor_is_placed_after_comment_marker {
3595 Some(comment_prefix.clone())
3596 } else {
3597 None
3598 }
3599 });
3600 (comment_delimiter, insert_extra_newline)
3601 } else {
3602 (None, false)
3603 };
3604
3605 let capacity_for_delimiter = comment_delimiter
3606 .as_deref()
3607 .map(str::len)
3608 .unwrap_or_default();
3609 let mut new_text =
3610 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3611 new_text.push('\n');
3612 new_text.extend(indent.chars());
3613 if let Some(delimiter) = &comment_delimiter {
3614 new_text.push_str(delimiter);
3615 }
3616 if insert_extra_newline {
3617 new_text = new_text.repeat(2);
3618 }
3619
3620 let anchor = buffer.anchor_after(end);
3621 let new_selection = selection.map(|_| anchor);
3622 (
3623 (start..end, new_text),
3624 (insert_extra_newline, new_selection),
3625 )
3626 })
3627 .unzip()
3628 };
3629
3630 this.edit_with_autoindent(edits, cx);
3631 let buffer = this.buffer.read(cx).snapshot(cx);
3632 let new_selections = selection_fixup_info
3633 .into_iter()
3634 .map(|(extra_newline_inserted, new_selection)| {
3635 let mut cursor = new_selection.end.to_point(&buffer);
3636 if extra_newline_inserted {
3637 cursor.row -= 1;
3638 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3639 }
3640 new_selection.map(|_| cursor)
3641 })
3642 .collect();
3643
3644 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3645 s.select(new_selections)
3646 });
3647 this.refresh_inline_completion(true, false, window, cx);
3648 });
3649 }
3650
3651 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3652 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3653
3654 let buffer = self.buffer.read(cx);
3655 let snapshot = buffer.snapshot(cx);
3656
3657 let mut edits = Vec::new();
3658 let mut rows = Vec::new();
3659
3660 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3661 let cursor = selection.head();
3662 let row = cursor.row;
3663
3664 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3665
3666 let newline = "\n".to_string();
3667 edits.push((start_of_line..start_of_line, newline));
3668
3669 rows.push(row + rows_inserted as u32);
3670 }
3671
3672 self.transact(window, cx, |editor, window, cx| {
3673 editor.edit(edits, cx);
3674
3675 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3676 let mut index = 0;
3677 s.move_cursors_with(|map, _, _| {
3678 let row = rows[index];
3679 index += 1;
3680
3681 let point = Point::new(row, 0);
3682 let boundary = map.next_line_boundary(point).1;
3683 let clipped = map.clip_point(boundary, Bias::Left);
3684
3685 (clipped, SelectionGoal::None)
3686 });
3687 });
3688
3689 let mut indent_edits = Vec::new();
3690 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3691 for row in rows {
3692 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3693 for (row, indent) in indents {
3694 if indent.len == 0 {
3695 continue;
3696 }
3697
3698 let text = match indent.kind {
3699 IndentKind::Space => " ".repeat(indent.len as usize),
3700 IndentKind::Tab => "\t".repeat(indent.len as usize),
3701 };
3702 let point = Point::new(row.0, 0);
3703 indent_edits.push((point..point, text));
3704 }
3705 }
3706 editor.edit(indent_edits, cx);
3707 });
3708 }
3709
3710 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3711 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3712
3713 let buffer = self.buffer.read(cx);
3714 let snapshot = buffer.snapshot(cx);
3715
3716 let mut edits = Vec::new();
3717 let mut rows = Vec::new();
3718 let mut rows_inserted = 0;
3719
3720 for selection in self.selections.all_adjusted(cx) {
3721 let cursor = selection.head();
3722 let row = cursor.row;
3723
3724 let point = Point::new(row + 1, 0);
3725 let start_of_line = snapshot.clip_point(point, Bias::Left);
3726
3727 let newline = "\n".to_string();
3728 edits.push((start_of_line..start_of_line, newline));
3729
3730 rows_inserted += 1;
3731 rows.push(row + rows_inserted);
3732 }
3733
3734 self.transact(window, cx, |editor, window, cx| {
3735 editor.edit(edits, cx);
3736
3737 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3738 let mut index = 0;
3739 s.move_cursors_with(|map, _, _| {
3740 let row = rows[index];
3741 index += 1;
3742
3743 let point = Point::new(row, 0);
3744 let boundary = map.next_line_boundary(point).1;
3745 let clipped = map.clip_point(boundary, Bias::Left);
3746
3747 (clipped, SelectionGoal::None)
3748 });
3749 });
3750
3751 let mut indent_edits = Vec::new();
3752 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3753 for row in rows {
3754 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3755 for (row, indent) in indents {
3756 if indent.len == 0 {
3757 continue;
3758 }
3759
3760 let text = match indent.kind {
3761 IndentKind::Space => " ".repeat(indent.len as usize),
3762 IndentKind::Tab => "\t".repeat(indent.len as usize),
3763 };
3764 let point = Point::new(row.0, 0);
3765 indent_edits.push((point..point, text));
3766 }
3767 }
3768 editor.edit(indent_edits, cx);
3769 });
3770 }
3771
3772 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3773 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3774 original_indent_columns: Vec::new(),
3775 });
3776 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3777 }
3778
3779 fn insert_with_autoindent_mode(
3780 &mut self,
3781 text: &str,
3782 autoindent_mode: Option<AutoindentMode>,
3783 window: &mut Window,
3784 cx: &mut Context<Self>,
3785 ) {
3786 if self.read_only(cx) {
3787 return;
3788 }
3789
3790 let text: Arc<str> = text.into();
3791 self.transact(window, cx, |this, window, cx| {
3792 let old_selections = this.selections.all_adjusted(cx);
3793 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3794 let anchors = {
3795 let snapshot = buffer.read(cx);
3796 old_selections
3797 .iter()
3798 .map(|s| {
3799 let anchor = snapshot.anchor_after(s.head());
3800 s.map(|_| anchor)
3801 })
3802 .collect::<Vec<_>>()
3803 };
3804 buffer.edit(
3805 old_selections
3806 .iter()
3807 .map(|s| (s.start..s.end, text.clone())),
3808 autoindent_mode,
3809 cx,
3810 );
3811 anchors
3812 });
3813
3814 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3815 s.select_anchors(selection_anchors);
3816 });
3817
3818 cx.notify();
3819 });
3820 }
3821
3822 fn trigger_completion_on_input(
3823 &mut self,
3824 text: &str,
3825 trigger_in_words: bool,
3826 window: &mut Window,
3827 cx: &mut Context<Self>,
3828 ) {
3829 let ignore_completion_provider = self
3830 .context_menu
3831 .borrow()
3832 .as_ref()
3833 .map(|menu| match menu {
3834 CodeContextMenu::Completions(completions_menu) => {
3835 completions_menu.ignore_completion_provider
3836 }
3837 CodeContextMenu::CodeActions(_) => false,
3838 })
3839 .unwrap_or(false);
3840
3841 if ignore_completion_provider {
3842 self.show_word_completions(&ShowWordCompletions, window, cx);
3843 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3844 self.show_completions(
3845 &ShowCompletions {
3846 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3847 },
3848 window,
3849 cx,
3850 );
3851 } else {
3852 self.hide_context_menu(window, cx);
3853 }
3854 }
3855
3856 fn is_completion_trigger(
3857 &self,
3858 text: &str,
3859 trigger_in_words: bool,
3860 cx: &mut Context<Self>,
3861 ) -> bool {
3862 let position = self.selections.newest_anchor().head();
3863 let multibuffer = self.buffer.read(cx);
3864 let Some(buffer) = position
3865 .buffer_id
3866 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3867 else {
3868 return false;
3869 };
3870
3871 if let Some(completion_provider) = &self.completion_provider {
3872 completion_provider.is_completion_trigger(
3873 &buffer,
3874 position.text_anchor,
3875 text,
3876 trigger_in_words,
3877 cx,
3878 )
3879 } else {
3880 false
3881 }
3882 }
3883
3884 /// If any empty selections is touching the start of its innermost containing autoclose
3885 /// region, expand it to select the brackets.
3886 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3887 let selections = self.selections.all::<usize>(cx);
3888 let buffer = self.buffer.read(cx).read(cx);
3889 let new_selections = self
3890 .selections_with_autoclose_regions(selections, &buffer)
3891 .map(|(mut selection, region)| {
3892 if !selection.is_empty() {
3893 return selection;
3894 }
3895
3896 if let Some(region) = region {
3897 let mut range = region.range.to_offset(&buffer);
3898 if selection.start == range.start && range.start >= region.pair.start.len() {
3899 range.start -= region.pair.start.len();
3900 if buffer.contains_str_at(range.start, ®ion.pair.start)
3901 && buffer.contains_str_at(range.end, ®ion.pair.end)
3902 {
3903 range.end += region.pair.end.len();
3904 selection.start = range.start;
3905 selection.end = range.end;
3906
3907 return selection;
3908 }
3909 }
3910 }
3911
3912 let always_treat_brackets_as_autoclosed = buffer
3913 .language_settings_at(selection.start, cx)
3914 .always_treat_brackets_as_autoclosed;
3915
3916 if !always_treat_brackets_as_autoclosed {
3917 return selection;
3918 }
3919
3920 if let Some(scope) = buffer.language_scope_at(selection.start) {
3921 for (pair, enabled) in scope.brackets() {
3922 if !enabled || !pair.close {
3923 continue;
3924 }
3925
3926 if buffer.contains_str_at(selection.start, &pair.end) {
3927 let pair_start_len = pair.start.len();
3928 if buffer.contains_str_at(
3929 selection.start.saturating_sub(pair_start_len),
3930 &pair.start,
3931 ) {
3932 selection.start -= pair_start_len;
3933 selection.end += pair.end.len();
3934
3935 return selection;
3936 }
3937 }
3938 }
3939 }
3940
3941 selection
3942 })
3943 .collect();
3944
3945 drop(buffer);
3946 self.change_selections(None, window, cx, |selections| {
3947 selections.select(new_selections)
3948 });
3949 }
3950
3951 /// Iterate the given selections, and for each one, find the smallest surrounding
3952 /// autoclose region. This uses the ordering of the selections and the autoclose
3953 /// regions to avoid repeated comparisons.
3954 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3955 &'a self,
3956 selections: impl IntoIterator<Item = Selection<D>>,
3957 buffer: &'a MultiBufferSnapshot,
3958 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3959 let mut i = 0;
3960 let mut regions = self.autoclose_regions.as_slice();
3961 selections.into_iter().map(move |selection| {
3962 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3963
3964 let mut enclosing = None;
3965 while let Some(pair_state) = regions.get(i) {
3966 if pair_state.range.end.to_offset(buffer) < range.start {
3967 regions = ®ions[i + 1..];
3968 i = 0;
3969 } else if pair_state.range.start.to_offset(buffer) > range.end {
3970 break;
3971 } else {
3972 if pair_state.selection_id == selection.id {
3973 enclosing = Some(pair_state);
3974 }
3975 i += 1;
3976 }
3977 }
3978
3979 (selection, enclosing)
3980 })
3981 }
3982
3983 /// Remove any autoclose regions that no longer contain their selection.
3984 fn invalidate_autoclose_regions(
3985 &mut self,
3986 mut selections: &[Selection<Anchor>],
3987 buffer: &MultiBufferSnapshot,
3988 ) {
3989 self.autoclose_regions.retain(|state| {
3990 let mut i = 0;
3991 while let Some(selection) = selections.get(i) {
3992 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3993 selections = &selections[1..];
3994 continue;
3995 }
3996 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3997 break;
3998 }
3999 if selection.id == state.selection_id {
4000 return true;
4001 } else {
4002 i += 1;
4003 }
4004 }
4005 false
4006 });
4007 }
4008
4009 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4010 let offset = position.to_offset(buffer);
4011 let (word_range, kind) = buffer.surrounding_word(offset, true);
4012 if offset > word_range.start && kind == Some(CharKind::Word) {
4013 Some(
4014 buffer
4015 .text_for_range(word_range.start..offset)
4016 .collect::<String>(),
4017 )
4018 } else {
4019 None
4020 }
4021 }
4022
4023 pub fn toggle_inlay_hints(
4024 &mut self,
4025 _: &ToggleInlayHints,
4026 _: &mut Window,
4027 cx: &mut Context<Self>,
4028 ) {
4029 self.refresh_inlay_hints(
4030 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4031 cx,
4032 );
4033 }
4034
4035 pub fn inlay_hints_enabled(&self) -> bool {
4036 self.inlay_hint_cache.enabled
4037 }
4038
4039 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4040 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
4041 return;
4042 }
4043
4044 let reason_description = reason.description();
4045 let ignore_debounce = matches!(
4046 reason,
4047 InlayHintRefreshReason::SettingsChange(_)
4048 | InlayHintRefreshReason::Toggle(_)
4049 | InlayHintRefreshReason::ExcerptsRemoved(_)
4050 | InlayHintRefreshReason::ModifiersChanged(_)
4051 );
4052 let (invalidate_cache, required_languages) = match reason {
4053 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4054 match self.inlay_hint_cache.modifiers_override(enabled) {
4055 Some(enabled) => {
4056 if enabled {
4057 (InvalidationStrategy::RefreshRequested, None)
4058 } else {
4059 self.splice_inlays(
4060 &self
4061 .visible_inlay_hints(cx)
4062 .iter()
4063 .map(|inlay| inlay.id)
4064 .collect::<Vec<InlayId>>(),
4065 Vec::new(),
4066 cx,
4067 );
4068 return;
4069 }
4070 }
4071 None => return,
4072 }
4073 }
4074 InlayHintRefreshReason::Toggle(enabled) => {
4075 if self.inlay_hint_cache.toggle(enabled) {
4076 if enabled {
4077 (InvalidationStrategy::RefreshRequested, None)
4078 } else {
4079 self.splice_inlays(
4080 &self
4081 .visible_inlay_hints(cx)
4082 .iter()
4083 .map(|inlay| inlay.id)
4084 .collect::<Vec<InlayId>>(),
4085 Vec::new(),
4086 cx,
4087 );
4088 return;
4089 }
4090 } else {
4091 return;
4092 }
4093 }
4094 InlayHintRefreshReason::SettingsChange(new_settings) => {
4095 match self.inlay_hint_cache.update_settings(
4096 &self.buffer,
4097 new_settings,
4098 self.visible_inlay_hints(cx),
4099 cx,
4100 ) {
4101 ControlFlow::Break(Some(InlaySplice {
4102 to_remove,
4103 to_insert,
4104 })) => {
4105 self.splice_inlays(&to_remove, to_insert, cx);
4106 return;
4107 }
4108 ControlFlow::Break(None) => return,
4109 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4110 }
4111 }
4112 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4113 if let Some(InlaySplice {
4114 to_remove,
4115 to_insert,
4116 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
4117 {
4118 self.splice_inlays(&to_remove, to_insert, cx);
4119 }
4120 return;
4121 }
4122 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4123 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4124 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4125 }
4126 InlayHintRefreshReason::RefreshRequested => {
4127 (InvalidationStrategy::RefreshRequested, None)
4128 }
4129 };
4130
4131 if let Some(InlaySplice {
4132 to_remove,
4133 to_insert,
4134 }) = self.inlay_hint_cache.spawn_hint_refresh(
4135 reason_description,
4136 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4137 invalidate_cache,
4138 ignore_debounce,
4139 cx,
4140 ) {
4141 self.splice_inlays(&to_remove, to_insert, cx);
4142 }
4143 }
4144
4145 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4146 self.display_map
4147 .read(cx)
4148 .current_inlays()
4149 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4150 .cloned()
4151 .collect()
4152 }
4153
4154 pub fn excerpts_for_inlay_hints_query(
4155 &self,
4156 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4157 cx: &mut Context<Editor>,
4158 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4159 let Some(project) = self.project.as_ref() else {
4160 return HashMap::default();
4161 };
4162 let project = project.read(cx);
4163 let multi_buffer = self.buffer().read(cx);
4164 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4165 let multi_buffer_visible_start = self
4166 .scroll_manager
4167 .anchor()
4168 .anchor
4169 .to_point(&multi_buffer_snapshot);
4170 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4171 multi_buffer_visible_start
4172 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4173 Bias::Left,
4174 );
4175 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4176 multi_buffer_snapshot
4177 .range_to_buffer_ranges(multi_buffer_visible_range)
4178 .into_iter()
4179 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4180 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4181 let buffer_file = project::File::from_dyn(buffer.file())?;
4182 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4183 let worktree_entry = buffer_worktree
4184 .read(cx)
4185 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4186 if worktree_entry.is_ignored {
4187 return None;
4188 }
4189
4190 let language = buffer.language()?;
4191 if let Some(restrict_to_languages) = restrict_to_languages {
4192 if !restrict_to_languages.contains(language) {
4193 return None;
4194 }
4195 }
4196 Some((
4197 excerpt_id,
4198 (
4199 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4200 buffer.version().clone(),
4201 excerpt_visible_range,
4202 ),
4203 ))
4204 })
4205 .collect()
4206 }
4207
4208 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4209 TextLayoutDetails {
4210 text_system: window.text_system().clone(),
4211 editor_style: self.style.clone().unwrap(),
4212 rem_size: window.rem_size(),
4213 scroll_anchor: self.scroll_manager.anchor(),
4214 visible_rows: self.visible_line_count(),
4215 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4216 }
4217 }
4218
4219 pub fn splice_inlays(
4220 &self,
4221 to_remove: &[InlayId],
4222 to_insert: Vec<Inlay>,
4223 cx: &mut Context<Self>,
4224 ) {
4225 self.display_map.update(cx, |display_map, cx| {
4226 display_map.splice_inlays(to_remove, to_insert, cx)
4227 });
4228 cx.notify();
4229 }
4230
4231 fn trigger_on_type_formatting(
4232 &self,
4233 input: String,
4234 window: &mut Window,
4235 cx: &mut Context<Self>,
4236 ) -> Option<Task<Result<()>>> {
4237 if input.len() != 1 {
4238 return None;
4239 }
4240
4241 let project = self.project.as_ref()?;
4242 let position = self.selections.newest_anchor().head();
4243 let (buffer, buffer_position) = self
4244 .buffer
4245 .read(cx)
4246 .text_anchor_for_position(position, cx)?;
4247
4248 let settings = language_settings::language_settings(
4249 buffer
4250 .read(cx)
4251 .language_at(buffer_position)
4252 .map(|l| l.name()),
4253 buffer.read(cx).file(),
4254 cx,
4255 );
4256 if !settings.use_on_type_format {
4257 return None;
4258 }
4259
4260 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4261 // hence we do LSP request & edit on host side only — add formats to host's history.
4262 let push_to_lsp_host_history = true;
4263 // If this is not the host, append its history with new edits.
4264 let push_to_client_history = project.read(cx).is_via_collab();
4265
4266 let on_type_formatting = project.update(cx, |project, cx| {
4267 project.on_type_format(
4268 buffer.clone(),
4269 buffer_position,
4270 input,
4271 push_to_lsp_host_history,
4272 cx,
4273 )
4274 });
4275 Some(cx.spawn_in(window, async move |editor, cx| {
4276 if let Some(transaction) = on_type_formatting.await? {
4277 if push_to_client_history {
4278 buffer
4279 .update(cx, |buffer, _| {
4280 buffer.push_transaction(transaction, Instant::now());
4281 buffer.finalize_last_transaction();
4282 })
4283 .ok();
4284 }
4285 editor.update(cx, |editor, cx| {
4286 editor.refresh_document_highlights(cx);
4287 })?;
4288 }
4289 Ok(())
4290 }))
4291 }
4292
4293 pub fn show_word_completions(
4294 &mut self,
4295 _: &ShowWordCompletions,
4296 window: &mut Window,
4297 cx: &mut Context<Self>,
4298 ) {
4299 self.open_completions_menu(true, None, window, cx);
4300 }
4301
4302 pub fn show_completions(
4303 &mut self,
4304 options: &ShowCompletions,
4305 window: &mut Window,
4306 cx: &mut Context<Self>,
4307 ) {
4308 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4309 }
4310
4311 fn open_completions_menu(
4312 &mut self,
4313 ignore_completion_provider: bool,
4314 trigger: Option<&str>,
4315 window: &mut Window,
4316 cx: &mut Context<Self>,
4317 ) {
4318 if self.pending_rename.is_some() {
4319 return;
4320 }
4321 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4322 return;
4323 }
4324
4325 let position = self.selections.newest_anchor().head();
4326 if position.diff_base_anchor.is_some() {
4327 return;
4328 }
4329 let (buffer, buffer_position) =
4330 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4331 output
4332 } else {
4333 return;
4334 };
4335 let buffer_snapshot = buffer.read(cx).snapshot();
4336 let show_completion_documentation = buffer_snapshot
4337 .settings_at(buffer_position, cx)
4338 .show_completion_documentation;
4339
4340 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4341
4342 let trigger_kind = match trigger {
4343 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4344 CompletionTriggerKind::TRIGGER_CHARACTER
4345 }
4346 _ => CompletionTriggerKind::INVOKED,
4347 };
4348 let completion_context = CompletionContext {
4349 trigger_character: trigger.and_then(|trigger| {
4350 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4351 Some(String::from(trigger))
4352 } else {
4353 None
4354 }
4355 }),
4356 trigger_kind,
4357 };
4358
4359 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4360 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4361 let word_to_exclude = buffer_snapshot
4362 .text_for_range(old_range.clone())
4363 .collect::<String>();
4364 (
4365 buffer_snapshot.anchor_before(old_range.start)
4366 ..buffer_snapshot.anchor_after(old_range.end),
4367 Some(word_to_exclude),
4368 )
4369 } else {
4370 (buffer_position..buffer_position, None)
4371 };
4372
4373 let completion_settings = language_settings(
4374 buffer_snapshot
4375 .language_at(buffer_position)
4376 .map(|language| language.name()),
4377 buffer_snapshot.file(),
4378 cx,
4379 )
4380 .completions;
4381
4382 // The document can be large, so stay in reasonable bounds when searching for words,
4383 // otherwise completion pop-up might be slow to appear.
4384 const WORD_LOOKUP_ROWS: u32 = 5_000;
4385 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4386 let min_word_search = buffer_snapshot.clip_point(
4387 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4388 Bias::Left,
4389 );
4390 let max_word_search = buffer_snapshot.clip_point(
4391 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4392 Bias::Right,
4393 );
4394 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4395 ..buffer_snapshot.point_to_offset(max_word_search);
4396
4397 let provider = self
4398 .completion_provider
4399 .as_ref()
4400 .filter(|_| !ignore_completion_provider);
4401 let skip_digits = query
4402 .as_ref()
4403 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4404
4405 let (mut words, provided_completions) = match provider {
4406 Some(provider) => {
4407 let completions = provider.completions(
4408 position.excerpt_id,
4409 &buffer,
4410 buffer_position,
4411 completion_context,
4412 window,
4413 cx,
4414 );
4415
4416 let words = match completion_settings.words {
4417 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4418 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4419 .background_spawn(async move {
4420 buffer_snapshot.words_in_range(WordsQuery {
4421 fuzzy_contents: None,
4422 range: word_search_range,
4423 skip_digits,
4424 })
4425 }),
4426 };
4427
4428 (words, completions)
4429 }
4430 None => (
4431 cx.background_spawn(async move {
4432 buffer_snapshot.words_in_range(WordsQuery {
4433 fuzzy_contents: None,
4434 range: word_search_range,
4435 skip_digits,
4436 })
4437 }),
4438 Task::ready(Ok(None)),
4439 ),
4440 };
4441
4442 let sort_completions = provider
4443 .as_ref()
4444 .map_or(false, |provider| provider.sort_completions());
4445
4446 let filter_completions = provider
4447 .as_ref()
4448 .map_or(true, |provider| provider.filter_completions());
4449
4450 let id = post_inc(&mut self.next_completion_id);
4451 let task = cx.spawn_in(window, async move |editor, cx| {
4452 async move {
4453 editor.update(cx, |this, _| {
4454 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4455 })?;
4456
4457 let mut completions = Vec::new();
4458 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4459 completions.extend(provided_completions);
4460 if completion_settings.words == WordsCompletionMode::Fallback {
4461 words = Task::ready(BTreeMap::default());
4462 }
4463 }
4464
4465 let mut words = words.await;
4466 if let Some(word_to_exclude) = &word_to_exclude {
4467 words.remove(word_to_exclude);
4468 }
4469 for lsp_completion in &completions {
4470 words.remove(&lsp_completion.new_text);
4471 }
4472 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4473 replace_range: old_range.clone(),
4474 new_text: word.clone(),
4475 label: CodeLabel::plain(word, None),
4476 icon_path: None,
4477 documentation: None,
4478 source: CompletionSource::BufferWord {
4479 word_range,
4480 resolved: false,
4481 },
4482 insert_text_mode: Some(InsertTextMode::AS_IS),
4483 confirm: None,
4484 }));
4485
4486 let menu = if completions.is_empty() {
4487 None
4488 } else {
4489 let mut menu = CompletionsMenu::new(
4490 id,
4491 sort_completions,
4492 show_completion_documentation,
4493 ignore_completion_provider,
4494 position,
4495 buffer.clone(),
4496 completions.into(),
4497 );
4498
4499 menu.filter(
4500 if filter_completions {
4501 query.as_deref()
4502 } else {
4503 None
4504 },
4505 cx.background_executor().clone(),
4506 )
4507 .await;
4508
4509 menu.visible().then_some(menu)
4510 };
4511
4512 editor.update_in(cx, |editor, window, cx| {
4513 match editor.context_menu.borrow().as_ref() {
4514 None => {}
4515 Some(CodeContextMenu::Completions(prev_menu)) => {
4516 if prev_menu.id > id {
4517 return;
4518 }
4519 }
4520 _ => return,
4521 }
4522
4523 if editor.focus_handle.is_focused(window) && menu.is_some() {
4524 let mut menu = menu.unwrap();
4525 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4526
4527 *editor.context_menu.borrow_mut() =
4528 Some(CodeContextMenu::Completions(menu));
4529
4530 if editor.show_edit_predictions_in_menu() {
4531 editor.update_visible_inline_completion(window, cx);
4532 } else {
4533 editor.discard_inline_completion(false, cx);
4534 }
4535
4536 cx.notify();
4537 } else if editor.completion_tasks.len() <= 1 {
4538 // If there are no more completion tasks and the last menu was
4539 // empty, we should hide it.
4540 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4541 // If it was already hidden and we don't show inline
4542 // completions in the menu, we should also show the
4543 // inline-completion when available.
4544 if was_hidden && editor.show_edit_predictions_in_menu() {
4545 editor.update_visible_inline_completion(window, cx);
4546 }
4547 }
4548 })?;
4549
4550 anyhow::Ok(())
4551 }
4552 .log_err()
4553 .await
4554 });
4555
4556 self.completion_tasks.push((id, task));
4557 }
4558
4559 #[cfg(feature = "test-support")]
4560 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4561 let menu = self.context_menu.borrow();
4562 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4563 let completions = menu.completions.borrow();
4564 Some(completions.to_vec())
4565 } else {
4566 None
4567 }
4568 }
4569
4570 pub fn confirm_completion(
4571 &mut self,
4572 action: &ConfirmCompletion,
4573 window: &mut Window,
4574 cx: &mut Context<Self>,
4575 ) -> Option<Task<Result<()>>> {
4576 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4577 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4578 }
4579
4580 pub fn confirm_completion_insert(
4581 &mut self,
4582 _: &ConfirmCompletionInsert,
4583 window: &mut Window,
4584 cx: &mut Context<Self>,
4585 ) -> Option<Task<Result<()>>> {
4586 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4587 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4588 }
4589
4590 pub fn confirm_completion_replace(
4591 &mut self,
4592 _: &ConfirmCompletionReplace,
4593 window: &mut Window,
4594 cx: &mut Context<Self>,
4595 ) -> Option<Task<Result<()>>> {
4596 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4597 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4598 }
4599
4600 pub fn compose_completion(
4601 &mut self,
4602 action: &ComposeCompletion,
4603 window: &mut Window,
4604 cx: &mut Context<Self>,
4605 ) -> Option<Task<Result<()>>> {
4606 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4607 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4608 }
4609
4610 fn do_completion(
4611 &mut self,
4612 item_ix: Option<usize>,
4613 intent: CompletionIntent,
4614 window: &mut Window,
4615 cx: &mut Context<Editor>,
4616 ) -> Option<Task<Result<()>>> {
4617 use language::ToOffset as _;
4618
4619 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4620 else {
4621 return None;
4622 };
4623
4624 let candidate_id = {
4625 let entries = completions_menu.entries.borrow();
4626 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4627 if self.show_edit_predictions_in_menu() {
4628 self.discard_inline_completion(true, cx);
4629 }
4630 mat.candidate_id
4631 };
4632
4633 let buffer_handle = completions_menu.buffer;
4634 let completion = completions_menu
4635 .completions
4636 .borrow()
4637 .get(candidate_id)?
4638 .clone();
4639 cx.stop_propagation();
4640
4641 let snippet;
4642 let new_text;
4643 if completion.is_snippet() {
4644 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4645 new_text = snippet.as_ref().unwrap().text.clone();
4646 } else {
4647 snippet = None;
4648 new_text = completion.new_text.clone();
4649 };
4650 let selections = self.selections.all::<usize>(cx);
4651
4652 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4653 let buffer = buffer_handle.read(cx);
4654 let old_text = buffer
4655 .text_for_range(replace_range.clone())
4656 .collect::<String>();
4657
4658 let newest_selection = self.selections.newest_anchor();
4659 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4660 return None;
4661 }
4662
4663 let lookbehind = newest_selection
4664 .start
4665 .text_anchor
4666 .to_offset(buffer)
4667 .saturating_sub(replace_range.start);
4668 let lookahead = replace_range
4669 .end
4670 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4671 let mut common_prefix_len = 0;
4672 for (a, b) in old_text.chars().zip(new_text.chars()) {
4673 if a == b {
4674 common_prefix_len += a.len_utf8();
4675 } else {
4676 break;
4677 }
4678 }
4679
4680 let snapshot = self.buffer.read(cx).snapshot(cx);
4681 let mut range_to_replace: Option<Range<usize>> = None;
4682 let mut ranges = Vec::new();
4683 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4684 for selection in &selections {
4685 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4686 let start = selection.start.saturating_sub(lookbehind);
4687 let end = selection.end + lookahead;
4688 if selection.id == newest_selection.id {
4689 range_to_replace = Some(start + common_prefix_len..end);
4690 }
4691 ranges.push(start + common_prefix_len..end);
4692 } else {
4693 common_prefix_len = 0;
4694 ranges.clear();
4695 ranges.extend(selections.iter().map(|s| {
4696 if s.id == newest_selection.id {
4697 range_to_replace = Some(replace_range.clone());
4698 replace_range.clone()
4699 } else {
4700 s.start..s.end
4701 }
4702 }));
4703 break;
4704 }
4705 if !self.linked_edit_ranges.is_empty() {
4706 let start_anchor = snapshot.anchor_before(selection.head());
4707 let end_anchor = snapshot.anchor_after(selection.tail());
4708 if let Some(ranges) = self
4709 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4710 {
4711 for (buffer, edits) in ranges {
4712 linked_edits.entry(buffer.clone()).or_default().extend(
4713 edits
4714 .into_iter()
4715 .map(|range| (range, new_text[common_prefix_len..].to_owned())),
4716 );
4717 }
4718 }
4719 }
4720 }
4721 let text = &new_text[common_prefix_len..];
4722
4723 let utf16_range_to_replace = range_to_replace.map(|range| {
4724 let newest_selection = self.selections.newest::<OffsetUtf16>(cx).range();
4725 let selection_start_utf16 = newest_selection.start.0 as isize;
4726
4727 range.start.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16
4728 ..range.end.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16
4729 });
4730 cx.emit(EditorEvent::InputHandled {
4731 utf16_range_to_replace,
4732 text: text.into(),
4733 });
4734
4735 self.transact(window, cx, |this, window, cx| {
4736 if let Some(mut snippet) = snippet {
4737 snippet.text = text.to_string();
4738 for tabstop in snippet
4739 .tabstops
4740 .iter_mut()
4741 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4742 {
4743 tabstop.start -= common_prefix_len as isize;
4744 tabstop.end -= common_prefix_len as isize;
4745 }
4746
4747 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4748 } else {
4749 this.buffer.update(cx, |buffer, cx| {
4750 let edits = ranges.iter().map(|range| (range.clone(), text));
4751 let auto_indent = if completion.insert_text_mode == Some(InsertTextMode::AS_IS)
4752 {
4753 None
4754 } else {
4755 this.autoindent_mode.clone()
4756 };
4757 buffer.edit(edits, auto_indent, cx);
4758 });
4759 }
4760 for (buffer, edits) in linked_edits {
4761 buffer.update(cx, |buffer, cx| {
4762 let snapshot = buffer.snapshot();
4763 let edits = edits
4764 .into_iter()
4765 .map(|(range, text)| {
4766 use text::ToPoint as TP;
4767 let end_point = TP::to_point(&range.end, &snapshot);
4768 let start_point = TP::to_point(&range.start, &snapshot);
4769 (start_point..end_point, text)
4770 })
4771 .sorted_by_key(|(range, _)| range.start);
4772 buffer.edit(edits, None, cx);
4773 })
4774 }
4775
4776 this.refresh_inline_completion(true, false, window, cx);
4777 });
4778
4779 let show_new_completions_on_confirm = completion
4780 .confirm
4781 .as_ref()
4782 .map_or(false, |confirm| confirm(intent, window, cx));
4783 if show_new_completions_on_confirm {
4784 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4785 }
4786
4787 let provider = self.completion_provider.as_ref()?;
4788 drop(completion);
4789 let apply_edits = provider.apply_additional_edits_for_completion(
4790 buffer_handle,
4791 completions_menu.completions.clone(),
4792 candidate_id,
4793 true,
4794 cx,
4795 );
4796
4797 let editor_settings = EditorSettings::get_global(cx);
4798 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4799 // After the code completion is finished, users often want to know what signatures are needed.
4800 // so we should automatically call signature_help
4801 self.show_signature_help(&ShowSignatureHelp, window, cx);
4802 }
4803
4804 Some(cx.foreground_executor().spawn(async move {
4805 apply_edits.await?;
4806 Ok(())
4807 }))
4808 }
4809
4810 pub fn toggle_code_actions(
4811 &mut self,
4812 action: &ToggleCodeActions,
4813 window: &mut Window,
4814 cx: &mut Context<Self>,
4815 ) {
4816 let mut context_menu = self.context_menu.borrow_mut();
4817 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4818 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4819 // Toggle if we're selecting the same one
4820 *context_menu = None;
4821 cx.notify();
4822 return;
4823 } else {
4824 // Otherwise, clear it and start a new one
4825 *context_menu = None;
4826 cx.notify();
4827 }
4828 }
4829 drop(context_menu);
4830 let snapshot = self.snapshot(window, cx);
4831 let deployed_from_indicator = action.deployed_from_indicator;
4832 let mut task = self.code_actions_task.take();
4833 let action = action.clone();
4834 cx.spawn_in(window, async move |editor, cx| {
4835 while let Some(prev_task) = task {
4836 prev_task.await.log_err();
4837 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4838 }
4839
4840 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4841 if editor.focus_handle.is_focused(window) {
4842 let multibuffer_point = action
4843 .deployed_from_indicator
4844 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4845 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4846 let (buffer, buffer_row) = snapshot
4847 .buffer_snapshot
4848 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4849 .and_then(|(buffer_snapshot, range)| {
4850 editor
4851 .buffer
4852 .read(cx)
4853 .buffer(buffer_snapshot.remote_id())
4854 .map(|buffer| (buffer, range.start.row))
4855 })?;
4856 let (_, code_actions) = editor
4857 .available_code_actions
4858 .clone()
4859 .and_then(|(location, code_actions)| {
4860 let snapshot = location.buffer.read(cx).snapshot();
4861 let point_range = location.range.to_point(&snapshot);
4862 let point_range = point_range.start.row..=point_range.end.row;
4863 if point_range.contains(&buffer_row) {
4864 Some((location, code_actions))
4865 } else {
4866 None
4867 }
4868 })
4869 .unzip();
4870 let buffer_id = buffer.read(cx).remote_id();
4871 let tasks = editor
4872 .tasks
4873 .get(&(buffer_id, buffer_row))
4874 .map(|t| Arc::new(t.to_owned()));
4875 if tasks.is_none() && code_actions.is_none() {
4876 return None;
4877 }
4878
4879 editor.completion_tasks.clear();
4880 editor.discard_inline_completion(false, cx);
4881 let task_context =
4882 tasks
4883 .as_ref()
4884 .zip(editor.project.clone())
4885 .map(|(tasks, project)| {
4886 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4887 });
4888
4889 let debugger_flag = cx.has_flag::<Debugger>();
4890
4891 Some(cx.spawn_in(window, async move |editor, cx| {
4892 let task_context = match task_context {
4893 Some(task_context) => task_context.await,
4894 None => None,
4895 };
4896 let resolved_tasks =
4897 tasks.zip(task_context).map(|(tasks, task_context)| {
4898 Rc::new(ResolvedTasks {
4899 templates: tasks.resolve(&task_context).collect(),
4900 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4901 multibuffer_point.row,
4902 tasks.column,
4903 )),
4904 })
4905 });
4906 let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
4907 tasks
4908 .templates
4909 .iter()
4910 .filter(|task| {
4911 if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
4912 debugger_flag
4913 } else {
4914 true
4915 }
4916 })
4917 .count()
4918 == 1
4919 }) && code_actions
4920 .as_ref()
4921 .map_or(true, |actions| actions.is_empty());
4922 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4923 *editor.context_menu.borrow_mut() =
4924 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4925 buffer,
4926 actions: CodeActionContents {
4927 tasks: resolved_tasks,
4928 actions: code_actions,
4929 },
4930 selected_item: Default::default(),
4931 scroll_handle: UniformListScrollHandle::default(),
4932 deployed_from_indicator,
4933 }));
4934 if spawn_straight_away {
4935 if let Some(task) = editor.confirm_code_action(
4936 &ConfirmCodeAction { item_ix: Some(0) },
4937 window,
4938 cx,
4939 ) {
4940 cx.notify();
4941 return task;
4942 }
4943 }
4944 cx.notify();
4945 Task::ready(Ok(()))
4946 }) {
4947 task.await
4948 } else {
4949 Ok(())
4950 }
4951 }))
4952 } else {
4953 Some(Task::ready(Ok(())))
4954 }
4955 })?;
4956 if let Some(task) = spawned_test_task {
4957 task.await?;
4958 }
4959
4960 Ok::<_, anyhow::Error>(())
4961 })
4962 .detach_and_log_err(cx);
4963 }
4964
4965 pub fn confirm_code_action(
4966 &mut self,
4967 action: &ConfirmCodeAction,
4968 window: &mut Window,
4969 cx: &mut Context<Self>,
4970 ) -> Option<Task<Result<()>>> {
4971 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4972
4973 let actions_menu =
4974 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4975 menu
4976 } else {
4977 return None;
4978 };
4979
4980 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4981 let action = actions_menu.actions.get(action_ix)?;
4982 let title = action.label();
4983 let buffer = actions_menu.buffer;
4984 let workspace = self.workspace()?;
4985
4986 match action {
4987 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4988 match resolved_task.task_type() {
4989 task::TaskType::Script => workspace.update(cx, |workspace, cx| {
4990 workspace::tasks::schedule_resolved_task(
4991 workspace,
4992 task_source_kind,
4993 resolved_task,
4994 false,
4995 cx,
4996 );
4997
4998 Some(Task::ready(Ok(())))
4999 }),
5000 task::TaskType::Debug(debug_args) => {
5001 if debug_args.locator.is_some() {
5002 workspace.update(cx, |workspace, cx| {
5003 workspace::tasks::schedule_resolved_task(
5004 workspace,
5005 task_source_kind,
5006 resolved_task,
5007 false,
5008 cx,
5009 );
5010 });
5011
5012 return Some(Task::ready(Ok(())));
5013 }
5014
5015 if let Some(project) = self.project.as_ref() {
5016 project
5017 .update(cx, |project, cx| {
5018 project.start_debug_session(
5019 resolved_task.resolved_debug_adapter_config().unwrap(),
5020 cx,
5021 )
5022 })
5023 .detach_and_log_err(cx);
5024 Some(Task::ready(Ok(())))
5025 } else {
5026 Some(Task::ready(Ok(())))
5027 }
5028 }
5029 }
5030 }
5031 CodeActionsItem::CodeAction {
5032 excerpt_id,
5033 action,
5034 provider,
5035 } => {
5036 let apply_code_action =
5037 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5038 let workspace = workspace.downgrade();
5039 Some(cx.spawn_in(window, async move |editor, cx| {
5040 let project_transaction = apply_code_action.await?;
5041 Self::open_project_transaction(
5042 &editor,
5043 workspace,
5044 project_transaction,
5045 title,
5046 cx,
5047 )
5048 .await
5049 }))
5050 }
5051 }
5052 }
5053
5054 pub async fn open_project_transaction(
5055 this: &WeakEntity<Editor>,
5056 workspace: WeakEntity<Workspace>,
5057 transaction: ProjectTransaction,
5058 title: String,
5059 cx: &mut AsyncWindowContext,
5060 ) -> Result<()> {
5061 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5062 cx.update(|_, cx| {
5063 entries.sort_unstable_by_key(|(buffer, _)| {
5064 buffer.read(cx).file().map(|f| f.path().clone())
5065 });
5066 })?;
5067
5068 // If the project transaction's edits are all contained within this editor, then
5069 // avoid opening a new editor to display them.
5070
5071 if let Some((buffer, transaction)) = entries.first() {
5072 if entries.len() == 1 {
5073 let excerpt = this.update(cx, |editor, cx| {
5074 editor
5075 .buffer()
5076 .read(cx)
5077 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5078 })?;
5079 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5080 if excerpted_buffer == *buffer {
5081 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5082 let excerpt_range = excerpt_range.to_offset(buffer);
5083 buffer
5084 .edited_ranges_for_transaction::<usize>(transaction)
5085 .all(|range| {
5086 excerpt_range.start <= range.start
5087 && excerpt_range.end >= range.end
5088 })
5089 })?;
5090
5091 if all_edits_within_excerpt {
5092 return Ok(());
5093 }
5094 }
5095 }
5096 }
5097 } else {
5098 return Ok(());
5099 }
5100
5101 let mut ranges_to_highlight = Vec::new();
5102 let excerpt_buffer = cx.new(|cx| {
5103 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5104 for (buffer_handle, transaction) in &entries {
5105 let edited_ranges = buffer_handle
5106 .read(cx)
5107 .edited_ranges_for_transaction::<Point>(transaction)
5108 .collect::<Vec<_>>();
5109 let (ranges, _) = multibuffer.set_excerpts_for_path(
5110 PathKey::for_buffer(buffer_handle, cx),
5111 buffer_handle.clone(),
5112 edited_ranges,
5113 DEFAULT_MULTIBUFFER_CONTEXT,
5114 cx,
5115 );
5116
5117 ranges_to_highlight.extend(ranges);
5118 }
5119 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5120 multibuffer
5121 })?;
5122
5123 workspace.update_in(cx, |workspace, window, cx| {
5124 let project = workspace.project().clone();
5125 let editor =
5126 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5127 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5128 editor.update(cx, |editor, cx| {
5129 editor.highlight_background::<Self>(
5130 &ranges_to_highlight,
5131 |theme| theme.editor_highlighted_line_background,
5132 cx,
5133 );
5134 });
5135 })?;
5136
5137 Ok(())
5138 }
5139
5140 pub fn clear_code_action_providers(&mut self) {
5141 self.code_action_providers.clear();
5142 self.available_code_actions.take();
5143 }
5144
5145 pub fn add_code_action_provider(
5146 &mut self,
5147 provider: Rc<dyn CodeActionProvider>,
5148 window: &mut Window,
5149 cx: &mut Context<Self>,
5150 ) {
5151 if self
5152 .code_action_providers
5153 .iter()
5154 .any(|existing_provider| existing_provider.id() == provider.id())
5155 {
5156 return;
5157 }
5158
5159 self.code_action_providers.push(provider);
5160 self.refresh_code_actions(window, cx);
5161 }
5162
5163 pub fn remove_code_action_provider(
5164 &mut self,
5165 id: Arc<str>,
5166 window: &mut Window,
5167 cx: &mut Context<Self>,
5168 ) {
5169 self.code_action_providers
5170 .retain(|provider| provider.id() != id);
5171 self.refresh_code_actions(window, cx);
5172 }
5173
5174 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5175 let newest_selection = self.selections.newest_anchor().clone();
5176 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5177 let buffer = self.buffer.read(cx);
5178 if newest_selection.head().diff_base_anchor.is_some() {
5179 return None;
5180 }
5181 let (start_buffer, start) =
5182 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5183 let (end_buffer, end) =
5184 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5185 if start_buffer != end_buffer {
5186 return None;
5187 }
5188
5189 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5190 cx.background_executor()
5191 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5192 .await;
5193
5194 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5195 let providers = this.code_action_providers.clone();
5196 let tasks = this
5197 .code_action_providers
5198 .iter()
5199 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5200 .collect::<Vec<_>>();
5201 (providers, tasks)
5202 })?;
5203
5204 let mut actions = Vec::new();
5205 for (provider, provider_actions) in
5206 providers.into_iter().zip(future::join_all(tasks).await)
5207 {
5208 if let Some(provider_actions) = provider_actions.log_err() {
5209 actions.extend(provider_actions.into_iter().map(|action| {
5210 AvailableCodeAction {
5211 excerpt_id: newest_selection.start.excerpt_id,
5212 action,
5213 provider: provider.clone(),
5214 }
5215 }));
5216 }
5217 }
5218
5219 this.update(cx, |this, cx| {
5220 this.available_code_actions = if actions.is_empty() {
5221 None
5222 } else {
5223 Some((
5224 Location {
5225 buffer: start_buffer,
5226 range: start..end,
5227 },
5228 actions.into(),
5229 ))
5230 };
5231 cx.notify();
5232 })
5233 }));
5234 None
5235 }
5236
5237 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5238 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5239 self.show_git_blame_inline = false;
5240
5241 self.show_git_blame_inline_delay_task =
5242 Some(cx.spawn_in(window, async move |this, cx| {
5243 cx.background_executor().timer(delay).await;
5244
5245 this.update(cx, |this, cx| {
5246 this.show_git_blame_inline = true;
5247 cx.notify();
5248 })
5249 .log_err();
5250 }));
5251 }
5252 }
5253
5254 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5255 if self.pending_rename.is_some() {
5256 return None;
5257 }
5258
5259 let provider = self.semantics_provider.clone()?;
5260 let buffer = self.buffer.read(cx);
5261 let newest_selection = self.selections.newest_anchor().clone();
5262 let cursor_position = newest_selection.head();
5263 let (cursor_buffer, cursor_buffer_position) =
5264 buffer.text_anchor_for_position(cursor_position, cx)?;
5265 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5266 if cursor_buffer != tail_buffer {
5267 return None;
5268 }
5269 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5270 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5271 cx.background_executor()
5272 .timer(Duration::from_millis(debounce))
5273 .await;
5274
5275 let highlights = if let Some(highlights) = cx
5276 .update(|cx| {
5277 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5278 })
5279 .ok()
5280 .flatten()
5281 {
5282 highlights.await.log_err()
5283 } else {
5284 None
5285 };
5286
5287 if let Some(highlights) = highlights {
5288 this.update(cx, |this, cx| {
5289 if this.pending_rename.is_some() {
5290 return;
5291 }
5292
5293 let buffer_id = cursor_position.buffer_id;
5294 let buffer = this.buffer.read(cx);
5295 if !buffer
5296 .text_anchor_for_position(cursor_position, cx)
5297 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5298 {
5299 return;
5300 }
5301
5302 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5303 let mut write_ranges = Vec::new();
5304 let mut read_ranges = Vec::new();
5305 for highlight in highlights {
5306 for (excerpt_id, excerpt_range) in
5307 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5308 {
5309 let start = highlight
5310 .range
5311 .start
5312 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5313 let end = highlight
5314 .range
5315 .end
5316 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5317 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5318 continue;
5319 }
5320
5321 let range = Anchor {
5322 buffer_id,
5323 excerpt_id,
5324 text_anchor: start,
5325 diff_base_anchor: None,
5326 }..Anchor {
5327 buffer_id,
5328 excerpt_id,
5329 text_anchor: end,
5330 diff_base_anchor: None,
5331 };
5332 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5333 write_ranges.push(range);
5334 } else {
5335 read_ranges.push(range);
5336 }
5337 }
5338 }
5339
5340 this.highlight_background::<DocumentHighlightRead>(
5341 &read_ranges,
5342 |theme| theme.editor_document_highlight_read_background,
5343 cx,
5344 );
5345 this.highlight_background::<DocumentHighlightWrite>(
5346 &write_ranges,
5347 |theme| theme.editor_document_highlight_write_background,
5348 cx,
5349 );
5350 cx.notify();
5351 })
5352 .log_err();
5353 }
5354 }));
5355 None
5356 }
5357
5358 pub fn refresh_selected_text_highlights(
5359 &mut self,
5360 window: &mut Window,
5361 cx: &mut Context<Editor>,
5362 ) {
5363 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5364 return;
5365 }
5366 self.selection_highlight_task.take();
5367 if !EditorSettings::get_global(cx).selection_highlight {
5368 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5369 return;
5370 }
5371 if self.selections.count() != 1 || self.selections.line_mode {
5372 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5373 return;
5374 }
5375 let selection = self.selections.newest::<Point>(cx);
5376 if selection.is_empty() || selection.start.row != selection.end.row {
5377 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5378 return;
5379 }
5380 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5381 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5382 cx.background_executor()
5383 .timer(Duration::from_millis(debounce))
5384 .await;
5385 let Some(Some(matches_task)) = editor
5386 .update_in(cx, |editor, _, cx| {
5387 if editor.selections.count() != 1 || editor.selections.line_mode {
5388 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5389 return None;
5390 }
5391 let selection = editor.selections.newest::<Point>(cx);
5392 if selection.is_empty() || selection.start.row != selection.end.row {
5393 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5394 return None;
5395 }
5396 let buffer = editor.buffer().read(cx).snapshot(cx);
5397 let query = buffer.text_for_range(selection.range()).collect::<String>();
5398 if query.trim().is_empty() {
5399 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5400 return None;
5401 }
5402 Some(cx.background_spawn(async move {
5403 let mut ranges = Vec::new();
5404 let selection_anchors = selection.range().to_anchors(&buffer);
5405 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5406 for (search_buffer, search_range, excerpt_id) in
5407 buffer.range_to_buffer_ranges(range)
5408 {
5409 ranges.extend(
5410 project::search::SearchQuery::text(
5411 query.clone(),
5412 false,
5413 false,
5414 false,
5415 Default::default(),
5416 Default::default(),
5417 None,
5418 )
5419 .unwrap()
5420 .search(search_buffer, Some(search_range.clone()))
5421 .await
5422 .into_iter()
5423 .filter_map(
5424 |match_range| {
5425 let start = search_buffer.anchor_after(
5426 search_range.start + match_range.start,
5427 );
5428 let end = search_buffer.anchor_before(
5429 search_range.start + match_range.end,
5430 );
5431 let range = Anchor::range_in_buffer(
5432 excerpt_id,
5433 search_buffer.remote_id(),
5434 start..end,
5435 );
5436 (range != selection_anchors).then_some(range)
5437 },
5438 ),
5439 );
5440 }
5441 }
5442 ranges
5443 }))
5444 })
5445 .log_err()
5446 else {
5447 return;
5448 };
5449 let matches = matches_task.await;
5450 editor
5451 .update_in(cx, |editor, _, cx| {
5452 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5453 if !matches.is_empty() {
5454 editor.highlight_background::<SelectedTextHighlight>(
5455 &matches,
5456 |theme| theme.editor_document_highlight_bracket_background,
5457 cx,
5458 )
5459 }
5460 })
5461 .log_err();
5462 }));
5463 }
5464
5465 pub fn refresh_inline_completion(
5466 &mut self,
5467 debounce: bool,
5468 user_requested: bool,
5469 window: &mut Window,
5470 cx: &mut Context<Self>,
5471 ) -> Option<()> {
5472 let provider = self.edit_prediction_provider()?;
5473 let cursor = self.selections.newest_anchor().head();
5474 let (buffer, cursor_buffer_position) =
5475 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5476
5477 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5478 self.discard_inline_completion(false, cx);
5479 return None;
5480 }
5481
5482 if !user_requested
5483 && (!self.should_show_edit_predictions()
5484 || !self.is_focused(window)
5485 || buffer.read(cx).is_empty())
5486 {
5487 self.discard_inline_completion(false, cx);
5488 return None;
5489 }
5490
5491 self.update_visible_inline_completion(window, cx);
5492 provider.refresh(
5493 self.project.clone(),
5494 buffer,
5495 cursor_buffer_position,
5496 debounce,
5497 cx,
5498 );
5499 Some(())
5500 }
5501
5502 fn show_edit_predictions_in_menu(&self) -> bool {
5503 match self.edit_prediction_settings {
5504 EditPredictionSettings::Disabled => false,
5505 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5506 }
5507 }
5508
5509 pub fn edit_predictions_enabled(&self) -> bool {
5510 match self.edit_prediction_settings {
5511 EditPredictionSettings::Disabled => false,
5512 EditPredictionSettings::Enabled { .. } => true,
5513 }
5514 }
5515
5516 fn edit_prediction_requires_modifier(&self) -> bool {
5517 match self.edit_prediction_settings {
5518 EditPredictionSettings::Disabled => false,
5519 EditPredictionSettings::Enabled {
5520 preview_requires_modifier,
5521 ..
5522 } => preview_requires_modifier,
5523 }
5524 }
5525
5526 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5527 if self.edit_prediction_provider.is_none() {
5528 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5529 } else {
5530 let selection = self.selections.newest_anchor();
5531 let cursor = selection.head();
5532
5533 if let Some((buffer, cursor_buffer_position)) =
5534 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5535 {
5536 self.edit_prediction_settings =
5537 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5538 }
5539 }
5540 }
5541
5542 fn edit_prediction_settings_at_position(
5543 &self,
5544 buffer: &Entity<Buffer>,
5545 buffer_position: language::Anchor,
5546 cx: &App,
5547 ) -> EditPredictionSettings {
5548 if self.mode != EditorMode::Full
5549 || !self.show_inline_completions_override.unwrap_or(true)
5550 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5551 {
5552 return EditPredictionSettings::Disabled;
5553 }
5554
5555 let buffer = buffer.read(cx);
5556
5557 let file = buffer.file();
5558
5559 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5560 return EditPredictionSettings::Disabled;
5561 };
5562
5563 let by_provider = matches!(
5564 self.menu_inline_completions_policy,
5565 MenuInlineCompletionsPolicy::ByProvider
5566 );
5567
5568 let show_in_menu = by_provider
5569 && self
5570 .edit_prediction_provider
5571 .as_ref()
5572 .map_or(false, |provider| {
5573 provider.provider.show_completions_in_menu()
5574 });
5575
5576 let preview_requires_modifier =
5577 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5578
5579 EditPredictionSettings::Enabled {
5580 show_in_menu,
5581 preview_requires_modifier,
5582 }
5583 }
5584
5585 fn should_show_edit_predictions(&self) -> bool {
5586 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5587 }
5588
5589 pub fn edit_prediction_preview_is_active(&self) -> bool {
5590 matches!(
5591 self.edit_prediction_preview,
5592 EditPredictionPreview::Active { .. }
5593 )
5594 }
5595
5596 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5597 let cursor = self.selections.newest_anchor().head();
5598 if let Some((buffer, cursor_position)) =
5599 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5600 {
5601 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5602 } else {
5603 false
5604 }
5605 }
5606
5607 fn edit_predictions_enabled_in_buffer(
5608 &self,
5609 buffer: &Entity<Buffer>,
5610 buffer_position: language::Anchor,
5611 cx: &App,
5612 ) -> bool {
5613 maybe!({
5614 if self.read_only(cx) {
5615 return Some(false);
5616 }
5617 let provider = self.edit_prediction_provider()?;
5618 if !provider.is_enabled(&buffer, buffer_position, cx) {
5619 return Some(false);
5620 }
5621 let buffer = buffer.read(cx);
5622 let Some(file) = buffer.file() else {
5623 return Some(true);
5624 };
5625 let settings = all_language_settings(Some(file), cx);
5626 Some(settings.edit_predictions_enabled_for_file(file, cx))
5627 })
5628 .unwrap_or(false)
5629 }
5630
5631 fn cycle_inline_completion(
5632 &mut self,
5633 direction: Direction,
5634 window: &mut Window,
5635 cx: &mut Context<Self>,
5636 ) -> Option<()> {
5637 let provider = self.edit_prediction_provider()?;
5638 let cursor = self.selections.newest_anchor().head();
5639 let (buffer, cursor_buffer_position) =
5640 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5641 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5642 return None;
5643 }
5644
5645 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5646 self.update_visible_inline_completion(window, cx);
5647
5648 Some(())
5649 }
5650
5651 pub fn show_inline_completion(
5652 &mut self,
5653 _: &ShowEditPrediction,
5654 window: &mut Window,
5655 cx: &mut Context<Self>,
5656 ) {
5657 if !self.has_active_inline_completion() {
5658 self.refresh_inline_completion(false, true, window, cx);
5659 return;
5660 }
5661
5662 self.update_visible_inline_completion(window, cx);
5663 }
5664
5665 pub fn display_cursor_names(
5666 &mut self,
5667 _: &DisplayCursorNames,
5668 window: &mut Window,
5669 cx: &mut Context<Self>,
5670 ) {
5671 self.show_cursor_names(window, cx);
5672 }
5673
5674 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5675 self.show_cursor_names = true;
5676 cx.notify();
5677 cx.spawn_in(window, async move |this, cx| {
5678 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5679 this.update(cx, |this, cx| {
5680 this.show_cursor_names = false;
5681 cx.notify()
5682 })
5683 .ok()
5684 })
5685 .detach();
5686 }
5687
5688 pub fn next_edit_prediction(
5689 &mut self,
5690 _: &NextEditPrediction,
5691 window: &mut Window,
5692 cx: &mut Context<Self>,
5693 ) {
5694 if self.has_active_inline_completion() {
5695 self.cycle_inline_completion(Direction::Next, window, cx);
5696 } else {
5697 let is_copilot_disabled = self
5698 .refresh_inline_completion(false, true, window, cx)
5699 .is_none();
5700 if is_copilot_disabled {
5701 cx.propagate();
5702 }
5703 }
5704 }
5705
5706 pub fn previous_edit_prediction(
5707 &mut self,
5708 _: &PreviousEditPrediction,
5709 window: &mut Window,
5710 cx: &mut Context<Self>,
5711 ) {
5712 if self.has_active_inline_completion() {
5713 self.cycle_inline_completion(Direction::Prev, window, cx);
5714 } else {
5715 let is_copilot_disabled = self
5716 .refresh_inline_completion(false, true, window, cx)
5717 .is_none();
5718 if is_copilot_disabled {
5719 cx.propagate();
5720 }
5721 }
5722 }
5723
5724 pub fn accept_edit_prediction(
5725 &mut self,
5726 _: &AcceptEditPrediction,
5727 window: &mut Window,
5728 cx: &mut Context<Self>,
5729 ) {
5730 if self.show_edit_predictions_in_menu() {
5731 self.hide_context_menu(window, cx);
5732 }
5733
5734 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5735 return;
5736 };
5737
5738 self.report_inline_completion_event(
5739 active_inline_completion.completion_id.clone(),
5740 true,
5741 cx,
5742 );
5743
5744 match &active_inline_completion.completion {
5745 InlineCompletion::Move { target, .. } => {
5746 let target = *target;
5747
5748 if let Some(position_map) = &self.last_position_map {
5749 if position_map
5750 .visible_row_range
5751 .contains(&target.to_display_point(&position_map.snapshot).row())
5752 || !self.edit_prediction_requires_modifier()
5753 {
5754 self.unfold_ranges(&[target..target], true, false, cx);
5755 // Note that this is also done in vim's handler of the Tab action.
5756 self.change_selections(
5757 Some(Autoscroll::newest()),
5758 window,
5759 cx,
5760 |selections| {
5761 selections.select_anchor_ranges([target..target]);
5762 },
5763 );
5764 self.clear_row_highlights::<EditPredictionPreview>();
5765
5766 self.edit_prediction_preview
5767 .set_previous_scroll_position(None);
5768 } else {
5769 self.edit_prediction_preview
5770 .set_previous_scroll_position(Some(
5771 position_map.snapshot.scroll_anchor,
5772 ));
5773
5774 self.highlight_rows::<EditPredictionPreview>(
5775 target..target,
5776 cx.theme().colors().editor_highlighted_line_background,
5777 true,
5778 cx,
5779 );
5780 self.request_autoscroll(Autoscroll::fit(), cx);
5781 }
5782 }
5783 }
5784 InlineCompletion::Edit { edits, .. } => {
5785 if let Some(provider) = self.edit_prediction_provider() {
5786 provider.accept(cx);
5787 }
5788
5789 let snapshot = self.buffer.read(cx).snapshot(cx);
5790 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5791
5792 self.buffer.update(cx, |buffer, cx| {
5793 buffer.edit(edits.iter().cloned(), None, cx)
5794 });
5795
5796 self.change_selections(None, window, cx, |s| {
5797 s.select_anchor_ranges([last_edit_end..last_edit_end])
5798 });
5799
5800 self.update_visible_inline_completion(window, cx);
5801 if self.active_inline_completion.is_none() {
5802 self.refresh_inline_completion(true, true, window, cx);
5803 }
5804
5805 cx.notify();
5806 }
5807 }
5808
5809 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5810 }
5811
5812 pub fn accept_partial_inline_completion(
5813 &mut self,
5814 _: &AcceptPartialEditPrediction,
5815 window: &mut Window,
5816 cx: &mut Context<Self>,
5817 ) {
5818 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5819 return;
5820 };
5821 if self.selections.count() != 1 {
5822 return;
5823 }
5824
5825 self.report_inline_completion_event(
5826 active_inline_completion.completion_id.clone(),
5827 true,
5828 cx,
5829 );
5830
5831 match &active_inline_completion.completion {
5832 InlineCompletion::Move { target, .. } => {
5833 let target = *target;
5834 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5835 selections.select_anchor_ranges([target..target]);
5836 });
5837 }
5838 InlineCompletion::Edit { edits, .. } => {
5839 // Find an insertion that starts at the cursor position.
5840 let snapshot = self.buffer.read(cx).snapshot(cx);
5841 let cursor_offset = self.selections.newest::<usize>(cx).head();
5842 let insertion = edits.iter().find_map(|(range, text)| {
5843 let range = range.to_offset(&snapshot);
5844 if range.is_empty() && range.start == cursor_offset {
5845 Some(text)
5846 } else {
5847 None
5848 }
5849 });
5850
5851 if let Some(text) = insertion {
5852 let mut partial_completion = text
5853 .chars()
5854 .by_ref()
5855 .take_while(|c| c.is_alphabetic())
5856 .collect::<String>();
5857 if partial_completion.is_empty() {
5858 partial_completion = text
5859 .chars()
5860 .by_ref()
5861 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5862 .collect::<String>();
5863 }
5864
5865 cx.emit(EditorEvent::InputHandled {
5866 utf16_range_to_replace: None,
5867 text: partial_completion.clone().into(),
5868 });
5869
5870 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5871
5872 self.refresh_inline_completion(true, true, window, cx);
5873 cx.notify();
5874 } else {
5875 self.accept_edit_prediction(&Default::default(), window, cx);
5876 }
5877 }
5878 }
5879 }
5880
5881 fn discard_inline_completion(
5882 &mut self,
5883 should_report_inline_completion_event: bool,
5884 cx: &mut Context<Self>,
5885 ) -> bool {
5886 if should_report_inline_completion_event {
5887 let completion_id = self
5888 .active_inline_completion
5889 .as_ref()
5890 .and_then(|active_completion| active_completion.completion_id.clone());
5891
5892 self.report_inline_completion_event(completion_id, false, cx);
5893 }
5894
5895 if let Some(provider) = self.edit_prediction_provider() {
5896 provider.discard(cx);
5897 }
5898
5899 self.take_active_inline_completion(cx)
5900 }
5901
5902 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5903 let Some(provider) = self.edit_prediction_provider() else {
5904 return;
5905 };
5906
5907 let Some((_, buffer, _)) = self
5908 .buffer
5909 .read(cx)
5910 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5911 else {
5912 return;
5913 };
5914
5915 let extension = buffer
5916 .read(cx)
5917 .file()
5918 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5919
5920 let event_type = match accepted {
5921 true => "Edit Prediction Accepted",
5922 false => "Edit Prediction Discarded",
5923 };
5924 telemetry::event!(
5925 event_type,
5926 provider = provider.name(),
5927 prediction_id = id,
5928 suggestion_accepted = accepted,
5929 file_extension = extension,
5930 );
5931 }
5932
5933 pub fn has_active_inline_completion(&self) -> bool {
5934 self.active_inline_completion.is_some()
5935 }
5936
5937 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5938 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5939 return false;
5940 };
5941
5942 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5943 self.clear_highlights::<InlineCompletionHighlight>(cx);
5944 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5945 true
5946 }
5947
5948 /// Returns true when we're displaying the edit prediction popover below the cursor
5949 /// like we are not previewing and the LSP autocomplete menu is visible
5950 /// or we are in `when_holding_modifier` mode.
5951 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5952 if self.edit_prediction_preview_is_active()
5953 || !self.show_edit_predictions_in_menu()
5954 || !self.edit_predictions_enabled()
5955 {
5956 return false;
5957 }
5958
5959 if self.has_visible_completions_menu() {
5960 return true;
5961 }
5962
5963 has_completion && self.edit_prediction_requires_modifier()
5964 }
5965
5966 fn handle_modifiers_changed(
5967 &mut self,
5968 modifiers: Modifiers,
5969 position_map: &PositionMap,
5970 window: &mut Window,
5971 cx: &mut Context<Self>,
5972 ) {
5973 if self.show_edit_predictions_in_menu() {
5974 self.update_edit_prediction_preview(&modifiers, window, cx);
5975 }
5976
5977 self.update_selection_mode(&modifiers, position_map, window, cx);
5978
5979 let mouse_position = window.mouse_position();
5980 if !position_map.text_hitbox.is_hovered(window) {
5981 return;
5982 }
5983
5984 self.update_hovered_link(
5985 position_map.point_for_position(mouse_position),
5986 &position_map.snapshot,
5987 modifiers,
5988 window,
5989 cx,
5990 )
5991 }
5992
5993 fn update_selection_mode(
5994 &mut self,
5995 modifiers: &Modifiers,
5996 position_map: &PositionMap,
5997 window: &mut Window,
5998 cx: &mut Context<Self>,
5999 ) {
6000 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6001 return;
6002 }
6003
6004 let mouse_position = window.mouse_position();
6005 let point_for_position = position_map.point_for_position(mouse_position);
6006 let position = point_for_position.previous_valid;
6007
6008 self.select(
6009 SelectPhase::BeginColumnar {
6010 position,
6011 reset: false,
6012 goal_column: point_for_position.exact_unclipped.column(),
6013 },
6014 window,
6015 cx,
6016 );
6017 }
6018
6019 fn update_edit_prediction_preview(
6020 &mut self,
6021 modifiers: &Modifiers,
6022 window: &mut Window,
6023 cx: &mut Context<Self>,
6024 ) {
6025 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6026 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6027 return;
6028 };
6029
6030 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6031 if matches!(
6032 self.edit_prediction_preview,
6033 EditPredictionPreview::Inactive { .. }
6034 ) {
6035 self.edit_prediction_preview = EditPredictionPreview::Active {
6036 previous_scroll_position: None,
6037 since: Instant::now(),
6038 };
6039
6040 self.update_visible_inline_completion(window, cx);
6041 cx.notify();
6042 }
6043 } else if let EditPredictionPreview::Active {
6044 previous_scroll_position,
6045 since,
6046 } = self.edit_prediction_preview
6047 {
6048 if let (Some(previous_scroll_position), Some(position_map)) =
6049 (previous_scroll_position, self.last_position_map.as_ref())
6050 {
6051 self.set_scroll_position(
6052 previous_scroll_position
6053 .scroll_position(&position_map.snapshot.display_snapshot),
6054 window,
6055 cx,
6056 );
6057 }
6058
6059 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6060 released_too_fast: since.elapsed() < Duration::from_millis(200),
6061 };
6062 self.clear_row_highlights::<EditPredictionPreview>();
6063 self.update_visible_inline_completion(window, cx);
6064 cx.notify();
6065 }
6066 }
6067
6068 fn update_visible_inline_completion(
6069 &mut self,
6070 _window: &mut Window,
6071 cx: &mut Context<Self>,
6072 ) -> Option<()> {
6073 let selection = self.selections.newest_anchor();
6074 let cursor = selection.head();
6075 let multibuffer = self.buffer.read(cx).snapshot(cx);
6076 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6077 let excerpt_id = cursor.excerpt_id;
6078
6079 let show_in_menu = self.show_edit_predictions_in_menu();
6080 let completions_menu_has_precedence = !show_in_menu
6081 && (self.context_menu.borrow().is_some()
6082 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6083
6084 if completions_menu_has_precedence
6085 || !offset_selection.is_empty()
6086 || self
6087 .active_inline_completion
6088 .as_ref()
6089 .map_or(false, |completion| {
6090 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6091 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6092 !invalidation_range.contains(&offset_selection.head())
6093 })
6094 {
6095 self.discard_inline_completion(false, cx);
6096 return None;
6097 }
6098
6099 self.take_active_inline_completion(cx);
6100 let Some(provider) = self.edit_prediction_provider() else {
6101 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6102 return None;
6103 };
6104
6105 let (buffer, cursor_buffer_position) =
6106 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6107
6108 self.edit_prediction_settings =
6109 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6110
6111 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6112
6113 if self.edit_prediction_indent_conflict {
6114 let cursor_point = cursor.to_point(&multibuffer);
6115
6116 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6117
6118 if let Some((_, indent)) = indents.iter().next() {
6119 if indent.len == cursor_point.column {
6120 self.edit_prediction_indent_conflict = false;
6121 }
6122 }
6123 }
6124
6125 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6126 let edits = inline_completion
6127 .edits
6128 .into_iter()
6129 .flat_map(|(range, new_text)| {
6130 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6131 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6132 Some((start..end, new_text))
6133 })
6134 .collect::<Vec<_>>();
6135 if edits.is_empty() {
6136 return None;
6137 }
6138
6139 let first_edit_start = edits.first().unwrap().0.start;
6140 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6141 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6142
6143 let last_edit_end = edits.last().unwrap().0.end;
6144 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6145 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6146
6147 let cursor_row = cursor.to_point(&multibuffer).row;
6148
6149 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6150
6151 let mut inlay_ids = Vec::new();
6152 let invalidation_row_range;
6153 let move_invalidation_row_range = if cursor_row < edit_start_row {
6154 Some(cursor_row..edit_end_row)
6155 } else if cursor_row > edit_end_row {
6156 Some(edit_start_row..cursor_row)
6157 } else {
6158 None
6159 };
6160 let is_move =
6161 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6162 let completion = if is_move {
6163 invalidation_row_range =
6164 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6165 let target = first_edit_start;
6166 InlineCompletion::Move { target, snapshot }
6167 } else {
6168 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6169 && !self.inline_completions_hidden_for_vim_mode;
6170
6171 if show_completions_in_buffer {
6172 if edits
6173 .iter()
6174 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6175 {
6176 let mut inlays = Vec::new();
6177 for (range, new_text) in &edits {
6178 let inlay = Inlay::inline_completion(
6179 post_inc(&mut self.next_inlay_id),
6180 range.start,
6181 new_text.as_str(),
6182 );
6183 inlay_ids.push(inlay.id);
6184 inlays.push(inlay);
6185 }
6186
6187 self.splice_inlays(&[], inlays, cx);
6188 } else {
6189 let background_color = cx.theme().status().deleted_background;
6190 self.highlight_text::<InlineCompletionHighlight>(
6191 edits.iter().map(|(range, _)| range.clone()).collect(),
6192 HighlightStyle {
6193 background_color: Some(background_color),
6194 ..Default::default()
6195 },
6196 cx,
6197 );
6198 }
6199 }
6200
6201 invalidation_row_range = edit_start_row..edit_end_row;
6202
6203 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6204 if provider.show_tab_accept_marker() {
6205 EditDisplayMode::TabAccept
6206 } else {
6207 EditDisplayMode::Inline
6208 }
6209 } else {
6210 EditDisplayMode::DiffPopover
6211 };
6212
6213 InlineCompletion::Edit {
6214 edits,
6215 edit_preview: inline_completion.edit_preview,
6216 display_mode,
6217 snapshot,
6218 }
6219 };
6220
6221 let invalidation_range = multibuffer
6222 .anchor_before(Point::new(invalidation_row_range.start, 0))
6223 ..multibuffer.anchor_after(Point::new(
6224 invalidation_row_range.end,
6225 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6226 ));
6227
6228 self.stale_inline_completion_in_menu = None;
6229 self.active_inline_completion = Some(InlineCompletionState {
6230 inlay_ids,
6231 completion,
6232 completion_id: inline_completion.id,
6233 invalidation_range,
6234 });
6235
6236 cx.notify();
6237
6238 Some(())
6239 }
6240
6241 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6242 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6243 }
6244
6245 fn render_code_actions_indicator(
6246 &self,
6247 _style: &EditorStyle,
6248 row: DisplayRow,
6249 is_active: bool,
6250 breakpoint: Option<&(Anchor, Breakpoint)>,
6251 cx: &mut Context<Self>,
6252 ) -> Option<IconButton> {
6253 let color = Color::Muted;
6254 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6255 let show_tooltip = !self.context_menu_visible();
6256
6257 if self.available_code_actions.is_some() {
6258 Some(
6259 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6260 .shape(ui::IconButtonShape::Square)
6261 .icon_size(IconSize::XSmall)
6262 .icon_color(color)
6263 .toggle_state(is_active)
6264 .when(show_tooltip, |this| {
6265 this.tooltip({
6266 let focus_handle = self.focus_handle.clone();
6267 move |window, cx| {
6268 Tooltip::for_action_in(
6269 "Toggle Code Actions",
6270 &ToggleCodeActions {
6271 deployed_from_indicator: None,
6272 },
6273 &focus_handle,
6274 window,
6275 cx,
6276 )
6277 }
6278 })
6279 })
6280 .on_click(cx.listener(move |editor, _e, window, cx| {
6281 window.focus(&editor.focus_handle(cx));
6282 editor.toggle_code_actions(
6283 &ToggleCodeActions {
6284 deployed_from_indicator: Some(row),
6285 },
6286 window,
6287 cx,
6288 );
6289 }))
6290 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6291 editor.set_breakpoint_context_menu(
6292 row,
6293 position,
6294 event.down.position,
6295 window,
6296 cx,
6297 );
6298 })),
6299 )
6300 } else {
6301 None
6302 }
6303 }
6304
6305 fn clear_tasks(&mut self) {
6306 self.tasks.clear()
6307 }
6308
6309 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6310 if self.tasks.insert(key, value).is_some() {
6311 // This case should hopefully be rare, but just in case...
6312 log::error!(
6313 "multiple different run targets found on a single line, only the last target will be rendered"
6314 )
6315 }
6316 }
6317
6318 /// Get all display points of breakpoints that will be rendered within editor
6319 ///
6320 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6321 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6322 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6323 fn active_breakpoints(
6324 &self,
6325 range: Range<DisplayRow>,
6326 window: &mut Window,
6327 cx: &mut Context<Self>,
6328 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6329 let mut breakpoint_display_points = HashMap::default();
6330
6331 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6332 return breakpoint_display_points;
6333 };
6334
6335 let snapshot = self.snapshot(window, cx);
6336
6337 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6338 let Some(project) = self.project.as_ref() else {
6339 return breakpoint_display_points;
6340 };
6341
6342 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6343 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6344
6345 for (buffer_snapshot, range, excerpt_id) in
6346 multi_buffer_snapshot.range_to_buffer_ranges(range)
6347 {
6348 let Some(buffer) = project.read_with(cx, |this, cx| {
6349 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6350 }) else {
6351 continue;
6352 };
6353 let breakpoints = breakpoint_store.read(cx).breakpoints(
6354 &buffer,
6355 Some(
6356 buffer_snapshot.anchor_before(range.start)
6357 ..buffer_snapshot.anchor_after(range.end),
6358 ),
6359 buffer_snapshot,
6360 cx,
6361 );
6362 for (anchor, breakpoint) in breakpoints {
6363 let multi_buffer_anchor =
6364 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6365 let position = multi_buffer_anchor
6366 .to_point(&multi_buffer_snapshot)
6367 .to_display_point(&snapshot);
6368
6369 breakpoint_display_points
6370 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6371 }
6372 }
6373
6374 breakpoint_display_points
6375 }
6376
6377 fn breakpoint_context_menu(
6378 &self,
6379 anchor: Anchor,
6380 window: &mut Window,
6381 cx: &mut Context<Self>,
6382 ) -> Entity<ui::ContextMenu> {
6383 let weak_editor = cx.weak_entity();
6384 let focus_handle = self.focus_handle(cx);
6385
6386 let row = self
6387 .buffer
6388 .read(cx)
6389 .snapshot(cx)
6390 .summary_for_anchor::<Point>(&anchor)
6391 .row;
6392
6393 let breakpoint = self
6394 .breakpoint_at_row(row, window, cx)
6395 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6396
6397 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6398 "Edit Log Breakpoint"
6399 } else {
6400 "Set Log Breakpoint"
6401 };
6402
6403 let condition_breakpoint_msg = if breakpoint
6404 .as_ref()
6405 .is_some_and(|bp| bp.1.condition.is_some())
6406 {
6407 "Edit Condition Breakpoint"
6408 } else {
6409 "Set Condition Breakpoint"
6410 };
6411
6412 let hit_condition_breakpoint_msg = if breakpoint
6413 .as_ref()
6414 .is_some_and(|bp| bp.1.hit_condition.is_some())
6415 {
6416 "Edit Hit Condition Breakpoint"
6417 } else {
6418 "Set Hit Condition Breakpoint"
6419 };
6420
6421 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6422 "Unset Breakpoint"
6423 } else {
6424 "Set Breakpoint"
6425 };
6426
6427 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6428 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6429
6430 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6431 BreakpointState::Enabled => Some("Disable"),
6432 BreakpointState::Disabled => Some("Enable"),
6433 });
6434
6435 let (anchor, breakpoint) =
6436 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6437
6438 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6439 menu.on_blur_subscription(Subscription::new(|| {}))
6440 .context(focus_handle)
6441 .when(run_to_cursor, |this| {
6442 let weak_editor = weak_editor.clone();
6443 this.entry("Run to cursor", None, move |window, cx| {
6444 weak_editor
6445 .update(cx, |editor, cx| {
6446 editor.change_selections(None, window, cx, |s| {
6447 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6448 });
6449 })
6450 .ok();
6451
6452 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6453 })
6454 .separator()
6455 })
6456 .when_some(toggle_state_msg, |this, msg| {
6457 this.entry(msg, None, {
6458 let weak_editor = weak_editor.clone();
6459 let breakpoint = breakpoint.clone();
6460 move |_window, cx| {
6461 weak_editor
6462 .update(cx, |this, cx| {
6463 this.edit_breakpoint_at_anchor(
6464 anchor,
6465 breakpoint.as_ref().clone(),
6466 BreakpointEditAction::InvertState,
6467 cx,
6468 );
6469 })
6470 .log_err();
6471 }
6472 })
6473 })
6474 .entry(set_breakpoint_msg, None, {
6475 let weak_editor = weak_editor.clone();
6476 let breakpoint = breakpoint.clone();
6477 move |_window, cx| {
6478 weak_editor
6479 .update(cx, |this, cx| {
6480 this.edit_breakpoint_at_anchor(
6481 anchor,
6482 breakpoint.as_ref().clone(),
6483 BreakpointEditAction::Toggle,
6484 cx,
6485 );
6486 })
6487 .log_err();
6488 }
6489 })
6490 .entry(log_breakpoint_msg, None, {
6491 let breakpoint = breakpoint.clone();
6492 let weak_editor = weak_editor.clone();
6493 move |window, cx| {
6494 weak_editor
6495 .update(cx, |this, cx| {
6496 this.add_edit_breakpoint_block(
6497 anchor,
6498 breakpoint.as_ref(),
6499 BreakpointPromptEditAction::Log,
6500 window,
6501 cx,
6502 );
6503 })
6504 .log_err();
6505 }
6506 })
6507 .entry(condition_breakpoint_msg, None, {
6508 let breakpoint = breakpoint.clone();
6509 let weak_editor = weak_editor.clone();
6510 move |window, cx| {
6511 weak_editor
6512 .update(cx, |this, cx| {
6513 this.add_edit_breakpoint_block(
6514 anchor,
6515 breakpoint.as_ref(),
6516 BreakpointPromptEditAction::Condition,
6517 window,
6518 cx,
6519 );
6520 })
6521 .log_err();
6522 }
6523 })
6524 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6525 weak_editor
6526 .update(cx, |this, cx| {
6527 this.add_edit_breakpoint_block(
6528 anchor,
6529 breakpoint.as_ref(),
6530 BreakpointPromptEditAction::HitCondition,
6531 window,
6532 cx,
6533 );
6534 })
6535 .log_err();
6536 })
6537 })
6538 }
6539
6540 fn render_breakpoint(
6541 &self,
6542 position: Anchor,
6543 row: DisplayRow,
6544 breakpoint: &Breakpoint,
6545 cx: &mut Context<Self>,
6546 ) -> IconButton {
6547 let (color, icon) = {
6548 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6549 (false, false) => ui::IconName::DebugBreakpoint,
6550 (true, false) => ui::IconName::DebugLogBreakpoint,
6551 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6552 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6553 };
6554
6555 let color = if self
6556 .gutter_breakpoint_indicator
6557 .0
6558 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6559 {
6560 Color::Hint
6561 } else {
6562 Color::Debugger
6563 };
6564
6565 (color, icon)
6566 };
6567
6568 let breakpoint = Arc::from(breakpoint.clone());
6569
6570 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6571 .icon_size(IconSize::XSmall)
6572 .size(ui::ButtonSize::None)
6573 .icon_color(color)
6574 .style(ButtonStyle::Transparent)
6575 .on_click(cx.listener({
6576 let breakpoint = breakpoint.clone();
6577
6578 move |editor, event: &ClickEvent, window, cx| {
6579 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6580 BreakpointEditAction::InvertState
6581 } else {
6582 BreakpointEditAction::Toggle
6583 };
6584
6585 window.focus(&editor.focus_handle(cx));
6586 editor.edit_breakpoint_at_anchor(
6587 position,
6588 breakpoint.as_ref().clone(),
6589 edit_action,
6590 cx,
6591 );
6592 }
6593 }))
6594 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6595 editor.set_breakpoint_context_menu(
6596 row,
6597 Some(position),
6598 event.down.position,
6599 window,
6600 cx,
6601 );
6602 }))
6603 }
6604
6605 fn build_tasks_context(
6606 project: &Entity<Project>,
6607 buffer: &Entity<Buffer>,
6608 buffer_row: u32,
6609 tasks: &Arc<RunnableTasks>,
6610 cx: &mut Context<Self>,
6611 ) -> Task<Option<task::TaskContext>> {
6612 let position = Point::new(buffer_row, tasks.column);
6613 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6614 let location = Location {
6615 buffer: buffer.clone(),
6616 range: range_start..range_start,
6617 };
6618 // Fill in the environmental variables from the tree-sitter captures
6619 let mut captured_task_variables = TaskVariables::default();
6620 for (capture_name, value) in tasks.extra_variables.clone() {
6621 captured_task_variables.insert(
6622 task::VariableName::Custom(capture_name.into()),
6623 value.clone(),
6624 );
6625 }
6626 project.update(cx, |project, cx| {
6627 project.task_store().update(cx, |task_store, cx| {
6628 task_store.task_context_for_location(captured_task_variables, location, cx)
6629 })
6630 })
6631 }
6632
6633 pub fn spawn_nearest_task(
6634 &mut self,
6635 action: &SpawnNearestTask,
6636 window: &mut Window,
6637 cx: &mut Context<Self>,
6638 ) {
6639 let Some((workspace, _)) = self.workspace.clone() else {
6640 return;
6641 };
6642 let Some(project) = self.project.clone() else {
6643 return;
6644 };
6645
6646 // Try to find a closest, enclosing node using tree-sitter that has a
6647 // task
6648 let Some((buffer, buffer_row, tasks)) = self
6649 .find_enclosing_node_task(cx)
6650 // Or find the task that's closest in row-distance.
6651 .or_else(|| self.find_closest_task(cx))
6652 else {
6653 return;
6654 };
6655
6656 let reveal_strategy = action.reveal;
6657 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6658 cx.spawn_in(window, async move |_, cx| {
6659 let context = task_context.await?;
6660 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6661
6662 let resolved = resolved_task.resolved.as_mut()?;
6663 resolved.reveal = reveal_strategy;
6664
6665 workspace
6666 .update(cx, |workspace, cx| {
6667 workspace::tasks::schedule_resolved_task(
6668 workspace,
6669 task_source_kind,
6670 resolved_task,
6671 false,
6672 cx,
6673 );
6674 })
6675 .ok()
6676 })
6677 .detach();
6678 }
6679
6680 fn find_closest_task(
6681 &mut self,
6682 cx: &mut Context<Self>,
6683 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6684 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6685
6686 let ((buffer_id, row), tasks) = self
6687 .tasks
6688 .iter()
6689 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6690
6691 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6692 let tasks = Arc::new(tasks.to_owned());
6693 Some((buffer, *row, tasks))
6694 }
6695
6696 fn find_enclosing_node_task(
6697 &mut self,
6698 cx: &mut Context<Self>,
6699 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6700 let snapshot = self.buffer.read(cx).snapshot(cx);
6701 let offset = self.selections.newest::<usize>(cx).head();
6702 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6703 let buffer_id = excerpt.buffer().remote_id();
6704
6705 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6706 let mut cursor = layer.node().walk();
6707
6708 while cursor.goto_first_child_for_byte(offset).is_some() {
6709 if cursor.node().end_byte() == offset {
6710 cursor.goto_next_sibling();
6711 }
6712 }
6713
6714 // Ascend to the smallest ancestor that contains the range and has a task.
6715 loop {
6716 let node = cursor.node();
6717 let node_range = node.byte_range();
6718 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6719
6720 // Check if this node contains our offset
6721 if node_range.start <= offset && node_range.end >= offset {
6722 // If it contains offset, check for task
6723 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6724 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6725 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6726 }
6727 }
6728
6729 if !cursor.goto_parent() {
6730 break;
6731 }
6732 }
6733 None
6734 }
6735
6736 fn render_run_indicator(
6737 &self,
6738 _style: &EditorStyle,
6739 is_active: bool,
6740 row: DisplayRow,
6741 breakpoint: Option<(Anchor, Breakpoint)>,
6742 cx: &mut Context<Self>,
6743 ) -> IconButton {
6744 let color = Color::Muted;
6745 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6746
6747 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6748 .shape(ui::IconButtonShape::Square)
6749 .icon_size(IconSize::XSmall)
6750 .icon_color(color)
6751 .toggle_state(is_active)
6752 .on_click(cx.listener(move |editor, _e, window, cx| {
6753 window.focus(&editor.focus_handle(cx));
6754 editor.toggle_code_actions(
6755 &ToggleCodeActions {
6756 deployed_from_indicator: Some(row),
6757 },
6758 window,
6759 cx,
6760 );
6761 }))
6762 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6763 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6764 }))
6765 }
6766
6767 pub fn context_menu_visible(&self) -> bool {
6768 !self.edit_prediction_preview_is_active()
6769 && self
6770 .context_menu
6771 .borrow()
6772 .as_ref()
6773 .map_or(false, |menu| menu.visible())
6774 }
6775
6776 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6777 self.context_menu
6778 .borrow()
6779 .as_ref()
6780 .map(|menu| menu.origin())
6781 }
6782
6783 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6784 self.context_menu_options = Some(options);
6785 }
6786
6787 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6788 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6789
6790 fn render_edit_prediction_popover(
6791 &mut self,
6792 text_bounds: &Bounds<Pixels>,
6793 content_origin: gpui::Point<Pixels>,
6794 editor_snapshot: &EditorSnapshot,
6795 visible_row_range: Range<DisplayRow>,
6796 scroll_top: f32,
6797 scroll_bottom: f32,
6798 line_layouts: &[LineWithInvisibles],
6799 line_height: Pixels,
6800 scroll_pixel_position: gpui::Point<Pixels>,
6801 newest_selection_head: Option<DisplayPoint>,
6802 editor_width: Pixels,
6803 style: &EditorStyle,
6804 window: &mut Window,
6805 cx: &mut App,
6806 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6807 let active_inline_completion = self.active_inline_completion.as_ref()?;
6808
6809 if self.edit_prediction_visible_in_cursor_popover(true) {
6810 return None;
6811 }
6812
6813 match &active_inline_completion.completion {
6814 InlineCompletion::Move { target, .. } => {
6815 let target_display_point = target.to_display_point(editor_snapshot);
6816
6817 if self.edit_prediction_requires_modifier() {
6818 if !self.edit_prediction_preview_is_active() {
6819 return None;
6820 }
6821
6822 self.render_edit_prediction_modifier_jump_popover(
6823 text_bounds,
6824 content_origin,
6825 visible_row_range,
6826 line_layouts,
6827 line_height,
6828 scroll_pixel_position,
6829 newest_selection_head,
6830 target_display_point,
6831 window,
6832 cx,
6833 )
6834 } else {
6835 self.render_edit_prediction_eager_jump_popover(
6836 text_bounds,
6837 content_origin,
6838 editor_snapshot,
6839 visible_row_range,
6840 scroll_top,
6841 scroll_bottom,
6842 line_height,
6843 scroll_pixel_position,
6844 target_display_point,
6845 editor_width,
6846 window,
6847 cx,
6848 )
6849 }
6850 }
6851 InlineCompletion::Edit {
6852 display_mode: EditDisplayMode::Inline,
6853 ..
6854 } => None,
6855 InlineCompletion::Edit {
6856 display_mode: EditDisplayMode::TabAccept,
6857 edits,
6858 ..
6859 } => {
6860 let range = &edits.first()?.0;
6861 let target_display_point = range.end.to_display_point(editor_snapshot);
6862
6863 self.render_edit_prediction_end_of_line_popover(
6864 "Accept",
6865 editor_snapshot,
6866 visible_row_range,
6867 target_display_point,
6868 line_height,
6869 scroll_pixel_position,
6870 content_origin,
6871 editor_width,
6872 window,
6873 cx,
6874 )
6875 }
6876 InlineCompletion::Edit {
6877 edits,
6878 edit_preview,
6879 display_mode: EditDisplayMode::DiffPopover,
6880 snapshot,
6881 } => self.render_edit_prediction_diff_popover(
6882 text_bounds,
6883 content_origin,
6884 editor_snapshot,
6885 visible_row_range,
6886 line_layouts,
6887 line_height,
6888 scroll_pixel_position,
6889 newest_selection_head,
6890 editor_width,
6891 style,
6892 edits,
6893 edit_preview,
6894 snapshot,
6895 window,
6896 cx,
6897 ),
6898 }
6899 }
6900
6901 fn render_edit_prediction_modifier_jump_popover(
6902 &mut self,
6903 text_bounds: &Bounds<Pixels>,
6904 content_origin: gpui::Point<Pixels>,
6905 visible_row_range: Range<DisplayRow>,
6906 line_layouts: &[LineWithInvisibles],
6907 line_height: Pixels,
6908 scroll_pixel_position: gpui::Point<Pixels>,
6909 newest_selection_head: Option<DisplayPoint>,
6910 target_display_point: DisplayPoint,
6911 window: &mut Window,
6912 cx: &mut App,
6913 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6914 let scrolled_content_origin =
6915 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6916
6917 const SCROLL_PADDING_Y: Pixels = px(12.);
6918
6919 if target_display_point.row() < visible_row_range.start {
6920 return self.render_edit_prediction_scroll_popover(
6921 |_| SCROLL_PADDING_Y,
6922 IconName::ArrowUp,
6923 visible_row_range,
6924 line_layouts,
6925 newest_selection_head,
6926 scrolled_content_origin,
6927 window,
6928 cx,
6929 );
6930 } else if target_display_point.row() >= visible_row_range.end {
6931 return self.render_edit_prediction_scroll_popover(
6932 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6933 IconName::ArrowDown,
6934 visible_row_range,
6935 line_layouts,
6936 newest_selection_head,
6937 scrolled_content_origin,
6938 window,
6939 cx,
6940 );
6941 }
6942
6943 const POLE_WIDTH: Pixels = px(2.);
6944
6945 let line_layout =
6946 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6947 let target_column = target_display_point.column() as usize;
6948
6949 let target_x = line_layout.x_for_index(target_column);
6950 let target_y =
6951 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6952
6953 let flag_on_right = target_x < text_bounds.size.width / 2.;
6954
6955 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6956 border_color.l += 0.001;
6957
6958 let mut element = v_flex()
6959 .items_end()
6960 .when(flag_on_right, |el| el.items_start())
6961 .child(if flag_on_right {
6962 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6963 .rounded_bl(px(0.))
6964 .rounded_tl(px(0.))
6965 .border_l_2()
6966 .border_color(border_color)
6967 } else {
6968 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6969 .rounded_br(px(0.))
6970 .rounded_tr(px(0.))
6971 .border_r_2()
6972 .border_color(border_color)
6973 })
6974 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6975 .into_any();
6976
6977 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6978
6979 let mut origin = scrolled_content_origin + point(target_x, target_y)
6980 - point(
6981 if flag_on_right {
6982 POLE_WIDTH
6983 } else {
6984 size.width - POLE_WIDTH
6985 },
6986 size.height - line_height,
6987 );
6988
6989 origin.x = origin.x.max(content_origin.x);
6990
6991 element.prepaint_at(origin, window, cx);
6992
6993 Some((element, origin))
6994 }
6995
6996 fn render_edit_prediction_scroll_popover(
6997 &mut self,
6998 to_y: impl Fn(Size<Pixels>) -> Pixels,
6999 scroll_icon: IconName,
7000 visible_row_range: Range<DisplayRow>,
7001 line_layouts: &[LineWithInvisibles],
7002 newest_selection_head: Option<DisplayPoint>,
7003 scrolled_content_origin: gpui::Point<Pixels>,
7004 window: &mut Window,
7005 cx: &mut App,
7006 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7007 let mut element = self
7008 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7009 .into_any();
7010
7011 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7012
7013 let cursor = newest_selection_head?;
7014 let cursor_row_layout =
7015 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7016 let cursor_column = cursor.column() as usize;
7017
7018 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7019
7020 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7021
7022 element.prepaint_at(origin, window, cx);
7023 Some((element, origin))
7024 }
7025
7026 fn render_edit_prediction_eager_jump_popover(
7027 &mut self,
7028 text_bounds: &Bounds<Pixels>,
7029 content_origin: gpui::Point<Pixels>,
7030 editor_snapshot: &EditorSnapshot,
7031 visible_row_range: Range<DisplayRow>,
7032 scroll_top: f32,
7033 scroll_bottom: f32,
7034 line_height: Pixels,
7035 scroll_pixel_position: gpui::Point<Pixels>,
7036 target_display_point: DisplayPoint,
7037 editor_width: Pixels,
7038 window: &mut Window,
7039 cx: &mut App,
7040 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7041 if target_display_point.row().as_f32() < scroll_top {
7042 let mut element = self
7043 .render_edit_prediction_line_popover(
7044 "Jump to Edit",
7045 Some(IconName::ArrowUp),
7046 window,
7047 cx,
7048 )?
7049 .into_any();
7050
7051 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7052 let offset = point(
7053 (text_bounds.size.width - size.width) / 2.,
7054 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7055 );
7056
7057 let origin = text_bounds.origin + offset;
7058 element.prepaint_at(origin, window, cx);
7059 Some((element, origin))
7060 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7061 let mut element = self
7062 .render_edit_prediction_line_popover(
7063 "Jump to Edit",
7064 Some(IconName::ArrowDown),
7065 window,
7066 cx,
7067 )?
7068 .into_any();
7069
7070 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7071 let offset = point(
7072 (text_bounds.size.width - size.width) / 2.,
7073 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7074 );
7075
7076 let origin = text_bounds.origin + offset;
7077 element.prepaint_at(origin, window, cx);
7078 Some((element, origin))
7079 } else {
7080 self.render_edit_prediction_end_of_line_popover(
7081 "Jump to Edit",
7082 editor_snapshot,
7083 visible_row_range,
7084 target_display_point,
7085 line_height,
7086 scroll_pixel_position,
7087 content_origin,
7088 editor_width,
7089 window,
7090 cx,
7091 )
7092 }
7093 }
7094
7095 fn render_edit_prediction_end_of_line_popover(
7096 self: &mut Editor,
7097 label: &'static str,
7098 editor_snapshot: &EditorSnapshot,
7099 visible_row_range: Range<DisplayRow>,
7100 target_display_point: DisplayPoint,
7101 line_height: Pixels,
7102 scroll_pixel_position: gpui::Point<Pixels>,
7103 content_origin: gpui::Point<Pixels>,
7104 editor_width: Pixels,
7105 window: &mut Window,
7106 cx: &mut App,
7107 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7108 let target_line_end = DisplayPoint::new(
7109 target_display_point.row(),
7110 editor_snapshot.line_len(target_display_point.row()),
7111 );
7112
7113 let mut element = self
7114 .render_edit_prediction_line_popover(label, None, window, cx)?
7115 .into_any();
7116
7117 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7118
7119 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7120
7121 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7122 let mut origin = start_point
7123 + line_origin
7124 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7125 origin.x = origin.x.max(content_origin.x);
7126
7127 let max_x = content_origin.x + editor_width - size.width;
7128
7129 if origin.x > max_x {
7130 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7131
7132 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7133 origin.y += offset;
7134 IconName::ArrowUp
7135 } else {
7136 origin.y -= offset;
7137 IconName::ArrowDown
7138 };
7139
7140 element = self
7141 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7142 .into_any();
7143
7144 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7145
7146 origin.x = content_origin.x + editor_width - size.width - px(2.);
7147 }
7148
7149 element.prepaint_at(origin, window, cx);
7150 Some((element, origin))
7151 }
7152
7153 fn render_edit_prediction_diff_popover(
7154 self: &Editor,
7155 text_bounds: &Bounds<Pixels>,
7156 content_origin: gpui::Point<Pixels>,
7157 editor_snapshot: &EditorSnapshot,
7158 visible_row_range: Range<DisplayRow>,
7159 line_layouts: &[LineWithInvisibles],
7160 line_height: Pixels,
7161 scroll_pixel_position: gpui::Point<Pixels>,
7162 newest_selection_head: Option<DisplayPoint>,
7163 editor_width: Pixels,
7164 style: &EditorStyle,
7165 edits: &Vec<(Range<Anchor>, String)>,
7166 edit_preview: &Option<language::EditPreview>,
7167 snapshot: &language::BufferSnapshot,
7168 window: &mut Window,
7169 cx: &mut App,
7170 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7171 let edit_start = edits
7172 .first()
7173 .unwrap()
7174 .0
7175 .start
7176 .to_display_point(editor_snapshot);
7177 let edit_end = edits
7178 .last()
7179 .unwrap()
7180 .0
7181 .end
7182 .to_display_point(editor_snapshot);
7183
7184 let is_visible = visible_row_range.contains(&edit_start.row())
7185 || visible_row_range.contains(&edit_end.row());
7186 if !is_visible {
7187 return None;
7188 }
7189
7190 let highlighted_edits =
7191 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7192
7193 let styled_text = highlighted_edits.to_styled_text(&style.text);
7194 let line_count = highlighted_edits.text.lines().count();
7195
7196 const BORDER_WIDTH: Pixels = px(1.);
7197
7198 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7199 let has_keybind = keybind.is_some();
7200
7201 let mut element = h_flex()
7202 .items_start()
7203 .child(
7204 h_flex()
7205 .bg(cx.theme().colors().editor_background)
7206 .border(BORDER_WIDTH)
7207 .shadow_sm()
7208 .border_color(cx.theme().colors().border)
7209 .rounded_l_lg()
7210 .when(line_count > 1, |el| el.rounded_br_lg())
7211 .pr_1()
7212 .child(styled_text),
7213 )
7214 .child(
7215 h_flex()
7216 .h(line_height + BORDER_WIDTH * 2.)
7217 .px_1p5()
7218 .gap_1()
7219 // Workaround: For some reason, there's a gap if we don't do this
7220 .ml(-BORDER_WIDTH)
7221 .shadow(smallvec![gpui::BoxShadow {
7222 color: gpui::black().opacity(0.05),
7223 offset: point(px(1.), px(1.)),
7224 blur_radius: px(2.),
7225 spread_radius: px(0.),
7226 }])
7227 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7228 .border(BORDER_WIDTH)
7229 .border_color(cx.theme().colors().border)
7230 .rounded_r_lg()
7231 .id("edit_prediction_diff_popover_keybind")
7232 .when(!has_keybind, |el| {
7233 let status_colors = cx.theme().status();
7234
7235 el.bg(status_colors.error_background)
7236 .border_color(status_colors.error.opacity(0.6))
7237 .child(Icon::new(IconName::Info).color(Color::Error))
7238 .cursor_default()
7239 .hoverable_tooltip(move |_window, cx| {
7240 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7241 })
7242 })
7243 .children(keybind),
7244 )
7245 .into_any();
7246
7247 let longest_row =
7248 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7249 let longest_line_width = if visible_row_range.contains(&longest_row) {
7250 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7251 } else {
7252 layout_line(
7253 longest_row,
7254 editor_snapshot,
7255 style,
7256 editor_width,
7257 |_| false,
7258 window,
7259 cx,
7260 )
7261 .width
7262 };
7263
7264 let viewport_bounds =
7265 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7266 right: -EditorElement::SCROLLBAR_WIDTH,
7267 ..Default::default()
7268 });
7269
7270 let x_after_longest =
7271 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7272 - scroll_pixel_position.x;
7273
7274 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7275
7276 // Fully visible if it can be displayed within the window (allow overlapping other
7277 // panes). However, this is only allowed if the popover starts within text_bounds.
7278 let can_position_to_the_right = x_after_longest < text_bounds.right()
7279 && x_after_longest + element_bounds.width < viewport_bounds.right();
7280
7281 let mut origin = if can_position_to_the_right {
7282 point(
7283 x_after_longest,
7284 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7285 - scroll_pixel_position.y,
7286 )
7287 } else {
7288 let cursor_row = newest_selection_head.map(|head| head.row());
7289 let above_edit = edit_start
7290 .row()
7291 .0
7292 .checked_sub(line_count as u32)
7293 .map(DisplayRow);
7294 let below_edit = Some(edit_end.row() + 1);
7295 let above_cursor =
7296 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7297 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7298
7299 // Place the edit popover adjacent to the edit if there is a location
7300 // available that is onscreen and does not obscure the cursor. Otherwise,
7301 // place it adjacent to the cursor.
7302 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7303 .into_iter()
7304 .flatten()
7305 .find(|&start_row| {
7306 let end_row = start_row + line_count as u32;
7307 visible_row_range.contains(&start_row)
7308 && visible_row_range.contains(&end_row)
7309 && cursor_row.map_or(true, |cursor_row| {
7310 !((start_row..end_row).contains(&cursor_row))
7311 })
7312 })?;
7313
7314 content_origin
7315 + point(
7316 -scroll_pixel_position.x,
7317 row_target.as_f32() * line_height - scroll_pixel_position.y,
7318 )
7319 };
7320
7321 origin.x -= BORDER_WIDTH;
7322
7323 window.defer_draw(element, origin, 1);
7324
7325 // Do not return an element, since it will already be drawn due to defer_draw.
7326 None
7327 }
7328
7329 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7330 px(30.)
7331 }
7332
7333 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7334 if self.read_only(cx) {
7335 cx.theme().players().read_only()
7336 } else {
7337 self.style.as_ref().unwrap().local_player
7338 }
7339 }
7340
7341 fn render_edit_prediction_accept_keybind(
7342 &self,
7343 window: &mut Window,
7344 cx: &App,
7345 ) -> Option<AnyElement> {
7346 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7347 let accept_keystroke = accept_binding.keystroke()?;
7348
7349 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7350
7351 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7352 Color::Accent
7353 } else {
7354 Color::Muted
7355 };
7356
7357 h_flex()
7358 .px_0p5()
7359 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7360 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7361 .text_size(TextSize::XSmall.rems(cx))
7362 .child(h_flex().children(ui::render_modifiers(
7363 &accept_keystroke.modifiers,
7364 PlatformStyle::platform(),
7365 Some(modifiers_color),
7366 Some(IconSize::XSmall.rems().into()),
7367 true,
7368 )))
7369 .when(is_platform_style_mac, |parent| {
7370 parent.child(accept_keystroke.key.clone())
7371 })
7372 .when(!is_platform_style_mac, |parent| {
7373 parent.child(
7374 Key::new(
7375 util::capitalize(&accept_keystroke.key),
7376 Some(Color::Default),
7377 )
7378 .size(Some(IconSize::XSmall.rems().into())),
7379 )
7380 })
7381 .into_any()
7382 .into()
7383 }
7384
7385 fn render_edit_prediction_line_popover(
7386 &self,
7387 label: impl Into<SharedString>,
7388 icon: Option<IconName>,
7389 window: &mut Window,
7390 cx: &App,
7391 ) -> Option<Stateful<Div>> {
7392 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7393
7394 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7395 let has_keybind = keybind.is_some();
7396
7397 let result = h_flex()
7398 .id("ep-line-popover")
7399 .py_0p5()
7400 .pl_1()
7401 .pr(padding_right)
7402 .gap_1()
7403 .rounded_md()
7404 .border_1()
7405 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7406 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7407 .shadow_sm()
7408 .when(!has_keybind, |el| {
7409 let status_colors = cx.theme().status();
7410
7411 el.bg(status_colors.error_background)
7412 .border_color(status_colors.error.opacity(0.6))
7413 .pl_2()
7414 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7415 .cursor_default()
7416 .hoverable_tooltip(move |_window, cx| {
7417 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7418 })
7419 })
7420 .children(keybind)
7421 .child(
7422 Label::new(label)
7423 .size(LabelSize::Small)
7424 .when(!has_keybind, |el| {
7425 el.color(cx.theme().status().error.into()).strikethrough()
7426 }),
7427 )
7428 .when(!has_keybind, |el| {
7429 el.child(
7430 h_flex().ml_1().child(
7431 Icon::new(IconName::Info)
7432 .size(IconSize::Small)
7433 .color(cx.theme().status().error.into()),
7434 ),
7435 )
7436 })
7437 .when_some(icon, |element, icon| {
7438 element.child(
7439 div()
7440 .mt(px(1.5))
7441 .child(Icon::new(icon).size(IconSize::Small)),
7442 )
7443 });
7444
7445 Some(result)
7446 }
7447
7448 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7449 let accent_color = cx.theme().colors().text_accent;
7450 let editor_bg_color = cx.theme().colors().editor_background;
7451 editor_bg_color.blend(accent_color.opacity(0.1))
7452 }
7453
7454 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7455 let accent_color = cx.theme().colors().text_accent;
7456 let editor_bg_color = cx.theme().colors().editor_background;
7457 editor_bg_color.blend(accent_color.opacity(0.6))
7458 }
7459
7460 fn render_edit_prediction_cursor_popover(
7461 &self,
7462 min_width: Pixels,
7463 max_width: Pixels,
7464 cursor_point: Point,
7465 style: &EditorStyle,
7466 accept_keystroke: Option<&gpui::Keystroke>,
7467 _window: &Window,
7468 cx: &mut Context<Editor>,
7469 ) -> Option<AnyElement> {
7470 let provider = self.edit_prediction_provider.as_ref()?;
7471
7472 if provider.provider.needs_terms_acceptance(cx) {
7473 return Some(
7474 h_flex()
7475 .min_w(min_width)
7476 .flex_1()
7477 .px_2()
7478 .py_1()
7479 .gap_3()
7480 .elevation_2(cx)
7481 .hover(|style| style.bg(cx.theme().colors().element_hover))
7482 .id("accept-terms")
7483 .cursor_pointer()
7484 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7485 .on_click(cx.listener(|this, _event, window, cx| {
7486 cx.stop_propagation();
7487 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7488 window.dispatch_action(
7489 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7490 cx,
7491 );
7492 }))
7493 .child(
7494 h_flex()
7495 .flex_1()
7496 .gap_2()
7497 .child(Icon::new(IconName::ZedPredict))
7498 .child(Label::new("Accept Terms of Service"))
7499 .child(div().w_full())
7500 .child(
7501 Icon::new(IconName::ArrowUpRight)
7502 .color(Color::Muted)
7503 .size(IconSize::Small),
7504 )
7505 .into_any_element(),
7506 )
7507 .into_any(),
7508 );
7509 }
7510
7511 let is_refreshing = provider.provider.is_refreshing(cx);
7512
7513 fn pending_completion_container() -> Div {
7514 h_flex()
7515 .h_full()
7516 .flex_1()
7517 .gap_2()
7518 .child(Icon::new(IconName::ZedPredict))
7519 }
7520
7521 let completion = match &self.active_inline_completion {
7522 Some(prediction) => {
7523 if !self.has_visible_completions_menu() {
7524 const RADIUS: Pixels = px(6.);
7525 const BORDER_WIDTH: Pixels = px(1.);
7526
7527 return Some(
7528 h_flex()
7529 .elevation_2(cx)
7530 .border(BORDER_WIDTH)
7531 .border_color(cx.theme().colors().border)
7532 .when(accept_keystroke.is_none(), |el| {
7533 el.border_color(cx.theme().status().error)
7534 })
7535 .rounded(RADIUS)
7536 .rounded_tl(px(0.))
7537 .overflow_hidden()
7538 .child(div().px_1p5().child(match &prediction.completion {
7539 InlineCompletion::Move { target, snapshot } => {
7540 use text::ToPoint as _;
7541 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7542 {
7543 Icon::new(IconName::ZedPredictDown)
7544 } else {
7545 Icon::new(IconName::ZedPredictUp)
7546 }
7547 }
7548 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7549 }))
7550 .child(
7551 h_flex()
7552 .gap_1()
7553 .py_1()
7554 .px_2()
7555 .rounded_r(RADIUS - BORDER_WIDTH)
7556 .border_l_1()
7557 .border_color(cx.theme().colors().border)
7558 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7559 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7560 el.child(
7561 Label::new("Hold")
7562 .size(LabelSize::Small)
7563 .when(accept_keystroke.is_none(), |el| {
7564 el.strikethrough()
7565 })
7566 .line_height_style(LineHeightStyle::UiLabel),
7567 )
7568 })
7569 .id("edit_prediction_cursor_popover_keybind")
7570 .when(accept_keystroke.is_none(), |el| {
7571 let status_colors = cx.theme().status();
7572
7573 el.bg(status_colors.error_background)
7574 .border_color(status_colors.error.opacity(0.6))
7575 .child(Icon::new(IconName::Info).color(Color::Error))
7576 .cursor_default()
7577 .hoverable_tooltip(move |_window, cx| {
7578 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7579 .into()
7580 })
7581 })
7582 .when_some(
7583 accept_keystroke.as_ref(),
7584 |el, accept_keystroke| {
7585 el.child(h_flex().children(ui::render_modifiers(
7586 &accept_keystroke.modifiers,
7587 PlatformStyle::platform(),
7588 Some(Color::Default),
7589 Some(IconSize::XSmall.rems().into()),
7590 false,
7591 )))
7592 },
7593 ),
7594 )
7595 .into_any(),
7596 );
7597 }
7598
7599 self.render_edit_prediction_cursor_popover_preview(
7600 prediction,
7601 cursor_point,
7602 style,
7603 cx,
7604 )?
7605 }
7606
7607 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7608 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7609 stale_completion,
7610 cursor_point,
7611 style,
7612 cx,
7613 )?,
7614
7615 None => {
7616 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7617 }
7618 },
7619
7620 None => pending_completion_container().child(Label::new("No Prediction")),
7621 };
7622
7623 let completion = if is_refreshing {
7624 completion
7625 .with_animation(
7626 "loading-completion",
7627 Animation::new(Duration::from_secs(2))
7628 .repeat()
7629 .with_easing(pulsating_between(0.4, 0.8)),
7630 |label, delta| label.opacity(delta),
7631 )
7632 .into_any_element()
7633 } else {
7634 completion.into_any_element()
7635 };
7636
7637 let has_completion = self.active_inline_completion.is_some();
7638
7639 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7640 Some(
7641 h_flex()
7642 .min_w(min_width)
7643 .max_w(max_width)
7644 .flex_1()
7645 .elevation_2(cx)
7646 .border_color(cx.theme().colors().border)
7647 .child(
7648 div()
7649 .flex_1()
7650 .py_1()
7651 .px_2()
7652 .overflow_hidden()
7653 .child(completion),
7654 )
7655 .when_some(accept_keystroke, |el, accept_keystroke| {
7656 if !accept_keystroke.modifiers.modified() {
7657 return el;
7658 }
7659
7660 el.child(
7661 h_flex()
7662 .h_full()
7663 .border_l_1()
7664 .rounded_r_lg()
7665 .border_color(cx.theme().colors().border)
7666 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7667 .gap_1()
7668 .py_1()
7669 .px_2()
7670 .child(
7671 h_flex()
7672 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7673 .when(is_platform_style_mac, |parent| parent.gap_1())
7674 .child(h_flex().children(ui::render_modifiers(
7675 &accept_keystroke.modifiers,
7676 PlatformStyle::platform(),
7677 Some(if !has_completion {
7678 Color::Muted
7679 } else {
7680 Color::Default
7681 }),
7682 None,
7683 false,
7684 ))),
7685 )
7686 .child(Label::new("Preview").into_any_element())
7687 .opacity(if has_completion { 1.0 } else { 0.4 }),
7688 )
7689 })
7690 .into_any(),
7691 )
7692 }
7693
7694 fn render_edit_prediction_cursor_popover_preview(
7695 &self,
7696 completion: &InlineCompletionState,
7697 cursor_point: Point,
7698 style: &EditorStyle,
7699 cx: &mut Context<Editor>,
7700 ) -> Option<Div> {
7701 use text::ToPoint as _;
7702
7703 fn render_relative_row_jump(
7704 prefix: impl Into<String>,
7705 current_row: u32,
7706 target_row: u32,
7707 ) -> Div {
7708 let (row_diff, arrow) = if target_row < current_row {
7709 (current_row - target_row, IconName::ArrowUp)
7710 } else {
7711 (target_row - current_row, IconName::ArrowDown)
7712 };
7713
7714 h_flex()
7715 .child(
7716 Label::new(format!("{}{}", prefix.into(), row_diff))
7717 .color(Color::Muted)
7718 .size(LabelSize::Small),
7719 )
7720 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7721 }
7722
7723 match &completion.completion {
7724 InlineCompletion::Move {
7725 target, snapshot, ..
7726 } => Some(
7727 h_flex()
7728 .px_2()
7729 .gap_2()
7730 .flex_1()
7731 .child(
7732 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7733 Icon::new(IconName::ZedPredictDown)
7734 } else {
7735 Icon::new(IconName::ZedPredictUp)
7736 },
7737 )
7738 .child(Label::new("Jump to Edit")),
7739 ),
7740
7741 InlineCompletion::Edit {
7742 edits,
7743 edit_preview,
7744 snapshot,
7745 display_mode: _,
7746 } => {
7747 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7748
7749 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7750 &snapshot,
7751 &edits,
7752 edit_preview.as_ref()?,
7753 true,
7754 cx,
7755 )
7756 .first_line_preview();
7757
7758 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7759 .with_default_highlights(&style.text, highlighted_edits.highlights);
7760
7761 let preview = h_flex()
7762 .gap_1()
7763 .min_w_16()
7764 .child(styled_text)
7765 .when(has_more_lines, |parent| parent.child("…"));
7766
7767 let left = if first_edit_row != cursor_point.row {
7768 render_relative_row_jump("", cursor_point.row, first_edit_row)
7769 .into_any_element()
7770 } else {
7771 Icon::new(IconName::ZedPredict).into_any_element()
7772 };
7773
7774 Some(
7775 h_flex()
7776 .h_full()
7777 .flex_1()
7778 .gap_2()
7779 .pr_1()
7780 .overflow_x_hidden()
7781 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7782 .child(left)
7783 .child(preview),
7784 )
7785 }
7786 }
7787 }
7788
7789 fn render_context_menu(
7790 &self,
7791 style: &EditorStyle,
7792 max_height_in_lines: u32,
7793 window: &mut Window,
7794 cx: &mut Context<Editor>,
7795 ) -> Option<AnyElement> {
7796 let menu = self.context_menu.borrow();
7797 let menu = menu.as_ref()?;
7798 if !menu.visible() {
7799 return None;
7800 };
7801 Some(menu.render(style, max_height_in_lines, window, cx))
7802 }
7803
7804 fn render_context_menu_aside(
7805 &mut self,
7806 max_size: Size<Pixels>,
7807 window: &mut Window,
7808 cx: &mut Context<Editor>,
7809 ) -> Option<AnyElement> {
7810 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7811 if menu.visible() {
7812 menu.render_aside(self, max_size, window, cx)
7813 } else {
7814 None
7815 }
7816 })
7817 }
7818
7819 fn hide_context_menu(
7820 &mut self,
7821 window: &mut Window,
7822 cx: &mut Context<Self>,
7823 ) -> Option<CodeContextMenu> {
7824 cx.notify();
7825 self.completion_tasks.clear();
7826 let context_menu = self.context_menu.borrow_mut().take();
7827 self.stale_inline_completion_in_menu.take();
7828 self.update_visible_inline_completion(window, cx);
7829 context_menu
7830 }
7831
7832 fn show_snippet_choices(
7833 &mut self,
7834 choices: &Vec<String>,
7835 selection: Range<Anchor>,
7836 cx: &mut Context<Self>,
7837 ) {
7838 if selection.start.buffer_id.is_none() {
7839 return;
7840 }
7841 let buffer_id = selection.start.buffer_id.unwrap();
7842 let buffer = self.buffer().read(cx).buffer(buffer_id);
7843 let id = post_inc(&mut self.next_completion_id);
7844
7845 if let Some(buffer) = buffer {
7846 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7847 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7848 ));
7849 }
7850 }
7851
7852 pub fn insert_snippet(
7853 &mut self,
7854 insertion_ranges: &[Range<usize>],
7855 snippet: Snippet,
7856 window: &mut Window,
7857 cx: &mut Context<Self>,
7858 ) -> Result<()> {
7859 struct Tabstop<T> {
7860 is_end_tabstop: bool,
7861 ranges: Vec<Range<T>>,
7862 choices: Option<Vec<String>>,
7863 }
7864
7865 let tabstops = self.buffer.update(cx, |buffer, cx| {
7866 let snippet_text: Arc<str> = snippet.text.clone().into();
7867 let edits = insertion_ranges
7868 .iter()
7869 .cloned()
7870 .map(|range| (range, snippet_text.clone()));
7871 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
7872
7873 let snapshot = &*buffer.read(cx);
7874 let snippet = &snippet;
7875 snippet
7876 .tabstops
7877 .iter()
7878 .map(|tabstop| {
7879 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7880 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7881 });
7882 let mut tabstop_ranges = tabstop
7883 .ranges
7884 .iter()
7885 .flat_map(|tabstop_range| {
7886 let mut delta = 0_isize;
7887 insertion_ranges.iter().map(move |insertion_range| {
7888 let insertion_start = insertion_range.start as isize + delta;
7889 delta +=
7890 snippet.text.len() as isize - insertion_range.len() as isize;
7891
7892 let start = ((insertion_start + tabstop_range.start) as usize)
7893 .min(snapshot.len());
7894 let end = ((insertion_start + tabstop_range.end) as usize)
7895 .min(snapshot.len());
7896 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7897 })
7898 })
7899 .collect::<Vec<_>>();
7900 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7901
7902 Tabstop {
7903 is_end_tabstop,
7904 ranges: tabstop_ranges,
7905 choices: tabstop.choices.clone(),
7906 }
7907 })
7908 .collect::<Vec<_>>()
7909 });
7910 if let Some(tabstop) = tabstops.first() {
7911 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7912 s.select_ranges(tabstop.ranges.iter().cloned());
7913 });
7914
7915 if let Some(choices) = &tabstop.choices {
7916 if let Some(selection) = tabstop.ranges.first() {
7917 self.show_snippet_choices(choices, selection.clone(), cx)
7918 }
7919 }
7920
7921 // If we're already at the last tabstop and it's at the end of the snippet,
7922 // we're done, we don't need to keep the state around.
7923 if !tabstop.is_end_tabstop {
7924 let choices = tabstops
7925 .iter()
7926 .map(|tabstop| tabstop.choices.clone())
7927 .collect();
7928
7929 let ranges = tabstops
7930 .into_iter()
7931 .map(|tabstop| tabstop.ranges)
7932 .collect::<Vec<_>>();
7933
7934 self.snippet_stack.push(SnippetState {
7935 active_index: 0,
7936 ranges,
7937 choices,
7938 });
7939 }
7940
7941 // Check whether the just-entered snippet ends with an auto-closable bracket.
7942 if self.autoclose_regions.is_empty() {
7943 let snapshot = self.buffer.read(cx).snapshot(cx);
7944 for selection in &mut self.selections.all::<Point>(cx) {
7945 let selection_head = selection.head();
7946 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7947 continue;
7948 };
7949
7950 let mut bracket_pair = None;
7951 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7952 let prev_chars = snapshot
7953 .reversed_chars_at(selection_head)
7954 .collect::<String>();
7955 for (pair, enabled) in scope.brackets() {
7956 if enabled
7957 && pair.close
7958 && prev_chars.starts_with(pair.start.as_str())
7959 && next_chars.starts_with(pair.end.as_str())
7960 {
7961 bracket_pair = Some(pair.clone());
7962 break;
7963 }
7964 }
7965 if let Some(pair) = bracket_pair {
7966 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
7967 let autoclose_enabled =
7968 self.use_autoclose && snapshot_settings.use_autoclose;
7969 if autoclose_enabled {
7970 let start = snapshot.anchor_after(selection_head);
7971 let end = snapshot.anchor_after(selection_head);
7972 self.autoclose_regions.push(AutocloseRegion {
7973 selection_id: selection.id,
7974 range: start..end,
7975 pair,
7976 });
7977 }
7978 }
7979 }
7980 }
7981 }
7982 Ok(())
7983 }
7984
7985 pub fn move_to_next_snippet_tabstop(
7986 &mut self,
7987 window: &mut Window,
7988 cx: &mut Context<Self>,
7989 ) -> bool {
7990 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7991 }
7992
7993 pub fn move_to_prev_snippet_tabstop(
7994 &mut self,
7995 window: &mut Window,
7996 cx: &mut Context<Self>,
7997 ) -> bool {
7998 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7999 }
8000
8001 pub fn move_to_snippet_tabstop(
8002 &mut self,
8003 bias: Bias,
8004 window: &mut Window,
8005 cx: &mut Context<Self>,
8006 ) -> bool {
8007 if let Some(mut snippet) = self.snippet_stack.pop() {
8008 match bias {
8009 Bias::Left => {
8010 if snippet.active_index > 0 {
8011 snippet.active_index -= 1;
8012 } else {
8013 self.snippet_stack.push(snippet);
8014 return false;
8015 }
8016 }
8017 Bias::Right => {
8018 if snippet.active_index + 1 < snippet.ranges.len() {
8019 snippet.active_index += 1;
8020 } else {
8021 self.snippet_stack.push(snippet);
8022 return false;
8023 }
8024 }
8025 }
8026 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8027 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8028 s.select_anchor_ranges(current_ranges.iter().cloned())
8029 });
8030
8031 if let Some(choices) = &snippet.choices[snippet.active_index] {
8032 if let Some(selection) = current_ranges.first() {
8033 self.show_snippet_choices(&choices, selection.clone(), cx);
8034 }
8035 }
8036
8037 // If snippet state is not at the last tabstop, push it back on the stack
8038 if snippet.active_index + 1 < snippet.ranges.len() {
8039 self.snippet_stack.push(snippet);
8040 }
8041 return true;
8042 }
8043 }
8044
8045 false
8046 }
8047
8048 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8049 self.transact(window, cx, |this, window, cx| {
8050 this.select_all(&SelectAll, window, cx);
8051 this.insert("", window, cx);
8052 });
8053 }
8054
8055 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8056 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8057 self.transact(window, cx, |this, window, cx| {
8058 this.select_autoclose_pair(window, cx);
8059 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8060 if !this.linked_edit_ranges.is_empty() {
8061 let selections = this.selections.all::<MultiBufferPoint>(cx);
8062 let snapshot = this.buffer.read(cx).snapshot(cx);
8063
8064 for selection in selections.iter() {
8065 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8066 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8067 if selection_start.buffer_id != selection_end.buffer_id {
8068 continue;
8069 }
8070 if let Some(ranges) =
8071 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8072 {
8073 for (buffer, entries) in ranges {
8074 linked_ranges.entry(buffer).or_default().extend(entries);
8075 }
8076 }
8077 }
8078 }
8079
8080 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8081 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8082 for selection in &mut selections {
8083 if selection.is_empty() {
8084 let old_head = selection.head();
8085 let mut new_head =
8086 movement::left(&display_map, old_head.to_display_point(&display_map))
8087 .to_point(&display_map);
8088 if let Some((buffer, line_buffer_range)) = display_map
8089 .buffer_snapshot
8090 .buffer_line_for_row(MultiBufferRow(old_head.row))
8091 {
8092 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8093 let indent_len = match indent_size.kind {
8094 IndentKind::Space => {
8095 buffer.settings_at(line_buffer_range.start, cx).tab_size
8096 }
8097 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8098 };
8099 if old_head.column <= indent_size.len && old_head.column > 0 {
8100 let indent_len = indent_len.get();
8101 new_head = cmp::min(
8102 new_head,
8103 MultiBufferPoint::new(
8104 old_head.row,
8105 ((old_head.column - 1) / indent_len) * indent_len,
8106 ),
8107 );
8108 }
8109 }
8110
8111 selection.set_head(new_head, SelectionGoal::None);
8112 }
8113 }
8114
8115 this.signature_help_state.set_backspace_pressed(true);
8116 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8117 s.select(selections)
8118 });
8119 this.insert("", window, cx);
8120 let empty_str: Arc<str> = Arc::from("");
8121 for (buffer, edits) in linked_ranges {
8122 let snapshot = buffer.read(cx).snapshot();
8123 use text::ToPoint as TP;
8124
8125 let edits = edits
8126 .into_iter()
8127 .map(|range| {
8128 let end_point = TP::to_point(&range.end, &snapshot);
8129 let mut start_point = TP::to_point(&range.start, &snapshot);
8130
8131 if end_point == start_point {
8132 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8133 .saturating_sub(1);
8134 start_point =
8135 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8136 };
8137
8138 (start_point..end_point, empty_str.clone())
8139 })
8140 .sorted_by_key(|(range, _)| range.start)
8141 .collect::<Vec<_>>();
8142 buffer.update(cx, |this, cx| {
8143 this.edit(edits, None, cx);
8144 })
8145 }
8146 this.refresh_inline_completion(true, false, window, cx);
8147 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8148 });
8149 }
8150
8151 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8152 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8153 self.transact(window, cx, |this, window, cx| {
8154 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8155 s.move_with(|map, selection| {
8156 if selection.is_empty() {
8157 let cursor = movement::right(map, selection.head());
8158 selection.end = cursor;
8159 selection.reversed = true;
8160 selection.goal = SelectionGoal::None;
8161 }
8162 })
8163 });
8164 this.insert("", window, cx);
8165 this.refresh_inline_completion(true, false, window, cx);
8166 });
8167 }
8168
8169 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8170 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8171 if self.move_to_prev_snippet_tabstop(window, cx) {
8172 return;
8173 }
8174 self.outdent(&Outdent, window, cx);
8175 }
8176
8177 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8178 if self.move_to_next_snippet_tabstop(window, cx) {
8179 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8180 return;
8181 }
8182 if self.read_only(cx) {
8183 return;
8184 }
8185 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8186 let mut selections = self.selections.all_adjusted(cx);
8187 let buffer = self.buffer.read(cx);
8188 let snapshot = buffer.snapshot(cx);
8189 let rows_iter = selections.iter().map(|s| s.head().row);
8190 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8191
8192 let mut edits = Vec::new();
8193 let mut prev_edited_row = 0;
8194 let mut row_delta = 0;
8195 for selection in &mut selections {
8196 if selection.start.row != prev_edited_row {
8197 row_delta = 0;
8198 }
8199 prev_edited_row = selection.end.row;
8200
8201 // If the selection is non-empty, then increase the indentation of the selected lines.
8202 if !selection.is_empty() {
8203 row_delta =
8204 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8205 continue;
8206 }
8207
8208 // If the selection is empty and the cursor is in the leading whitespace before the
8209 // suggested indentation, then auto-indent the line.
8210 let cursor = selection.head();
8211 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8212 if let Some(suggested_indent) =
8213 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8214 {
8215 if cursor.column < suggested_indent.len
8216 && cursor.column <= current_indent.len
8217 && current_indent.len <= suggested_indent.len
8218 {
8219 selection.start = Point::new(cursor.row, suggested_indent.len);
8220 selection.end = selection.start;
8221 if row_delta == 0 {
8222 edits.extend(Buffer::edit_for_indent_size_adjustment(
8223 cursor.row,
8224 current_indent,
8225 suggested_indent,
8226 ));
8227 row_delta = suggested_indent.len - current_indent.len;
8228 }
8229 continue;
8230 }
8231 }
8232
8233 // Otherwise, insert a hard or soft tab.
8234 let settings = buffer.language_settings_at(cursor, cx);
8235 let tab_size = if settings.hard_tabs {
8236 IndentSize::tab()
8237 } else {
8238 let tab_size = settings.tab_size.get();
8239 let indent_remainder = snapshot
8240 .text_for_range(Point::new(cursor.row, 0)..cursor)
8241 .flat_map(str::chars)
8242 .fold(row_delta % tab_size, |counter: u32, c| {
8243 if c == '\t' {
8244 0
8245 } else {
8246 (counter + 1) % tab_size
8247 }
8248 });
8249
8250 let chars_to_next_tab_stop = tab_size - indent_remainder;
8251 IndentSize::spaces(chars_to_next_tab_stop)
8252 };
8253 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8254 selection.end = selection.start;
8255 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8256 row_delta += tab_size.len;
8257 }
8258
8259 self.transact(window, cx, |this, window, cx| {
8260 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8261 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8262 s.select(selections)
8263 });
8264 this.refresh_inline_completion(true, false, window, cx);
8265 });
8266 }
8267
8268 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8269 if self.read_only(cx) {
8270 return;
8271 }
8272 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8273 let mut selections = self.selections.all::<Point>(cx);
8274 let mut prev_edited_row = 0;
8275 let mut row_delta = 0;
8276 let mut edits = Vec::new();
8277 let buffer = self.buffer.read(cx);
8278 let snapshot = buffer.snapshot(cx);
8279 for selection in &mut selections {
8280 if selection.start.row != prev_edited_row {
8281 row_delta = 0;
8282 }
8283 prev_edited_row = selection.end.row;
8284
8285 row_delta =
8286 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8287 }
8288
8289 self.transact(window, cx, |this, window, cx| {
8290 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8291 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8292 s.select(selections)
8293 });
8294 });
8295 }
8296
8297 fn indent_selection(
8298 buffer: &MultiBuffer,
8299 snapshot: &MultiBufferSnapshot,
8300 selection: &mut Selection<Point>,
8301 edits: &mut Vec<(Range<Point>, String)>,
8302 delta_for_start_row: u32,
8303 cx: &App,
8304 ) -> u32 {
8305 let settings = buffer.language_settings_at(selection.start, cx);
8306 let tab_size = settings.tab_size.get();
8307 let indent_kind = if settings.hard_tabs {
8308 IndentKind::Tab
8309 } else {
8310 IndentKind::Space
8311 };
8312 let mut start_row = selection.start.row;
8313 let mut end_row = selection.end.row + 1;
8314
8315 // If a selection ends at the beginning of a line, don't indent
8316 // that last line.
8317 if selection.end.column == 0 && selection.end.row > selection.start.row {
8318 end_row -= 1;
8319 }
8320
8321 // Avoid re-indenting a row that has already been indented by a
8322 // previous selection, but still update this selection's column
8323 // to reflect that indentation.
8324 if delta_for_start_row > 0 {
8325 start_row += 1;
8326 selection.start.column += delta_for_start_row;
8327 if selection.end.row == selection.start.row {
8328 selection.end.column += delta_for_start_row;
8329 }
8330 }
8331
8332 let mut delta_for_end_row = 0;
8333 let has_multiple_rows = start_row + 1 != end_row;
8334 for row in start_row..end_row {
8335 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8336 let indent_delta = match (current_indent.kind, indent_kind) {
8337 (IndentKind::Space, IndentKind::Space) => {
8338 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8339 IndentSize::spaces(columns_to_next_tab_stop)
8340 }
8341 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8342 (_, IndentKind::Tab) => IndentSize::tab(),
8343 };
8344
8345 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8346 0
8347 } else {
8348 selection.start.column
8349 };
8350 let row_start = Point::new(row, start);
8351 edits.push((
8352 row_start..row_start,
8353 indent_delta.chars().collect::<String>(),
8354 ));
8355
8356 // Update this selection's endpoints to reflect the indentation.
8357 if row == selection.start.row {
8358 selection.start.column += indent_delta.len;
8359 }
8360 if row == selection.end.row {
8361 selection.end.column += indent_delta.len;
8362 delta_for_end_row = indent_delta.len;
8363 }
8364 }
8365
8366 if selection.start.row == selection.end.row {
8367 delta_for_start_row + delta_for_end_row
8368 } else {
8369 delta_for_end_row
8370 }
8371 }
8372
8373 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8374 if self.read_only(cx) {
8375 return;
8376 }
8377 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8378 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8379 let selections = self.selections.all::<Point>(cx);
8380 let mut deletion_ranges = Vec::new();
8381 let mut last_outdent = None;
8382 {
8383 let buffer = self.buffer.read(cx);
8384 let snapshot = buffer.snapshot(cx);
8385 for selection in &selections {
8386 let settings = buffer.language_settings_at(selection.start, cx);
8387 let tab_size = settings.tab_size.get();
8388 let mut rows = selection.spanned_rows(false, &display_map);
8389
8390 // Avoid re-outdenting a row that has already been outdented by a
8391 // previous selection.
8392 if let Some(last_row) = last_outdent {
8393 if last_row == rows.start {
8394 rows.start = rows.start.next_row();
8395 }
8396 }
8397 let has_multiple_rows = rows.len() > 1;
8398 for row in rows.iter_rows() {
8399 let indent_size = snapshot.indent_size_for_line(row);
8400 if indent_size.len > 0 {
8401 let deletion_len = match indent_size.kind {
8402 IndentKind::Space => {
8403 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8404 if columns_to_prev_tab_stop == 0 {
8405 tab_size
8406 } else {
8407 columns_to_prev_tab_stop
8408 }
8409 }
8410 IndentKind::Tab => 1,
8411 };
8412 let start = if has_multiple_rows
8413 || deletion_len > selection.start.column
8414 || indent_size.len < selection.start.column
8415 {
8416 0
8417 } else {
8418 selection.start.column - deletion_len
8419 };
8420 deletion_ranges.push(
8421 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8422 );
8423 last_outdent = Some(row);
8424 }
8425 }
8426 }
8427 }
8428
8429 self.transact(window, cx, |this, window, cx| {
8430 this.buffer.update(cx, |buffer, cx| {
8431 let empty_str: Arc<str> = Arc::default();
8432 buffer.edit(
8433 deletion_ranges
8434 .into_iter()
8435 .map(|range| (range, empty_str.clone())),
8436 None,
8437 cx,
8438 );
8439 });
8440 let selections = this.selections.all::<usize>(cx);
8441 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8442 s.select(selections)
8443 });
8444 });
8445 }
8446
8447 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8448 if self.read_only(cx) {
8449 return;
8450 }
8451 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8452 let selections = self
8453 .selections
8454 .all::<usize>(cx)
8455 .into_iter()
8456 .map(|s| s.range());
8457
8458 self.transact(window, cx, |this, window, cx| {
8459 this.buffer.update(cx, |buffer, cx| {
8460 buffer.autoindent_ranges(selections, cx);
8461 });
8462 let selections = this.selections.all::<usize>(cx);
8463 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8464 s.select(selections)
8465 });
8466 });
8467 }
8468
8469 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8470 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8471 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8472 let selections = self.selections.all::<Point>(cx);
8473
8474 let mut new_cursors = Vec::new();
8475 let mut edit_ranges = Vec::new();
8476 let mut selections = selections.iter().peekable();
8477 while let Some(selection) = selections.next() {
8478 let mut rows = selection.spanned_rows(false, &display_map);
8479 let goal_display_column = selection.head().to_display_point(&display_map).column();
8480
8481 // Accumulate contiguous regions of rows that we want to delete.
8482 while let Some(next_selection) = selections.peek() {
8483 let next_rows = next_selection.spanned_rows(false, &display_map);
8484 if next_rows.start <= rows.end {
8485 rows.end = next_rows.end;
8486 selections.next().unwrap();
8487 } else {
8488 break;
8489 }
8490 }
8491
8492 let buffer = &display_map.buffer_snapshot;
8493 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8494 let edit_end;
8495 let cursor_buffer_row;
8496 if buffer.max_point().row >= rows.end.0 {
8497 // If there's a line after the range, delete the \n from the end of the row range
8498 // and position the cursor on the next line.
8499 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8500 cursor_buffer_row = rows.end;
8501 } else {
8502 // If there isn't a line after the range, delete the \n from the line before the
8503 // start of the row range and position the cursor there.
8504 edit_start = edit_start.saturating_sub(1);
8505 edit_end = buffer.len();
8506 cursor_buffer_row = rows.start.previous_row();
8507 }
8508
8509 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8510 *cursor.column_mut() =
8511 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8512
8513 new_cursors.push((
8514 selection.id,
8515 buffer.anchor_after(cursor.to_point(&display_map)),
8516 ));
8517 edit_ranges.push(edit_start..edit_end);
8518 }
8519
8520 self.transact(window, cx, |this, window, cx| {
8521 let buffer = this.buffer.update(cx, |buffer, cx| {
8522 let empty_str: Arc<str> = Arc::default();
8523 buffer.edit(
8524 edit_ranges
8525 .into_iter()
8526 .map(|range| (range, empty_str.clone())),
8527 None,
8528 cx,
8529 );
8530 buffer.snapshot(cx)
8531 });
8532 let new_selections = new_cursors
8533 .into_iter()
8534 .map(|(id, cursor)| {
8535 let cursor = cursor.to_point(&buffer);
8536 Selection {
8537 id,
8538 start: cursor,
8539 end: cursor,
8540 reversed: false,
8541 goal: SelectionGoal::None,
8542 }
8543 })
8544 .collect();
8545
8546 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8547 s.select(new_selections);
8548 });
8549 });
8550 }
8551
8552 pub fn join_lines_impl(
8553 &mut self,
8554 insert_whitespace: bool,
8555 window: &mut Window,
8556 cx: &mut Context<Self>,
8557 ) {
8558 if self.read_only(cx) {
8559 return;
8560 }
8561 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8562 for selection in self.selections.all::<Point>(cx) {
8563 let start = MultiBufferRow(selection.start.row);
8564 // Treat single line selections as if they include the next line. Otherwise this action
8565 // would do nothing for single line selections individual cursors.
8566 let end = if selection.start.row == selection.end.row {
8567 MultiBufferRow(selection.start.row + 1)
8568 } else {
8569 MultiBufferRow(selection.end.row)
8570 };
8571
8572 if let Some(last_row_range) = row_ranges.last_mut() {
8573 if start <= last_row_range.end {
8574 last_row_range.end = end;
8575 continue;
8576 }
8577 }
8578 row_ranges.push(start..end);
8579 }
8580
8581 let snapshot = self.buffer.read(cx).snapshot(cx);
8582 let mut cursor_positions = Vec::new();
8583 for row_range in &row_ranges {
8584 let anchor = snapshot.anchor_before(Point::new(
8585 row_range.end.previous_row().0,
8586 snapshot.line_len(row_range.end.previous_row()),
8587 ));
8588 cursor_positions.push(anchor..anchor);
8589 }
8590
8591 self.transact(window, cx, |this, window, cx| {
8592 for row_range in row_ranges.into_iter().rev() {
8593 for row in row_range.iter_rows().rev() {
8594 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8595 let next_line_row = row.next_row();
8596 let indent = snapshot.indent_size_for_line(next_line_row);
8597 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8598
8599 let replace =
8600 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8601 " "
8602 } else {
8603 ""
8604 };
8605
8606 this.buffer.update(cx, |buffer, cx| {
8607 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8608 });
8609 }
8610 }
8611
8612 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8613 s.select_anchor_ranges(cursor_positions)
8614 });
8615 });
8616 }
8617
8618 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8619 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8620 self.join_lines_impl(true, window, cx);
8621 }
8622
8623 pub fn sort_lines_case_sensitive(
8624 &mut self,
8625 _: &SortLinesCaseSensitive,
8626 window: &mut Window,
8627 cx: &mut Context<Self>,
8628 ) {
8629 self.manipulate_lines(window, cx, |lines| lines.sort())
8630 }
8631
8632 pub fn sort_lines_case_insensitive(
8633 &mut self,
8634 _: &SortLinesCaseInsensitive,
8635 window: &mut Window,
8636 cx: &mut Context<Self>,
8637 ) {
8638 self.manipulate_lines(window, cx, |lines| {
8639 lines.sort_by_key(|line| line.to_lowercase())
8640 })
8641 }
8642
8643 pub fn unique_lines_case_insensitive(
8644 &mut self,
8645 _: &UniqueLinesCaseInsensitive,
8646 window: &mut Window,
8647 cx: &mut Context<Self>,
8648 ) {
8649 self.manipulate_lines(window, cx, |lines| {
8650 let mut seen = HashSet::default();
8651 lines.retain(|line| seen.insert(line.to_lowercase()));
8652 })
8653 }
8654
8655 pub fn unique_lines_case_sensitive(
8656 &mut self,
8657 _: &UniqueLinesCaseSensitive,
8658 window: &mut Window,
8659 cx: &mut Context<Self>,
8660 ) {
8661 self.manipulate_lines(window, cx, |lines| {
8662 let mut seen = HashSet::default();
8663 lines.retain(|line| seen.insert(*line));
8664 })
8665 }
8666
8667 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8668 let Some(project) = self.project.clone() else {
8669 return;
8670 };
8671 self.reload(project, window, cx)
8672 .detach_and_notify_err(window, cx);
8673 }
8674
8675 pub fn restore_file(
8676 &mut self,
8677 _: &::git::RestoreFile,
8678 window: &mut Window,
8679 cx: &mut Context<Self>,
8680 ) {
8681 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8682 let mut buffer_ids = HashSet::default();
8683 let snapshot = self.buffer().read(cx).snapshot(cx);
8684 for selection in self.selections.all::<usize>(cx) {
8685 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8686 }
8687
8688 let buffer = self.buffer().read(cx);
8689 let ranges = buffer_ids
8690 .into_iter()
8691 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8692 .collect::<Vec<_>>();
8693
8694 self.restore_hunks_in_ranges(ranges, window, cx);
8695 }
8696
8697 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8698 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8699 let selections = self
8700 .selections
8701 .all(cx)
8702 .into_iter()
8703 .map(|s| s.range())
8704 .collect();
8705 self.restore_hunks_in_ranges(selections, window, cx);
8706 }
8707
8708 pub fn restore_hunks_in_ranges(
8709 &mut self,
8710 ranges: Vec<Range<Point>>,
8711 window: &mut Window,
8712 cx: &mut Context<Editor>,
8713 ) {
8714 let mut revert_changes = HashMap::default();
8715 let chunk_by = self
8716 .snapshot(window, cx)
8717 .hunks_for_ranges(ranges)
8718 .into_iter()
8719 .chunk_by(|hunk| hunk.buffer_id);
8720 for (buffer_id, hunks) in &chunk_by {
8721 let hunks = hunks.collect::<Vec<_>>();
8722 for hunk in &hunks {
8723 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8724 }
8725 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8726 }
8727 drop(chunk_by);
8728 if !revert_changes.is_empty() {
8729 self.transact(window, cx, |editor, window, cx| {
8730 editor.restore(revert_changes, window, cx);
8731 });
8732 }
8733 }
8734
8735 pub fn open_active_item_in_terminal(
8736 &mut self,
8737 _: &OpenInTerminal,
8738 window: &mut Window,
8739 cx: &mut Context<Self>,
8740 ) {
8741 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8742 let project_path = buffer.read(cx).project_path(cx)?;
8743 let project = self.project.as_ref()?.read(cx);
8744 let entry = project.entry_for_path(&project_path, cx)?;
8745 let parent = match &entry.canonical_path {
8746 Some(canonical_path) => canonical_path.to_path_buf(),
8747 None => project.absolute_path(&project_path, cx)?,
8748 }
8749 .parent()?
8750 .to_path_buf();
8751 Some(parent)
8752 }) {
8753 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8754 }
8755 }
8756
8757 fn set_breakpoint_context_menu(
8758 &mut self,
8759 display_row: DisplayRow,
8760 position: Option<Anchor>,
8761 clicked_point: gpui::Point<Pixels>,
8762 window: &mut Window,
8763 cx: &mut Context<Self>,
8764 ) {
8765 if !cx.has_flag::<Debugger>() {
8766 return;
8767 }
8768 let source = self
8769 .buffer
8770 .read(cx)
8771 .snapshot(cx)
8772 .anchor_before(Point::new(display_row.0, 0u32));
8773
8774 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8775
8776 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8777 self,
8778 source,
8779 clicked_point,
8780 context_menu,
8781 window,
8782 cx,
8783 );
8784 }
8785
8786 fn add_edit_breakpoint_block(
8787 &mut self,
8788 anchor: Anchor,
8789 breakpoint: &Breakpoint,
8790 edit_action: BreakpointPromptEditAction,
8791 window: &mut Window,
8792 cx: &mut Context<Self>,
8793 ) {
8794 let weak_editor = cx.weak_entity();
8795 let bp_prompt = cx.new(|cx| {
8796 BreakpointPromptEditor::new(
8797 weak_editor,
8798 anchor,
8799 breakpoint.clone(),
8800 edit_action,
8801 window,
8802 cx,
8803 )
8804 });
8805
8806 let height = bp_prompt.update(cx, |this, cx| {
8807 this.prompt
8808 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8809 });
8810 let cloned_prompt = bp_prompt.clone();
8811 let blocks = vec![BlockProperties {
8812 style: BlockStyle::Sticky,
8813 placement: BlockPlacement::Above(anchor),
8814 height: Some(height),
8815 render: Arc::new(move |cx| {
8816 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8817 cloned_prompt.clone().into_any_element()
8818 }),
8819 priority: 0,
8820 }];
8821
8822 let focus_handle = bp_prompt.focus_handle(cx);
8823 window.focus(&focus_handle);
8824
8825 let block_ids = self.insert_blocks(blocks, None, cx);
8826 bp_prompt.update(cx, |prompt, _| {
8827 prompt.add_block_ids(block_ids);
8828 });
8829 }
8830
8831 fn breakpoint_at_cursor_head(
8832 &self,
8833 window: &mut Window,
8834 cx: &mut Context<Self>,
8835 ) -> Option<(Anchor, Breakpoint)> {
8836 let cursor_position: Point = self.selections.newest(cx).head();
8837 self.breakpoint_at_row(cursor_position.row, window, cx)
8838 }
8839
8840 pub(crate) fn breakpoint_at_row(
8841 &self,
8842 row: u32,
8843 window: &mut Window,
8844 cx: &mut Context<Self>,
8845 ) -> Option<(Anchor, Breakpoint)> {
8846 let snapshot = self.snapshot(window, cx);
8847 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
8848
8849 let project = self.project.clone()?;
8850
8851 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
8852 snapshot
8853 .buffer_snapshot
8854 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
8855 })?;
8856
8857 let enclosing_excerpt = breakpoint_position.excerpt_id;
8858 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8859 let buffer_snapshot = buffer.read(cx).snapshot();
8860
8861 let row = buffer_snapshot
8862 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
8863 .row;
8864
8865 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
8866 let anchor_end = snapshot
8867 .buffer_snapshot
8868 .anchor_after(Point::new(row, line_len));
8869
8870 let bp = self
8871 .breakpoint_store
8872 .as_ref()?
8873 .read_with(cx, |breakpoint_store, cx| {
8874 breakpoint_store
8875 .breakpoints(
8876 &buffer,
8877 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
8878 &buffer_snapshot,
8879 cx,
8880 )
8881 .next()
8882 .and_then(|(anchor, bp)| {
8883 let breakpoint_row = buffer_snapshot
8884 .summary_for_anchor::<text::PointUtf16>(anchor)
8885 .row;
8886
8887 if breakpoint_row == row {
8888 snapshot
8889 .buffer_snapshot
8890 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8891 .map(|anchor| (anchor, bp.clone()))
8892 } else {
8893 None
8894 }
8895 })
8896 });
8897 bp
8898 }
8899
8900 pub fn edit_log_breakpoint(
8901 &mut self,
8902 _: &EditLogBreakpoint,
8903 window: &mut Window,
8904 cx: &mut Context<Self>,
8905 ) {
8906 let (anchor, bp) = self
8907 .breakpoint_at_cursor_head(window, cx)
8908 .unwrap_or_else(|| {
8909 let cursor_position: Point = self.selections.newest(cx).head();
8910
8911 let breakpoint_position = self
8912 .snapshot(window, cx)
8913 .display_snapshot
8914 .buffer_snapshot
8915 .anchor_after(Point::new(cursor_position.row, 0));
8916
8917 (
8918 breakpoint_position,
8919 Breakpoint {
8920 message: None,
8921 state: BreakpointState::Enabled,
8922 condition: None,
8923 hit_condition: None,
8924 },
8925 )
8926 });
8927
8928 self.add_edit_breakpoint_block(anchor, &bp, BreakpointPromptEditAction::Log, window, cx);
8929 }
8930
8931 pub fn enable_breakpoint(
8932 &mut self,
8933 _: &crate::actions::EnableBreakpoint,
8934 window: &mut Window,
8935 cx: &mut Context<Self>,
8936 ) {
8937 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8938 if breakpoint.is_disabled() {
8939 self.edit_breakpoint_at_anchor(
8940 anchor,
8941 breakpoint,
8942 BreakpointEditAction::InvertState,
8943 cx,
8944 );
8945 }
8946 }
8947 }
8948
8949 pub fn disable_breakpoint(
8950 &mut self,
8951 _: &crate::actions::DisableBreakpoint,
8952 window: &mut Window,
8953 cx: &mut Context<Self>,
8954 ) {
8955 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8956 if breakpoint.is_enabled() {
8957 self.edit_breakpoint_at_anchor(
8958 anchor,
8959 breakpoint,
8960 BreakpointEditAction::InvertState,
8961 cx,
8962 );
8963 }
8964 }
8965 }
8966
8967 pub fn toggle_breakpoint(
8968 &mut self,
8969 _: &crate::actions::ToggleBreakpoint,
8970 window: &mut Window,
8971 cx: &mut Context<Self>,
8972 ) {
8973 let edit_action = BreakpointEditAction::Toggle;
8974
8975 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8976 self.edit_breakpoint_at_anchor(anchor, breakpoint, edit_action, cx);
8977 } else {
8978 let cursor_position: Point = self.selections.newest(cx).head();
8979
8980 let breakpoint_position = self
8981 .snapshot(window, cx)
8982 .display_snapshot
8983 .buffer_snapshot
8984 .anchor_after(Point::new(cursor_position.row, 0));
8985
8986 self.edit_breakpoint_at_anchor(
8987 breakpoint_position,
8988 Breakpoint::new_standard(),
8989 edit_action,
8990 cx,
8991 );
8992 }
8993 }
8994
8995 pub fn edit_breakpoint_at_anchor(
8996 &mut self,
8997 breakpoint_position: Anchor,
8998 breakpoint: Breakpoint,
8999 edit_action: BreakpointEditAction,
9000 cx: &mut Context<Self>,
9001 ) {
9002 let Some(breakpoint_store) = &self.breakpoint_store else {
9003 return;
9004 };
9005
9006 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9007 if breakpoint_position == Anchor::min() {
9008 self.buffer()
9009 .read(cx)
9010 .excerpt_buffer_ids()
9011 .into_iter()
9012 .next()
9013 } else {
9014 None
9015 }
9016 }) else {
9017 return;
9018 };
9019
9020 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9021 return;
9022 };
9023
9024 breakpoint_store.update(cx, |breakpoint_store, cx| {
9025 breakpoint_store.toggle_breakpoint(
9026 buffer,
9027 (breakpoint_position.text_anchor, breakpoint),
9028 edit_action,
9029 cx,
9030 );
9031 });
9032
9033 cx.notify();
9034 }
9035
9036 #[cfg(any(test, feature = "test-support"))]
9037 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9038 self.breakpoint_store.clone()
9039 }
9040
9041 pub fn prepare_restore_change(
9042 &self,
9043 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9044 hunk: &MultiBufferDiffHunk,
9045 cx: &mut App,
9046 ) -> Option<()> {
9047 if hunk.is_created_file() {
9048 return None;
9049 }
9050 let buffer = self.buffer.read(cx);
9051 let diff = buffer.diff_for(hunk.buffer_id)?;
9052 let buffer = buffer.buffer(hunk.buffer_id)?;
9053 let buffer = buffer.read(cx);
9054 let original_text = diff
9055 .read(cx)
9056 .base_text()
9057 .as_rope()
9058 .slice(hunk.diff_base_byte_range.clone());
9059 let buffer_snapshot = buffer.snapshot();
9060 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9061 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9062 probe
9063 .0
9064 .start
9065 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9066 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9067 }) {
9068 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9069 Some(())
9070 } else {
9071 None
9072 }
9073 }
9074
9075 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9076 self.manipulate_lines(window, cx, |lines| lines.reverse())
9077 }
9078
9079 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9080 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9081 }
9082
9083 fn manipulate_lines<Fn>(
9084 &mut self,
9085 window: &mut Window,
9086 cx: &mut Context<Self>,
9087 mut callback: Fn,
9088 ) where
9089 Fn: FnMut(&mut Vec<&str>),
9090 {
9091 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9092
9093 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9094 let buffer = self.buffer.read(cx).snapshot(cx);
9095
9096 let mut edits = Vec::new();
9097
9098 let selections = self.selections.all::<Point>(cx);
9099 let mut selections = selections.iter().peekable();
9100 let mut contiguous_row_selections = Vec::new();
9101 let mut new_selections = Vec::new();
9102 let mut added_lines = 0;
9103 let mut removed_lines = 0;
9104
9105 while let Some(selection) = selections.next() {
9106 let (start_row, end_row) = consume_contiguous_rows(
9107 &mut contiguous_row_selections,
9108 selection,
9109 &display_map,
9110 &mut selections,
9111 );
9112
9113 let start_point = Point::new(start_row.0, 0);
9114 let end_point = Point::new(
9115 end_row.previous_row().0,
9116 buffer.line_len(end_row.previous_row()),
9117 );
9118 let text = buffer
9119 .text_for_range(start_point..end_point)
9120 .collect::<String>();
9121
9122 let mut lines = text.split('\n').collect_vec();
9123
9124 let lines_before = lines.len();
9125 callback(&mut lines);
9126 let lines_after = lines.len();
9127
9128 edits.push((start_point..end_point, lines.join("\n")));
9129
9130 // Selections must change based on added and removed line count
9131 let start_row =
9132 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9133 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9134 new_selections.push(Selection {
9135 id: selection.id,
9136 start: start_row,
9137 end: end_row,
9138 goal: SelectionGoal::None,
9139 reversed: selection.reversed,
9140 });
9141
9142 if lines_after > lines_before {
9143 added_lines += lines_after - lines_before;
9144 } else if lines_before > lines_after {
9145 removed_lines += lines_before - lines_after;
9146 }
9147 }
9148
9149 self.transact(window, cx, |this, window, cx| {
9150 let buffer = this.buffer.update(cx, |buffer, cx| {
9151 buffer.edit(edits, None, cx);
9152 buffer.snapshot(cx)
9153 });
9154
9155 // Recalculate offsets on newly edited buffer
9156 let new_selections = new_selections
9157 .iter()
9158 .map(|s| {
9159 let start_point = Point::new(s.start.0, 0);
9160 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9161 Selection {
9162 id: s.id,
9163 start: buffer.point_to_offset(start_point),
9164 end: buffer.point_to_offset(end_point),
9165 goal: s.goal,
9166 reversed: s.reversed,
9167 }
9168 })
9169 .collect();
9170
9171 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9172 s.select(new_selections);
9173 });
9174
9175 this.request_autoscroll(Autoscroll::fit(), cx);
9176 });
9177 }
9178
9179 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9180 self.manipulate_text(window, cx, |text| {
9181 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9182 if has_upper_case_characters {
9183 text.to_lowercase()
9184 } else {
9185 text.to_uppercase()
9186 }
9187 })
9188 }
9189
9190 pub fn convert_to_upper_case(
9191 &mut self,
9192 _: &ConvertToUpperCase,
9193 window: &mut Window,
9194 cx: &mut Context<Self>,
9195 ) {
9196 self.manipulate_text(window, cx, |text| text.to_uppercase())
9197 }
9198
9199 pub fn convert_to_lower_case(
9200 &mut self,
9201 _: &ConvertToLowerCase,
9202 window: &mut Window,
9203 cx: &mut Context<Self>,
9204 ) {
9205 self.manipulate_text(window, cx, |text| text.to_lowercase())
9206 }
9207
9208 pub fn convert_to_title_case(
9209 &mut self,
9210 _: &ConvertToTitleCase,
9211 window: &mut Window,
9212 cx: &mut Context<Self>,
9213 ) {
9214 self.manipulate_text(window, cx, |text| {
9215 text.split('\n')
9216 .map(|line| line.to_case(Case::Title))
9217 .join("\n")
9218 })
9219 }
9220
9221 pub fn convert_to_snake_case(
9222 &mut self,
9223 _: &ConvertToSnakeCase,
9224 window: &mut Window,
9225 cx: &mut Context<Self>,
9226 ) {
9227 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9228 }
9229
9230 pub fn convert_to_kebab_case(
9231 &mut self,
9232 _: &ConvertToKebabCase,
9233 window: &mut Window,
9234 cx: &mut Context<Self>,
9235 ) {
9236 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9237 }
9238
9239 pub fn convert_to_upper_camel_case(
9240 &mut self,
9241 _: &ConvertToUpperCamelCase,
9242 window: &mut Window,
9243 cx: &mut Context<Self>,
9244 ) {
9245 self.manipulate_text(window, cx, |text| {
9246 text.split('\n')
9247 .map(|line| line.to_case(Case::UpperCamel))
9248 .join("\n")
9249 })
9250 }
9251
9252 pub fn convert_to_lower_camel_case(
9253 &mut self,
9254 _: &ConvertToLowerCamelCase,
9255 window: &mut Window,
9256 cx: &mut Context<Self>,
9257 ) {
9258 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9259 }
9260
9261 pub fn convert_to_opposite_case(
9262 &mut self,
9263 _: &ConvertToOppositeCase,
9264 window: &mut Window,
9265 cx: &mut Context<Self>,
9266 ) {
9267 self.manipulate_text(window, cx, |text| {
9268 text.chars()
9269 .fold(String::with_capacity(text.len()), |mut t, c| {
9270 if c.is_uppercase() {
9271 t.extend(c.to_lowercase());
9272 } else {
9273 t.extend(c.to_uppercase());
9274 }
9275 t
9276 })
9277 })
9278 }
9279
9280 pub fn convert_to_rot13(
9281 &mut self,
9282 _: &ConvertToRot13,
9283 window: &mut Window,
9284 cx: &mut Context<Self>,
9285 ) {
9286 self.manipulate_text(window, cx, |text| {
9287 text.chars()
9288 .map(|c| match c {
9289 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9290 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9291 _ => c,
9292 })
9293 .collect()
9294 })
9295 }
9296
9297 pub fn convert_to_rot47(
9298 &mut self,
9299 _: &ConvertToRot47,
9300 window: &mut Window,
9301 cx: &mut Context<Self>,
9302 ) {
9303 self.manipulate_text(window, cx, |text| {
9304 text.chars()
9305 .map(|c| {
9306 let code_point = c as u32;
9307 if code_point >= 33 && code_point <= 126 {
9308 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9309 }
9310 c
9311 })
9312 .collect()
9313 })
9314 }
9315
9316 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9317 where
9318 Fn: FnMut(&str) -> String,
9319 {
9320 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9321 let buffer = self.buffer.read(cx).snapshot(cx);
9322
9323 let mut new_selections = Vec::new();
9324 let mut edits = Vec::new();
9325 let mut selection_adjustment = 0i32;
9326
9327 for selection in self.selections.all::<usize>(cx) {
9328 let selection_is_empty = selection.is_empty();
9329
9330 let (start, end) = if selection_is_empty {
9331 let word_range = movement::surrounding_word(
9332 &display_map,
9333 selection.start.to_display_point(&display_map),
9334 );
9335 let start = word_range.start.to_offset(&display_map, Bias::Left);
9336 let end = word_range.end.to_offset(&display_map, Bias::Left);
9337 (start, end)
9338 } else {
9339 (selection.start, selection.end)
9340 };
9341
9342 let text = buffer.text_for_range(start..end).collect::<String>();
9343 let old_length = text.len() as i32;
9344 let text = callback(&text);
9345
9346 new_selections.push(Selection {
9347 start: (start as i32 - selection_adjustment) as usize,
9348 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9349 goal: SelectionGoal::None,
9350 ..selection
9351 });
9352
9353 selection_adjustment += old_length - text.len() as i32;
9354
9355 edits.push((start..end, text));
9356 }
9357
9358 self.transact(window, cx, |this, window, cx| {
9359 this.buffer.update(cx, |buffer, cx| {
9360 buffer.edit(edits, None, cx);
9361 });
9362
9363 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9364 s.select(new_selections);
9365 });
9366
9367 this.request_autoscroll(Autoscroll::fit(), cx);
9368 });
9369 }
9370
9371 pub fn duplicate(
9372 &mut self,
9373 upwards: bool,
9374 whole_lines: bool,
9375 window: &mut Window,
9376 cx: &mut Context<Self>,
9377 ) {
9378 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9379
9380 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9381 let buffer = &display_map.buffer_snapshot;
9382 let selections = self.selections.all::<Point>(cx);
9383
9384 let mut edits = Vec::new();
9385 let mut selections_iter = selections.iter().peekable();
9386 while let Some(selection) = selections_iter.next() {
9387 let mut rows = selection.spanned_rows(false, &display_map);
9388 // duplicate line-wise
9389 if whole_lines || selection.start == selection.end {
9390 // Avoid duplicating the same lines twice.
9391 while let Some(next_selection) = selections_iter.peek() {
9392 let next_rows = next_selection.spanned_rows(false, &display_map);
9393 if next_rows.start < rows.end {
9394 rows.end = next_rows.end;
9395 selections_iter.next().unwrap();
9396 } else {
9397 break;
9398 }
9399 }
9400
9401 // Copy the text from the selected row region and splice it either at the start
9402 // or end of the region.
9403 let start = Point::new(rows.start.0, 0);
9404 let end = Point::new(
9405 rows.end.previous_row().0,
9406 buffer.line_len(rows.end.previous_row()),
9407 );
9408 let text = buffer
9409 .text_for_range(start..end)
9410 .chain(Some("\n"))
9411 .collect::<String>();
9412 let insert_location = if upwards {
9413 Point::new(rows.end.0, 0)
9414 } else {
9415 start
9416 };
9417 edits.push((insert_location..insert_location, text));
9418 } else {
9419 // duplicate character-wise
9420 let start = selection.start;
9421 let end = selection.end;
9422 let text = buffer.text_for_range(start..end).collect::<String>();
9423 edits.push((selection.end..selection.end, text));
9424 }
9425 }
9426
9427 self.transact(window, cx, |this, _, cx| {
9428 this.buffer.update(cx, |buffer, cx| {
9429 buffer.edit(edits, None, cx);
9430 });
9431
9432 this.request_autoscroll(Autoscroll::fit(), cx);
9433 });
9434 }
9435
9436 pub fn duplicate_line_up(
9437 &mut self,
9438 _: &DuplicateLineUp,
9439 window: &mut Window,
9440 cx: &mut Context<Self>,
9441 ) {
9442 self.duplicate(true, true, window, cx);
9443 }
9444
9445 pub fn duplicate_line_down(
9446 &mut self,
9447 _: &DuplicateLineDown,
9448 window: &mut Window,
9449 cx: &mut Context<Self>,
9450 ) {
9451 self.duplicate(false, true, window, cx);
9452 }
9453
9454 pub fn duplicate_selection(
9455 &mut self,
9456 _: &DuplicateSelection,
9457 window: &mut Window,
9458 cx: &mut Context<Self>,
9459 ) {
9460 self.duplicate(false, false, window, cx);
9461 }
9462
9463 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9464 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9465
9466 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9467 let buffer = self.buffer.read(cx).snapshot(cx);
9468
9469 let mut edits = Vec::new();
9470 let mut unfold_ranges = Vec::new();
9471 let mut refold_creases = Vec::new();
9472
9473 let selections = self.selections.all::<Point>(cx);
9474 let mut selections = selections.iter().peekable();
9475 let mut contiguous_row_selections = Vec::new();
9476 let mut new_selections = Vec::new();
9477
9478 while let Some(selection) = selections.next() {
9479 // Find all the selections that span a contiguous row range
9480 let (start_row, end_row) = consume_contiguous_rows(
9481 &mut contiguous_row_selections,
9482 selection,
9483 &display_map,
9484 &mut selections,
9485 );
9486
9487 // Move the text spanned by the row range to be before the line preceding the row range
9488 if start_row.0 > 0 {
9489 let range_to_move = Point::new(
9490 start_row.previous_row().0,
9491 buffer.line_len(start_row.previous_row()),
9492 )
9493 ..Point::new(
9494 end_row.previous_row().0,
9495 buffer.line_len(end_row.previous_row()),
9496 );
9497 let insertion_point = display_map
9498 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9499 .0;
9500
9501 // Don't move lines across excerpts
9502 if buffer
9503 .excerpt_containing(insertion_point..range_to_move.end)
9504 .is_some()
9505 {
9506 let text = buffer
9507 .text_for_range(range_to_move.clone())
9508 .flat_map(|s| s.chars())
9509 .skip(1)
9510 .chain(['\n'])
9511 .collect::<String>();
9512
9513 edits.push((
9514 buffer.anchor_after(range_to_move.start)
9515 ..buffer.anchor_before(range_to_move.end),
9516 String::new(),
9517 ));
9518 let insertion_anchor = buffer.anchor_after(insertion_point);
9519 edits.push((insertion_anchor..insertion_anchor, text));
9520
9521 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9522
9523 // Move selections up
9524 new_selections.extend(contiguous_row_selections.drain(..).map(
9525 |mut selection| {
9526 selection.start.row -= row_delta;
9527 selection.end.row -= row_delta;
9528 selection
9529 },
9530 ));
9531
9532 // Move folds up
9533 unfold_ranges.push(range_to_move.clone());
9534 for fold in display_map.folds_in_range(
9535 buffer.anchor_before(range_to_move.start)
9536 ..buffer.anchor_after(range_to_move.end),
9537 ) {
9538 let mut start = fold.range.start.to_point(&buffer);
9539 let mut end = fold.range.end.to_point(&buffer);
9540 start.row -= row_delta;
9541 end.row -= row_delta;
9542 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9543 }
9544 }
9545 }
9546
9547 // If we didn't move line(s), preserve the existing selections
9548 new_selections.append(&mut contiguous_row_selections);
9549 }
9550
9551 self.transact(window, cx, |this, window, cx| {
9552 this.unfold_ranges(&unfold_ranges, true, true, cx);
9553 this.buffer.update(cx, |buffer, cx| {
9554 for (range, text) in edits {
9555 buffer.edit([(range, text)], None, cx);
9556 }
9557 });
9558 this.fold_creases(refold_creases, true, window, cx);
9559 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9560 s.select(new_selections);
9561 })
9562 });
9563 }
9564
9565 pub fn move_line_down(
9566 &mut self,
9567 _: &MoveLineDown,
9568 window: &mut Window,
9569 cx: &mut Context<Self>,
9570 ) {
9571 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9572
9573 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9574 let buffer = self.buffer.read(cx).snapshot(cx);
9575
9576 let mut edits = Vec::new();
9577 let mut unfold_ranges = Vec::new();
9578 let mut refold_creases = Vec::new();
9579
9580 let selections = self.selections.all::<Point>(cx);
9581 let mut selections = selections.iter().peekable();
9582 let mut contiguous_row_selections = Vec::new();
9583 let mut new_selections = Vec::new();
9584
9585 while let Some(selection) = selections.next() {
9586 // Find all the selections that span a contiguous row range
9587 let (start_row, end_row) = consume_contiguous_rows(
9588 &mut contiguous_row_selections,
9589 selection,
9590 &display_map,
9591 &mut selections,
9592 );
9593
9594 // Move the text spanned by the row range to be after the last line of the row range
9595 if end_row.0 <= buffer.max_point().row {
9596 let range_to_move =
9597 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9598 let insertion_point = display_map
9599 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9600 .0;
9601
9602 // Don't move lines across excerpt boundaries
9603 if buffer
9604 .excerpt_containing(range_to_move.start..insertion_point)
9605 .is_some()
9606 {
9607 let mut text = String::from("\n");
9608 text.extend(buffer.text_for_range(range_to_move.clone()));
9609 text.pop(); // Drop trailing newline
9610 edits.push((
9611 buffer.anchor_after(range_to_move.start)
9612 ..buffer.anchor_before(range_to_move.end),
9613 String::new(),
9614 ));
9615 let insertion_anchor = buffer.anchor_after(insertion_point);
9616 edits.push((insertion_anchor..insertion_anchor, text));
9617
9618 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9619
9620 // Move selections down
9621 new_selections.extend(contiguous_row_selections.drain(..).map(
9622 |mut selection| {
9623 selection.start.row += row_delta;
9624 selection.end.row += row_delta;
9625 selection
9626 },
9627 ));
9628
9629 // Move folds down
9630 unfold_ranges.push(range_to_move.clone());
9631 for fold in display_map.folds_in_range(
9632 buffer.anchor_before(range_to_move.start)
9633 ..buffer.anchor_after(range_to_move.end),
9634 ) {
9635 let mut start = fold.range.start.to_point(&buffer);
9636 let mut end = fold.range.end.to_point(&buffer);
9637 start.row += row_delta;
9638 end.row += row_delta;
9639 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9640 }
9641 }
9642 }
9643
9644 // If we didn't move line(s), preserve the existing selections
9645 new_selections.append(&mut contiguous_row_selections);
9646 }
9647
9648 self.transact(window, cx, |this, window, cx| {
9649 this.unfold_ranges(&unfold_ranges, true, true, cx);
9650 this.buffer.update(cx, |buffer, cx| {
9651 for (range, text) in edits {
9652 buffer.edit([(range, text)], None, cx);
9653 }
9654 });
9655 this.fold_creases(refold_creases, true, window, cx);
9656 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9657 s.select(new_selections)
9658 });
9659 });
9660 }
9661
9662 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9663 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9664 let text_layout_details = &self.text_layout_details(window);
9665 self.transact(window, cx, |this, window, cx| {
9666 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9667 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9668 s.move_with(|display_map, selection| {
9669 if !selection.is_empty() {
9670 return;
9671 }
9672
9673 let mut head = selection.head();
9674 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9675 if head.column() == display_map.line_len(head.row()) {
9676 transpose_offset = display_map
9677 .buffer_snapshot
9678 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9679 }
9680
9681 if transpose_offset == 0 {
9682 return;
9683 }
9684
9685 *head.column_mut() += 1;
9686 head = display_map.clip_point(head, Bias::Right);
9687 let goal = SelectionGoal::HorizontalPosition(
9688 display_map
9689 .x_for_display_point(head, text_layout_details)
9690 .into(),
9691 );
9692 selection.collapse_to(head, goal);
9693
9694 let transpose_start = display_map
9695 .buffer_snapshot
9696 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9697 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9698 let transpose_end = display_map
9699 .buffer_snapshot
9700 .clip_offset(transpose_offset + 1, Bias::Right);
9701 if let Some(ch) =
9702 display_map.buffer_snapshot.chars_at(transpose_start).next()
9703 {
9704 edits.push((transpose_start..transpose_offset, String::new()));
9705 edits.push((transpose_end..transpose_end, ch.to_string()));
9706 }
9707 }
9708 });
9709 edits
9710 });
9711 this.buffer
9712 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9713 let selections = this.selections.all::<usize>(cx);
9714 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9715 s.select(selections);
9716 });
9717 });
9718 }
9719
9720 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9721 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9722 self.rewrap_impl(RewrapOptions::default(), cx)
9723 }
9724
9725 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9726 let buffer = self.buffer.read(cx).snapshot(cx);
9727 let selections = self.selections.all::<Point>(cx);
9728 let mut selections = selections.iter().peekable();
9729
9730 let mut edits = Vec::new();
9731 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9732
9733 while let Some(selection) = selections.next() {
9734 let mut start_row = selection.start.row;
9735 let mut end_row = selection.end.row;
9736
9737 // Skip selections that overlap with a range that has already been rewrapped.
9738 let selection_range = start_row..end_row;
9739 if rewrapped_row_ranges
9740 .iter()
9741 .any(|range| range.overlaps(&selection_range))
9742 {
9743 continue;
9744 }
9745
9746 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9747
9748 // Since not all lines in the selection may be at the same indent
9749 // level, choose the indent size that is the most common between all
9750 // of the lines.
9751 //
9752 // If there is a tie, we use the deepest indent.
9753 let (indent_size, indent_end) = {
9754 let mut indent_size_occurrences = HashMap::default();
9755 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9756
9757 for row in start_row..=end_row {
9758 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9759 rows_by_indent_size.entry(indent).or_default().push(row);
9760 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9761 }
9762
9763 let indent_size = indent_size_occurrences
9764 .into_iter()
9765 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9766 .map(|(indent, _)| indent)
9767 .unwrap_or_default();
9768 let row = rows_by_indent_size[&indent_size][0];
9769 let indent_end = Point::new(row, indent_size.len);
9770
9771 (indent_size, indent_end)
9772 };
9773
9774 let mut line_prefix = indent_size.chars().collect::<String>();
9775
9776 let mut inside_comment = false;
9777 if let Some(comment_prefix) =
9778 buffer
9779 .language_scope_at(selection.head())
9780 .and_then(|language| {
9781 language
9782 .line_comment_prefixes()
9783 .iter()
9784 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9785 .cloned()
9786 })
9787 {
9788 line_prefix.push_str(&comment_prefix);
9789 inside_comment = true;
9790 }
9791
9792 let language_settings = buffer.language_settings_at(selection.head(), cx);
9793 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9794 RewrapBehavior::InComments => inside_comment,
9795 RewrapBehavior::InSelections => !selection.is_empty(),
9796 RewrapBehavior::Anywhere => true,
9797 };
9798
9799 let should_rewrap = options.override_language_settings
9800 || allow_rewrap_based_on_language
9801 || self.hard_wrap.is_some();
9802 if !should_rewrap {
9803 continue;
9804 }
9805
9806 if selection.is_empty() {
9807 'expand_upwards: while start_row > 0 {
9808 let prev_row = start_row - 1;
9809 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9810 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9811 {
9812 start_row = prev_row;
9813 } else {
9814 break 'expand_upwards;
9815 }
9816 }
9817
9818 'expand_downwards: while end_row < buffer.max_point().row {
9819 let next_row = end_row + 1;
9820 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9821 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9822 {
9823 end_row = next_row;
9824 } else {
9825 break 'expand_downwards;
9826 }
9827 }
9828 }
9829
9830 let start = Point::new(start_row, 0);
9831 let start_offset = start.to_offset(&buffer);
9832 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9833 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9834 let Some(lines_without_prefixes) = selection_text
9835 .lines()
9836 .map(|line| {
9837 line.strip_prefix(&line_prefix)
9838 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9839 .ok_or_else(|| {
9840 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9841 })
9842 })
9843 .collect::<Result<Vec<_>, _>>()
9844 .log_err()
9845 else {
9846 continue;
9847 };
9848
9849 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9850 buffer
9851 .language_settings_at(Point::new(start_row, 0), cx)
9852 .preferred_line_length as usize
9853 });
9854 let wrapped_text = wrap_with_prefix(
9855 line_prefix,
9856 lines_without_prefixes.join("\n"),
9857 wrap_column,
9858 tab_size,
9859 options.preserve_existing_whitespace,
9860 );
9861
9862 // TODO: should always use char-based diff while still supporting cursor behavior that
9863 // matches vim.
9864 let mut diff_options = DiffOptions::default();
9865 if options.override_language_settings {
9866 diff_options.max_word_diff_len = 0;
9867 diff_options.max_word_diff_line_count = 0;
9868 } else {
9869 diff_options.max_word_diff_len = usize::MAX;
9870 diff_options.max_word_diff_line_count = usize::MAX;
9871 }
9872
9873 for (old_range, new_text) in
9874 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9875 {
9876 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9877 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9878 edits.push((edit_start..edit_end, new_text));
9879 }
9880
9881 rewrapped_row_ranges.push(start_row..=end_row);
9882 }
9883
9884 self.buffer
9885 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9886 }
9887
9888 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9889 let mut text = String::new();
9890 let buffer = self.buffer.read(cx).snapshot(cx);
9891 let mut selections = self.selections.all::<Point>(cx);
9892 let mut clipboard_selections = Vec::with_capacity(selections.len());
9893 {
9894 let max_point = buffer.max_point();
9895 let mut is_first = true;
9896 for selection in &mut selections {
9897 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9898 if is_entire_line {
9899 selection.start = Point::new(selection.start.row, 0);
9900 if !selection.is_empty() && selection.end.column == 0 {
9901 selection.end = cmp::min(max_point, selection.end);
9902 } else {
9903 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9904 }
9905 selection.goal = SelectionGoal::None;
9906 }
9907 if is_first {
9908 is_first = false;
9909 } else {
9910 text += "\n";
9911 }
9912 let mut len = 0;
9913 for chunk in buffer.text_for_range(selection.start..selection.end) {
9914 text.push_str(chunk);
9915 len += chunk.len();
9916 }
9917 clipboard_selections.push(ClipboardSelection {
9918 len,
9919 is_entire_line,
9920 first_line_indent: buffer
9921 .indent_size_for_line(MultiBufferRow(selection.start.row))
9922 .len,
9923 });
9924 }
9925 }
9926
9927 self.transact(window, cx, |this, window, cx| {
9928 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9929 s.select(selections);
9930 });
9931 this.insert("", window, cx);
9932 });
9933 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9934 }
9935
9936 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9937 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9938 let item = self.cut_common(window, cx);
9939 cx.write_to_clipboard(item);
9940 }
9941
9942 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9943 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9944 self.change_selections(None, window, cx, |s| {
9945 s.move_with(|snapshot, sel| {
9946 if sel.is_empty() {
9947 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9948 }
9949 });
9950 });
9951 let item = self.cut_common(window, cx);
9952 cx.set_global(KillRing(item))
9953 }
9954
9955 pub fn kill_ring_yank(
9956 &mut self,
9957 _: &KillRingYank,
9958 window: &mut Window,
9959 cx: &mut Context<Self>,
9960 ) {
9961 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9962 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9963 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9964 (kill_ring.text().to_string(), kill_ring.metadata_json())
9965 } else {
9966 return;
9967 }
9968 } else {
9969 return;
9970 };
9971 self.do_paste(&text, metadata, false, window, cx);
9972 }
9973
9974 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9975 self.do_copy(true, cx);
9976 }
9977
9978 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9979 self.do_copy(false, cx);
9980 }
9981
9982 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9983 let selections = self.selections.all::<Point>(cx);
9984 let buffer = self.buffer.read(cx).read(cx);
9985 let mut text = String::new();
9986
9987 let mut clipboard_selections = Vec::with_capacity(selections.len());
9988 {
9989 let max_point = buffer.max_point();
9990 let mut is_first = true;
9991 for selection in &selections {
9992 let mut start = selection.start;
9993 let mut end = selection.end;
9994 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9995 if is_entire_line {
9996 start = Point::new(start.row, 0);
9997 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9998 }
9999
10000 let mut trimmed_selections = Vec::new();
10001 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10002 let row = MultiBufferRow(start.row);
10003 let first_indent = buffer.indent_size_for_line(row);
10004 if first_indent.len == 0 || start.column > first_indent.len {
10005 trimmed_selections.push(start..end);
10006 } else {
10007 trimmed_selections.push(
10008 Point::new(row.0, first_indent.len)
10009 ..Point::new(row.0, buffer.line_len(row)),
10010 );
10011 for row in start.row + 1..=end.row {
10012 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10013 if row_indent_size.len >= first_indent.len {
10014 trimmed_selections.push(
10015 Point::new(row, first_indent.len)
10016 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
10017 );
10018 } else {
10019 trimmed_selections.clear();
10020 trimmed_selections.push(start..end);
10021 break;
10022 }
10023 }
10024 }
10025 } else {
10026 trimmed_selections.push(start..end);
10027 }
10028
10029 for trimmed_range in trimmed_selections {
10030 if is_first {
10031 is_first = false;
10032 } else {
10033 text += "\n";
10034 }
10035 let mut len = 0;
10036 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10037 text.push_str(chunk);
10038 len += chunk.len();
10039 }
10040 clipboard_selections.push(ClipboardSelection {
10041 len,
10042 is_entire_line,
10043 first_line_indent: buffer
10044 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10045 .len,
10046 });
10047 }
10048 }
10049 }
10050
10051 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10052 text,
10053 clipboard_selections,
10054 ));
10055 }
10056
10057 pub fn do_paste(
10058 &mut self,
10059 text: &String,
10060 clipboard_selections: Option<Vec<ClipboardSelection>>,
10061 handle_entire_lines: bool,
10062 window: &mut Window,
10063 cx: &mut Context<Self>,
10064 ) {
10065 if self.read_only(cx) {
10066 return;
10067 }
10068
10069 let clipboard_text = Cow::Borrowed(text);
10070
10071 self.transact(window, cx, |this, window, cx| {
10072 if let Some(mut clipboard_selections) = clipboard_selections {
10073 let old_selections = this.selections.all::<usize>(cx);
10074 let all_selections_were_entire_line =
10075 clipboard_selections.iter().all(|s| s.is_entire_line);
10076 let first_selection_indent_column =
10077 clipboard_selections.first().map(|s| s.first_line_indent);
10078 if clipboard_selections.len() != old_selections.len() {
10079 clipboard_selections.drain(..);
10080 }
10081 let cursor_offset = this.selections.last::<usize>(cx).head();
10082 let mut auto_indent_on_paste = true;
10083
10084 this.buffer.update(cx, |buffer, cx| {
10085 let snapshot = buffer.read(cx);
10086 auto_indent_on_paste = snapshot
10087 .language_settings_at(cursor_offset, cx)
10088 .auto_indent_on_paste;
10089
10090 let mut start_offset = 0;
10091 let mut edits = Vec::new();
10092 let mut original_indent_columns = Vec::new();
10093 for (ix, selection) in old_selections.iter().enumerate() {
10094 let to_insert;
10095 let entire_line;
10096 let original_indent_column;
10097 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10098 let end_offset = start_offset + clipboard_selection.len;
10099 to_insert = &clipboard_text[start_offset..end_offset];
10100 entire_line = clipboard_selection.is_entire_line;
10101 start_offset = end_offset + 1;
10102 original_indent_column = Some(clipboard_selection.first_line_indent);
10103 } else {
10104 to_insert = clipboard_text.as_str();
10105 entire_line = all_selections_were_entire_line;
10106 original_indent_column = first_selection_indent_column
10107 }
10108
10109 // If the corresponding selection was empty when this slice of the
10110 // clipboard text was written, then the entire line containing the
10111 // selection was copied. If this selection is also currently empty,
10112 // then paste the line before the current line of the buffer.
10113 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10114 let column = selection.start.to_point(&snapshot).column as usize;
10115 let line_start = selection.start - column;
10116 line_start..line_start
10117 } else {
10118 selection.range()
10119 };
10120
10121 edits.push((range, to_insert));
10122 original_indent_columns.push(original_indent_column);
10123 }
10124 drop(snapshot);
10125
10126 buffer.edit(
10127 edits,
10128 if auto_indent_on_paste {
10129 Some(AutoindentMode::Block {
10130 original_indent_columns,
10131 })
10132 } else {
10133 None
10134 },
10135 cx,
10136 );
10137 });
10138
10139 let selections = this.selections.all::<usize>(cx);
10140 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10141 s.select(selections)
10142 });
10143 } else {
10144 this.insert(&clipboard_text, window, cx);
10145 }
10146 });
10147 }
10148
10149 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10150 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10151 if let Some(item) = cx.read_from_clipboard() {
10152 let entries = item.entries();
10153
10154 match entries.first() {
10155 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10156 // of all the pasted entries.
10157 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10158 .do_paste(
10159 clipboard_string.text(),
10160 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10161 true,
10162 window,
10163 cx,
10164 ),
10165 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10166 }
10167 }
10168 }
10169
10170 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10171 if self.read_only(cx) {
10172 return;
10173 }
10174
10175 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10176
10177 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10178 if let Some((selections, _)) =
10179 self.selection_history.transaction(transaction_id).cloned()
10180 {
10181 self.change_selections(None, window, cx, |s| {
10182 s.select_anchors(selections.to_vec());
10183 });
10184 } else {
10185 log::error!(
10186 "No entry in selection_history found for undo. \
10187 This may correspond to a bug where undo does not update the selection. \
10188 If this is occurring, please add details to \
10189 https://github.com/zed-industries/zed/issues/22692"
10190 );
10191 }
10192 self.request_autoscroll(Autoscroll::fit(), cx);
10193 self.unmark_text(window, cx);
10194 self.refresh_inline_completion(true, false, window, cx);
10195 cx.emit(EditorEvent::Edited { transaction_id });
10196 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10197 }
10198 }
10199
10200 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10201 if self.read_only(cx) {
10202 return;
10203 }
10204
10205 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10206
10207 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10208 if let Some((_, Some(selections))) =
10209 self.selection_history.transaction(transaction_id).cloned()
10210 {
10211 self.change_selections(None, window, cx, |s| {
10212 s.select_anchors(selections.to_vec());
10213 });
10214 } else {
10215 log::error!(
10216 "No entry in selection_history found for redo. \
10217 This may correspond to a bug where undo does not update the selection. \
10218 If this is occurring, please add details to \
10219 https://github.com/zed-industries/zed/issues/22692"
10220 );
10221 }
10222 self.request_autoscroll(Autoscroll::fit(), cx);
10223 self.unmark_text(window, cx);
10224 self.refresh_inline_completion(true, false, window, cx);
10225 cx.emit(EditorEvent::Edited { transaction_id });
10226 }
10227 }
10228
10229 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10230 self.buffer
10231 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10232 }
10233
10234 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10235 self.buffer
10236 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10237 }
10238
10239 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10240 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10241 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10242 s.move_with(|map, selection| {
10243 let cursor = if selection.is_empty() {
10244 movement::left(map, selection.start)
10245 } else {
10246 selection.start
10247 };
10248 selection.collapse_to(cursor, SelectionGoal::None);
10249 });
10250 })
10251 }
10252
10253 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10254 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10255 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10256 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10257 })
10258 }
10259
10260 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10261 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10262 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10263 s.move_with(|map, selection| {
10264 let cursor = if selection.is_empty() {
10265 movement::right(map, selection.end)
10266 } else {
10267 selection.end
10268 };
10269 selection.collapse_to(cursor, SelectionGoal::None)
10270 });
10271 })
10272 }
10273
10274 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10275 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10276 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10277 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10278 })
10279 }
10280
10281 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10282 if self.take_rename(true, window, cx).is_some() {
10283 return;
10284 }
10285
10286 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10287 cx.propagate();
10288 return;
10289 }
10290
10291 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10292
10293 let text_layout_details = &self.text_layout_details(window);
10294 let selection_count = self.selections.count();
10295 let first_selection = self.selections.first_anchor();
10296
10297 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10298 s.move_with(|map, selection| {
10299 if !selection.is_empty() {
10300 selection.goal = SelectionGoal::None;
10301 }
10302 let (cursor, goal) = movement::up(
10303 map,
10304 selection.start,
10305 selection.goal,
10306 false,
10307 text_layout_details,
10308 );
10309 selection.collapse_to(cursor, goal);
10310 });
10311 });
10312
10313 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10314 {
10315 cx.propagate();
10316 }
10317 }
10318
10319 pub fn move_up_by_lines(
10320 &mut self,
10321 action: &MoveUpByLines,
10322 window: &mut Window,
10323 cx: &mut Context<Self>,
10324 ) {
10325 if self.take_rename(true, window, cx).is_some() {
10326 return;
10327 }
10328
10329 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10330 cx.propagate();
10331 return;
10332 }
10333
10334 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10335
10336 let text_layout_details = &self.text_layout_details(window);
10337
10338 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10339 s.move_with(|map, selection| {
10340 if !selection.is_empty() {
10341 selection.goal = SelectionGoal::None;
10342 }
10343 let (cursor, goal) = movement::up_by_rows(
10344 map,
10345 selection.start,
10346 action.lines,
10347 selection.goal,
10348 false,
10349 text_layout_details,
10350 );
10351 selection.collapse_to(cursor, goal);
10352 });
10353 })
10354 }
10355
10356 pub fn move_down_by_lines(
10357 &mut self,
10358 action: &MoveDownByLines,
10359 window: &mut Window,
10360 cx: &mut Context<Self>,
10361 ) {
10362 if self.take_rename(true, window, cx).is_some() {
10363 return;
10364 }
10365
10366 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10367 cx.propagate();
10368 return;
10369 }
10370
10371 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10372
10373 let text_layout_details = &self.text_layout_details(window);
10374
10375 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10376 s.move_with(|map, selection| {
10377 if !selection.is_empty() {
10378 selection.goal = SelectionGoal::None;
10379 }
10380 let (cursor, goal) = movement::down_by_rows(
10381 map,
10382 selection.start,
10383 action.lines,
10384 selection.goal,
10385 false,
10386 text_layout_details,
10387 );
10388 selection.collapse_to(cursor, goal);
10389 });
10390 })
10391 }
10392
10393 pub fn select_down_by_lines(
10394 &mut self,
10395 action: &SelectDownByLines,
10396 window: &mut Window,
10397 cx: &mut Context<Self>,
10398 ) {
10399 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10400 let text_layout_details = &self.text_layout_details(window);
10401 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10402 s.move_heads_with(|map, head, goal| {
10403 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10404 })
10405 })
10406 }
10407
10408 pub fn select_up_by_lines(
10409 &mut self,
10410 action: &SelectUpByLines,
10411 window: &mut Window,
10412 cx: &mut Context<Self>,
10413 ) {
10414 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10415 let text_layout_details = &self.text_layout_details(window);
10416 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10417 s.move_heads_with(|map, head, goal| {
10418 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10419 })
10420 })
10421 }
10422
10423 pub fn select_page_up(
10424 &mut self,
10425 _: &SelectPageUp,
10426 window: &mut Window,
10427 cx: &mut Context<Self>,
10428 ) {
10429 let Some(row_count) = self.visible_row_count() else {
10430 return;
10431 };
10432
10433 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10434
10435 let text_layout_details = &self.text_layout_details(window);
10436
10437 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10438 s.move_heads_with(|map, head, goal| {
10439 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10440 })
10441 })
10442 }
10443
10444 pub fn move_page_up(
10445 &mut self,
10446 action: &MovePageUp,
10447 window: &mut Window,
10448 cx: &mut Context<Self>,
10449 ) {
10450 if self.take_rename(true, window, cx).is_some() {
10451 return;
10452 }
10453
10454 if self
10455 .context_menu
10456 .borrow_mut()
10457 .as_mut()
10458 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10459 .unwrap_or(false)
10460 {
10461 return;
10462 }
10463
10464 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10465 cx.propagate();
10466 return;
10467 }
10468
10469 let Some(row_count) = self.visible_row_count() else {
10470 return;
10471 };
10472
10473 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10474
10475 let autoscroll = if action.center_cursor {
10476 Autoscroll::center()
10477 } else {
10478 Autoscroll::fit()
10479 };
10480
10481 let text_layout_details = &self.text_layout_details(window);
10482
10483 self.change_selections(Some(autoscroll), window, cx, |s| {
10484 s.move_with(|map, selection| {
10485 if !selection.is_empty() {
10486 selection.goal = SelectionGoal::None;
10487 }
10488 let (cursor, goal) = movement::up_by_rows(
10489 map,
10490 selection.end,
10491 row_count,
10492 selection.goal,
10493 false,
10494 text_layout_details,
10495 );
10496 selection.collapse_to(cursor, goal);
10497 });
10498 });
10499 }
10500
10501 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10502 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10503 let text_layout_details = &self.text_layout_details(window);
10504 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10505 s.move_heads_with(|map, head, goal| {
10506 movement::up(map, head, goal, false, text_layout_details)
10507 })
10508 })
10509 }
10510
10511 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10512 self.take_rename(true, window, cx);
10513
10514 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10515 cx.propagate();
10516 return;
10517 }
10518
10519 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10520
10521 let text_layout_details = &self.text_layout_details(window);
10522 let selection_count = self.selections.count();
10523 let first_selection = self.selections.first_anchor();
10524
10525 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10526 s.move_with(|map, selection| {
10527 if !selection.is_empty() {
10528 selection.goal = SelectionGoal::None;
10529 }
10530 let (cursor, goal) = movement::down(
10531 map,
10532 selection.end,
10533 selection.goal,
10534 false,
10535 text_layout_details,
10536 );
10537 selection.collapse_to(cursor, goal);
10538 });
10539 });
10540
10541 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10542 {
10543 cx.propagate();
10544 }
10545 }
10546
10547 pub fn select_page_down(
10548 &mut self,
10549 _: &SelectPageDown,
10550 window: &mut Window,
10551 cx: &mut Context<Self>,
10552 ) {
10553 let Some(row_count) = self.visible_row_count() else {
10554 return;
10555 };
10556
10557 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10558
10559 let text_layout_details = &self.text_layout_details(window);
10560
10561 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10562 s.move_heads_with(|map, head, goal| {
10563 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10564 })
10565 })
10566 }
10567
10568 pub fn move_page_down(
10569 &mut self,
10570 action: &MovePageDown,
10571 window: &mut Window,
10572 cx: &mut Context<Self>,
10573 ) {
10574 if self.take_rename(true, window, cx).is_some() {
10575 return;
10576 }
10577
10578 if self
10579 .context_menu
10580 .borrow_mut()
10581 .as_mut()
10582 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10583 .unwrap_or(false)
10584 {
10585 return;
10586 }
10587
10588 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10589 cx.propagate();
10590 return;
10591 }
10592
10593 let Some(row_count) = self.visible_row_count() else {
10594 return;
10595 };
10596
10597 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10598
10599 let autoscroll = if action.center_cursor {
10600 Autoscroll::center()
10601 } else {
10602 Autoscroll::fit()
10603 };
10604
10605 let text_layout_details = &self.text_layout_details(window);
10606 self.change_selections(Some(autoscroll), window, cx, |s| {
10607 s.move_with(|map, selection| {
10608 if !selection.is_empty() {
10609 selection.goal = SelectionGoal::None;
10610 }
10611 let (cursor, goal) = movement::down_by_rows(
10612 map,
10613 selection.end,
10614 row_count,
10615 selection.goal,
10616 false,
10617 text_layout_details,
10618 );
10619 selection.collapse_to(cursor, goal);
10620 });
10621 });
10622 }
10623
10624 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10625 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10626 let text_layout_details = &self.text_layout_details(window);
10627 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10628 s.move_heads_with(|map, head, goal| {
10629 movement::down(map, head, goal, false, text_layout_details)
10630 })
10631 });
10632 }
10633
10634 pub fn context_menu_first(
10635 &mut self,
10636 _: &ContextMenuFirst,
10637 _window: &mut Window,
10638 cx: &mut Context<Self>,
10639 ) {
10640 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10641 context_menu.select_first(self.completion_provider.as_deref(), cx);
10642 }
10643 }
10644
10645 pub fn context_menu_prev(
10646 &mut self,
10647 _: &ContextMenuPrevious,
10648 _window: &mut Window,
10649 cx: &mut Context<Self>,
10650 ) {
10651 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10652 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10653 }
10654 }
10655
10656 pub fn context_menu_next(
10657 &mut self,
10658 _: &ContextMenuNext,
10659 _window: &mut Window,
10660 cx: &mut Context<Self>,
10661 ) {
10662 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10663 context_menu.select_next(self.completion_provider.as_deref(), cx);
10664 }
10665 }
10666
10667 pub fn context_menu_last(
10668 &mut self,
10669 _: &ContextMenuLast,
10670 _window: &mut Window,
10671 cx: &mut Context<Self>,
10672 ) {
10673 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10674 context_menu.select_last(self.completion_provider.as_deref(), cx);
10675 }
10676 }
10677
10678 pub fn move_to_previous_word_start(
10679 &mut self,
10680 _: &MoveToPreviousWordStart,
10681 window: &mut Window,
10682 cx: &mut Context<Self>,
10683 ) {
10684 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10685 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10686 s.move_cursors_with(|map, head, _| {
10687 (
10688 movement::previous_word_start(map, head),
10689 SelectionGoal::None,
10690 )
10691 });
10692 })
10693 }
10694
10695 pub fn move_to_previous_subword_start(
10696 &mut self,
10697 _: &MoveToPreviousSubwordStart,
10698 window: &mut Window,
10699 cx: &mut Context<Self>,
10700 ) {
10701 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10702 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10703 s.move_cursors_with(|map, head, _| {
10704 (
10705 movement::previous_subword_start(map, head),
10706 SelectionGoal::None,
10707 )
10708 });
10709 })
10710 }
10711
10712 pub fn select_to_previous_word_start(
10713 &mut self,
10714 _: &SelectToPreviousWordStart,
10715 window: &mut Window,
10716 cx: &mut Context<Self>,
10717 ) {
10718 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10719 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10720 s.move_heads_with(|map, head, _| {
10721 (
10722 movement::previous_word_start(map, head),
10723 SelectionGoal::None,
10724 )
10725 });
10726 })
10727 }
10728
10729 pub fn select_to_previous_subword_start(
10730 &mut self,
10731 _: &SelectToPreviousSubwordStart,
10732 window: &mut Window,
10733 cx: &mut Context<Self>,
10734 ) {
10735 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10736 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10737 s.move_heads_with(|map, head, _| {
10738 (
10739 movement::previous_subword_start(map, head),
10740 SelectionGoal::None,
10741 )
10742 });
10743 })
10744 }
10745
10746 pub fn delete_to_previous_word_start(
10747 &mut self,
10748 action: &DeleteToPreviousWordStart,
10749 window: &mut Window,
10750 cx: &mut Context<Self>,
10751 ) {
10752 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10753 self.transact(window, cx, |this, window, cx| {
10754 this.select_autoclose_pair(window, cx);
10755 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10756 s.move_with(|map, selection| {
10757 if selection.is_empty() {
10758 let cursor = if action.ignore_newlines {
10759 movement::previous_word_start(map, selection.head())
10760 } else {
10761 movement::previous_word_start_or_newline(map, selection.head())
10762 };
10763 selection.set_head(cursor, SelectionGoal::None);
10764 }
10765 });
10766 });
10767 this.insert("", window, cx);
10768 });
10769 }
10770
10771 pub fn delete_to_previous_subword_start(
10772 &mut self,
10773 _: &DeleteToPreviousSubwordStart,
10774 window: &mut Window,
10775 cx: &mut Context<Self>,
10776 ) {
10777 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10778 self.transact(window, cx, |this, window, cx| {
10779 this.select_autoclose_pair(window, cx);
10780 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10781 s.move_with(|map, selection| {
10782 if selection.is_empty() {
10783 let cursor = movement::previous_subword_start(map, selection.head());
10784 selection.set_head(cursor, SelectionGoal::None);
10785 }
10786 });
10787 });
10788 this.insert("", window, cx);
10789 });
10790 }
10791
10792 pub fn move_to_next_word_end(
10793 &mut self,
10794 _: &MoveToNextWordEnd,
10795 window: &mut Window,
10796 cx: &mut Context<Self>,
10797 ) {
10798 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10799 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10800 s.move_cursors_with(|map, head, _| {
10801 (movement::next_word_end(map, head), SelectionGoal::None)
10802 });
10803 })
10804 }
10805
10806 pub fn move_to_next_subword_end(
10807 &mut self,
10808 _: &MoveToNextSubwordEnd,
10809 window: &mut Window,
10810 cx: &mut Context<Self>,
10811 ) {
10812 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10813 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10814 s.move_cursors_with(|map, head, _| {
10815 (movement::next_subword_end(map, head), SelectionGoal::None)
10816 });
10817 })
10818 }
10819
10820 pub fn select_to_next_word_end(
10821 &mut self,
10822 _: &SelectToNextWordEnd,
10823 window: &mut Window,
10824 cx: &mut Context<Self>,
10825 ) {
10826 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10827 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10828 s.move_heads_with(|map, head, _| {
10829 (movement::next_word_end(map, head), SelectionGoal::None)
10830 });
10831 })
10832 }
10833
10834 pub fn select_to_next_subword_end(
10835 &mut self,
10836 _: &SelectToNextSubwordEnd,
10837 window: &mut Window,
10838 cx: &mut Context<Self>,
10839 ) {
10840 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10841 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10842 s.move_heads_with(|map, head, _| {
10843 (movement::next_subword_end(map, head), SelectionGoal::None)
10844 });
10845 })
10846 }
10847
10848 pub fn delete_to_next_word_end(
10849 &mut self,
10850 action: &DeleteToNextWordEnd,
10851 window: &mut Window,
10852 cx: &mut Context<Self>,
10853 ) {
10854 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10855 self.transact(window, cx, |this, window, cx| {
10856 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10857 s.move_with(|map, selection| {
10858 if selection.is_empty() {
10859 let cursor = if action.ignore_newlines {
10860 movement::next_word_end(map, selection.head())
10861 } else {
10862 movement::next_word_end_or_newline(map, selection.head())
10863 };
10864 selection.set_head(cursor, SelectionGoal::None);
10865 }
10866 });
10867 });
10868 this.insert("", window, cx);
10869 });
10870 }
10871
10872 pub fn delete_to_next_subword_end(
10873 &mut self,
10874 _: &DeleteToNextSubwordEnd,
10875 window: &mut Window,
10876 cx: &mut Context<Self>,
10877 ) {
10878 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10879 self.transact(window, cx, |this, window, cx| {
10880 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10881 s.move_with(|map, selection| {
10882 if selection.is_empty() {
10883 let cursor = movement::next_subword_end(map, selection.head());
10884 selection.set_head(cursor, SelectionGoal::None);
10885 }
10886 });
10887 });
10888 this.insert("", window, cx);
10889 });
10890 }
10891
10892 pub fn move_to_beginning_of_line(
10893 &mut self,
10894 action: &MoveToBeginningOfLine,
10895 window: &mut Window,
10896 cx: &mut Context<Self>,
10897 ) {
10898 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10899 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10900 s.move_cursors_with(|map, head, _| {
10901 (
10902 movement::indented_line_beginning(
10903 map,
10904 head,
10905 action.stop_at_soft_wraps,
10906 action.stop_at_indent,
10907 ),
10908 SelectionGoal::None,
10909 )
10910 });
10911 })
10912 }
10913
10914 pub fn select_to_beginning_of_line(
10915 &mut self,
10916 action: &SelectToBeginningOfLine,
10917 window: &mut Window,
10918 cx: &mut Context<Self>,
10919 ) {
10920 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10921 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10922 s.move_heads_with(|map, head, _| {
10923 (
10924 movement::indented_line_beginning(
10925 map,
10926 head,
10927 action.stop_at_soft_wraps,
10928 action.stop_at_indent,
10929 ),
10930 SelectionGoal::None,
10931 )
10932 });
10933 });
10934 }
10935
10936 pub fn delete_to_beginning_of_line(
10937 &mut self,
10938 action: &DeleteToBeginningOfLine,
10939 window: &mut Window,
10940 cx: &mut Context<Self>,
10941 ) {
10942 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10943 self.transact(window, cx, |this, window, cx| {
10944 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10945 s.move_with(|_, selection| {
10946 selection.reversed = true;
10947 });
10948 });
10949
10950 this.select_to_beginning_of_line(
10951 &SelectToBeginningOfLine {
10952 stop_at_soft_wraps: false,
10953 stop_at_indent: action.stop_at_indent,
10954 },
10955 window,
10956 cx,
10957 );
10958 this.backspace(&Backspace, window, cx);
10959 });
10960 }
10961
10962 pub fn move_to_end_of_line(
10963 &mut self,
10964 action: &MoveToEndOfLine,
10965 window: &mut Window,
10966 cx: &mut Context<Self>,
10967 ) {
10968 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10969 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10970 s.move_cursors_with(|map, head, _| {
10971 (
10972 movement::line_end(map, head, action.stop_at_soft_wraps),
10973 SelectionGoal::None,
10974 )
10975 });
10976 })
10977 }
10978
10979 pub fn select_to_end_of_line(
10980 &mut self,
10981 action: &SelectToEndOfLine,
10982 window: &mut Window,
10983 cx: &mut Context<Self>,
10984 ) {
10985 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10986 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10987 s.move_heads_with(|map, head, _| {
10988 (
10989 movement::line_end(map, head, action.stop_at_soft_wraps),
10990 SelectionGoal::None,
10991 )
10992 });
10993 })
10994 }
10995
10996 pub fn delete_to_end_of_line(
10997 &mut self,
10998 _: &DeleteToEndOfLine,
10999 window: &mut Window,
11000 cx: &mut Context<Self>,
11001 ) {
11002 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11003 self.transact(window, cx, |this, window, cx| {
11004 this.select_to_end_of_line(
11005 &SelectToEndOfLine {
11006 stop_at_soft_wraps: false,
11007 },
11008 window,
11009 cx,
11010 );
11011 this.delete(&Delete, window, cx);
11012 });
11013 }
11014
11015 pub fn cut_to_end_of_line(
11016 &mut self,
11017 _: &CutToEndOfLine,
11018 window: &mut Window,
11019 cx: &mut Context<Self>,
11020 ) {
11021 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11022 self.transact(window, cx, |this, window, cx| {
11023 this.select_to_end_of_line(
11024 &SelectToEndOfLine {
11025 stop_at_soft_wraps: false,
11026 },
11027 window,
11028 cx,
11029 );
11030 this.cut(&Cut, window, cx);
11031 });
11032 }
11033
11034 pub fn move_to_start_of_paragraph(
11035 &mut self,
11036 _: &MoveToStartOfParagraph,
11037 window: &mut Window,
11038 cx: &mut Context<Self>,
11039 ) {
11040 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11041 cx.propagate();
11042 return;
11043 }
11044 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11045 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11046 s.move_with(|map, selection| {
11047 selection.collapse_to(
11048 movement::start_of_paragraph(map, selection.head(), 1),
11049 SelectionGoal::None,
11050 )
11051 });
11052 })
11053 }
11054
11055 pub fn move_to_end_of_paragraph(
11056 &mut self,
11057 _: &MoveToEndOfParagraph,
11058 window: &mut Window,
11059 cx: &mut Context<Self>,
11060 ) {
11061 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11062 cx.propagate();
11063 return;
11064 }
11065 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11066 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11067 s.move_with(|map, selection| {
11068 selection.collapse_to(
11069 movement::end_of_paragraph(map, selection.head(), 1),
11070 SelectionGoal::None,
11071 )
11072 });
11073 })
11074 }
11075
11076 pub fn select_to_start_of_paragraph(
11077 &mut self,
11078 _: &SelectToStartOfParagraph,
11079 window: &mut Window,
11080 cx: &mut Context<Self>,
11081 ) {
11082 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11083 cx.propagate();
11084 return;
11085 }
11086 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11087 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11088 s.move_heads_with(|map, head, _| {
11089 (
11090 movement::start_of_paragraph(map, head, 1),
11091 SelectionGoal::None,
11092 )
11093 });
11094 })
11095 }
11096
11097 pub fn select_to_end_of_paragraph(
11098 &mut self,
11099 _: &SelectToEndOfParagraph,
11100 window: &mut Window,
11101 cx: &mut Context<Self>,
11102 ) {
11103 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11104 cx.propagate();
11105 return;
11106 }
11107 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11108 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11109 s.move_heads_with(|map, head, _| {
11110 (
11111 movement::end_of_paragraph(map, head, 1),
11112 SelectionGoal::None,
11113 )
11114 });
11115 })
11116 }
11117
11118 pub fn move_to_start_of_excerpt(
11119 &mut self,
11120 _: &MoveToStartOfExcerpt,
11121 window: &mut Window,
11122 cx: &mut Context<Self>,
11123 ) {
11124 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11125 cx.propagate();
11126 return;
11127 }
11128 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11129 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11130 s.move_with(|map, selection| {
11131 selection.collapse_to(
11132 movement::start_of_excerpt(
11133 map,
11134 selection.head(),
11135 workspace::searchable::Direction::Prev,
11136 ),
11137 SelectionGoal::None,
11138 )
11139 });
11140 })
11141 }
11142
11143 pub fn move_to_start_of_next_excerpt(
11144 &mut self,
11145 _: &MoveToStartOfNextExcerpt,
11146 window: &mut Window,
11147 cx: &mut Context<Self>,
11148 ) {
11149 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11150 cx.propagate();
11151 return;
11152 }
11153
11154 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11155 s.move_with(|map, selection| {
11156 selection.collapse_to(
11157 movement::start_of_excerpt(
11158 map,
11159 selection.head(),
11160 workspace::searchable::Direction::Next,
11161 ),
11162 SelectionGoal::None,
11163 )
11164 });
11165 })
11166 }
11167
11168 pub fn move_to_end_of_excerpt(
11169 &mut self,
11170 _: &MoveToEndOfExcerpt,
11171 window: &mut Window,
11172 cx: &mut Context<Self>,
11173 ) {
11174 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11175 cx.propagate();
11176 return;
11177 }
11178 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11179 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11180 s.move_with(|map, selection| {
11181 selection.collapse_to(
11182 movement::end_of_excerpt(
11183 map,
11184 selection.head(),
11185 workspace::searchable::Direction::Next,
11186 ),
11187 SelectionGoal::None,
11188 )
11189 });
11190 })
11191 }
11192
11193 pub fn move_to_end_of_previous_excerpt(
11194 &mut self,
11195 _: &MoveToEndOfPreviousExcerpt,
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_with(|map, selection| {
11206 selection.collapse_to(
11207 movement::end_of_excerpt(
11208 map,
11209 selection.head(),
11210 workspace::searchable::Direction::Prev,
11211 ),
11212 SelectionGoal::None,
11213 )
11214 });
11215 })
11216 }
11217
11218 pub fn select_to_start_of_excerpt(
11219 &mut self,
11220 _: &SelectToStartOfExcerpt,
11221 window: &mut Window,
11222 cx: &mut Context<Self>,
11223 ) {
11224 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11225 cx.propagate();
11226 return;
11227 }
11228 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11229 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11230 s.move_heads_with(|map, head, _| {
11231 (
11232 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11233 SelectionGoal::None,
11234 )
11235 });
11236 })
11237 }
11238
11239 pub fn select_to_start_of_next_excerpt(
11240 &mut self,
11241 _: &SelectToStartOfNextExcerpt,
11242 window: &mut Window,
11243 cx: &mut Context<Self>,
11244 ) {
11245 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11246 cx.propagate();
11247 return;
11248 }
11249 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11250 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11251 s.move_heads_with(|map, head, _| {
11252 (
11253 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11254 SelectionGoal::None,
11255 )
11256 });
11257 })
11258 }
11259
11260 pub fn select_to_end_of_excerpt(
11261 &mut self,
11262 _: &SelectToEndOfExcerpt,
11263 window: &mut Window,
11264 cx: &mut Context<Self>,
11265 ) {
11266 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11267 cx.propagate();
11268 return;
11269 }
11270 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11271 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11272 s.move_heads_with(|map, head, _| {
11273 (
11274 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11275 SelectionGoal::None,
11276 )
11277 });
11278 })
11279 }
11280
11281 pub fn select_to_end_of_previous_excerpt(
11282 &mut self,
11283 _: &SelectToEndOfPreviousExcerpt,
11284 window: &mut Window,
11285 cx: &mut Context<Self>,
11286 ) {
11287 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11288 cx.propagate();
11289 return;
11290 }
11291 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11292 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11293 s.move_heads_with(|map, head, _| {
11294 (
11295 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11296 SelectionGoal::None,
11297 )
11298 });
11299 })
11300 }
11301
11302 pub fn move_to_beginning(
11303 &mut self,
11304 _: &MoveToBeginning,
11305 window: &mut Window,
11306 cx: &mut Context<Self>,
11307 ) {
11308 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11309 cx.propagate();
11310 return;
11311 }
11312 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11313 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11314 s.select_ranges(vec![0..0]);
11315 });
11316 }
11317
11318 pub fn select_to_beginning(
11319 &mut self,
11320 _: &SelectToBeginning,
11321 window: &mut Window,
11322 cx: &mut Context<Self>,
11323 ) {
11324 let mut selection = self.selections.last::<Point>(cx);
11325 selection.set_head(Point::zero(), SelectionGoal::None);
11326 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11327 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11328 s.select(vec![selection]);
11329 });
11330 }
11331
11332 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11333 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11334 cx.propagate();
11335 return;
11336 }
11337 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11338 let cursor = self.buffer.read(cx).read(cx).len();
11339 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11340 s.select_ranges(vec![cursor..cursor])
11341 });
11342 }
11343
11344 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11345 self.nav_history = nav_history;
11346 }
11347
11348 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11349 self.nav_history.as_ref()
11350 }
11351
11352 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11353 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11354 }
11355
11356 fn push_to_nav_history(
11357 &mut self,
11358 cursor_anchor: Anchor,
11359 new_position: Option<Point>,
11360 is_deactivate: bool,
11361 cx: &mut Context<Self>,
11362 ) {
11363 if let Some(nav_history) = self.nav_history.as_mut() {
11364 let buffer = self.buffer.read(cx).read(cx);
11365 let cursor_position = cursor_anchor.to_point(&buffer);
11366 let scroll_state = self.scroll_manager.anchor();
11367 let scroll_top_row = scroll_state.top_row(&buffer);
11368 drop(buffer);
11369
11370 if let Some(new_position) = new_position {
11371 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11372 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11373 return;
11374 }
11375 }
11376
11377 nav_history.push(
11378 Some(NavigationData {
11379 cursor_anchor,
11380 cursor_position,
11381 scroll_anchor: scroll_state,
11382 scroll_top_row,
11383 }),
11384 cx,
11385 );
11386 cx.emit(EditorEvent::PushedToNavHistory {
11387 anchor: cursor_anchor,
11388 is_deactivate,
11389 })
11390 }
11391 }
11392
11393 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11394 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11395 let buffer = self.buffer.read(cx).snapshot(cx);
11396 let mut selection = self.selections.first::<usize>(cx);
11397 selection.set_head(buffer.len(), SelectionGoal::None);
11398 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11399 s.select(vec![selection]);
11400 });
11401 }
11402
11403 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11404 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11405 let end = self.buffer.read(cx).read(cx).len();
11406 self.change_selections(None, window, cx, |s| {
11407 s.select_ranges(vec![0..end]);
11408 });
11409 }
11410
11411 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11412 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11414 let mut selections = self.selections.all::<Point>(cx);
11415 let max_point = display_map.buffer_snapshot.max_point();
11416 for selection in &mut selections {
11417 let rows = selection.spanned_rows(true, &display_map);
11418 selection.start = Point::new(rows.start.0, 0);
11419 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11420 selection.reversed = false;
11421 }
11422 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11423 s.select(selections);
11424 });
11425 }
11426
11427 pub fn split_selection_into_lines(
11428 &mut self,
11429 _: &SplitSelectionIntoLines,
11430 window: &mut Window,
11431 cx: &mut Context<Self>,
11432 ) {
11433 let selections = self
11434 .selections
11435 .all::<Point>(cx)
11436 .into_iter()
11437 .map(|selection| selection.start..selection.end)
11438 .collect::<Vec<_>>();
11439 self.unfold_ranges(&selections, true, true, cx);
11440
11441 let mut new_selection_ranges = Vec::new();
11442 {
11443 let buffer = self.buffer.read(cx).read(cx);
11444 for selection in selections {
11445 for row in selection.start.row..selection.end.row {
11446 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11447 new_selection_ranges.push(cursor..cursor);
11448 }
11449
11450 let is_multiline_selection = selection.start.row != selection.end.row;
11451 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11452 // so this action feels more ergonomic when paired with other selection operations
11453 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11454 if !should_skip_last {
11455 new_selection_ranges.push(selection.end..selection.end);
11456 }
11457 }
11458 }
11459 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11460 s.select_ranges(new_selection_ranges);
11461 });
11462 }
11463
11464 pub fn add_selection_above(
11465 &mut self,
11466 _: &AddSelectionAbove,
11467 window: &mut Window,
11468 cx: &mut Context<Self>,
11469 ) {
11470 self.add_selection(true, window, cx);
11471 }
11472
11473 pub fn add_selection_below(
11474 &mut self,
11475 _: &AddSelectionBelow,
11476 window: &mut Window,
11477 cx: &mut Context<Self>,
11478 ) {
11479 self.add_selection(false, window, cx);
11480 }
11481
11482 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11483 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11484
11485 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11486 let mut selections = self.selections.all::<Point>(cx);
11487 let text_layout_details = self.text_layout_details(window);
11488 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11489 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11490 let range = oldest_selection.display_range(&display_map).sorted();
11491
11492 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11493 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11494 let positions = start_x.min(end_x)..start_x.max(end_x);
11495
11496 selections.clear();
11497 let mut stack = Vec::new();
11498 for row in range.start.row().0..=range.end.row().0 {
11499 if let Some(selection) = self.selections.build_columnar_selection(
11500 &display_map,
11501 DisplayRow(row),
11502 &positions,
11503 oldest_selection.reversed,
11504 &text_layout_details,
11505 ) {
11506 stack.push(selection.id);
11507 selections.push(selection);
11508 }
11509 }
11510
11511 if above {
11512 stack.reverse();
11513 }
11514
11515 AddSelectionsState { above, stack }
11516 });
11517
11518 let last_added_selection = *state.stack.last().unwrap();
11519 let mut new_selections = Vec::new();
11520 if above == state.above {
11521 let end_row = if above {
11522 DisplayRow(0)
11523 } else {
11524 display_map.max_point().row()
11525 };
11526
11527 'outer: for selection in selections {
11528 if selection.id == last_added_selection {
11529 let range = selection.display_range(&display_map).sorted();
11530 debug_assert_eq!(range.start.row(), range.end.row());
11531 let mut row = range.start.row();
11532 let positions =
11533 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11534 px(start)..px(end)
11535 } else {
11536 let start_x =
11537 display_map.x_for_display_point(range.start, &text_layout_details);
11538 let end_x =
11539 display_map.x_for_display_point(range.end, &text_layout_details);
11540 start_x.min(end_x)..start_x.max(end_x)
11541 };
11542
11543 while row != end_row {
11544 if above {
11545 row.0 -= 1;
11546 } else {
11547 row.0 += 1;
11548 }
11549
11550 if let Some(new_selection) = self.selections.build_columnar_selection(
11551 &display_map,
11552 row,
11553 &positions,
11554 selection.reversed,
11555 &text_layout_details,
11556 ) {
11557 state.stack.push(new_selection.id);
11558 if above {
11559 new_selections.push(new_selection);
11560 new_selections.push(selection);
11561 } else {
11562 new_selections.push(selection);
11563 new_selections.push(new_selection);
11564 }
11565
11566 continue 'outer;
11567 }
11568 }
11569 }
11570
11571 new_selections.push(selection);
11572 }
11573 } else {
11574 new_selections = selections;
11575 new_selections.retain(|s| s.id != last_added_selection);
11576 state.stack.pop();
11577 }
11578
11579 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11580 s.select(new_selections);
11581 });
11582 if state.stack.len() > 1 {
11583 self.add_selections_state = Some(state);
11584 }
11585 }
11586
11587 pub fn select_next_match_internal(
11588 &mut self,
11589 display_map: &DisplaySnapshot,
11590 replace_newest: bool,
11591 autoscroll: Option<Autoscroll>,
11592 window: &mut Window,
11593 cx: &mut Context<Self>,
11594 ) -> Result<()> {
11595 fn select_next_match_ranges(
11596 this: &mut Editor,
11597 range: Range<usize>,
11598 replace_newest: bool,
11599 auto_scroll: Option<Autoscroll>,
11600 window: &mut Window,
11601 cx: &mut Context<Editor>,
11602 ) {
11603 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
11604 this.change_selections(auto_scroll, window, cx, |s| {
11605 if replace_newest {
11606 s.delete(s.newest_anchor().id);
11607 }
11608 s.insert_range(range.clone());
11609 });
11610 }
11611
11612 let buffer = &display_map.buffer_snapshot;
11613 let mut selections = self.selections.all::<usize>(cx);
11614 if let Some(mut select_next_state) = self.select_next_state.take() {
11615 let query = &select_next_state.query;
11616 if !select_next_state.done {
11617 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11618 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11619 let mut next_selected_range = None;
11620
11621 let bytes_after_last_selection =
11622 buffer.bytes_in_range(last_selection.end..buffer.len());
11623 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11624 let query_matches = query
11625 .stream_find_iter(bytes_after_last_selection)
11626 .map(|result| (last_selection.end, result))
11627 .chain(
11628 query
11629 .stream_find_iter(bytes_before_first_selection)
11630 .map(|result| (0, result)),
11631 );
11632
11633 for (start_offset, query_match) in query_matches {
11634 let query_match = query_match.unwrap(); // can only fail due to I/O
11635 let offset_range =
11636 start_offset + query_match.start()..start_offset + query_match.end();
11637 let display_range = offset_range.start.to_display_point(display_map)
11638 ..offset_range.end.to_display_point(display_map);
11639
11640 if !select_next_state.wordwise
11641 || (!movement::is_inside_word(display_map, display_range.start)
11642 && !movement::is_inside_word(display_map, display_range.end))
11643 {
11644 // TODO: This is n^2, because we might check all the selections
11645 if !selections
11646 .iter()
11647 .any(|selection| selection.range().overlaps(&offset_range))
11648 {
11649 next_selected_range = Some(offset_range);
11650 break;
11651 }
11652 }
11653 }
11654
11655 if let Some(next_selected_range) = next_selected_range {
11656 select_next_match_ranges(
11657 self,
11658 next_selected_range,
11659 replace_newest,
11660 autoscroll,
11661 window,
11662 cx,
11663 );
11664 } else {
11665 select_next_state.done = true;
11666 }
11667 }
11668
11669 self.select_next_state = Some(select_next_state);
11670 } else {
11671 let mut only_carets = true;
11672 let mut same_text_selected = true;
11673 let mut selected_text = None;
11674
11675 let mut selections_iter = selections.iter().peekable();
11676 while let Some(selection) = selections_iter.next() {
11677 if selection.start != selection.end {
11678 only_carets = false;
11679 }
11680
11681 if same_text_selected {
11682 if selected_text.is_none() {
11683 selected_text =
11684 Some(buffer.text_for_range(selection.range()).collect::<String>());
11685 }
11686
11687 if let Some(next_selection) = selections_iter.peek() {
11688 if next_selection.range().len() == selection.range().len() {
11689 let next_selected_text = buffer
11690 .text_for_range(next_selection.range())
11691 .collect::<String>();
11692 if Some(next_selected_text) != selected_text {
11693 same_text_selected = false;
11694 selected_text = None;
11695 }
11696 } else {
11697 same_text_selected = false;
11698 selected_text = None;
11699 }
11700 }
11701 }
11702 }
11703
11704 if only_carets {
11705 for selection in &mut selections {
11706 let word_range = movement::surrounding_word(
11707 display_map,
11708 selection.start.to_display_point(display_map),
11709 );
11710 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11711 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11712 selection.goal = SelectionGoal::None;
11713 selection.reversed = false;
11714 select_next_match_ranges(
11715 self,
11716 selection.start..selection.end,
11717 replace_newest,
11718 autoscroll,
11719 window,
11720 cx,
11721 );
11722 }
11723
11724 if selections.len() == 1 {
11725 let selection = selections
11726 .last()
11727 .expect("ensured that there's only one selection");
11728 let query = buffer
11729 .text_for_range(selection.start..selection.end)
11730 .collect::<String>();
11731 let is_empty = query.is_empty();
11732 let select_state = SelectNextState {
11733 query: AhoCorasick::new(&[query])?,
11734 wordwise: true,
11735 done: is_empty,
11736 };
11737 self.select_next_state = Some(select_state);
11738 } else {
11739 self.select_next_state = None;
11740 }
11741 } else if let Some(selected_text) = selected_text {
11742 self.select_next_state = Some(SelectNextState {
11743 query: AhoCorasick::new(&[selected_text])?,
11744 wordwise: false,
11745 done: false,
11746 });
11747 self.select_next_match_internal(
11748 display_map,
11749 replace_newest,
11750 autoscroll,
11751 window,
11752 cx,
11753 )?;
11754 }
11755 }
11756 Ok(())
11757 }
11758
11759 pub fn select_all_matches(
11760 &mut self,
11761 _action: &SelectAllMatches,
11762 window: &mut Window,
11763 cx: &mut Context<Self>,
11764 ) -> Result<()> {
11765 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11766
11767 self.push_to_selection_history();
11768 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11769
11770 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11771 let Some(select_next_state) = self.select_next_state.as_mut() else {
11772 return Ok(());
11773 };
11774 if select_next_state.done {
11775 return Ok(());
11776 }
11777
11778 let mut new_selections = Vec::new();
11779
11780 let reversed = self.selections.oldest::<usize>(cx).reversed;
11781 let buffer = &display_map.buffer_snapshot;
11782 let query_matches = select_next_state
11783 .query
11784 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11785
11786 for query_match in query_matches.into_iter() {
11787 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
11788 let offset_range = if reversed {
11789 query_match.end()..query_match.start()
11790 } else {
11791 query_match.start()..query_match.end()
11792 };
11793 let display_range = offset_range.start.to_display_point(&display_map)
11794 ..offset_range.end.to_display_point(&display_map);
11795
11796 if !select_next_state.wordwise
11797 || (!movement::is_inside_word(&display_map, display_range.start)
11798 && !movement::is_inside_word(&display_map, display_range.end))
11799 {
11800 new_selections.push(offset_range.start..offset_range.end);
11801 }
11802 }
11803
11804 select_next_state.done = true;
11805 self.unfold_ranges(&new_selections.clone(), false, false, cx);
11806 self.change_selections(None, window, cx, |selections| {
11807 selections.select_ranges(new_selections)
11808 });
11809
11810 Ok(())
11811 }
11812
11813 pub fn select_next(
11814 &mut self,
11815 action: &SelectNext,
11816 window: &mut Window,
11817 cx: &mut Context<Self>,
11818 ) -> Result<()> {
11819 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11820 self.push_to_selection_history();
11821 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11822 self.select_next_match_internal(
11823 &display_map,
11824 action.replace_newest,
11825 Some(Autoscroll::newest()),
11826 window,
11827 cx,
11828 )?;
11829 Ok(())
11830 }
11831
11832 pub fn select_previous(
11833 &mut self,
11834 action: &SelectPrevious,
11835 window: &mut Window,
11836 cx: &mut Context<Self>,
11837 ) -> Result<()> {
11838 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11839 self.push_to_selection_history();
11840 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11841 let buffer = &display_map.buffer_snapshot;
11842 let mut selections = self.selections.all::<usize>(cx);
11843 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11844 let query = &select_prev_state.query;
11845 if !select_prev_state.done {
11846 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11847 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11848 let mut next_selected_range = None;
11849 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11850 let bytes_before_last_selection =
11851 buffer.reversed_bytes_in_range(0..last_selection.start);
11852 let bytes_after_first_selection =
11853 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11854 let query_matches = query
11855 .stream_find_iter(bytes_before_last_selection)
11856 .map(|result| (last_selection.start, result))
11857 .chain(
11858 query
11859 .stream_find_iter(bytes_after_first_selection)
11860 .map(|result| (buffer.len(), result)),
11861 );
11862 for (end_offset, query_match) in query_matches {
11863 let query_match = query_match.unwrap(); // can only fail due to I/O
11864 let offset_range =
11865 end_offset - query_match.end()..end_offset - query_match.start();
11866 let display_range = offset_range.start.to_display_point(&display_map)
11867 ..offset_range.end.to_display_point(&display_map);
11868
11869 if !select_prev_state.wordwise
11870 || (!movement::is_inside_word(&display_map, display_range.start)
11871 && !movement::is_inside_word(&display_map, display_range.end))
11872 {
11873 next_selected_range = Some(offset_range);
11874 break;
11875 }
11876 }
11877
11878 if let Some(next_selected_range) = next_selected_range {
11879 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11880 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11881 if action.replace_newest {
11882 s.delete(s.newest_anchor().id);
11883 }
11884 s.insert_range(next_selected_range);
11885 });
11886 } else {
11887 select_prev_state.done = true;
11888 }
11889 }
11890
11891 self.select_prev_state = Some(select_prev_state);
11892 } else {
11893 let mut only_carets = true;
11894 let mut same_text_selected = true;
11895 let mut selected_text = None;
11896
11897 let mut selections_iter = selections.iter().peekable();
11898 while let Some(selection) = selections_iter.next() {
11899 if selection.start != selection.end {
11900 only_carets = false;
11901 }
11902
11903 if same_text_selected {
11904 if selected_text.is_none() {
11905 selected_text =
11906 Some(buffer.text_for_range(selection.range()).collect::<String>());
11907 }
11908
11909 if let Some(next_selection) = selections_iter.peek() {
11910 if next_selection.range().len() == selection.range().len() {
11911 let next_selected_text = buffer
11912 .text_for_range(next_selection.range())
11913 .collect::<String>();
11914 if Some(next_selected_text) != selected_text {
11915 same_text_selected = false;
11916 selected_text = None;
11917 }
11918 } else {
11919 same_text_selected = false;
11920 selected_text = None;
11921 }
11922 }
11923 }
11924 }
11925
11926 if only_carets {
11927 for selection in &mut selections {
11928 let word_range = movement::surrounding_word(
11929 &display_map,
11930 selection.start.to_display_point(&display_map),
11931 );
11932 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11933 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11934 selection.goal = SelectionGoal::None;
11935 selection.reversed = false;
11936 }
11937 if selections.len() == 1 {
11938 let selection = selections
11939 .last()
11940 .expect("ensured that there's only one selection");
11941 let query = buffer
11942 .text_for_range(selection.start..selection.end)
11943 .collect::<String>();
11944 let is_empty = query.is_empty();
11945 let select_state = SelectNextState {
11946 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11947 wordwise: true,
11948 done: is_empty,
11949 };
11950 self.select_prev_state = Some(select_state);
11951 } else {
11952 self.select_prev_state = None;
11953 }
11954
11955 self.unfold_ranges(
11956 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11957 false,
11958 true,
11959 cx,
11960 );
11961 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11962 s.select(selections);
11963 });
11964 } else if let Some(selected_text) = selected_text {
11965 self.select_prev_state = Some(SelectNextState {
11966 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11967 wordwise: false,
11968 done: false,
11969 });
11970 self.select_previous(action, window, cx)?;
11971 }
11972 }
11973 Ok(())
11974 }
11975
11976 pub fn toggle_comments(
11977 &mut self,
11978 action: &ToggleComments,
11979 window: &mut Window,
11980 cx: &mut Context<Self>,
11981 ) {
11982 if self.read_only(cx) {
11983 return;
11984 }
11985 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11986 let text_layout_details = &self.text_layout_details(window);
11987 self.transact(window, cx, |this, window, cx| {
11988 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11989 let mut edits = Vec::new();
11990 let mut selection_edit_ranges = Vec::new();
11991 let mut last_toggled_row = None;
11992 let snapshot = this.buffer.read(cx).read(cx);
11993 let empty_str: Arc<str> = Arc::default();
11994 let mut suffixes_inserted = Vec::new();
11995 let ignore_indent = action.ignore_indent;
11996
11997 fn comment_prefix_range(
11998 snapshot: &MultiBufferSnapshot,
11999 row: MultiBufferRow,
12000 comment_prefix: &str,
12001 comment_prefix_whitespace: &str,
12002 ignore_indent: bool,
12003 ) -> Range<Point> {
12004 let indent_size = if ignore_indent {
12005 0
12006 } else {
12007 snapshot.indent_size_for_line(row).len
12008 };
12009
12010 let start = Point::new(row.0, indent_size);
12011
12012 let mut line_bytes = snapshot
12013 .bytes_in_range(start..snapshot.max_point())
12014 .flatten()
12015 .copied();
12016
12017 // If this line currently begins with the line comment prefix, then record
12018 // the range containing the prefix.
12019 if line_bytes
12020 .by_ref()
12021 .take(comment_prefix.len())
12022 .eq(comment_prefix.bytes())
12023 {
12024 // Include any whitespace that matches the comment prefix.
12025 let matching_whitespace_len = line_bytes
12026 .zip(comment_prefix_whitespace.bytes())
12027 .take_while(|(a, b)| a == b)
12028 .count() as u32;
12029 let end = Point::new(
12030 start.row,
12031 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12032 );
12033 start..end
12034 } else {
12035 start..start
12036 }
12037 }
12038
12039 fn comment_suffix_range(
12040 snapshot: &MultiBufferSnapshot,
12041 row: MultiBufferRow,
12042 comment_suffix: &str,
12043 comment_suffix_has_leading_space: bool,
12044 ) -> Range<Point> {
12045 let end = Point::new(row.0, snapshot.line_len(row));
12046 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12047
12048 let mut line_end_bytes = snapshot
12049 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12050 .flatten()
12051 .copied();
12052
12053 let leading_space_len = if suffix_start_column > 0
12054 && line_end_bytes.next() == Some(b' ')
12055 && comment_suffix_has_leading_space
12056 {
12057 1
12058 } else {
12059 0
12060 };
12061
12062 // If this line currently begins with the line comment prefix, then record
12063 // the range containing the prefix.
12064 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12065 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12066 start..end
12067 } else {
12068 end..end
12069 }
12070 }
12071
12072 // TODO: Handle selections that cross excerpts
12073 for selection in &mut selections {
12074 let start_column = snapshot
12075 .indent_size_for_line(MultiBufferRow(selection.start.row))
12076 .len;
12077 let language = if let Some(language) =
12078 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12079 {
12080 language
12081 } else {
12082 continue;
12083 };
12084
12085 selection_edit_ranges.clear();
12086
12087 // If multiple selections contain a given row, avoid processing that
12088 // row more than once.
12089 let mut start_row = MultiBufferRow(selection.start.row);
12090 if last_toggled_row == Some(start_row) {
12091 start_row = start_row.next_row();
12092 }
12093 let end_row =
12094 if selection.end.row > selection.start.row && selection.end.column == 0 {
12095 MultiBufferRow(selection.end.row - 1)
12096 } else {
12097 MultiBufferRow(selection.end.row)
12098 };
12099 last_toggled_row = Some(end_row);
12100
12101 if start_row > end_row {
12102 continue;
12103 }
12104
12105 // If the language has line comments, toggle those.
12106 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12107
12108 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12109 if ignore_indent {
12110 full_comment_prefixes = full_comment_prefixes
12111 .into_iter()
12112 .map(|s| Arc::from(s.trim_end()))
12113 .collect();
12114 }
12115
12116 if !full_comment_prefixes.is_empty() {
12117 let first_prefix = full_comment_prefixes
12118 .first()
12119 .expect("prefixes is non-empty");
12120 let prefix_trimmed_lengths = full_comment_prefixes
12121 .iter()
12122 .map(|p| p.trim_end_matches(' ').len())
12123 .collect::<SmallVec<[usize; 4]>>();
12124
12125 let mut all_selection_lines_are_comments = true;
12126
12127 for row in start_row.0..=end_row.0 {
12128 let row = MultiBufferRow(row);
12129 if start_row < end_row && snapshot.is_line_blank(row) {
12130 continue;
12131 }
12132
12133 let prefix_range = full_comment_prefixes
12134 .iter()
12135 .zip(prefix_trimmed_lengths.iter().copied())
12136 .map(|(prefix, trimmed_prefix_len)| {
12137 comment_prefix_range(
12138 snapshot.deref(),
12139 row,
12140 &prefix[..trimmed_prefix_len],
12141 &prefix[trimmed_prefix_len..],
12142 ignore_indent,
12143 )
12144 })
12145 .max_by_key(|range| range.end.column - range.start.column)
12146 .expect("prefixes is non-empty");
12147
12148 if prefix_range.is_empty() {
12149 all_selection_lines_are_comments = false;
12150 }
12151
12152 selection_edit_ranges.push(prefix_range);
12153 }
12154
12155 if all_selection_lines_are_comments {
12156 edits.extend(
12157 selection_edit_ranges
12158 .iter()
12159 .cloned()
12160 .map(|range| (range, empty_str.clone())),
12161 );
12162 } else {
12163 let min_column = selection_edit_ranges
12164 .iter()
12165 .map(|range| range.start.column)
12166 .min()
12167 .unwrap_or(0);
12168 edits.extend(selection_edit_ranges.iter().map(|range| {
12169 let position = Point::new(range.start.row, min_column);
12170 (position..position, first_prefix.clone())
12171 }));
12172 }
12173 } else if let Some((full_comment_prefix, comment_suffix)) =
12174 language.block_comment_delimiters()
12175 {
12176 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12177 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12178 let prefix_range = comment_prefix_range(
12179 snapshot.deref(),
12180 start_row,
12181 comment_prefix,
12182 comment_prefix_whitespace,
12183 ignore_indent,
12184 );
12185 let suffix_range = comment_suffix_range(
12186 snapshot.deref(),
12187 end_row,
12188 comment_suffix.trim_start_matches(' '),
12189 comment_suffix.starts_with(' '),
12190 );
12191
12192 if prefix_range.is_empty() || suffix_range.is_empty() {
12193 edits.push((
12194 prefix_range.start..prefix_range.start,
12195 full_comment_prefix.clone(),
12196 ));
12197 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12198 suffixes_inserted.push((end_row, comment_suffix.len()));
12199 } else {
12200 edits.push((prefix_range, empty_str.clone()));
12201 edits.push((suffix_range, empty_str.clone()));
12202 }
12203 } else {
12204 continue;
12205 }
12206 }
12207
12208 drop(snapshot);
12209 this.buffer.update(cx, |buffer, cx| {
12210 buffer.edit(edits, None, cx);
12211 });
12212
12213 // Adjust selections so that they end before any comment suffixes that
12214 // were inserted.
12215 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12216 let mut selections = this.selections.all::<Point>(cx);
12217 let snapshot = this.buffer.read(cx).read(cx);
12218 for selection in &mut selections {
12219 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12220 match row.cmp(&MultiBufferRow(selection.end.row)) {
12221 Ordering::Less => {
12222 suffixes_inserted.next();
12223 continue;
12224 }
12225 Ordering::Greater => break,
12226 Ordering::Equal => {
12227 if selection.end.column == snapshot.line_len(row) {
12228 if selection.is_empty() {
12229 selection.start.column -= suffix_len as u32;
12230 }
12231 selection.end.column -= suffix_len as u32;
12232 }
12233 break;
12234 }
12235 }
12236 }
12237 }
12238
12239 drop(snapshot);
12240 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12241 s.select(selections)
12242 });
12243
12244 let selections = this.selections.all::<Point>(cx);
12245 let selections_on_single_row = selections.windows(2).all(|selections| {
12246 selections[0].start.row == selections[1].start.row
12247 && selections[0].end.row == selections[1].end.row
12248 && selections[0].start.row == selections[0].end.row
12249 });
12250 let selections_selecting = selections
12251 .iter()
12252 .any(|selection| selection.start != selection.end);
12253 let advance_downwards = action.advance_downwards
12254 && selections_on_single_row
12255 && !selections_selecting
12256 && !matches!(this.mode, EditorMode::SingleLine { .. });
12257
12258 if advance_downwards {
12259 let snapshot = this.buffer.read(cx).snapshot(cx);
12260
12261 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12262 s.move_cursors_with(|display_snapshot, display_point, _| {
12263 let mut point = display_point.to_point(display_snapshot);
12264 point.row += 1;
12265 point = snapshot.clip_point(point, Bias::Left);
12266 let display_point = point.to_display_point(display_snapshot);
12267 let goal = SelectionGoal::HorizontalPosition(
12268 display_snapshot
12269 .x_for_display_point(display_point, text_layout_details)
12270 .into(),
12271 );
12272 (display_point, goal)
12273 })
12274 });
12275 }
12276 });
12277 }
12278
12279 pub fn select_enclosing_symbol(
12280 &mut self,
12281 _: &SelectEnclosingSymbol,
12282 window: &mut Window,
12283 cx: &mut Context<Self>,
12284 ) {
12285 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12286
12287 let buffer = self.buffer.read(cx).snapshot(cx);
12288 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12289
12290 fn update_selection(
12291 selection: &Selection<usize>,
12292 buffer_snap: &MultiBufferSnapshot,
12293 ) -> Option<Selection<usize>> {
12294 let cursor = selection.head();
12295 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12296 for symbol in symbols.iter().rev() {
12297 let start = symbol.range.start.to_offset(buffer_snap);
12298 let end = symbol.range.end.to_offset(buffer_snap);
12299 let new_range = start..end;
12300 if start < selection.start || end > selection.end {
12301 return Some(Selection {
12302 id: selection.id,
12303 start: new_range.start,
12304 end: new_range.end,
12305 goal: SelectionGoal::None,
12306 reversed: selection.reversed,
12307 });
12308 }
12309 }
12310 None
12311 }
12312
12313 let mut selected_larger_symbol = false;
12314 let new_selections = old_selections
12315 .iter()
12316 .map(|selection| match update_selection(selection, &buffer) {
12317 Some(new_selection) => {
12318 if new_selection.range() != selection.range() {
12319 selected_larger_symbol = true;
12320 }
12321 new_selection
12322 }
12323 None => selection.clone(),
12324 })
12325 .collect::<Vec<_>>();
12326
12327 if selected_larger_symbol {
12328 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12329 s.select(new_selections);
12330 });
12331 }
12332 }
12333
12334 pub fn select_larger_syntax_node(
12335 &mut self,
12336 _: &SelectLargerSyntaxNode,
12337 window: &mut Window,
12338 cx: &mut Context<Self>,
12339 ) {
12340 let Some(visible_row_count) = self.visible_row_count() else {
12341 return;
12342 };
12343 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12344 if old_selections.is_empty() {
12345 return;
12346 }
12347
12348 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12349
12350 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12351 let buffer = self.buffer.read(cx).snapshot(cx);
12352
12353 let mut selected_larger_node = false;
12354 let mut new_selections = old_selections
12355 .iter()
12356 .map(|selection| {
12357 let old_range = selection.start..selection.end;
12358 let mut new_range = old_range.clone();
12359 let mut new_node = None;
12360 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12361 {
12362 new_node = Some(node);
12363 new_range = match containing_range {
12364 MultiOrSingleBufferOffsetRange::Single(_) => break,
12365 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12366 };
12367 if !display_map.intersects_fold(new_range.start)
12368 && !display_map.intersects_fold(new_range.end)
12369 {
12370 break;
12371 }
12372 }
12373
12374 if let Some(node) = new_node {
12375 // Log the ancestor, to support using this action as a way to explore TreeSitter
12376 // nodes. Parent and grandparent are also logged because this operation will not
12377 // visit nodes that have the same range as their parent.
12378 log::info!("Node: {node:?}");
12379 let parent = node.parent();
12380 log::info!("Parent: {parent:?}");
12381 let grandparent = parent.and_then(|x| x.parent());
12382 log::info!("Grandparent: {grandparent:?}");
12383 }
12384
12385 selected_larger_node |= new_range != old_range;
12386 Selection {
12387 id: selection.id,
12388 start: new_range.start,
12389 end: new_range.end,
12390 goal: SelectionGoal::None,
12391 reversed: selection.reversed,
12392 }
12393 })
12394 .collect::<Vec<_>>();
12395
12396 if !selected_larger_node {
12397 return; // don't put this call in the history
12398 }
12399
12400 // scroll based on transformation done to the last selection created by the user
12401 let (last_old, last_new) = old_selections
12402 .last()
12403 .zip(new_selections.last().cloned())
12404 .expect("old_selections isn't empty");
12405
12406 // revert selection
12407 let is_selection_reversed = {
12408 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12409 new_selections.last_mut().expect("checked above").reversed =
12410 should_newest_selection_be_reversed;
12411 should_newest_selection_be_reversed
12412 };
12413
12414 if selected_larger_node {
12415 self.select_syntax_node_history.disable_clearing = true;
12416 self.change_selections(None, window, cx, |s| {
12417 s.select(new_selections.clone());
12418 });
12419 self.select_syntax_node_history.disable_clearing = false;
12420 }
12421
12422 let start_row = last_new.start.to_display_point(&display_map).row().0;
12423 let end_row = last_new.end.to_display_point(&display_map).row().0;
12424 let selection_height = end_row - start_row + 1;
12425 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12426
12427 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
12428 let scroll_behavior = if fits_on_the_screen {
12429 self.request_autoscroll(Autoscroll::fit(), cx);
12430 SelectSyntaxNodeScrollBehavior::FitSelection
12431 } else if is_selection_reversed {
12432 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12433 SelectSyntaxNodeScrollBehavior::CursorTop
12434 } else {
12435 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12436 SelectSyntaxNodeScrollBehavior::CursorBottom
12437 };
12438
12439 self.select_syntax_node_history.push((
12440 old_selections,
12441 scroll_behavior,
12442 is_selection_reversed,
12443 ));
12444 }
12445
12446 pub fn select_smaller_syntax_node(
12447 &mut self,
12448 _: &SelectSmallerSyntaxNode,
12449 window: &mut Window,
12450 cx: &mut Context<Self>,
12451 ) {
12452 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12453
12454 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12455 self.select_syntax_node_history.pop()
12456 {
12457 if let Some(selection) = selections.last_mut() {
12458 selection.reversed = is_selection_reversed;
12459 }
12460
12461 self.select_syntax_node_history.disable_clearing = true;
12462 self.change_selections(None, window, cx, |s| {
12463 s.select(selections.to_vec());
12464 });
12465 self.select_syntax_node_history.disable_clearing = false;
12466
12467 match scroll_behavior {
12468 SelectSyntaxNodeScrollBehavior::CursorTop => {
12469 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12470 }
12471 SelectSyntaxNodeScrollBehavior::FitSelection => {
12472 self.request_autoscroll(Autoscroll::fit(), cx);
12473 }
12474 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12475 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12476 }
12477 }
12478 }
12479 }
12480
12481 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12482 if !EditorSettings::get_global(cx).gutter.runnables {
12483 self.clear_tasks();
12484 return Task::ready(());
12485 }
12486 let project = self.project.as_ref().map(Entity::downgrade);
12487 let task_sources = self.lsp_task_sources(cx);
12488 cx.spawn_in(window, async move |editor, cx| {
12489 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12490 let Some(project) = project.and_then(|p| p.upgrade()) else {
12491 return;
12492 };
12493 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
12494 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12495 }) else {
12496 return;
12497 };
12498
12499 let hide_runnables = project
12500 .update(cx, |project, cx| {
12501 // Do not display any test indicators in non-dev server remote projects.
12502 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12503 })
12504 .unwrap_or(true);
12505 if hide_runnables {
12506 return;
12507 }
12508 let new_rows =
12509 cx.background_spawn({
12510 let snapshot = display_snapshot.clone();
12511 async move {
12512 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12513 }
12514 })
12515 .await;
12516 let Ok(lsp_tasks) =
12517 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
12518 else {
12519 return;
12520 };
12521 let lsp_tasks = lsp_tasks.await;
12522
12523 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
12524 lsp_tasks
12525 .into_iter()
12526 .flat_map(|(kind, tasks)| {
12527 tasks.into_iter().filter_map(move |(location, task)| {
12528 Some((kind.clone(), location?, task))
12529 })
12530 })
12531 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
12532 let buffer = location.target.buffer;
12533 let buffer_snapshot = buffer.read(cx).snapshot();
12534 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
12535 |(excerpt_id, snapshot, _)| {
12536 if snapshot.remote_id() == buffer_snapshot.remote_id() {
12537 display_snapshot
12538 .buffer_snapshot
12539 .anchor_in_excerpt(excerpt_id, location.target.range.start)
12540 } else {
12541 None
12542 }
12543 },
12544 );
12545 if let Some(offset) = offset {
12546 let task_buffer_range =
12547 location.target.range.to_point(&buffer_snapshot);
12548 let context_buffer_range =
12549 task_buffer_range.to_offset(&buffer_snapshot);
12550 let context_range = BufferOffset(context_buffer_range.start)
12551 ..BufferOffset(context_buffer_range.end);
12552
12553 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
12554 .or_insert_with(|| RunnableTasks {
12555 templates: Vec::new(),
12556 offset,
12557 column: task_buffer_range.start.column,
12558 extra_variables: HashMap::default(),
12559 context_range,
12560 })
12561 .templates
12562 .push((kind, task.original_task().clone()));
12563 }
12564
12565 acc
12566 })
12567 }) else {
12568 return;
12569 };
12570
12571 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12572 editor
12573 .update(cx, |editor, _| {
12574 editor.clear_tasks();
12575 for (key, mut value) in rows {
12576 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
12577 value.templates.extend(lsp_tasks.templates);
12578 }
12579
12580 editor.insert_tasks(key, value);
12581 }
12582 for (key, value) in lsp_tasks_by_rows {
12583 editor.insert_tasks(key, value);
12584 }
12585 })
12586 .ok();
12587 })
12588 }
12589 fn fetch_runnable_ranges(
12590 snapshot: &DisplaySnapshot,
12591 range: Range<Anchor>,
12592 ) -> Vec<language::RunnableRange> {
12593 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12594 }
12595
12596 fn runnable_rows(
12597 project: Entity<Project>,
12598 snapshot: DisplaySnapshot,
12599 runnable_ranges: Vec<RunnableRange>,
12600 mut cx: AsyncWindowContext,
12601 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
12602 runnable_ranges
12603 .into_iter()
12604 .filter_map(|mut runnable| {
12605 let tasks = cx
12606 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12607 .ok()?;
12608 if tasks.is_empty() {
12609 return None;
12610 }
12611
12612 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12613
12614 let row = snapshot
12615 .buffer_snapshot
12616 .buffer_line_for_row(MultiBufferRow(point.row))?
12617 .1
12618 .start
12619 .row;
12620
12621 let context_range =
12622 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12623 Some((
12624 (runnable.buffer_id, row),
12625 RunnableTasks {
12626 templates: tasks,
12627 offset: snapshot
12628 .buffer_snapshot
12629 .anchor_before(runnable.run_range.start),
12630 context_range,
12631 column: point.column,
12632 extra_variables: runnable.extra_captures,
12633 },
12634 ))
12635 })
12636 .collect()
12637 }
12638
12639 fn templates_with_tags(
12640 project: &Entity<Project>,
12641 runnable: &mut Runnable,
12642 cx: &mut App,
12643 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12644 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12645 let (worktree_id, file) = project
12646 .buffer_for_id(runnable.buffer, cx)
12647 .and_then(|buffer| buffer.read(cx).file())
12648 .map(|file| (file.worktree_id(cx), file.clone()))
12649 .unzip();
12650
12651 (
12652 project.task_store().read(cx).task_inventory().cloned(),
12653 worktree_id,
12654 file,
12655 )
12656 });
12657
12658 let mut templates_with_tags = mem::take(&mut runnable.tags)
12659 .into_iter()
12660 .flat_map(|RunnableTag(tag)| {
12661 inventory
12662 .as_ref()
12663 .into_iter()
12664 .flat_map(|inventory| {
12665 inventory.read(cx).list_tasks(
12666 file.clone(),
12667 Some(runnable.language.clone()),
12668 worktree_id,
12669 cx,
12670 )
12671 })
12672 .filter(move |(_, template)| {
12673 template.tags.iter().any(|source_tag| source_tag == &tag)
12674 })
12675 })
12676 .sorted_by_key(|(kind, _)| kind.to_owned())
12677 .collect::<Vec<_>>();
12678 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
12679 // Strongest source wins; if we have worktree tag binding, prefer that to
12680 // global and language bindings;
12681 // if we have a global binding, prefer that to language binding.
12682 let first_mismatch = templates_with_tags
12683 .iter()
12684 .position(|(tag_source, _)| tag_source != leading_tag_source);
12685 if let Some(index) = first_mismatch {
12686 templates_with_tags.truncate(index);
12687 }
12688 }
12689
12690 templates_with_tags
12691 }
12692
12693 pub fn move_to_enclosing_bracket(
12694 &mut self,
12695 _: &MoveToEnclosingBracket,
12696 window: &mut Window,
12697 cx: &mut Context<Self>,
12698 ) {
12699 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12700 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12701 s.move_offsets_with(|snapshot, selection| {
12702 let Some(enclosing_bracket_ranges) =
12703 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12704 else {
12705 return;
12706 };
12707
12708 let mut best_length = usize::MAX;
12709 let mut best_inside = false;
12710 let mut best_in_bracket_range = false;
12711 let mut best_destination = None;
12712 for (open, close) in enclosing_bracket_ranges {
12713 let close = close.to_inclusive();
12714 let length = close.end() - open.start;
12715 let inside = selection.start >= open.end && selection.end <= *close.start();
12716 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12717 || close.contains(&selection.head());
12718
12719 // If best is next to a bracket and current isn't, skip
12720 if !in_bracket_range && best_in_bracket_range {
12721 continue;
12722 }
12723
12724 // Prefer smaller lengths unless best is inside and current isn't
12725 if length > best_length && (best_inside || !inside) {
12726 continue;
12727 }
12728
12729 best_length = length;
12730 best_inside = inside;
12731 best_in_bracket_range = in_bracket_range;
12732 best_destination = Some(
12733 if close.contains(&selection.start) && close.contains(&selection.end) {
12734 if inside { open.end } else { open.start }
12735 } else if inside {
12736 *close.start()
12737 } else {
12738 *close.end()
12739 },
12740 );
12741 }
12742
12743 if let Some(destination) = best_destination {
12744 selection.collapse_to(destination, SelectionGoal::None);
12745 }
12746 })
12747 });
12748 }
12749
12750 pub fn undo_selection(
12751 &mut self,
12752 _: &UndoSelection,
12753 window: &mut Window,
12754 cx: &mut Context<Self>,
12755 ) {
12756 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12757 self.end_selection(window, cx);
12758 self.selection_history.mode = SelectionHistoryMode::Undoing;
12759 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12760 self.change_selections(None, window, cx, |s| {
12761 s.select_anchors(entry.selections.to_vec())
12762 });
12763 self.select_next_state = entry.select_next_state;
12764 self.select_prev_state = entry.select_prev_state;
12765 self.add_selections_state = entry.add_selections_state;
12766 self.request_autoscroll(Autoscroll::newest(), cx);
12767 }
12768 self.selection_history.mode = SelectionHistoryMode::Normal;
12769 }
12770
12771 pub fn redo_selection(
12772 &mut self,
12773 _: &RedoSelection,
12774 window: &mut Window,
12775 cx: &mut Context<Self>,
12776 ) {
12777 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12778 self.end_selection(window, cx);
12779 self.selection_history.mode = SelectionHistoryMode::Redoing;
12780 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12781 self.change_selections(None, window, cx, |s| {
12782 s.select_anchors(entry.selections.to_vec())
12783 });
12784 self.select_next_state = entry.select_next_state;
12785 self.select_prev_state = entry.select_prev_state;
12786 self.add_selections_state = entry.add_selections_state;
12787 self.request_autoscroll(Autoscroll::newest(), cx);
12788 }
12789 self.selection_history.mode = SelectionHistoryMode::Normal;
12790 }
12791
12792 pub fn expand_excerpts(
12793 &mut self,
12794 action: &ExpandExcerpts,
12795 _: &mut Window,
12796 cx: &mut Context<Self>,
12797 ) {
12798 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12799 }
12800
12801 pub fn expand_excerpts_down(
12802 &mut self,
12803 action: &ExpandExcerptsDown,
12804 _: &mut Window,
12805 cx: &mut Context<Self>,
12806 ) {
12807 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12808 }
12809
12810 pub fn expand_excerpts_up(
12811 &mut self,
12812 action: &ExpandExcerptsUp,
12813 _: &mut Window,
12814 cx: &mut Context<Self>,
12815 ) {
12816 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12817 }
12818
12819 pub fn expand_excerpts_for_direction(
12820 &mut self,
12821 lines: u32,
12822 direction: ExpandExcerptDirection,
12823
12824 cx: &mut Context<Self>,
12825 ) {
12826 let selections = self.selections.disjoint_anchors();
12827
12828 let lines = if lines == 0 {
12829 EditorSettings::get_global(cx).expand_excerpt_lines
12830 } else {
12831 lines
12832 };
12833
12834 self.buffer.update(cx, |buffer, cx| {
12835 let snapshot = buffer.snapshot(cx);
12836 let mut excerpt_ids = selections
12837 .iter()
12838 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12839 .collect::<Vec<_>>();
12840 excerpt_ids.sort();
12841 excerpt_ids.dedup();
12842 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12843 })
12844 }
12845
12846 pub fn expand_excerpt(
12847 &mut self,
12848 excerpt: ExcerptId,
12849 direction: ExpandExcerptDirection,
12850 window: &mut Window,
12851 cx: &mut Context<Self>,
12852 ) {
12853 let current_scroll_position = self.scroll_position(cx);
12854 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
12855 let mut should_scroll_up = false;
12856
12857 if direction == ExpandExcerptDirection::Down {
12858 let multi_buffer = self.buffer.read(cx);
12859 let snapshot = multi_buffer.snapshot(cx);
12860 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
12861 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
12862 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
12863 let buffer_snapshot = buffer.read(cx).snapshot();
12864 let excerpt_end_row =
12865 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
12866 let last_row = buffer_snapshot.max_point().row;
12867 let lines_below = last_row.saturating_sub(excerpt_end_row);
12868 should_scroll_up = lines_below >= lines_to_expand;
12869 }
12870 }
12871 }
12872 }
12873
12874 self.buffer.update(cx, |buffer, cx| {
12875 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
12876 });
12877
12878 if should_scroll_up {
12879 let new_scroll_position =
12880 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
12881 self.set_scroll_position(new_scroll_position, window, cx);
12882 }
12883 }
12884
12885 pub fn go_to_singleton_buffer_point(
12886 &mut self,
12887 point: Point,
12888 window: &mut Window,
12889 cx: &mut Context<Self>,
12890 ) {
12891 self.go_to_singleton_buffer_range(point..point, window, cx);
12892 }
12893
12894 pub fn go_to_singleton_buffer_range(
12895 &mut self,
12896 range: Range<Point>,
12897 window: &mut Window,
12898 cx: &mut Context<Self>,
12899 ) {
12900 let multibuffer = self.buffer().read(cx);
12901 let Some(buffer) = multibuffer.as_singleton() else {
12902 return;
12903 };
12904 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12905 return;
12906 };
12907 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12908 return;
12909 };
12910 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12911 s.select_anchor_ranges([start..end])
12912 });
12913 }
12914
12915 fn go_to_diagnostic(
12916 &mut self,
12917 _: &GoToDiagnostic,
12918 window: &mut Window,
12919 cx: &mut Context<Self>,
12920 ) {
12921 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12922 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12923 }
12924
12925 fn go_to_prev_diagnostic(
12926 &mut self,
12927 _: &GoToPreviousDiagnostic,
12928 window: &mut Window,
12929 cx: &mut Context<Self>,
12930 ) {
12931 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12932 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12933 }
12934
12935 pub fn go_to_diagnostic_impl(
12936 &mut self,
12937 direction: Direction,
12938 window: &mut Window,
12939 cx: &mut Context<Self>,
12940 ) {
12941 let buffer = self.buffer.read(cx).snapshot(cx);
12942 let selection = self.selections.newest::<usize>(cx);
12943 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12944 if direction == Direction::Next {
12945 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12946 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12947 return;
12948 };
12949 self.activate_diagnostics(
12950 buffer_id,
12951 popover.local_diagnostic.diagnostic.group_id,
12952 window,
12953 cx,
12954 );
12955 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12956 let primary_range_start = active_diagnostics.primary_range.start;
12957 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12958 let mut new_selection = s.newest_anchor().clone();
12959 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12960 s.select_anchors(vec![new_selection.clone()]);
12961 });
12962 self.refresh_inline_completion(false, true, window, cx);
12963 }
12964 return;
12965 }
12966 }
12967
12968 let active_group_id = self
12969 .active_diagnostics
12970 .as_ref()
12971 .map(|active_group| active_group.group_id);
12972 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12973 active_diagnostics
12974 .primary_range
12975 .to_offset(&buffer)
12976 .to_inclusive()
12977 });
12978 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12979 if active_primary_range.contains(&selection.head()) {
12980 *active_primary_range.start()
12981 } else {
12982 selection.head()
12983 }
12984 } else {
12985 selection.head()
12986 };
12987
12988 let snapshot = self.snapshot(window, cx);
12989 let primary_diagnostics_before = buffer
12990 .diagnostics_in_range::<usize>(0..search_start)
12991 .filter(|entry| entry.diagnostic.is_primary)
12992 .filter(|entry| entry.range.start != entry.range.end)
12993 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12994 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12995 .collect::<Vec<_>>();
12996 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12997 primary_diagnostics_before
12998 .iter()
12999 .position(|entry| entry.diagnostic.group_id == active_group_id)
13000 });
13001
13002 let primary_diagnostics_after = buffer
13003 .diagnostics_in_range::<usize>(search_start..buffer.len())
13004 .filter(|entry| entry.diagnostic.is_primary)
13005 .filter(|entry| entry.range.start != entry.range.end)
13006 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
13007 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
13008 .collect::<Vec<_>>();
13009 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
13010 primary_diagnostics_after
13011 .iter()
13012 .enumerate()
13013 .rev()
13014 .find_map(|(i, entry)| {
13015 if entry.diagnostic.group_id == active_group_id {
13016 Some(i)
13017 } else {
13018 None
13019 }
13020 })
13021 });
13022
13023 let next_primary_diagnostic = match direction {
13024 Direction::Prev => primary_diagnostics_before
13025 .iter()
13026 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
13027 .rev()
13028 .next(),
13029 Direction::Next => primary_diagnostics_after
13030 .iter()
13031 .skip(
13032 last_same_group_diagnostic_after
13033 .map(|index| index + 1)
13034 .unwrap_or(0),
13035 )
13036 .next(),
13037 };
13038
13039 // Cycle around to the start of the buffer, potentially moving back to the start of
13040 // the currently active diagnostic.
13041 let cycle_around = || match direction {
13042 Direction::Prev => primary_diagnostics_after
13043 .iter()
13044 .rev()
13045 .chain(primary_diagnostics_before.iter().rev())
13046 .next(),
13047 Direction::Next => primary_diagnostics_before
13048 .iter()
13049 .chain(primary_diagnostics_after.iter())
13050 .next(),
13051 };
13052
13053 if let Some((primary_range, group_id)) = next_primary_diagnostic
13054 .or_else(cycle_around)
13055 .map(|entry| (&entry.range, entry.diagnostic.group_id))
13056 {
13057 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
13058 return;
13059 };
13060 self.activate_diagnostics(buffer_id, group_id, window, cx);
13061 if self.active_diagnostics.is_some() {
13062 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13063 s.select(vec![Selection {
13064 id: selection.id,
13065 start: primary_range.start,
13066 end: primary_range.start,
13067 reversed: false,
13068 goal: SelectionGoal::None,
13069 }]);
13070 });
13071 self.refresh_inline_completion(false, true, window, cx);
13072 }
13073 }
13074 }
13075
13076 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13077 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13078 let snapshot = self.snapshot(window, cx);
13079 let selection = self.selections.newest::<Point>(cx);
13080 self.go_to_hunk_before_or_after_position(
13081 &snapshot,
13082 selection.head(),
13083 Direction::Next,
13084 window,
13085 cx,
13086 );
13087 }
13088
13089 pub fn go_to_hunk_before_or_after_position(
13090 &mut self,
13091 snapshot: &EditorSnapshot,
13092 position: Point,
13093 direction: Direction,
13094 window: &mut Window,
13095 cx: &mut Context<Editor>,
13096 ) {
13097 let row = if direction == Direction::Next {
13098 self.hunk_after_position(snapshot, position)
13099 .map(|hunk| hunk.row_range.start)
13100 } else {
13101 self.hunk_before_position(snapshot, position)
13102 };
13103
13104 if let Some(row) = row {
13105 let destination = Point::new(row.0, 0);
13106 let autoscroll = Autoscroll::center();
13107
13108 self.unfold_ranges(&[destination..destination], false, false, cx);
13109 self.change_selections(Some(autoscroll), window, cx, |s| {
13110 s.select_ranges([destination..destination]);
13111 });
13112 }
13113 }
13114
13115 fn hunk_after_position(
13116 &mut self,
13117 snapshot: &EditorSnapshot,
13118 position: Point,
13119 ) -> Option<MultiBufferDiffHunk> {
13120 snapshot
13121 .buffer_snapshot
13122 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13123 .find(|hunk| hunk.row_range.start.0 > position.row)
13124 .or_else(|| {
13125 snapshot
13126 .buffer_snapshot
13127 .diff_hunks_in_range(Point::zero()..position)
13128 .find(|hunk| hunk.row_range.end.0 < position.row)
13129 })
13130 }
13131
13132 fn go_to_prev_hunk(
13133 &mut self,
13134 _: &GoToPreviousHunk,
13135 window: &mut Window,
13136 cx: &mut Context<Self>,
13137 ) {
13138 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13139 let snapshot = self.snapshot(window, cx);
13140 let selection = self.selections.newest::<Point>(cx);
13141 self.go_to_hunk_before_or_after_position(
13142 &snapshot,
13143 selection.head(),
13144 Direction::Prev,
13145 window,
13146 cx,
13147 );
13148 }
13149
13150 fn hunk_before_position(
13151 &mut self,
13152 snapshot: &EditorSnapshot,
13153 position: Point,
13154 ) -> Option<MultiBufferRow> {
13155 snapshot
13156 .buffer_snapshot
13157 .diff_hunk_before(position)
13158 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13159 }
13160
13161 fn go_to_line<T: 'static>(
13162 &mut self,
13163 position: Anchor,
13164 highlight_color: Option<Hsla>,
13165 window: &mut Window,
13166 cx: &mut Context<Self>,
13167 ) {
13168 let snapshot = self.snapshot(window, cx).display_snapshot;
13169 let position = position.to_point(&snapshot.buffer_snapshot);
13170 let start = snapshot
13171 .buffer_snapshot
13172 .clip_point(Point::new(position.row, 0), Bias::Left);
13173 let end = start + Point::new(1, 0);
13174 let start = snapshot.buffer_snapshot.anchor_before(start);
13175 let end = snapshot.buffer_snapshot.anchor_before(end);
13176
13177 self.highlight_rows::<T>(
13178 start..end,
13179 highlight_color
13180 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13181 false,
13182 cx,
13183 );
13184 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13185 }
13186
13187 pub fn go_to_definition(
13188 &mut self,
13189 _: &GoToDefinition,
13190 window: &mut Window,
13191 cx: &mut Context<Self>,
13192 ) -> Task<Result<Navigated>> {
13193 let definition =
13194 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13195 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13196 cx.spawn_in(window, async move |editor, cx| {
13197 if definition.await? == Navigated::Yes {
13198 return Ok(Navigated::Yes);
13199 }
13200 match fallback_strategy {
13201 GoToDefinitionFallback::None => Ok(Navigated::No),
13202 GoToDefinitionFallback::FindAllReferences => {
13203 match editor.update_in(cx, |editor, window, cx| {
13204 editor.find_all_references(&FindAllReferences, window, cx)
13205 })? {
13206 Some(references) => references.await,
13207 None => Ok(Navigated::No),
13208 }
13209 }
13210 }
13211 })
13212 }
13213
13214 pub fn go_to_declaration(
13215 &mut self,
13216 _: &GoToDeclaration,
13217 window: &mut Window,
13218 cx: &mut Context<Self>,
13219 ) -> Task<Result<Navigated>> {
13220 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13221 }
13222
13223 pub fn go_to_declaration_split(
13224 &mut self,
13225 _: &GoToDeclaration,
13226 window: &mut Window,
13227 cx: &mut Context<Self>,
13228 ) -> Task<Result<Navigated>> {
13229 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13230 }
13231
13232 pub fn go_to_implementation(
13233 &mut self,
13234 _: &GoToImplementation,
13235 window: &mut Window,
13236 cx: &mut Context<Self>,
13237 ) -> Task<Result<Navigated>> {
13238 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13239 }
13240
13241 pub fn go_to_implementation_split(
13242 &mut self,
13243 _: &GoToImplementationSplit,
13244 window: &mut Window,
13245 cx: &mut Context<Self>,
13246 ) -> Task<Result<Navigated>> {
13247 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13248 }
13249
13250 pub fn go_to_type_definition(
13251 &mut self,
13252 _: &GoToTypeDefinition,
13253 window: &mut Window,
13254 cx: &mut Context<Self>,
13255 ) -> Task<Result<Navigated>> {
13256 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13257 }
13258
13259 pub fn go_to_definition_split(
13260 &mut self,
13261 _: &GoToDefinitionSplit,
13262 window: &mut Window,
13263 cx: &mut Context<Self>,
13264 ) -> Task<Result<Navigated>> {
13265 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13266 }
13267
13268 pub fn go_to_type_definition_split(
13269 &mut self,
13270 _: &GoToTypeDefinitionSplit,
13271 window: &mut Window,
13272 cx: &mut Context<Self>,
13273 ) -> Task<Result<Navigated>> {
13274 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13275 }
13276
13277 fn go_to_definition_of_kind(
13278 &mut self,
13279 kind: GotoDefinitionKind,
13280 split: bool,
13281 window: &mut Window,
13282 cx: &mut Context<Self>,
13283 ) -> Task<Result<Navigated>> {
13284 let Some(provider) = self.semantics_provider.clone() else {
13285 return Task::ready(Ok(Navigated::No));
13286 };
13287 let head = self.selections.newest::<usize>(cx).head();
13288 let buffer = self.buffer.read(cx);
13289 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13290 text_anchor
13291 } else {
13292 return Task::ready(Ok(Navigated::No));
13293 };
13294
13295 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13296 return Task::ready(Ok(Navigated::No));
13297 };
13298
13299 cx.spawn_in(window, async move |editor, cx| {
13300 let definitions = definitions.await?;
13301 let navigated = editor
13302 .update_in(cx, |editor, window, cx| {
13303 editor.navigate_to_hover_links(
13304 Some(kind),
13305 definitions
13306 .into_iter()
13307 .filter(|location| {
13308 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13309 })
13310 .map(HoverLink::Text)
13311 .collect::<Vec<_>>(),
13312 split,
13313 window,
13314 cx,
13315 )
13316 })?
13317 .await?;
13318 anyhow::Ok(navigated)
13319 })
13320 }
13321
13322 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13323 let selection = self.selections.newest_anchor();
13324 let head = selection.head();
13325 let tail = selection.tail();
13326
13327 let Some((buffer, start_position)) =
13328 self.buffer.read(cx).text_anchor_for_position(head, cx)
13329 else {
13330 return;
13331 };
13332
13333 let end_position = if head != tail {
13334 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13335 return;
13336 };
13337 Some(pos)
13338 } else {
13339 None
13340 };
13341
13342 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13343 let url = if let Some(end_pos) = end_position {
13344 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13345 } else {
13346 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13347 };
13348
13349 if let Some(url) = url {
13350 editor.update(cx, |_, cx| {
13351 cx.open_url(&url);
13352 })
13353 } else {
13354 Ok(())
13355 }
13356 });
13357
13358 url_finder.detach();
13359 }
13360
13361 pub fn open_selected_filename(
13362 &mut self,
13363 _: &OpenSelectedFilename,
13364 window: &mut Window,
13365 cx: &mut Context<Self>,
13366 ) {
13367 let Some(workspace) = self.workspace() else {
13368 return;
13369 };
13370
13371 let position = self.selections.newest_anchor().head();
13372
13373 let Some((buffer, buffer_position)) =
13374 self.buffer.read(cx).text_anchor_for_position(position, cx)
13375 else {
13376 return;
13377 };
13378
13379 let project = self.project.clone();
13380
13381 cx.spawn_in(window, async move |_, cx| {
13382 let result = find_file(&buffer, project, buffer_position, cx).await;
13383
13384 if let Some((_, path)) = result {
13385 workspace
13386 .update_in(cx, |workspace, window, cx| {
13387 workspace.open_resolved_path(path, window, cx)
13388 })?
13389 .await?;
13390 }
13391 anyhow::Ok(())
13392 })
13393 .detach();
13394 }
13395
13396 pub(crate) fn navigate_to_hover_links(
13397 &mut self,
13398 kind: Option<GotoDefinitionKind>,
13399 mut definitions: Vec<HoverLink>,
13400 split: bool,
13401 window: &mut Window,
13402 cx: &mut Context<Editor>,
13403 ) -> Task<Result<Navigated>> {
13404 // If there is one definition, just open it directly
13405 if definitions.len() == 1 {
13406 let definition = definitions.pop().unwrap();
13407
13408 enum TargetTaskResult {
13409 Location(Option<Location>),
13410 AlreadyNavigated,
13411 }
13412
13413 let target_task = match definition {
13414 HoverLink::Text(link) => {
13415 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13416 }
13417 HoverLink::InlayHint(lsp_location, server_id) => {
13418 let computation =
13419 self.compute_target_location(lsp_location, server_id, window, cx);
13420 cx.background_spawn(async move {
13421 let location = computation.await?;
13422 Ok(TargetTaskResult::Location(location))
13423 })
13424 }
13425 HoverLink::Url(url) => {
13426 cx.open_url(&url);
13427 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13428 }
13429 HoverLink::File(path) => {
13430 if let Some(workspace) = self.workspace() {
13431 cx.spawn_in(window, async move |_, cx| {
13432 workspace
13433 .update_in(cx, |workspace, window, cx| {
13434 workspace.open_resolved_path(path, window, cx)
13435 })?
13436 .await
13437 .map(|_| TargetTaskResult::AlreadyNavigated)
13438 })
13439 } else {
13440 Task::ready(Ok(TargetTaskResult::Location(None)))
13441 }
13442 }
13443 };
13444 cx.spawn_in(window, async move |editor, cx| {
13445 let target = match target_task.await.context("target resolution task")? {
13446 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13447 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13448 TargetTaskResult::Location(Some(target)) => target,
13449 };
13450
13451 editor.update_in(cx, |editor, window, cx| {
13452 let Some(workspace) = editor.workspace() else {
13453 return Navigated::No;
13454 };
13455 let pane = workspace.read(cx).active_pane().clone();
13456
13457 let range = target.range.to_point(target.buffer.read(cx));
13458 let range = editor.range_for_match(&range);
13459 let range = collapse_multiline_range(range);
13460
13461 if !split
13462 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13463 {
13464 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13465 } else {
13466 window.defer(cx, move |window, cx| {
13467 let target_editor: Entity<Self> =
13468 workspace.update(cx, |workspace, cx| {
13469 let pane = if split {
13470 workspace.adjacent_pane(window, cx)
13471 } else {
13472 workspace.active_pane().clone()
13473 };
13474
13475 workspace.open_project_item(
13476 pane,
13477 target.buffer.clone(),
13478 true,
13479 true,
13480 window,
13481 cx,
13482 )
13483 });
13484 target_editor.update(cx, |target_editor, cx| {
13485 // When selecting a definition in a different buffer, disable the nav history
13486 // to avoid creating a history entry at the previous cursor location.
13487 pane.update(cx, |pane, _| pane.disable_history());
13488 target_editor.go_to_singleton_buffer_range(range, window, cx);
13489 pane.update(cx, |pane, _| pane.enable_history());
13490 });
13491 });
13492 }
13493 Navigated::Yes
13494 })
13495 })
13496 } else if !definitions.is_empty() {
13497 cx.spawn_in(window, async move |editor, cx| {
13498 let (title, location_tasks, workspace) = editor
13499 .update_in(cx, |editor, window, cx| {
13500 let tab_kind = match kind {
13501 Some(GotoDefinitionKind::Implementation) => "Implementations",
13502 _ => "Definitions",
13503 };
13504 let title = definitions
13505 .iter()
13506 .find_map(|definition| match definition {
13507 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13508 let buffer = origin.buffer.read(cx);
13509 format!(
13510 "{} for {}",
13511 tab_kind,
13512 buffer
13513 .text_for_range(origin.range.clone())
13514 .collect::<String>()
13515 )
13516 }),
13517 HoverLink::InlayHint(_, _) => None,
13518 HoverLink::Url(_) => None,
13519 HoverLink::File(_) => None,
13520 })
13521 .unwrap_or(tab_kind.to_string());
13522 let location_tasks = definitions
13523 .into_iter()
13524 .map(|definition| match definition {
13525 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13526 HoverLink::InlayHint(lsp_location, server_id) => editor
13527 .compute_target_location(lsp_location, server_id, window, cx),
13528 HoverLink::Url(_) => Task::ready(Ok(None)),
13529 HoverLink::File(_) => Task::ready(Ok(None)),
13530 })
13531 .collect::<Vec<_>>();
13532 (title, location_tasks, editor.workspace().clone())
13533 })
13534 .context("location tasks preparation")?;
13535
13536 let locations = future::join_all(location_tasks)
13537 .await
13538 .into_iter()
13539 .filter_map(|location| location.transpose())
13540 .collect::<Result<_>>()
13541 .context("location tasks")?;
13542
13543 let Some(workspace) = workspace else {
13544 return Ok(Navigated::No);
13545 };
13546 let opened = workspace
13547 .update_in(cx, |workspace, window, cx| {
13548 Self::open_locations_in_multibuffer(
13549 workspace,
13550 locations,
13551 title,
13552 split,
13553 MultibufferSelectionMode::First,
13554 window,
13555 cx,
13556 )
13557 })
13558 .ok();
13559
13560 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13561 })
13562 } else {
13563 Task::ready(Ok(Navigated::No))
13564 }
13565 }
13566
13567 fn compute_target_location(
13568 &self,
13569 lsp_location: lsp::Location,
13570 server_id: LanguageServerId,
13571 window: &mut Window,
13572 cx: &mut Context<Self>,
13573 ) -> Task<anyhow::Result<Option<Location>>> {
13574 let Some(project) = self.project.clone() else {
13575 return Task::ready(Ok(None));
13576 };
13577
13578 cx.spawn_in(window, async move |editor, cx| {
13579 let location_task = editor.update(cx, |_, cx| {
13580 project.update(cx, |project, cx| {
13581 let language_server_name = project
13582 .language_server_statuses(cx)
13583 .find(|(id, _)| server_id == *id)
13584 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13585 language_server_name.map(|language_server_name| {
13586 project.open_local_buffer_via_lsp(
13587 lsp_location.uri.clone(),
13588 server_id,
13589 language_server_name,
13590 cx,
13591 )
13592 })
13593 })
13594 })?;
13595 let location = match location_task {
13596 Some(task) => Some({
13597 let target_buffer_handle = task.await.context("open local buffer")?;
13598 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13599 let target_start = target_buffer
13600 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13601 let target_end = target_buffer
13602 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13603 target_buffer.anchor_after(target_start)
13604 ..target_buffer.anchor_before(target_end)
13605 })?;
13606 Location {
13607 buffer: target_buffer_handle,
13608 range,
13609 }
13610 }),
13611 None => None,
13612 };
13613 Ok(location)
13614 })
13615 }
13616
13617 pub fn find_all_references(
13618 &mut self,
13619 _: &FindAllReferences,
13620 window: &mut Window,
13621 cx: &mut Context<Self>,
13622 ) -> Option<Task<Result<Navigated>>> {
13623 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13624
13625 let selection = self.selections.newest::<usize>(cx);
13626 let multi_buffer = self.buffer.read(cx);
13627 let head = selection.head();
13628
13629 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13630 let head_anchor = multi_buffer_snapshot.anchor_at(
13631 head,
13632 if head < selection.tail() {
13633 Bias::Right
13634 } else {
13635 Bias::Left
13636 },
13637 );
13638
13639 match self
13640 .find_all_references_task_sources
13641 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13642 {
13643 Ok(_) => {
13644 log::info!(
13645 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13646 );
13647 return None;
13648 }
13649 Err(i) => {
13650 self.find_all_references_task_sources.insert(i, head_anchor);
13651 }
13652 }
13653
13654 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13655 let workspace = self.workspace()?;
13656 let project = workspace.read(cx).project().clone();
13657 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13658 Some(cx.spawn_in(window, async move |editor, cx| {
13659 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13660 if let Ok(i) = editor
13661 .find_all_references_task_sources
13662 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13663 {
13664 editor.find_all_references_task_sources.remove(i);
13665 }
13666 });
13667
13668 let locations = references.await?;
13669 if locations.is_empty() {
13670 return anyhow::Ok(Navigated::No);
13671 }
13672
13673 workspace.update_in(cx, |workspace, window, cx| {
13674 let title = locations
13675 .first()
13676 .as_ref()
13677 .map(|location| {
13678 let buffer = location.buffer.read(cx);
13679 format!(
13680 "References to `{}`",
13681 buffer
13682 .text_for_range(location.range.clone())
13683 .collect::<String>()
13684 )
13685 })
13686 .unwrap();
13687 Self::open_locations_in_multibuffer(
13688 workspace,
13689 locations,
13690 title,
13691 false,
13692 MultibufferSelectionMode::First,
13693 window,
13694 cx,
13695 );
13696 Navigated::Yes
13697 })
13698 }))
13699 }
13700
13701 /// Opens a multibuffer with the given project locations in it
13702 pub fn open_locations_in_multibuffer(
13703 workspace: &mut Workspace,
13704 mut locations: Vec<Location>,
13705 title: String,
13706 split: bool,
13707 multibuffer_selection_mode: MultibufferSelectionMode,
13708 window: &mut Window,
13709 cx: &mut Context<Workspace>,
13710 ) {
13711 // If there are multiple definitions, open them in a multibuffer
13712 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13713 let mut locations = locations.into_iter().peekable();
13714 let mut ranges: Vec<Range<Anchor>> = Vec::new();
13715 let capability = workspace.project().read(cx).capability();
13716
13717 let excerpt_buffer = cx.new(|cx| {
13718 let mut multibuffer = MultiBuffer::new(capability);
13719 while let Some(location) = locations.next() {
13720 let buffer = location.buffer.read(cx);
13721 let mut ranges_for_buffer = Vec::new();
13722 let range = location.range.to_point(buffer);
13723 ranges_for_buffer.push(range.clone());
13724
13725 while let Some(next_location) = locations.peek() {
13726 if next_location.buffer == location.buffer {
13727 ranges_for_buffer.push(next_location.range.to_point(buffer));
13728 locations.next();
13729 } else {
13730 break;
13731 }
13732 }
13733
13734 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13735 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
13736 PathKey::for_buffer(&location.buffer, cx),
13737 location.buffer.clone(),
13738 ranges_for_buffer,
13739 DEFAULT_MULTIBUFFER_CONTEXT,
13740 cx,
13741 );
13742 ranges.extend(new_ranges)
13743 }
13744
13745 multibuffer.with_title(title)
13746 });
13747
13748 let editor = cx.new(|cx| {
13749 Editor::for_multibuffer(
13750 excerpt_buffer,
13751 Some(workspace.project().clone()),
13752 window,
13753 cx,
13754 )
13755 });
13756 editor.update(cx, |editor, cx| {
13757 match multibuffer_selection_mode {
13758 MultibufferSelectionMode::First => {
13759 if let Some(first_range) = ranges.first() {
13760 editor.change_selections(None, window, cx, |selections| {
13761 selections.clear_disjoint();
13762 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13763 });
13764 }
13765 editor.highlight_background::<Self>(
13766 &ranges,
13767 |theme| theme.editor_highlighted_line_background,
13768 cx,
13769 );
13770 }
13771 MultibufferSelectionMode::All => {
13772 editor.change_selections(None, window, cx, |selections| {
13773 selections.clear_disjoint();
13774 selections.select_anchor_ranges(ranges);
13775 });
13776 }
13777 }
13778 editor.register_buffers_with_language_servers(cx);
13779 });
13780
13781 let item = Box::new(editor);
13782 let item_id = item.item_id();
13783
13784 if split {
13785 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13786 } else {
13787 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13788 let (preview_item_id, preview_item_idx) =
13789 workspace.active_pane().update(cx, |pane, _| {
13790 (pane.preview_item_id(), pane.preview_item_idx())
13791 });
13792
13793 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13794
13795 if let Some(preview_item_id) = preview_item_id {
13796 workspace.active_pane().update(cx, |pane, cx| {
13797 pane.remove_item(preview_item_id, false, false, window, cx);
13798 });
13799 }
13800 } else {
13801 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13802 }
13803 }
13804 workspace.active_pane().update(cx, |pane, cx| {
13805 pane.set_preview_item_id(Some(item_id), cx);
13806 });
13807 }
13808
13809 pub fn rename(
13810 &mut self,
13811 _: &Rename,
13812 window: &mut Window,
13813 cx: &mut Context<Self>,
13814 ) -> Option<Task<Result<()>>> {
13815 use language::ToOffset as _;
13816
13817 let provider = self.semantics_provider.clone()?;
13818 let selection = self.selections.newest_anchor().clone();
13819 let (cursor_buffer, cursor_buffer_position) = self
13820 .buffer
13821 .read(cx)
13822 .text_anchor_for_position(selection.head(), cx)?;
13823 let (tail_buffer, cursor_buffer_position_end) = self
13824 .buffer
13825 .read(cx)
13826 .text_anchor_for_position(selection.tail(), cx)?;
13827 if tail_buffer != cursor_buffer {
13828 return None;
13829 }
13830
13831 let snapshot = cursor_buffer.read(cx).snapshot();
13832 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13833 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13834 let prepare_rename = provider
13835 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13836 .unwrap_or_else(|| Task::ready(Ok(None)));
13837 drop(snapshot);
13838
13839 Some(cx.spawn_in(window, async move |this, cx| {
13840 let rename_range = if let Some(range) = prepare_rename.await? {
13841 Some(range)
13842 } else {
13843 this.update(cx, |this, cx| {
13844 let buffer = this.buffer.read(cx).snapshot(cx);
13845 let mut buffer_highlights = this
13846 .document_highlights_for_position(selection.head(), &buffer)
13847 .filter(|highlight| {
13848 highlight.start.excerpt_id == selection.head().excerpt_id
13849 && highlight.end.excerpt_id == selection.head().excerpt_id
13850 });
13851 buffer_highlights
13852 .next()
13853 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13854 })?
13855 };
13856 if let Some(rename_range) = rename_range {
13857 this.update_in(cx, |this, window, cx| {
13858 let snapshot = cursor_buffer.read(cx).snapshot();
13859 let rename_buffer_range = rename_range.to_offset(&snapshot);
13860 let cursor_offset_in_rename_range =
13861 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13862 let cursor_offset_in_rename_range_end =
13863 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13864
13865 this.take_rename(false, window, cx);
13866 let buffer = this.buffer.read(cx).read(cx);
13867 let cursor_offset = selection.head().to_offset(&buffer);
13868 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13869 let rename_end = rename_start + rename_buffer_range.len();
13870 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13871 let mut old_highlight_id = None;
13872 let old_name: Arc<str> = buffer
13873 .chunks(rename_start..rename_end, true)
13874 .map(|chunk| {
13875 if old_highlight_id.is_none() {
13876 old_highlight_id = chunk.syntax_highlight_id;
13877 }
13878 chunk.text
13879 })
13880 .collect::<String>()
13881 .into();
13882
13883 drop(buffer);
13884
13885 // Position the selection in the rename editor so that it matches the current selection.
13886 this.show_local_selections = false;
13887 let rename_editor = cx.new(|cx| {
13888 let mut editor = Editor::single_line(window, cx);
13889 editor.buffer.update(cx, |buffer, cx| {
13890 buffer.edit([(0..0, old_name.clone())], None, cx)
13891 });
13892 let rename_selection_range = match cursor_offset_in_rename_range
13893 .cmp(&cursor_offset_in_rename_range_end)
13894 {
13895 Ordering::Equal => {
13896 editor.select_all(&SelectAll, window, cx);
13897 return editor;
13898 }
13899 Ordering::Less => {
13900 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13901 }
13902 Ordering::Greater => {
13903 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13904 }
13905 };
13906 if rename_selection_range.end > old_name.len() {
13907 editor.select_all(&SelectAll, window, cx);
13908 } else {
13909 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13910 s.select_ranges([rename_selection_range]);
13911 });
13912 }
13913 editor
13914 });
13915 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13916 if e == &EditorEvent::Focused {
13917 cx.emit(EditorEvent::FocusedIn)
13918 }
13919 })
13920 .detach();
13921
13922 let write_highlights =
13923 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13924 let read_highlights =
13925 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13926 let ranges = write_highlights
13927 .iter()
13928 .flat_map(|(_, ranges)| ranges.iter())
13929 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13930 .cloned()
13931 .collect();
13932
13933 this.highlight_text::<Rename>(
13934 ranges,
13935 HighlightStyle {
13936 fade_out: Some(0.6),
13937 ..Default::default()
13938 },
13939 cx,
13940 );
13941 let rename_focus_handle = rename_editor.focus_handle(cx);
13942 window.focus(&rename_focus_handle);
13943 let block_id = this.insert_blocks(
13944 [BlockProperties {
13945 style: BlockStyle::Flex,
13946 placement: BlockPlacement::Below(range.start),
13947 height: Some(1),
13948 render: Arc::new({
13949 let rename_editor = rename_editor.clone();
13950 move |cx: &mut BlockContext| {
13951 let mut text_style = cx.editor_style.text.clone();
13952 if let Some(highlight_style) = old_highlight_id
13953 .and_then(|h| h.style(&cx.editor_style.syntax))
13954 {
13955 text_style = text_style.highlight(highlight_style);
13956 }
13957 div()
13958 .block_mouse_down()
13959 .pl(cx.anchor_x)
13960 .child(EditorElement::new(
13961 &rename_editor,
13962 EditorStyle {
13963 background: cx.theme().system().transparent,
13964 local_player: cx.editor_style.local_player,
13965 text: text_style,
13966 scrollbar_width: cx.editor_style.scrollbar_width,
13967 syntax: cx.editor_style.syntax.clone(),
13968 status: cx.editor_style.status.clone(),
13969 inlay_hints_style: HighlightStyle {
13970 font_weight: Some(FontWeight::BOLD),
13971 ..make_inlay_hints_style(cx.app)
13972 },
13973 inline_completion_styles: make_suggestion_styles(
13974 cx.app,
13975 ),
13976 ..EditorStyle::default()
13977 },
13978 ))
13979 .into_any_element()
13980 }
13981 }),
13982 priority: 0,
13983 }],
13984 Some(Autoscroll::fit()),
13985 cx,
13986 )[0];
13987 this.pending_rename = Some(RenameState {
13988 range,
13989 old_name,
13990 editor: rename_editor,
13991 block_id,
13992 });
13993 })?;
13994 }
13995
13996 Ok(())
13997 }))
13998 }
13999
14000 pub fn confirm_rename(
14001 &mut self,
14002 _: &ConfirmRename,
14003 window: &mut Window,
14004 cx: &mut Context<Self>,
14005 ) -> Option<Task<Result<()>>> {
14006 let rename = self.take_rename(false, window, cx)?;
14007 let workspace = self.workspace()?.downgrade();
14008 let (buffer, start) = self
14009 .buffer
14010 .read(cx)
14011 .text_anchor_for_position(rename.range.start, cx)?;
14012 let (end_buffer, _) = self
14013 .buffer
14014 .read(cx)
14015 .text_anchor_for_position(rename.range.end, cx)?;
14016 if buffer != end_buffer {
14017 return None;
14018 }
14019
14020 let old_name = rename.old_name;
14021 let new_name = rename.editor.read(cx).text(cx);
14022
14023 let rename = self.semantics_provider.as_ref()?.perform_rename(
14024 &buffer,
14025 start,
14026 new_name.clone(),
14027 cx,
14028 )?;
14029
14030 Some(cx.spawn_in(window, async move |editor, cx| {
14031 let project_transaction = rename.await?;
14032 Self::open_project_transaction(
14033 &editor,
14034 workspace,
14035 project_transaction,
14036 format!("Rename: {} → {}", old_name, new_name),
14037 cx,
14038 )
14039 .await?;
14040
14041 editor.update(cx, |editor, cx| {
14042 editor.refresh_document_highlights(cx);
14043 })?;
14044 Ok(())
14045 }))
14046 }
14047
14048 fn take_rename(
14049 &mut self,
14050 moving_cursor: bool,
14051 window: &mut Window,
14052 cx: &mut Context<Self>,
14053 ) -> Option<RenameState> {
14054 let rename = self.pending_rename.take()?;
14055 if rename.editor.focus_handle(cx).is_focused(window) {
14056 window.focus(&self.focus_handle);
14057 }
14058
14059 self.remove_blocks(
14060 [rename.block_id].into_iter().collect(),
14061 Some(Autoscroll::fit()),
14062 cx,
14063 );
14064 self.clear_highlights::<Rename>(cx);
14065 self.show_local_selections = true;
14066
14067 if moving_cursor {
14068 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14069 editor.selections.newest::<usize>(cx).head()
14070 });
14071
14072 // Update the selection to match the position of the selection inside
14073 // the rename editor.
14074 let snapshot = self.buffer.read(cx).read(cx);
14075 let rename_range = rename.range.to_offset(&snapshot);
14076 let cursor_in_editor = snapshot
14077 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14078 .min(rename_range.end);
14079 drop(snapshot);
14080
14081 self.change_selections(None, window, cx, |s| {
14082 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14083 });
14084 } else {
14085 self.refresh_document_highlights(cx);
14086 }
14087
14088 Some(rename)
14089 }
14090
14091 pub fn pending_rename(&self) -> Option<&RenameState> {
14092 self.pending_rename.as_ref()
14093 }
14094
14095 fn format(
14096 &mut self,
14097 _: &Format,
14098 window: &mut Window,
14099 cx: &mut Context<Self>,
14100 ) -> Option<Task<Result<()>>> {
14101 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14102
14103 let project = match &self.project {
14104 Some(project) => project.clone(),
14105 None => return None,
14106 };
14107
14108 Some(self.perform_format(
14109 project,
14110 FormatTrigger::Manual,
14111 FormatTarget::Buffers,
14112 window,
14113 cx,
14114 ))
14115 }
14116
14117 fn format_selections(
14118 &mut self,
14119 _: &FormatSelections,
14120 window: &mut Window,
14121 cx: &mut Context<Self>,
14122 ) -> Option<Task<Result<()>>> {
14123 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14124
14125 let project = match &self.project {
14126 Some(project) => project.clone(),
14127 None => return None,
14128 };
14129
14130 let ranges = self
14131 .selections
14132 .all_adjusted(cx)
14133 .into_iter()
14134 .map(|selection| selection.range())
14135 .collect_vec();
14136
14137 Some(self.perform_format(
14138 project,
14139 FormatTrigger::Manual,
14140 FormatTarget::Ranges(ranges),
14141 window,
14142 cx,
14143 ))
14144 }
14145
14146 fn perform_format(
14147 &mut self,
14148 project: Entity<Project>,
14149 trigger: FormatTrigger,
14150 target: FormatTarget,
14151 window: &mut Window,
14152 cx: &mut Context<Self>,
14153 ) -> Task<Result<()>> {
14154 let buffer = self.buffer.clone();
14155 let (buffers, target) = match target {
14156 FormatTarget::Buffers => {
14157 let mut buffers = buffer.read(cx).all_buffers();
14158 if trigger == FormatTrigger::Save {
14159 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14160 }
14161 (buffers, LspFormatTarget::Buffers)
14162 }
14163 FormatTarget::Ranges(selection_ranges) => {
14164 let multi_buffer = buffer.read(cx);
14165 let snapshot = multi_buffer.read(cx);
14166 let mut buffers = HashSet::default();
14167 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14168 BTreeMap::new();
14169 for selection_range in selection_ranges {
14170 for (buffer, buffer_range, _) in
14171 snapshot.range_to_buffer_ranges(selection_range)
14172 {
14173 let buffer_id = buffer.remote_id();
14174 let start = buffer.anchor_before(buffer_range.start);
14175 let end = buffer.anchor_after(buffer_range.end);
14176 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14177 buffer_id_to_ranges
14178 .entry(buffer_id)
14179 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14180 .or_insert_with(|| vec![start..end]);
14181 }
14182 }
14183 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14184 }
14185 };
14186
14187 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14188 let selections_prev = transaction_id_prev
14189 .and_then(|transaction_id_prev| {
14190 // default to selections as they were after the last edit, if we have them,
14191 // instead of how they are now.
14192 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14193 // will take you back to where you made the last edit, instead of staying where you scrolled
14194 self.selection_history
14195 .transaction(transaction_id_prev)
14196 .map(|t| t.0.clone())
14197 })
14198 .unwrap_or_else(|| {
14199 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14200 self.selections.disjoint_anchors()
14201 });
14202
14203 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14204 let format = project.update(cx, |project, cx| {
14205 project.format(buffers, target, true, trigger, cx)
14206 });
14207
14208 cx.spawn_in(window, async move |editor, cx| {
14209 let transaction = futures::select_biased! {
14210 transaction = format.log_err().fuse() => transaction,
14211 () = timeout => {
14212 log::warn!("timed out waiting for formatting");
14213 None
14214 }
14215 };
14216
14217 buffer
14218 .update(cx, |buffer, cx| {
14219 if let Some(transaction) = transaction {
14220 if !buffer.is_singleton() {
14221 buffer.push_transaction(&transaction.0, cx);
14222 }
14223 }
14224 cx.notify();
14225 })
14226 .ok();
14227
14228 if let Some(transaction_id_now) =
14229 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14230 {
14231 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14232 if has_new_transaction {
14233 _ = editor.update(cx, |editor, _| {
14234 editor
14235 .selection_history
14236 .insert_transaction(transaction_id_now, selections_prev);
14237 });
14238 }
14239 }
14240
14241 Ok(())
14242 })
14243 }
14244
14245 fn organize_imports(
14246 &mut self,
14247 _: &OrganizeImports,
14248 window: &mut Window,
14249 cx: &mut Context<Self>,
14250 ) -> Option<Task<Result<()>>> {
14251 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14252 let project = match &self.project {
14253 Some(project) => project.clone(),
14254 None => return None,
14255 };
14256 Some(self.perform_code_action_kind(
14257 project,
14258 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14259 window,
14260 cx,
14261 ))
14262 }
14263
14264 fn perform_code_action_kind(
14265 &mut self,
14266 project: Entity<Project>,
14267 kind: CodeActionKind,
14268 window: &mut Window,
14269 cx: &mut Context<Self>,
14270 ) -> Task<Result<()>> {
14271 let buffer = self.buffer.clone();
14272 let buffers = buffer.read(cx).all_buffers();
14273 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14274 let apply_action = project.update(cx, |project, cx| {
14275 project.apply_code_action_kind(buffers, kind, true, cx)
14276 });
14277 cx.spawn_in(window, async move |_, cx| {
14278 let transaction = futures::select_biased! {
14279 () = timeout => {
14280 log::warn!("timed out waiting for executing code action");
14281 None
14282 }
14283 transaction = apply_action.log_err().fuse() => transaction,
14284 };
14285 buffer
14286 .update(cx, |buffer, cx| {
14287 // check if we need this
14288 if let Some(transaction) = transaction {
14289 if !buffer.is_singleton() {
14290 buffer.push_transaction(&transaction.0, cx);
14291 }
14292 }
14293 cx.notify();
14294 })
14295 .ok();
14296 Ok(())
14297 })
14298 }
14299
14300 fn restart_language_server(
14301 &mut self,
14302 _: &RestartLanguageServer,
14303 _: &mut Window,
14304 cx: &mut Context<Self>,
14305 ) {
14306 if let Some(project) = self.project.clone() {
14307 self.buffer.update(cx, |multi_buffer, cx| {
14308 project.update(cx, |project, cx| {
14309 project.restart_language_servers_for_buffers(
14310 multi_buffer.all_buffers().into_iter().collect(),
14311 cx,
14312 );
14313 });
14314 })
14315 }
14316 }
14317
14318 fn stop_language_server(
14319 &mut self,
14320 _: &StopLanguageServer,
14321 _: &mut Window,
14322 cx: &mut Context<Self>,
14323 ) {
14324 if let Some(project) = self.project.clone() {
14325 self.buffer.update(cx, |multi_buffer, cx| {
14326 project.update(cx, |project, cx| {
14327 project.stop_language_servers_for_buffers(
14328 multi_buffer.all_buffers().into_iter().collect(),
14329 cx,
14330 );
14331 cx.emit(project::Event::RefreshInlayHints);
14332 });
14333 });
14334 }
14335 }
14336
14337 fn cancel_language_server_work(
14338 workspace: &mut Workspace,
14339 _: &actions::CancelLanguageServerWork,
14340 _: &mut Window,
14341 cx: &mut Context<Workspace>,
14342 ) {
14343 let project = workspace.project();
14344 let buffers = workspace
14345 .active_item(cx)
14346 .and_then(|item| item.act_as::<Editor>(cx))
14347 .map_or(HashSet::default(), |editor| {
14348 editor.read(cx).buffer.read(cx).all_buffers()
14349 });
14350 project.update(cx, |project, cx| {
14351 project.cancel_language_server_work_for_buffers(buffers, cx);
14352 });
14353 }
14354
14355 fn show_character_palette(
14356 &mut self,
14357 _: &ShowCharacterPalette,
14358 window: &mut Window,
14359 _: &mut Context<Self>,
14360 ) {
14361 window.show_character_palette();
14362 }
14363
14364 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14365 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
14366 let buffer = self.buffer.read(cx).snapshot(cx);
14367 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
14368 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
14369 let is_valid = buffer
14370 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14371 .any(|entry| {
14372 entry.diagnostic.is_primary
14373 && !entry.range.is_empty()
14374 && entry.range.start == primary_range_start
14375 && entry.diagnostic.message == active_diagnostics.primary_message
14376 });
14377
14378 if is_valid != active_diagnostics.is_valid {
14379 active_diagnostics.is_valid = is_valid;
14380 if is_valid {
14381 let mut new_styles = HashMap::default();
14382 for (block_id, diagnostic) in &active_diagnostics.blocks {
14383 new_styles.insert(
14384 *block_id,
14385 diagnostic_block_renderer(diagnostic.clone(), None, true),
14386 );
14387 }
14388 self.display_map.update(cx, |display_map, _cx| {
14389 display_map.replace_blocks(new_styles);
14390 });
14391 } else {
14392 self.dismiss_diagnostics(cx);
14393 }
14394 }
14395 }
14396 }
14397
14398 fn activate_diagnostics(
14399 &mut self,
14400 buffer_id: BufferId,
14401 group_id: usize,
14402 window: &mut Window,
14403 cx: &mut Context<Self>,
14404 ) {
14405 self.dismiss_diagnostics(cx);
14406 let snapshot = self.snapshot(window, cx);
14407 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
14408 let buffer = self.buffer.read(cx).snapshot(cx);
14409
14410 let mut primary_range = None;
14411 let mut primary_message = None;
14412 let diagnostic_group = buffer
14413 .diagnostic_group(buffer_id, group_id)
14414 .filter_map(|entry| {
14415 let start = entry.range.start;
14416 let end = entry.range.end;
14417 if snapshot.is_line_folded(MultiBufferRow(start.row))
14418 && (start.row == end.row
14419 || snapshot.is_line_folded(MultiBufferRow(end.row)))
14420 {
14421 return None;
14422 }
14423 if entry.diagnostic.is_primary {
14424 primary_range = Some(entry.range.clone());
14425 primary_message = Some(entry.diagnostic.message.clone());
14426 }
14427 Some(entry)
14428 })
14429 .collect::<Vec<_>>();
14430 let primary_range = primary_range?;
14431 let primary_message = primary_message?;
14432
14433 let blocks = display_map
14434 .insert_blocks(
14435 diagnostic_group.iter().map(|entry| {
14436 let diagnostic = entry.diagnostic.clone();
14437 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
14438 BlockProperties {
14439 style: BlockStyle::Fixed,
14440 placement: BlockPlacement::Below(
14441 buffer.anchor_after(entry.range.start),
14442 ),
14443 height: Some(message_height),
14444 render: diagnostic_block_renderer(diagnostic, None, true),
14445 priority: 0,
14446 }
14447 }),
14448 cx,
14449 )
14450 .into_iter()
14451 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
14452 .collect();
14453
14454 Some(ActiveDiagnosticGroup {
14455 primary_range: buffer.anchor_before(primary_range.start)
14456 ..buffer.anchor_after(primary_range.end),
14457 primary_message,
14458 group_id,
14459 blocks,
14460 is_valid: true,
14461 })
14462 });
14463 }
14464
14465 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14466 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
14467 self.display_map.update(cx, |display_map, cx| {
14468 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
14469 });
14470 cx.notify();
14471 }
14472 }
14473
14474 /// Disable inline diagnostics rendering for this editor.
14475 pub fn disable_inline_diagnostics(&mut self) {
14476 self.inline_diagnostics_enabled = false;
14477 self.inline_diagnostics_update = Task::ready(());
14478 self.inline_diagnostics.clear();
14479 }
14480
14481 pub fn inline_diagnostics_enabled(&self) -> bool {
14482 self.inline_diagnostics_enabled
14483 }
14484
14485 pub fn show_inline_diagnostics(&self) -> bool {
14486 self.show_inline_diagnostics
14487 }
14488
14489 pub fn toggle_inline_diagnostics(
14490 &mut self,
14491 _: &ToggleInlineDiagnostics,
14492 window: &mut Window,
14493 cx: &mut Context<Editor>,
14494 ) {
14495 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14496 self.refresh_inline_diagnostics(false, window, cx);
14497 }
14498
14499 fn refresh_inline_diagnostics(
14500 &mut self,
14501 debounce: bool,
14502 window: &mut Window,
14503 cx: &mut Context<Self>,
14504 ) {
14505 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14506 self.inline_diagnostics_update = Task::ready(());
14507 self.inline_diagnostics.clear();
14508 return;
14509 }
14510
14511 let debounce_ms = ProjectSettings::get_global(cx)
14512 .diagnostics
14513 .inline
14514 .update_debounce_ms;
14515 let debounce = if debounce && debounce_ms > 0 {
14516 Some(Duration::from_millis(debounce_ms))
14517 } else {
14518 None
14519 };
14520 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14521 if let Some(debounce) = debounce {
14522 cx.background_executor().timer(debounce).await;
14523 }
14524 let Some(snapshot) = editor
14525 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14526 .ok()
14527 else {
14528 return;
14529 };
14530
14531 let new_inline_diagnostics = cx
14532 .background_spawn(async move {
14533 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14534 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14535 let message = diagnostic_entry
14536 .diagnostic
14537 .message
14538 .split_once('\n')
14539 .map(|(line, _)| line)
14540 .map(SharedString::new)
14541 .unwrap_or_else(|| {
14542 SharedString::from(diagnostic_entry.diagnostic.message)
14543 });
14544 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14545 let (Ok(i) | Err(i)) = inline_diagnostics
14546 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14547 inline_diagnostics.insert(
14548 i,
14549 (
14550 start_anchor,
14551 InlineDiagnostic {
14552 message,
14553 group_id: diagnostic_entry.diagnostic.group_id,
14554 start: diagnostic_entry.range.start.to_point(&snapshot),
14555 is_primary: diagnostic_entry.diagnostic.is_primary,
14556 severity: diagnostic_entry.diagnostic.severity,
14557 },
14558 ),
14559 );
14560 }
14561 inline_diagnostics
14562 })
14563 .await;
14564
14565 editor
14566 .update(cx, |editor, cx| {
14567 editor.inline_diagnostics = new_inline_diagnostics;
14568 cx.notify();
14569 })
14570 .ok();
14571 });
14572 }
14573
14574 pub fn set_selections_from_remote(
14575 &mut self,
14576 selections: Vec<Selection<Anchor>>,
14577 pending_selection: Option<Selection<Anchor>>,
14578 window: &mut Window,
14579 cx: &mut Context<Self>,
14580 ) {
14581 let old_cursor_position = self.selections.newest_anchor().head();
14582 self.selections.change_with(cx, |s| {
14583 s.select_anchors(selections);
14584 if let Some(pending_selection) = pending_selection {
14585 s.set_pending(pending_selection, SelectMode::Character);
14586 } else {
14587 s.clear_pending();
14588 }
14589 });
14590 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14591 }
14592
14593 fn push_to_selection_history(&mut self) {
14594 self.selection_history.push(SelectionHistoryEntry {
14595 selections: self.selections.disjoint_anchors(),
14596 select_next_state: self.select_next_state.clone(),
14597 select_prev_state: self.select_prev_state.clone(),
14598 add_selections_state: self.add_selections_state.clone(),
14599 });
14600 }
14601
14602 pub fn transact(
14603 &mut self,
14604 window: &mut Window,
14605 cx: &mut Context<Self>,
14606 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14607 ) -> Option<TransactionId> {
14608 self.start_transaction_at(Instant::now(), window, cx);
14609 update(self, window, cx);
14610 self.end_transaction_at(Instant::now(), cx)
14611 }
14612
14613 pub fn start_transaction_at(
14614 &mut self,
14615 now: Instant,
14616 window: &mut Window,
14617 cx: &mut Context<Self>,
14618 ) {
14619 self.end_selection(window, cx);
14620 if let Some(tx_id) = self
14621 .buffer
14622 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14623 {
14624 self.selection_history
14625 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14626 cx.emit(EditorEvent::TransactionBegun {
14627 transaction_id: tx_id,
14628 })
14629 }
14630 }
14631
14632 pub fn end_transaction_at(
14633 &mut self,
14634 now: Instant,
14635 cx: &mut Context<Self>,
14636 ) -> Option<TransactionId> {
14637 if let Some(transaction_id) = self
14638 .buffer
14639 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14640 {
14641 if let Some((_, end_selections)) =
14642 self.selection_history.transaction_mut(transaction_id)
14643 {
14644 *end_selections = Some(self.selections.disjoint_anchors());
14645 } else {
14646 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14647 }
14648
14649 cx.emit(EditorEvent::Edited { transaction_id });
14650 Some(transaction_id)
14651 } else {
14652 None
14653 }
14654 }
14655
14656 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14657 if self.selection_mark_mode {
14658 self.change_selections(None, window, cx, |s| {
14659 s.move_with(|_, sel| {
14660 sel.collapse_to(sel.head(), SelectionGoal::None);
14661 });
14662 })
14663 }
14664 self.selection_mark_mode = true;
14665 cx.notify();
14666 }
14667
14668 pub fn swap_selection_ends(
14669 &mut self,
14670 _: &actions::SwapSelectionEnds,
14671 window: &mut Window,
14672 cx: &mut Context<Self>,
14673 ) {
14674 self.change_selections(None, window, cx, |s| {
14675 s.move_with(|_, sel| {
14676 if sel.start != sel.end {
14677 sel.reversed = !sel.reversed
14678 }
14679 });
14680 });
14681 self.request_autoscroll(Autoscroll::newest(), cx);
14682 cx.notify();
14683 }
14684
14685 pub fn toggle_fold(
14686 &mut self,
14687 _: &actions::ToggleFold,
14688 window: &mut Window,
14689 cx: &mut Context<Self>,
14690 ) {
14691 if self.is_singleton(cx) {
14692 let selection = self.selections.newest::<Point>(cx);
14693
14694 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14695 let range = if selection.is_empty() {
14696 let point = selection.head().to_display_point(&display_map);
14697 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14698 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14699 .to_point(&display_map);
14700 start..end
14701 } else {
14702 selection.range()
14703 };
14704 if display_map.folds_in_range(range).next().is_some() {
14705 self.unfold_lines(&Default::default(), window, cx)
14706 } else {
14707 self.fold(&Default::default(), window, cx)
14708 }
14709 } else {
14710 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14711 let buffer_ids: HashSet<_> = self
14712 .selections
14713 .disjoint_anchor_ranges()
14714 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14715 .collect();
14716
14717 let should_unfold = buffer_ids
14718 .iter()
14719 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14720
14721 for buffer_id in buffer_ids {
14722 if should_unfold {
14723 self.unfold_buffer(buffer_id, cx);
14724 } else {
14725 self.fold_buffer(buffer_id, cx);
14726 }
14727 }
14728 }
14729 }
14730
14731 pub fn toggle_fold_recursive(
14732 &mut self,
14733 _: &actions::ToggleFoldRecursive,
14734 window: &mut Window,
14735 cx: &mut Context<Self>,
14736 ) {
14737 let selection = self.selections.newest::<Point>(cx);
14738
14739 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14740 let range = if selection.is_empty() {
14741 let point = selection.head().to_display_point(&display_map);
14742 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14743 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14744 .to_point(&display_map);
14745 start..end
14746 } else {
14747 selection.range()
14748 };
14749 if display_map.folds_in_range(range).next().is_some() {
14750 self.unfold_recursive(&Default::default(), window, cx)
14751 } else {
14752 self.fold_recursive(&Default::default(), window, cx)
14753 }
14754 }
14755
14756 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14757 if self.is_singleton(cx) {
14758 let mut to_fold = Vec::new();
14759 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14760 let selections = self.selections.all_adjusted(cx);
14761
14762 for selection in selections {
14763 let range = selection.range().sorted();
14764 let buffer_start_row = range.start.row;
14765
14766 if range.start.row != range.end.row {
14767 let mut found = false;
14768 let mut row = range.start.row;
14769 while row <= range.end.row {
14770 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14771 {
14772 found = true;
14773 row = crease.range().end.row + 1;
14774 to_fold.push(crease);
14775 } else {
14776 row += 1
14777 }
14778 }
14779 if found {
14780 continue;
14781 }
14782 }
14783
14784 for row in (0..=range.start.row).rev() {
14785 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14786 if crease.range().end.row >= buffer_start_row {
14787 to_fold.push(crease);
14788 if row <= range.start.row {
14789 break;
14790 }
14791 }
14792 }
14793 }
14794 }
14795
14796 self.fold_creases(to_fold, true, window, cx);
14797 } else {
14798 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14799 let buffer_ids = self
14800 .selections
14801 .disjoint_anchor_ranges()
14802 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14803 .collect::<HashSet<_>>();
14804 for buffer_id in buffer_ids {
14805 self.fold_buffer(buffer_id, cx);
14806 }
14807 }
14808 }
14809
14810 fn fold_at_level(
14811 &mut self,
14812 fold_at: &FoldAtLevel,
14813 window: &mut Window,
14814 cx: &mut Context<Self>,
14815 ) {
14816 if !self.buffer.read(cx).is_singleton() {
14817 return;
14818 }
14819
14820 let fold_at_level = fold_at.0;
14821 let snapshot = self.buffer.read(cx).snapshot(cx);
14822 let mut to_fold = Vec::new();
14823 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14824
14825 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14826 while start_row < end_row {
14827 match self
14828 .snapshot(window, cx)
14829 .crease_for_buffer_row(MultiBufferRow(start_row))
14830 {
14831 Some(crease) => {
14832 let nested_start_row = crease.range().start.row + 1;
14833 let nested_end_row = crease.range().end.row;
14834
14835 if current_level < fold_at_level {
14836 stack.push((nested_start_row, nested_end_row, current_level + 1));
14837 } else if current_level == fold_at_level {
14838 to_fold.push(crease);
14839 }
14840
14841 start_row = nested_end_row + 1;
14842 }
14843 None => start_row += 1,
14844 }
14845 }
14846 }
14847
14848 self.fold_creases(to_fold, true, window, cx);
14849 }
14850
14851 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14852 if self.buffer.read(cx).is_singleton() {
14853 let mut fold_ranges = Vec::new();
14854 let snapshot = self.buffer.read(cx).snapshot(cx);
14855
14856 for row in 0..snapshot.max_row().0 {
14857 if let Some(foldable_range) = self
14858 .snapshot(window, cx)
14859 .crease_for_buffer_row(MultiBufferRow(row))
14860 {
14861 fold_ranges.push(foldable_range);
14862 }
14863 }
14864
14865 self.fold_creases(fold_ranges, true, window, cx);
14866 } else {
14867 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14868 editor
14869 .update_in(cx, |editor, _, cx| {
14870 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14871 editor.fold_buffer(buffer_id, cx);
14872 }
14873 })
14874 .ok();
14875 });
14876 }
14877 }
14878
14879 pub fn fold_function_bodies(
14880 &mut self,
14881 _: &actions::FoldFunctionBodies,
14882 window: &mut Window,
14883 cx: &mut Context<Self>,
14884 ) {
14885 let snapshot = self.buffer.read(cx).snapshot(cx);
14886
14887 let ranges = snapshot
14888 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14889 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14890 .collect::<Vec<_>>();
14891
14892 let creases = ranges
14893 .into_iter()
14894 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14895 .collect();
14896
14897 self.fold_creases(creases, true, window, cx);
14898 }
14899
14900 pub fn fold_recursive(
14901 &mut self,
14902 _: &actions::FoldRecursive,
14903 window: &mut Window,
14904 cx: &mut Context<Self>,
14905 ) {
14906 let mut to_fold = Vec::new();
14907 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14908 let selections = self.selections.all_adjusted(cx);
14909
14910 for selection in selections {
14911 let range = selection.range().sorted();
14912 let buffer_start_row = range.start.row;
14913
14914 if range.start.row != range.end.row {
14915 let mut found = false;
14916 for row in range.start.row..=range.end.row {
14917 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14918 found = true;
14919 to_fold.push(crease);
14920 }
14921 }
14922 if found {
14923 continue;
14924 }
14925 }
14926
14927 for row in (0..=range.start.row).rev() {
14928 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14929 if crease.range().end.row >= buffer_start_row {
14930 to_fold.push(crease);
14931 } else {
14932 break;
14933 }
14934 }
14935 }
14936 }
14937
14938 self.fold_creases(to_fold, true, window, cx);
14939 }
14940
14941 pub fn fold_at(
14942 &mut self,
14943 buffer_row: MultiBufferRow,
14944 window: &mut Window,
14945 cx: &mut Context<Self>,
14946 ) {
14947 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14948
14949 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14950 let autoscroll = self
14951 .selections
14952 .all::<Point>(cx)
14953 .iter()
14954 .any(|selection| crease.range().overlaps(&selection.range()));
14955
14956 self.fold_creases(vec![crease], autoscroll, window, cx);
14957 }
14958 }
14959
14960 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14961 if self.is_singleton(cx) {
14962 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14963 let buffer = &display_map.buffer_snapshot;
14964 let selections = self.selections.all::<Point>(cx);
14965 let ranges = selections
14966 .iter()
14967 .map(|s| {
14968 let range = s.display_range(&display_map).sorted();
14969 let mut start = range.start.to_point(&display_map);
14970 let mut end = range.end.to_point(&display_map);
14971 start.column = 0;
14972 end.column = buffer.line_len(MultiBufferRow(end.row));
14973 start..end
14974 })
14975 .collect::<Vec<_>>();
14976
14977 self.unfold_ranges(&ranges, true, true, cx);
14978 } else {
14979 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14980 let buffer_ids = self
14981 .selections
14982 .disjoint_anchor_ranges()
14983 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14984 .collect::<HashSet<_>>();
14985 for buffer_id in buffer_ids {
14986 self.unfold_buffer(buffer_id, cx);
14987 }
14988 }
14989 }
14990
14991 pub fn unfold_recursive(
14992 &mut self,
14993 _: &UnfoldRecursive,
14994 _window: &mut Window,
14995 cx: &mut Context<Self>,
14996 ) {
14997 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14998 let selections = self.selections.all::<Point>(cx);
14999 let ranges = selections
15000 .iter()
15001 .map(|s| {
15002 let mut range = s.display_range(&display_map).sorted();
15003 *range.start.column_mut() = 0;
15004 *range.end.column_mut() = display_map.line_len(range.end.row());
15005 let start = range.start.to_point(&display_map);
15006 let end = range.end.to_point(&display_map);
15007 start..end
15008 })
15009 .collect::<Vec<_>>();
15010
15011 self.unfold_ranges(&ranges, true, true, cx);
15012 }
15013
15014 pub fn unfold_at(
15015 &mut self,
15016 buffer_row: MultiBufferRow,
15017 _window: &mut Window,
15018 cx: &mut Context<Self>,
15019 ) {
15020 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15021
15022 let intersection_range = Point::new(buffer_row.0, 0)
15023 ..Point::new(
15024 buffer_row.0,
15025 display_map.buffer_snapshot.line_len(buffer_row),
15026 );
15027
15028 let autoscroll = self
15029 .selections
15030 .all::<Point>(cx)
15031 .iter()
15032 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15033
15034 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15035 }
15036
15037 pub fn unfold_all(
15038 &mut self,
15039 _: &actions::UnfoldAll,
15040 _window: &mut Window,
15041 cx: &mut Context<Self>,
15042 ) {
15043 if self.buffer.read(cx).is_singleton() {
15044 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15045 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15046 } else {
15047 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15048 editor
15049 .update(cx, |editor, cx| {
15050 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15051 editor.unfold_buffer(buffer_id, cx);
15052 }
15053 })
15054 .ok();
15055 });
15056 }
15057 }
15058
15059 pub fn fold_selected_ranges(
15060 &mut self,
15061 _: &FoldSelectedRanges,
15062 window: &mut Window,
15063 cx: &mut Context<Self>,
15064 ) {
15065 let selections = self.selections.all_adjusted(cx);
15066 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15067 let ranges = selections
15068 .into_iter()
15069 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15070 .collect::<Vec<_>>();
15071 self.fold_creases(ranges, true, window, cx);
15072 }
15073
15074 pub fn fold_ranges<T: ToOffset + Clone>(
15075 &mut self,
15076 ranges: Vec<Range<T>>,
15077 auto_scroll: bool,
15078 window: &mut Window,
15079 cx: &mut Context<Self>,
15080 ) {
15081 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15082 let ranges = ranges
15083 .into_iter()
15084 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15085 .collect::<Vec<_>>();
15086 self.fold_creases(ranges, auto_scroll, window, cx);
15087 }
15088
15089 pub fn fold_creases<T: ToOffset + Clone>(
15090 &mut self,
15091 creases: Vec<Crease<T>>,
15092 auto_scroll: bool,
15093 window: &mut Window,
15094 cx: &mut Context<Self>,
15095 ) {
15096 if creases.is_empty() {
15097 return;
15098 }
15099
15100 let mut buffers_affected = HashSet::default();
15101 let multi_buffer = self.buffer().read(cx);
15102 for crease in &creases {
15103 if let Some((_, buffer, _)) =
15104 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15105 {
15106 buffers_affected.insert(buffer.read(cx).remote_id());
15107 };
15108 }
15109
15110 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15111
15112 if auto_scroll {
15113 self.request_autoscroll(Autoscroll::fit(), cx);
15114 }
15115
15116 cx.notify();
15117
15118 if let Some(active_diagnostics) = self.active_diagnostics.take() {
15119 // Clear diagnostics block when folding a range that contains it.
15120 let snapshot = self.snapshot(window, cx);
15121 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
15122 drop(snapshot);
15123 self.active_diagnostics = Some(active_diagnostics);
15124 self.dismiss_diagnostics(cx);
15125 } else {
15126 self.active_diagnostics = Some(active_diagnostics);
15127 }
15128 }
15129
15130 self.scrollbar_marker_state.dirty = true;
15131 self.folds_did_change(cx);
15132 }
15133
15134 /// Removes any folds whose ranges intersect any of the given ranges.
15135 pub fn unfold_ranges<T: ToOffset + Clone>(
15136 &mut self,
15137 ranges: &[Range<T>],
15138 inclusive: bool,
15139 auto_scroll: bool,
15140 cx: &mut Context<Self>,
15141 ) {
15142 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15143 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15144 });
15145 self.folds_did_change(cx);
15146 }
15147
15148 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15149 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15150 return;
15151 }
15152 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15153 self.display_map.update(cx, |display_map, cx| {
15154 display_map.fold_buffers([buffer_id], cx)
15155 });
15156 cx.emit(EditorEvent::BufferFoldToggled {
15157 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15158 folded: true,
15159 });
15160 cx.notify();
15161 }
15162
15163 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15164 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15165 return;
15166 }
15167 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15168 self.display_map.update(cx, |display_map, cx| {
15169 display_map.unfold_buffers([buffer_id], cx);
15170 });
15171 cx.emit(EditorEvent::BufferFoldToggled {
15172 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15173 folded: false,
15174 });
15175 cx.notify();
15176 }
15177
15178 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15179 self.display_map.read(cx).is_buffer_folded(buffer)
15180 }
15181
15182 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15183 self.display_map.read(cx).folded_buffers()
15184 }
15185
15186 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15187 self.display_map.update(cx, |display_map, cx| {
15188 display_map.disable_header_for_buffer(buffer_id, cx);
15189 });
15190 cx.notify();
15191 }
15192
15193 /// Removes any folds with the given ranges.
15194 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15195 &mut self,
15196 ranges: &[Range<T>],
15197 type_id: TypeId,
15198 auto_scroll: bool,
15199 cx: &mut Context<Self>,
15200 ) {
15201 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15202 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15203 });
15204 self.folds_did_change(cx);
15205 }
15206
15207 fn remove_folds_with<T: ToOffset + Clone>(
15208 &mut self,
15209 ranges: &[Range<T>],
15210 auto_scroll: bool,
15211 cx: &mut Context<Self>,
15212 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15213 ) {
15214 if ranges.is_empty() {
15215 return;
15216 }
15217
15218 let mut buffers_affected = HashSet::default();
15219 let multi_buffer = self.buffer().read(cx);
15220 for range in ranges {
15221 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15222 buffers_affected.insert(buffer.read(cx).remote_id());
15223 };
15224 }
15225
15226 self.display_map.update(cx, update);
15227
15228 if auto_scroll {
15229 self.request_autoscroll(Autoscroll::fit(), cx);
15230 }
15231
15232 cx.notify();
15233 self.scrollbar_marker_state.dirty = true;
15234 self.active_indent_guides_state.dirty = true;
15235 }
15236
15237 pub fn update_fold_widths(
15238 &mut self,
15239 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15240 cx: &mut Context<Self>,
15241 ) -> bool {
15242 self.display_map
15243 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15244 }
15245
15246 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15247 self.display_map.read(cx).fold_placeholder.clone()
15248 }
15249
15250 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15251 self.buffer.update(cx, |buffer, cx| {
15252 buffer.set_all_diff_hunks_expanded(cx);
15253 });
15254 }
15255
15256 pub fn expand_all_diff_hunks(
15257 &mut self,
15258 _: &ExpandAllDiffHunks,
15259 _window: &mut Window,
15260 cx: &mut Context<Self>,
15261 ) {
15262 self.buffer.update(cx, |buffer, cx| {
15263 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15264 });
15265 }
15266
15267 pub fn toggle_selected_diff_hunks(
15268 &mut self,
15269 _: &ToggleSelectedDiffHunks,
15270 _window: &mut Window,
15271 cx: &mut Context<Self>,
15272 ) {
15273 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15274 self.toggle_diff_hunks_in_ranges(ranges, cx);
15275 }
15276
15277 pub fn diff_hunks_in_ranges<'a>(
15278 &'a self,
15279 ranges: &'a [Range<Anchor>],
15280 buffer: &'a MultiBufferSnapshot,
15281 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15282 ranges.iter().flat_map(move |range| {
15283 let end_excerpt_id = range.end.excerpt_id;
15284 let range = range.to_point(buffer);
15285 let mut peek_end = range.end;
15286 if range.end.row < buffer.max_row().0 {
15287 peek_end = Point::new(range.end.row + 1, 0);
15288 }
15289 buffer
15290 .diff_hunks_in_range(range.start..peek_end)
15291 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15292 })
15293 }
15294
15295 pub fn has_stageable_diff_hunks_in_ranges(
15296 &self,
15297 ranges: &[Range<Anchor>],
15298 snapshot: &MultiBufferSnapshot,
15299 ) -> bool {
15300 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15301 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15302 }
15303
15304 pub fn toggle_staged_selected_diff_hunks(
15305 &mut self,
15306 _: &::git::ToggleStaged,
15307 _: &mut Window,
15308 cx: &mut Context<Self>,
15309 ) {
15310 let snapshot = self.buffer.read(cx).snapshot(cx);
15311 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15312 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15313 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15314 }
15315
15316 pub fn set_render_diff_hunk_controls(
15317 &mut self,
15318 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15319 cx: &mut Context<Self>,
15320 ) {
15321 self.render_diff_hunk_controls = render_diff_hunk_controls;
15322 cx.notify();
15323 }
15324
15325 pub fn stage_and_next(
15326 &mut self,
15327 _: &::git::StageAndNext,
15328 window: &mut Window,
15329 cx: &mut Context<Self>,
15330 ) {
15331 self.do_stage_or_unstage_and_next(true, window, cx);
15332 }
15333
15334 pub fn unstage_and_next(
15335 &mut self,
15336 _: &::git::UnstageAndNext,
15337 window: &mut Window,
15338 cx: &mut Context<Self>,
15339 ) {
15340 self.do_stage_or_unstage_and_next(false, window, cx);
15341 }
15342
15343 pub fn stage_or_unstage_diff_hunks(
15344 &mut self,
15345 stage: bool,
15346 ranges: Vec<Range<Anchor>>,
15347 cx: &mut Context<Self>,
15348 ) {
15349 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15350 cx.spawn(async move |this, cx| {
15351 task.await?;
15352 this.update(cx, |this, cx| {
15353 let snapshot = this.buffer.read(cx).snapshot(cx);
15354 let chunk_by = this
15355 .diff_hunks_in_ranges(&ranges, &snapshot)
15356 .chunk_by(|hunk| hunk.buffer_id);
15357 for (buffer_id, hunks) in &chunk_by {
15358 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15359 }
15360 })
15361 })
15362 .detach_and_log_err(cx);
15363 }
15364
15365 fn save_buffers_for_ranges_if_needed(
15366 &mut self,
15367 ranges: &[Range<Anchor>],
15368 cx: &mut Context<Editor>,
15369 ) -> Task<Result<()>> {
15370 let multibuffer = self.buffer.read(cx);
15371 let snapshot = multibuffer.read(cx);
15372 let buffer_ids: HashSet<_> = ranges
15373 .iter()
15374 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15375 .collect();
15376 drop(snapshot);
15377
15378 let mut buffers = HashSet::default();
15379 for buffer_id in buffer_ids {
15380 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15381 let buffer = buffer_entity.read(cx);
15382 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15383 {
15384 buffers.insert(buffer_entity);
15385 }
15386 }
15387 }
15388
15389 if let Some(project) = &self.project {
15390 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15391 } else {
15392 Task::ready(Ok(()))
15393 }
15394 }
15395
15396 fn do_stage_or_unstage_and_next(
15397 &mut self,
15398 stage: bool,
15399 window: &mut Window,
15400 cx: &mut Context<Self>,
15401 ) {
15402 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15403
15404 if ranges.iter().any(|range| range.start != range.end) {
15405 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15406 return;
15407 }
15408
15409 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15410 let snapshot = self.snapshot(window, cx);
15411 let position = self.selections.newest::<Point>(cx).head();
15412 let mut row = snapshot
15413 .buffer_snapshot
15414 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15415 .find(|hunk| hunk.row_range.start.0 > position.row)
15416 .map(|hunk| hunk.row_range.start);
15417
15418 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15419 // Outside of the project diff editor, wrap around to the beginning.
15420 if !all_diff_hunks_expanded {
15421 row = row.or_else(|| {
15422 snapshot
15423 .buffer_snapshot
15424 .diff_hunks_in_range(Point::zero()..position)
15425 .find(|hunk| hunk.row_range.end.0 < position.row)
15426 .map(|hunk| hunk.row_range.start)
15427 });
15428 }
15429
15430 if let Some(row) = row {
15431 let destination = Point::new(row.0, 0);
15432 let autoscroll = Autoscroll::center();
15433
15434 self.unfold_ranges(&[destination..destination], false, false, cx);
15435 self.change_selections(Some(autoscroll), window, cx, |s| {
15436 s.select_ranges([destination..destination]);
15437 });
15438 }
15439 }
15440
15441 fn do_stage_or_unstage(
15442 &self,
15443 stage: bool,
15444 buffer_id: BufferId,
15445 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15446 cx: &mut App,
15447 ) -> Option<()> {
15448 let project = self.project.as_ref()?;
15449 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15450 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15451 let buffer_snapshot = buffer.read(cx).snapshot();
15452 let file_exists = buffer_snapshot
15453 .file()
15454 .is_some_and(|file| file.disk_state().exists());
15455 diff.update(cx, |diff, cx| {
15456 diff.stage_or_unstage_hunks(
15457 stage,
15458 &hunks
15459 .map(|hunk| buffer_diff::DiffHunk {
15460 buffer_range: hunk.buffer_range,
15461 diff_base_byte_range: hunk.diff_base_byte_range,
15462 secondary_status: hunk.secondary_status,
15463 range: Point::zero()..Point::zero(), // unused
15464 })
15465 .collect::<Vec<_>>(),
15466 &buffer_snapshot,
15467 file_exists,
15468 cx,
15469 )
15470 });
15471 None
15472 }
15473
15474 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15475 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15476 self.buffer
15477 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15478 }
15479
15480 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15481 self.buffer.update(cx, |buffer, cx| {
15482 let ranges = vec![Anchor::min()..Anchor::max()];
15483 if !buffer.all_diff_hunks_expanded()
15484 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15485 {
15486 buffer.collapse_diff_hunks(ranges, cx);
15487 true
15488 } else {
15489 false
15490 }
15491 })
15492 }
15493
15494 fn toggle_diff_hunks_in_ranges(
15495 &mut self,
15496 ranges: Vec<Range<Anchor>>,
15497 cx: &mut Context<Editor>,
15498 ) {
15499 self.buffer.update(cx, |buffer, cx| {
15500 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15501 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15502 })
15503 }
15504
15505 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15506 self.buffer.update(cx, |buffer, cx| {
15507 let snapshot = buffer.snapshot(cx);
15508 let excerpt_id = range.end.excerpt_id;
15509 let point_range = range.to_point(&snapshot);
15510 let expand = !buffer.single_hunk_is_expanded(range, cx);
15511 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15512 })
15513 }
15514
15515 pub(crate) fn apply_all_diff_hunks(
15516 &mut self,
15517 _: &ApplyAllDiffHunks,
15518 window: &mut Window,
15519 cx: &mut Context<Self>,
15520 ) {
15521 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15522
15523 let buffers = self.buffer.read(cx).all_buffers();
15524 for branch_buffer in buffers {
15525 branch_buffer.update(cx, |branch_buffer, cx| {
15526 branch_buffer.merge_into_base(Vec::new(), cx);
15527 });
15528 }
15529
15530 if let Some(project) = self.project.clone() {
15531 self.save(true, project, window, cx).detach_and_log_err(cx);
15532 }
15533 }
15534
15535 pub(crate) fn apply_selected_diff_hunks(
15536 &mut self,
15537 _: &ApplyDiffHunk,
15538 window: &mut Window,
15539 cx: &mut Context<Self>,
15540 ) {
15541 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15542 let snapshot = self.snapshot(window, cx);
15543 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15544 let mut ranges_by_buffer = HashMap::default();
15545 self.transact(window, cx, |editor, _window, cx| {
15546 for hunk in hunks {
15547 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15548 ranges_by_buffer
15549 .entry(buffer.clone())
15550 .or_insert_with(Vec::new)
15551 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15552 }
15553 }
15554
15555 for (buffer, ranges) in ranges_by_buffer {
15556 buffer.update(cx, |buffer, cx| {
15557 buffer.merge_into_base(ranges, cx);
15558 });
15559 }
15560 });
15561
15562 if let Some(project) = self.project.clone() {
15563 self.save(true, project, window, cx).detach_and_log_err(cx);
15564 }
15565 }
15566
15567 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15568 if hovered != self.gutter_hovered {
15569 self.gutter_hovered = hovered;
15570 cx.notify();
15571 }
15572 }
15573
15574 pub fn insert_blocks(
15575 &mut self,
15576 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15577 autoscroll: Option<Autoscroll>,
15578 cx: &mut Context<Self>,
15579 ) -> Vec<CustomBlockId> {
15580 let blocks = self
15581 .display_map
15582 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15583 if let Some(autoscroll) = autoscroll {
15584 self.request_autoscroll(autoscroll, cx);
15585 }
15586 cx.notify();
15587 blocks
15588 }
15589
15590 pub fn resize_blocks(
15591 &mut self,
15592 heights: HashMap<CustomBlockId, u32>,
15593 autoscroll: Option<Autoscroll>,
15594 cx: &mut Context<Self>,
15595 ) {
15596 self.display_map
15597 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15598 if let Some(autoscroll) = autoscroll {
15599 self.request_autoscroll(autoscroll, cx);
15600 }
15601 cx.notify();
15602 }
15603
15604 pub fn replace_blocks(
15605 &mut self,
15606 renderers: HashMap<CustomBlockId, RenderBlock>,
15607 autoscroll: Option<Autoscroll>,
15608 cx: &mut Context<Self>,
15609 ) {
15610 self.display_map
15611 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15612 if let Some(autoscroll) = autoscroll {
15613 self.request_autoscroll(autoscroll, cx);
15614 }
15615 cx.notify();
15616 }
15617
15618 pub fn remove_blocks(
15619 &mut self,
15620 block_ids: HashSet<CustomBlockId>,
15621 autoscroll: Option<Autoscroll>,
15622 cx: &mut Context<Self>,
15623 ) {
15624 self.display_map.update(cx, |display_map, cx| {
15625 display_map.remove_blocks(block_ids, cx)
15626 });
15627 if let Some(autoscroll) = autoscroll {
15628 self.request_autoscroll(autoscroll, cx);
15629 }
15630 cx.notify();
15631 }
15632
15633 pub fn row_for_block(
15634 &self,
15635 block_id: CustomBlockId,
15636 cx: &mut Context<Self>,
15637 ) -> Option<DisplayRow> {
15638 self.display_map
15639 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15640 }
15641
15642 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15643 self.focused_block = Some(focused_block);
15644 }
15645
15646 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15647 self.focused_block.take()
15648 }
15649
15650 pub fn insert_creases(
15651 &mut self,
15652 creases: impl IntoIterator<Item = Crease<Anchor>>,
15653 cx: &mut Context<Self>,
15654 ) -> Vec<CreaseId> {
15655 self.display_map
15656 .update(cx, |map, cx| map.insert_creases(creases, cx))
15657 }
15658
15659 pub fn remove_creases(
15660 &mut self,
15661 ids: impl IntoIterator<Item = CreaseId>,
15662 cx: &mut Context<Self>,
15663 ) {
15664 self.display_map
15665 .update(cx, |map, cx| map.remove_creases(ids, cx));
15666 }
15667
15668 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15669 self.display_map
15670 .update(cx, |map, cx| map.snapshot(cx))
15671 .longest_row()
15672 }
15673
15674 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15675 self.display_map
15676 .update(cx, |map, cx| map.snapshot(cx))
15677 .max_point()
15678 }
15679
15680 pub fn text(&self, cx: &App) -> String {
15681 self.buffer.read(cx).read(cx).text()
15682 }
15683
15684 pub fn is_empty(&self, cx: &App) -> bool {
15685 self.buffer.read(cx).read(cx).is_empty()
15686 }
15687
15688 pub fn text_option(&self, cx: &App) -> Option<String> {
15689 let text = self.text(cx);
15690 let text = text.trim();
15691
15692 if text.is_empty() {
15693 return None;
15694 }
15695
15696 Some(text.to_string())
15697 }
15698
15699 pub fn set_text(
15700 &mut self,
15701 text: impl Into<Arc<str>>,
15702 window: &mut Window,
15703 cx: &mut Context<Self>,
15704 ) {
15705 self.transact(window, cx, |this, _, cx| {
15706 this.buffer
15707 .read(cx)
15708 .as_singleton()
15709 .expect("you can only call set_text on editors for singleton buffers")
15710 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15711 });
15712 }
15713
15714 pub fn display_text(&self, cx: &mut App) -> String {
15715 self.display_map
15716 .update(cx, |map, cx| map.snapshot(cx))
15717 .text()
15718 }
15719
15720 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15721 let mut wrap_guides = smallvec::smallvec![];
15722
15723 if self.show_wrap_guides == Some(false) {
15724 return wrap_guides;
15725 }
15726
15727 let settings = self.buffer.read(cx).language_settings(cx);
15728 if settings.show_wrap_guides {
15729 match self.soft_wrap_mode(cx) {
15730 SoftWrap::Column(soft_wrap) => {
15731 wrap_guides.push((soft_wrap as usize, true));
15732 }
15733 SoftWrap::Bounded(soft_wrap) => {
15734 wrap_guides.push((soft_wrap as usize, true));
15735 }
15736 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15737 }
15738 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15739 }
15740
15741 wrap_guides
15742 }
15743
15744 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15745 let settings = self.buffer.read(cx).language_settings(cx);
15746 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15747 match mode {
15748 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15749 SoftWrap::None
15750 }
15751 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15752 language_settings::SoftWrap::PreferredLineLength => {
15753 SoftWrap::Column(settings.preferred_line_length)
15754 }
15755 language_settings::SoftWrap::Bounded => {
15756 SoftWrap::Bounded(settings.preferred_line_length)
15757 }
15758 }
15759 }
15760
15761 pub fn set_soft_wrap_mode(
15762 &mut self,
15763 mode: language_settings::SoftWrap,
15764
15765 cx: &mut Context<Self>,
15766 ) {
15767 self.soft_wrap_mode_override = Some(mode);
15768 cx.notify();
15769 }
15770
15771 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15772 self.hard_wrap = hard_wrap;
15773 cx.notify();
15774 }
15775
15776 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15777 self.text_style_refinement = Some(style);
15778 }
15779
15780 /// called by the Element so we know what style we were most recently rendered with.
15781 pub(crate) fn set_style(
15782 &mut self,
15783 style: EditorStyle,
15784 window: &mut Window,
15785 cx: &mut Context<Self>,
15786 ) {
15787 let rem_size = window.rem_size();
15788 self.display_map.update(cx, |map, cx| {
15789 map.set_font(
15790 style.text.font(),
15791 style.text.font_size.to_pixels(rem_size),
15792 cx,
15793 )
15794 });
15795 self.style = Some(style);
15796 }
15797
15798 pub fn style(&self) -> Option<&EditorStyle> {
15799 self.style.as_ref()
15800 }
15801
15802 // Called by the element. This method is not designed to be called outside of the editor
15803 // element's layout code because it does not notify when rewrapping is computed synchronously.
15804 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15805 self.display_map
15806 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15807 }
15808
15809 pub fn set_soft_wrap(&mut self) {
15810 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15811 }
15812
15813 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15814 if self.soft_wrap_mode_override.is_some() {
15815 self.soft_wrap_mode_override.take();
15816 } else {
15817 let soft_wrap = match self.soft_wrap_mode(cx) {
15818 SoftWrap::GitDiff => return,
15819 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15820 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15821 language_settings::SoftWrap::None
15822 }
15823 };
15824 self.soft_wrap_mode_override = Some(soft_wrap);
15825 }
15826 cx.notify();
15827 }
15828
15829 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15830 let Some(workspace) = self.workspace() else {
15831 return;
15832 };
15833 let fs = workspace.read(cx).app_state().fs.clone();
15834 let current_show = TabBarSettings::get_global(cx).show;
15835 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15836 setting.show = Some(!current_show);
15837 });
15838 }
15839
15840 pub fn toggle_indent_guides(
15841 &mut self,
15842 _: &ToggleIndentGuides,
15843 _: &mut Window,
15844 cx: &mut Context<Self>,
15845 ) {
15846 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15847 self.buffer
15848 .read(cx)
15849 .language_settings(cx)
15850 .indent_guides
15851 .enabled
15852 });
15853 self.show_indent_guides = Some(!currently_enabled);
15854 cx.notify();
15855 }
15856
15857 fn should_show_indent_guides(&self) -> Option<bool> {
15858 self.show_indent_guides
15859 }
15860
15861 pub fn toggle_line_numbers(
15862 &mut self,
15863 _: &ToggleLineNumbers,
15864 _: &mut Window,
15865 cx: &mut Context<Self>,
15866 ) {
15867 let mut editor_settings = EditorSettings::get_global(cx).clone();
15868 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15869 EditorSettings::override_global(editor_settings, cx);
15870 }
15871
15872 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15873 if let Some(show_line_numbers) = self.show_line_numbers {
15874 return show_line_numbers;
15875 }
15876 EditorSettings::get_global(cx).gutter.line_numbers
15877 }
15878
15879 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15880 self.use_relative_line_numbers
15881 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15882 }
15883
15884 pub fn toggle_relative_line_numbers(
15885 &mut self,
15886 _: &ToggleRelativeLineNumbers,
15887 _: &mut Window,
15888 cx: &mut Context<Self>,
15889 ) {
15890 let is_relative = self.should_use_relative_line_numbers(cx);
15891 self.set_relative_line_number(Some(!is_relative), cx)
15892 }
15893
15894 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15895 self.use_relative_line_numbers = is_relative;
15896 cx.notify();
15897 }
15898
15899 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15900 self.show_gutter = show_gutter;
15901 cx.notify();
15902 }
15903
15904 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15905 self.show_scrollbars = show_scrollbars;
15906 cx.notify();
15907 }
15908
15909 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15910 self.show_line_numbers = Some(show_line_numbers);
15911 cx.notify();
15912 }
15913
15914 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15915 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15916 cx.notify();
15917 }
15918
15919 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15920 self.show_code_actions = Some(show_code_actions);
15921 cx.notify();
15922 }
15923
15924 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15925 self.show_runnables = Some(show_runnables);
15926 cx.notify();
15927 }
15928
15929 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15930 self.show_breakpoints = Some(show_breakpoints);
15931 cx.notify();
15932 }
15933
15934 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15935 if self.display_map.read(cx).masked != masked {
15936 self.display_map.update(cx, |map, _| map.masked = masked);
15937 }
15938 cx.notify()
15939 }
15940
15941 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15942 self.show_wrap_guides = Some(show_wrap_guides);
15943 cx.notify();
15944 }
15945
15946 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15947 self.show_indent_guides = Some(show_indent_guides);
15948 cx.notify();
15949 }
15950
15951 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15952 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15953 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15954 if let Some(dir) = file.abs_path(cx).parent() {
15955 return Some(dir.to_owned());
15956 }
15957 }
15958
15959 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15960 return Some(project_path.path.to_path_buf());
15961 }
15962 }
15963
15964 None
15965 }
15966
15967 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15968 self.active_excerpt(cx)?
15969 .1
15970 .read(cx)
15971 .file()
15972 .and_then(|f| f.as_local())
15973 }
15974
15975 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15976 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15977 let buffer = buffer.read(cx);
15978 if let Some(project_path) = buffer.project_path(cx) {
15979 let project = self.project.as_ref()?.read(cx);
15980 project.absolute_path(&project_path, cx)
15981 } else {
15982 buffer
15983 .file()
15984 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15985 }
15986 })
15987 }
15988
15989 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15990 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15991 let project_path = buffer.read(cx).project_path(cx)?;
15992 let project = self.project.as_ref()?.read(cx);
15993 let entry = project.entry_for_path(&project_path, cx)?;
15994 let path = entry.path.to_path_buf();
15995 Some(path)
15996 })
15997 }
15998
15999 pub fn reveal_in_finder(
16000 &mut self,
16001 _: &RevealInFileManager,
16002 _window: &mut Window,
16003 cx: &mut Context<Self>,
16004 ) {
16005 if let Some(target) = self.target_file(cx) {
16006 cx.reveal_path(&target.abs_path(cx));
16007 }
16008 }
16009
16010 pub fn copy_path(
16011 &mut self,
16012 _: &zed_actions::workspace::CopyPath,
16013 _window: &mut Window,
16014 cx: &mut Context<Self>,
16015 ) {
16016 if let Some(path) = self.target_file_abs_path(cx) {
16017 if let Some(path) = path.to_str() {
16018 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16019 }
16020 }
16021 }
16022
16023 pub fn copy_relative_path(
16024 &mut self,
16025 _: &zed_actions::workspace::CopyRelativePath,
16026 _window: &mut Window,
16027 cx: &mut Context<Self>,
16028 ) {
16029 if let Some(path) = self.target_file_path(cx) {
16030 if let Some(path) = path.to_str() {
16031 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16032 }
16033 }
16034 }
16035
16036 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16037 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16038 buffer.read(cx).project_path(cx)
16039 } else {
16040 None
16041 }
16042 }
16043
16044 // Returns true if the editor handled a go-to-line request
16045 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16046 maybe!({
16047 let breakpoint_store = self.breakpoint_store.as_ref()?;
16048
16049 let Some((_, _, active_position)) =
16050 breakpoint_store.read(cx).active_position().cloned()
16051 else {
16052 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16053 return None;
16054 };
16055
16056 let snapshot = self
16057 .project
16058 .as_ref()?
16059 .read(cx)
16060 .buffer_for_id(active_position.buffer_id?, cx)?
16061 .read(cx)
16062 .snapshot();
16063
16064 let mut handled = false;
16065 for (id, ExcerptRange { context, .. }) in self
16066 .buffer
16067 .read(cx)
16068 .excerpts_for_buffer(active_position.buffer_id?, cx)
16069 {
16070 if context.start.cmp(&active_position, &snapshot).is_ge()
16071 || context.end.cmp(&active_position, &snapshot).is_lt()
16072 {
16073 continue;
16074 }
16075 let snapshot = self.buffer.read(cx).snapshot(cx);
16076 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
16077
16078 handled = true;
16079 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16080 self.go_to_line::<DebugCurrentRowHighlight>(
16081 multibuffer_anchor,
16082 Some(cx.theme().colors().editor_debugger_active_line_background),
16083 window,
16084 cx,
16085 );
16086
16087 cx.notify();
16088 }
16089 handled.then_some(())
16090 })
16091 .is_some()
16092 }
16093
16094 pub fn copy_file_name_without_extension(
16095 &mut self,
16096 _: &CopyFileNameWithoutExtension,
16097 _: &mut Window,
16098 cx: &mut Context<Self>,
16099 ) {
16100 if let Some(file) = self.target_file(cx) {
16101 if let Some(file_stem) = file.path().file_stem() {
16102 if let Some(name) = file_stem.to_str() {
16103 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16104 }
16105 }
16106 }
16107 }
16108
16109 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16110 if let Some(file) = self.target_file(cx) {
16111 if let Some(file_name) = file.path().file_name() {
16112 if let Some(name) = file_name.to_str() {
16113 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16114 }
16115 }
16116 }
16117 }
16118
16119 pub fn toggle_git_blame(
16120 &mut self,
16121 _: &::git::Blame,
16122 window: &mut Window,
16123 cx: &mut Context<Self>,
16124 ) {
16125 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16126
16127 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16128 self.start_git_blame(true, window, cx);
16129 }
16130
16131 cx.notify();
16132 }
16133
16134 pub fn toggle_git_blame_inline(
16135 &mut self,
16136 _: &ToggleGitBlameInline,
16137 window: &mut Window,
16138 cx: &mut Context<Self>,
16139 ) {
16140 self.toggle_git_blame_inline_internal(true, window, cx);
16141 cx.notify();
16142 }
16143
16144 pub fn open_git_blame_commit(
16145 &mut self,
16146 _: &OpenGitBlameCommit,
16147 window: &mut Window,
16148 cx: &mut Context<Self>,
16149 ) {
16150 self.open_git_blame_commit_internal(window, cx);
16151 }
16152
16153 fn open_git_blame_commit_internal(
16154 &mut self,
16155 window: &mut Window,
16156 cx: &mut Context<Self>,
16157 ) -> Option<()> {
16158 let blame = self.blame.as_ref()?;
16159 let snapshot = self.snapshot(window, cx);
16160 let cursor = self.selections.newest::<Point>(cx).head();
16161 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16162 let blame_entry = blame
16163 .update(cx, |blame, cx| {
16164 blame
16165 .blame_for_rows(
16166 &[RowInfo {
16167 buffer_id: Some(buffer.remote_id()),
16168 buffer_row: Some(point.row),
16169 ..Default::default()
16170 }],
16171 cx,
16172 )
16173 .next()
16174 })
16175 .flatten()?;
16176 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16177 let repo = blame.read(cx).repository(cx)?;
16178 let workspace = self.workspace()?.downgrade();
16179 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16180 None
16181 }
16182
16183 pub fn git_blame_inline_enabled(&self) -> bool {
16184 self.git_blame_inline_enabled
16185 }
16186
16187 pub fn toggle_selection_menu(
16188 &mut self,
16189 _: &ToggleSelectionMenu,
16190 _: &mut Window,
16191 cx: &mut Context<Self>,
16192 ) {
16193 self.show_selection_menu = self
16194 .show_selection_menu
16195 .map(|show_selections_menu| !show_selections_menu)
16196 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16197
16198 cx.notify();
16199 }
16200
16201 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16202 self.show_selection_menu
16203 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16204 }
16205
16206 fn start_git_blame(
16207 &mut self,
16208 user_triggered: bool,
16209 window: &mut Window,
16210 cx: &mut Context<Self>,
16211 ) {
16212 if let Some(project) = self.project.as_ref() {
16213 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16214 return;
16215 };
16216
16217 if buffer.read(cx).file().is_none() {
16218 return;
16219 }
16220
16221 let focused = self.focus_handle(cx).contains_focused(window, cx);
16222
16223 let project = project.clone();
16224 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16225 self.blame_subscription =
16226 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16227 self.blame = Some(blame);
16228 }
16229 }
16230
16231 fn toggle_git_blame_inline_internal(
16232 &mut self,
16233 user_triggered: bool,
16234 window: &mut Window,
16235 cx: &mut Context<Self>,
16236 ) {
16237 if self.git_blame_inline_enabled {
16238 self.git_blame_inline_enabled = false;
16239 self.show_git_blame_inline = false;
16240 self.show_git_blame_inline_delay_task.take();
16241 } else {
16242 self.git_blame_inline_enabled = true;
16243 self.start_git_blame_inline(user_triggered, window, cx);
16244 }
16245
16246 cx.notify();
16247 }
16248
16249 fn start_git_blame_inline(
16250 &mut self,
16251 user_triggered: bool,
16252 window: &mut Window,
16253 cx: &mut Context<Self>,
16254 ) {
16255 self.start_git_blame(user_triggered, window, cx);
16256
16257 if ProjectSettings::get_global(cx)
16258 .git
16259 .inline_blame_delay()
16260 .is_some()
16261 {
16262 self.start_inline_blame_timer(window, cx);
16263 } else {
16264 self.show_git_blame_inline = true
16265 }
16266 }
16267
16268 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16269 self.blame.as_ref()
16270 }
16271
16272 pub fn show_git_blame_gutter(&self) -> bool {
16273 self.show_git_blame_gutter
16274 }
16275
16276 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16277 self.show_git_blame_gutter && self.has_blame_entries(cx)
16278 }
16279
16280 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16281 self.show_git_blame_inline
16282 && (self.focus_handle.is_focused(window)
16283 || self
16284 .git_blame_inline_tooltip
16285 .as_ref()
16286 .and_then(|t| t.upgrade())
16287 .is_some())
16288 && !self.newest_selection_head_on_empty_line(cx)
16289 && self.has_blame_entries(cx)
16290 }
16291
16292 fn has_blame_entries(&self, cx: &App) -> bool {
16293 self.blame()
16294 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16295 }
16296
16297 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16298 let cursor_anchor = self.selections.newest_anchor().head();
16299
16300 let snapshot = self.buffer.read(cx).snapshot(cx);
16301 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16302
16303 snapshot.line_len(buffer_row) == 0
16304 }
16305
16306 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16307 let buffer_and_selection = maybe!({
16308 let selection = self.selections.newest::<Point>(cx);
16309 let selection_range = selection.range();
16310
16311 let multi_buffer = self.buffer().read(cx);
16312 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16313 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16314
16315 let (buffer, range, _) = if selection.reversed {
16316 buffer_ranges.first()
16317 } else {
16318 buffer_ranges.last()
16319 }?;
16320
16321 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16322 ..text::ToPoint::to_point(&range.end, &buffer).row;
16323 Some((
16324 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16325 selection,
16326 ))
16327 });
16328
16329 let Some((buffer, selection)) = buffer_and_selection else {
16330 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16331 };
16332
16333 let Some(project) = self.project.as_ref() else {
16334 return Task::ready(Err(anyhow!("editor does not have project")));
16335 };
16336
16337 project.update(cx, |project, cx| {
16338 project.get_permalink_to_line(&buffer, selection, cx)
16339 })
16340 }
16341
16342 pub fn copy_permalink_to_line(
16343 &mut self,
16344 _: &CopyPermalinkToLine,
16345 window: &mut Window,
16346 cx: &mut Context<Self>,
16347 ) {
16348 let permalink_task = self.get_permalink_to_line(cx);
16349 let workspace = self.workspace();
16350
16351 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16352 Ok(permalink) => {
16353 cx.update(|_, cx| {
16354 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16355 })
16356 .ok();
16357 }
16358 Err(err) => {
16359 let message = format!("Failed to copy permalink: {err}");
16360
16361 Err::<(), anyhow::Error>(err).log_err();
16362
16363 if let Some(workspace) = workspace {
16364 workspace
16365 .update_in(cx, |workspace, _, cx| {
16366 struct CopyPermalinkToLine;
16367
16368 workspace.show_toast(
16369 Toast::new(
16370 NotificationId::unique::<CopyPermalinkToLine>(),
16371 message,
16372 ),
16373 cx,
16374 )
16375 })
16376 .ok();
16377 }
16378 }
16379 })
16380 .detach();
16381 }
16382
16383 pub fn copy_file_location(
16384 &mut self,
16385 _: &CopyFileLocation,
16386 _: &mut Window,
16387 cx: &mut Context<Self>,
16388 ) {
16389 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16390 if let Some(file) = self.target_file(cx) {
16391 if let Some(path) = file.path().to_str() {
16392 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16393 }
16394 }
16395 }
16396
16397 pub fn open_permalink_to_line(
16398 &mut self,
16399 _: &OpenPermalinkToLine,
16400 window: &mut Window,
16401 cx: &mut Context<Self>,
16402 ) {
16403 let permalink_task = self.get_permalink_to_line(cx);
16404 let workspace = self.workspace();
16405
16406 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16407 Ok(permalink) => {
16408 cx.update(|_, cx| {
16409 cx.open_url(permalink.as_ref());
16410 })
16411 .ok();
16412 }
16413 Err(err) => {
16414 let message = format!("Failed to open permalink: {err}");
16415
16416 Err::<(), anyhow::Error>(err).log_err();
16417
16418 if let Some(workspace) = workspace {
16419 workspace
16420 .update(cx, |workspace, cx| {
16421 struct OpenPermalinkToLine;
16422
16423 workspace.show_toast(
16424 Toast::new(
16425 NotificationId::unique::<OpenPermalinkToLine>(),
16426 message,
16427 ),
16428 cx,
16429 )
16430 })
16431 .ok();
16432 }
16433 }
16434 })
16435 .detach();
16436 }
16437
16438 pub fn insert_uuid_v4(
16439 &mut self,
16440 _: &InsertUuidV4,
16441 window: &mut Window,
16442 cx: &mut Context<Self>,
16443 ) {
16444 self.insert_uuid(UuidVersion::V4, window, cx);
16445 }
16446
16447 pub fn insert_uuid_v7(
16448 &mut self,
16449 _: &InsertUuidV7,
16450 window: &mut Window,
16451 cx: &mut Context<Self>,
16452 ) {
16453 self.insert_uuid(UuidVersion::V7, window, cx);
16454 }
16455
16456 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16457 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16458 self.transact(window, cx, |this, window, cx| {
16459 let edits = this
16460 .selections
16461 .all::<Point>(cx)
16462 .into_iter()
16463 .map(|selection| {
16464 let uuid = match version {
16465 UuidVersion::V4 => uuid::Uuid::new_v4(),
16466 UuidVersion::V7 => uuid::Uuid::now_v7(),
16467 };
16468
16469 (selection.range(), uuid.to_string())
16470 });
16471 this.edit(edits, cx);
16472 this.refresh_inline_completion(true, false, window, cx);
16473 });
16474 }
16475
16476 pub fn open_selections_in_multibuffer(
16477 &mut self,
16478 _: &OpenSelectionsInMultibuffer,
16479 window: &mut Window,
16480 cx: &mut Context<Self>,
16481 ) {
16482 let multibuffer = self.buffer.read(cx);
16483
16484 let Some(buffer) = multibuffer.as_singleton() else {
16485 return;
16486 };
16487
16488 let Some(workspace) = self.workspace() else {
16489 return;
16490 };
16491
16492 let locations = self
16493 .selections
16494 .disjoint_anchors()
16495 .iter()
16496 .map(|range| Location {
16497 buffer: buffer.clone(),
16498 range: range.start.text_anchor..range.end.text_anchor,
16499 })
16500 .collect::<Vec<_>>();
16501
16502 let title = multibuffer.title(cx).to_string();
16503
16504 cx.spawn_in(window, async move |_, cx| {
16505 workspace.update_in(cx, |workspace, window, cx| {
16506 Self::open_locations_in_multibuffer(
16507 workspace,
16508 locations,
16509 format!("Selections for '{title}'"),
16510 false,
16511 MultibufferSelectionMode::All,
16512 window,
16513 cx,
16514 );
16515 })
16516 })
16517 .detach();
16518 }
16519
16520 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16521 /// last highlight added will be used.
16522 ///
16523 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16524 pub fn highlight_rows<T: 'static>(
16525 &mut self,
16526 range: Range<Anchor>,
16527 color: Hsla,
16528 should_autoscroll: bool,
16529 cx: &mut Context<Self>,
16530 ) {
16531 let snapshot = self.buffer().read(cx).snapshot(cx);
16532 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16533 let ix = row_highlights.binary_search_by(|highlight| {
16534 Ordering::Equal
16535 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16536 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16537 });
16538
16539 if let Err(mut ix) = ix {
16540 let index = post_inc(&mut self.highlight_order);
16541
16542 // If this range intersects with the preceding highlight, then merge it with
16543 // the preceding highlight. Otherwise insert a new highlight.
16544 let mut merged = false;
16545 if ix > 0 {
16546 let prev_highlight = &mut row_highlights[ix - 1];
16547 if prev_highlight
16548 .range
16549 .end
16550 .cmp(&range.start, &snapshot)
16551 .is_ge()
16552 {
16553 ix -= 1;
16554 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16555 prev_highlight.range.end = range.end;
16556 }
16557 merged = true;
16558 prev_highlight.index = index;
16559 prev_highlight.color = color;
16560 prev_highlight.should_autoscroll = should_autoscroll;
16561 }
16562 }
16563
16564 if !merged {
16565 row_highlights.insert(
16566 ix,
16567 RowHighlight {
16568 range: range.clone(),
16569 index,
16570 color,
16571 should_autoscroll,
16572 },
16573 );
16574 }
16575
16576 // If any of the following highlights intersect with this one, merge them.
16577 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16578 let highlight = &row_highlights[ix];
16579 if next_highlight
16580 .range
16581 .start
16582 .cmp(&highlight.range.end, &snapshot)
16583 .is_le()
16584 {
16585 if next_highlight
16586 .range
16587 .end
16588 .cmp(&highlight.range.end, &snapshot)
16589 .is_gt()
16590 {
16591 row_highlights[ix].range.end = next_highlight.range.end;
16592 }
16593 row_highlights.remove(ix + 1);
16594 } else {
16595 break;
16596 }
16597 }
16598 }
16599 }
16600
16601 /// Remove any highlighted row ranges of the given type that intersect the
16602 /// given ranges.
16603 pub fn remove_highlighted_rows<T: 'static>(
16604 &mut self,
16605 ranges_to_remove: Vec<Range<Anchor>>,
16606 cx: &mut Context<Self>,
16607 ) {
16608 let snapshot = self.buffer().read(cx).snapshot(cx);
16609 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16610 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16611 row_highlights.retain(|highlight| {
16612 while let Some(range_to_remove) = ranges_to_remove.peek() {
16613 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16614 Ordering::Less | Ordering::Equal => {
16615 ranges_to_remove.next();
16616 }
16617 Ordering::Greater => {
16618 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16619 Ordering::Less | Ordering::Equal => {
16620 return false;
16621 }
16622 Ordering::Greater => break,
16623 }
16624 }
16625 }
16626 }
16627
16628 true
16629 })
16630 }
16631
16632 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16633 pub fn clear_row_highlights<T: 'static>(&mut self) {
16634 self.highlighted_rows.remove(&TypeId::of::<T>());
16635 }
16636
16637 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16638 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16639 self.highlighted_rows
16640 .get(&TypeId::of::<T>())
16641 .map_or(&[] as &[_], |vec| vec.as_slice())
16642 .iter()
16643 .map(|highlight| (highlight.range.clone(), highlight.color))
16644 }
16645
16646 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16647 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16648 /// Allows to ignore certain kinds of highlights.
16649 pub fn highlighted_display_rows(
16650 &self,
16651 window: &mut Window,
16652 cx: &mut App,
16653 ) -> BTreeMap<DisplayRow, LineHighlight> {
16654 let snapshot = self.snapshot(window, cx);
16655 let mut used_highlight_orders = HashMap::default();
16656 self.highlighted_rows
16657 .iter()
16658 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16659 .fold(
16660 BTreeMap::<DisplayRow, LineHighlight>::new(),
16661 |mut unique_rows, highlight| {
16662 let start = highlight.range.start.to_display_point(&snapshot);
16663 let end = highlight.range.end.to_display_point(&snapshot);
16664 let start_row = start.row().0;
16665 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16666 && end.column() == 0
16667 {
16668 end.row().0.saturating_sub(1)
16669 } else {
16670 end.row().0
16671 };
16672 for row in start_row..=end_row {
16673 let used_index =
16674 used_highlight_orders.entry(row).or_insert(highlight.index);
16675 if highlight.index >= *used_index {
16676 *used_index = highlight.index;
16677 unique_rows.insert(DisplayRow(row), highlight.color.into());
16678 }
16679 }
16680 unique_rows
16681 },
16682 )
16683 }
16684
16685 pub fn highlighted_display_row_for_autoscroll(
16686 &self,
16687 snapshot: &DisplaySnapshot,
16688 ) -> Option<DisplayRow> {
16689 self.highlighted_rows
16690 .values()
16691 .flat_map(|highlighted_rows| highlighted_rows.iter())
16692 .filter_map(|highlight| {
16693 if highlight.should_autoscroll {
16694 Some(highlight.range.start.to_display_point(snapshot).row())
16695 } else {
16696 None
16697 }
16698 })
16699 .min()
16700 }
16701
16702 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16703 self.highlight_background::<SearchWithinRange>(
16704 ranges,
16705 |colors| colors.editor_document_highlight_read_background,
16706 cx,
16707 )
16708 }
16709
16710 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16711 self.breadcrumb_header = Some(new_header);
16712 }
16713
16714 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16715 self.clear_background_highlights::<SearchWithinRange>(cx);
16716 }
16717
16718 pub fn highlight_background<T: 'static>(
16719 &mut self,
16720 ranges: &[Range<Anchor>],
16721 color_fetcher: fn(&ThemeColors) -> Hsla,
16722 cx: &mut Context<Self>,
16723 ) {
16724 self.background_highlights
16725 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16726 self.scrollbar_marker_state.dirty = true;
16727 cx.notify();
16728 }
16729
16730 pub fn clear_background_highlights<T: 'static>(
16731 &mut self,
16732 cx: &mut Context<Self>,
16733 ) -> Option<BackgroundHighlight> {
16734 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16735 if !text_highlights.1.is_empty() {
16736 self.scrollbar_marker_state.dirty = true;
16737 cx.notify();
16738 }
16739 Some(text_highlights)
16740 }
16741
16742 pub fn highlight_gutter<T: 'static>(
16743 &mut self,
16744 ranges: &[Range<Anchor>],
16745 color_fetcher: fn(&App) -> Hsla,
16746 cx: &mut Context<Self>,
16747 ) {
16748 self.gutter_highlights
16749 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16750 cx.notify();
16751 }
16752
16753 pub fn clear_gutter_highlights<T: 'static>(
16754 &mut self,
16755 cx: &mut Context<Self>,
16756 ) -> Option<GutterHighlight> {
16757 cx.notify();
16758 self.gutter_highlights.remove(&TypeId::of::<T>())
16759 }
16760
16761 #[cfg(feature = "test-support")]
16762 pub fn all_text_background_highlights(
16763 &self,
16764 window: &mut Window,
16765 cx: &mut Context<Self>,
16766 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16767 let snapshot = self.snapshot(window, cx);
16768 let buffer = &snapshot.buffer_snapshot;
16769 let start = buffer.anchor_before(0);
16770 let end = buffer.anchor_after(buffer.len());
16771 let theme = cx.theme().colors();
16772 self.background_highlights_in_range(start..end, &snapshot, theme)
16773 }
16774
16775 #[cfg(feature = "test-support")]
16776 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16777 let snapshot = self.buffer().read(cx).snapshot(cx);
16778
16779 let highlights = self
16780 .background_highlights
16781 .get(&TypeId::of::<items::BufferSearchHighlights>());
16782
16783 if let Some((_color, ranges)) = highlights {
16784 ranges
16785 .iter()
16786 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16787 .collect_vec()
16788 } else {
16789 vec![]
16790 }
16791 }
16792
16793 fn document_highlights_for_position<'a>(
16794 &'a self,
16795 position: Anchor,
16796 buffer: &'a MultiBufferSnapshot,
16797 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16798 let read_highlights = self
16799 .background_highlights
16800 .get(&TypeId::of::<DocumentHighlightRead>())
16801 .map(|h| &h.1);
16802 let write_highlights = self
16803 .background_highlights
16804 .get(&TypeId::of::<DocumentHighlightWrite>())
16805 .map(|h| &h.1);
16806 let left_position = position.bias_left(buffer);
16807 let right_position = position.bias_right(buffer);
16808 read_highlights
16809 .into_iter()
16810 .chain(write_highlights)
16811 .flat_map(move |ranges| {
16812 let start_ix = match ranges.binary_search_by(|probe| {
16813 let cmp = probe.end.cmp(&left_position, buffer);
16814 if cmp.is_ge() {
16815 Ordering::Greater
16816 } else {
16817 Ordering::Less
16818 }
16819 }) {
16820 Ok(i) | Err(i) => i,
16821 };
16822
16823 ranges[start_ix..]
16824 .iter()
16825 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16826 })
16827 }
16828
16829 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16830 self.background_highlights
16831 .get(&TypeId::of::<T>())
16832 .map_or(false, |(_, highlights)| !highlights.is_empty())
16833 }
16834
16835 pub fn background_highlights_in_range(
16836 &self,
16837 search_range: Range<Anchor>,
16838 display_snapshot: &DisplaySnapshot,
16839 theme: &ThemeColors,
16840 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16841 let mut results = Vec::new();
16842 for (color_fetcher, ranges) in self.background_highlights.values() {
16843 let color = color_fetcher(theme);
16844 let start_ix = match ranges.binary_search_by(|probe| {
16845 let cmp = probe
16846 .end
16847 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16848 if cmp.is_gt() {
16849 Ordering::Greater
16850 } else {
16851 Ordering::Less
16852 }
16853 }) {
16854 Ok(i) | Err(i) => i,
16855 };
16856 for range in &ranges[start_ix..] {
16857 if range
16858 .start
16859 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16860 .is_ge()
16861 {
16862 break;
16863 }
16864
16865 let start = range.start.to_display_point(display_snapshot);
16866 let end = range.end.to_display_point(display_snapshot);
16867 results.push((start..end, color))
16868 }
16869 }
16870 results
16871 }
16872
16873 pub fn background_highlight_row_ranges<T: 'static>(
16874 &self,
16875 search_range: Range<Anchor>,
16876 display_snapshot: &DisplaySnapshot,
16877 count: usize,
16878 ) -> Vec<RangeInclusive<DisplayPoint>> {
16879 let mut results = Vec::new();
16880 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16881 return vec![];
16882 };
16883
16884 let start_ix = match ranges.binary_search_by(|probe| {
16885 let cmp = probe
16886 .end
16887 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16888 if cmp.is_gt() {
16889 Ordering::Greater
16890 } else {
16891 Ordering::Less
16892 }
16893 }) {
16894 Ok(i) | Err(i) => i,
16895 };
16896 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16897 if let (Some(start_display), Some(end_display)) = (start, end) {
16898 results.push(
16899 start_display.to_display_point(display_snapshot)
16900 ..=end_display.to_display_point(display_snapshot),
16901 );
16902 }
16903 };
16904 let mut start_row: Option<Point> = None;
16905 let mut end_row: Option<Point> = None;
16906 if ranges.len() > count {
16907 return Vec::new();
16908 }
16909 for range in &ranges[start_ix..] {
16910 if range
16911 .start
16912 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16913 .is_ge()
16914 {
16915 break;
16916 }
16917 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16918 if let Some(current_row) = &end_row {
16919 if end.row == current_row.row {
16920 continue;
16921 }
16922 }
16923 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16924 if start_row.is_none() {
16925 assert_eq!(end_row, None);
16926 start_row = Some(start);
16927 end_row = Some(end);
16928 continue;
16929 }
16930 if let Some(current_end) = end_row.as_mut() {
16931 if start.row > current_end.row + 1 {
16932 push_region(start_row, end_row);
16933 start_row = Some(start);
16934 end_row = Some(end);
16935 } else {
16936 // Merge two hunks.
16937 *current_end = end;
16938 }
16939 } else {
16940 unreachable!();
16941 }
16942 }
16943 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16944 push_region(start_row, end_row);
16945 results
16946 }
16947
16948 pub fn gutter_highlights_in_range(
16949 &self,
16950 search_range: Range<Anchor>,
16951 display_snapshot: &DisplaySnapshot,
16952 cx: &App,
16953 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16954 let mut results = Vec::new();
16955 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16956 let color = color_fetcher(cx);
16957 let start_ix = match ranges.binary_search_by(|probe| {
16958 let cmp = probe
16959 .end
16960 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16961 if cmp.is_gt() {
16962 Ordering::Greater
16963 } else {
16964 Ordering::Less
16965 }
16966 }) {
16967 Ok(i) | Err(i) => i,
16968 };
16969 for range in &ranges[start_ix..] {
16970 if range
16971 .start
16972 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16973 .is_ge()
16974 {
16975 break;
16976 }
16977
16978 let start = range.start.to_display_point(display_snapshot);
16979 let end = range.end.to_display_point(display_snapshot);
16980 results.push((start..end, color))
16981 }
16982 }
16983 results
16984 }
16985
16986 /// Get the text ranges corresponding to the redaction query
16987 pub fn redacted_ranges(
16988 &self,
16989 search_range: Range<Anchor>,
16990 display_snapshot: &DisplaySnapshot,
16991 cx: &App,
16992 ) -> Vec<Range<DisplayPoint>> {
16993 display_snapshot
16994 .buffer_snapshot
16995 .redacted_ranges(search_range, |file| {
16996 if let Some(file) = file {
16997 file.is_private()
16998 && EditorSettings::get(
16999 Some(SettingsLocation {
17000 worktree_id: file.worktree_id(cx),
17001 path: file.path().as_ref(),
17002 }),
17003 cx,
17004 )
17005 .redact_private_values
17006 } else {
17007 false
17008 }
17009 })
17010 .map(|range| {
17011 range.start.to_display_point(display_snapshot)
17012 ..range.end.to_display_point(display_snapshot)
17013 })
17014 .collect()
17015 }
17016
17017 pub fn highlight_text<T: 'static>(
17018 &mut self,
17019 ranges: Vec<Range<Anchor>>,
17020 style: HighlightStyle,
17021 cx: &mut Context<Self>,
17022 ) {
17023 self.display_map.update(cx, |map, _| {
17024 map.highlight_text(TypeId::of::<T>(), ranges, style)
17025 });
17026 cx.notify();
17027 }
17028
17029 pub(crate) fn highlight_inlays<T: 'static>(
17030 &mut self,
17031 highlights: Vec<InlayHighlight>,
17032 style: HighlightStyle,
17033 cx: &mut Context<Self>,
17034 ) {
17035 self.display_map.update(cx, |map, _| {
17036 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17037 });
17038 cx.notify();
17039 }
17040
17041 pub fn text_highlights<'a, T: 'static>(
17042 &'a self,
17043 cx: &'a App,
17044 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17045 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17046 }
17047
17048 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17049 let cleared = self
17050 .display_map
17051 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17052 if cleared {
17053 cx.notify();
17054 }
17055 }
17056
17057 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17058 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17059 && self.focus_handle.is_focused(window)
17060 }
17061
17062 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17063 self.show_cursor_when_unfocused = is_enabled;
17064 cx.notify();
17065 }
17066
17067 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17068 cx.notify();
17069 }
17070
17071 fn on_buffer_event(
17072 &mut self,
17073 multibuffer: &Entity<MultiBuffer>,
17074 event: &multi_buffer::Event,
17075 window: &mut Window,
17076 cx: &mut Context<Self>,
17077 ) {
17078 match event {
17079 multi_buffer::Event::Edited {
17080 singleton_buffer_edited,
17081 edited_buffer: buffer_edited,
17082 } => {
17083 self.scrollbar_marker_state.dirty = true;
17084 self.active_indent_guides_state.dirty = true;
17085 self.refresh_active_diagnostics(cx);
17086 self.refresh_code_actions(window, cx);
17087 if self.has_active_inline_completion() {
17088 self.update_visible_inline_completion(window, cx);
17089 }
17090 if let Some(buffer) = buffer_edited {
17091 let buffer_id = buffer.read(cx).remote_id();
17092 if !self.registered_buffers.contains_key(&buffer_id) {
17093 if let Some(project) = self.project.as_ref() {
17094 project.update(cx, |project, cx| {
17095 self.registered_buffers.insert(
17096 buffer_id,
17097 project.register_buffer_with_language_servers(&buffer, cx),
17098 );
17099 })
17100 }
17101 }
17102 }
17103 cx.emit(EditorEvent::BufferEdited);
17104 cx.emit(SearchEvent::MatchesInvalidated);
17105 if *singleton_buffer_edited {
17106 if let Some(project) = &self.project {
17107 #[allow(clippy::mutable_key_type)]
17108 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17109 multibuffer
17110 .all_buffers()
17111 .into_iter()
17112 .filter_map(|buffer| {
17113 buffer.update(cx, |buffer, cx| {
17114 let language = buffer.language()?;
17115 let should_discard = project.update(cx, |project, cx| {
17116 project.is_local()
17117 && !project.has_language_servers_for(buffer, cx)
17118 });
17119 should_discard.not().then_some(language.clone())
17120 })
17121 })
17122 .collect::<HashSet<_>>()
17123 });
17124 if !languages_affected.is_empty() {
17125 self.refresh_inlay_hints(
17126 InlayHintRefreshReason::BufferEdited(languages_affected),
17127 cx,
17128 );
17129 }
17130 }
17131 }
17132
17133 let Some(project) = &self.project else { return };
17134 let (telemetry, is_via_ssh) = {
17135 let project = project.read(cx);
17136 let telemetry = project.client().telemetry().clone();
17137 let is_via_ssh = project.is_via_ssh();
17138 (telemetry, is_via_ssh)
17139 };
17140 refresh_linked_ranges(self, window, cx);
17141 telemetry.log_edit_event("editor", is_via_ssh);
17142 }
17143 multi_buffer::Event::ExcerptsAdded {
17144 buffer,
17145 predecessor,
17146 excerpts,
17147 } => {
17148 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17149 let buffer_id = buffer.read(cx).remote_id();
17150 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17151 if let Some(project) = &self.project {
17152 get_uncommitted_diff_for_buffer(
17153 project,
17154 [buffer.clone()],
17155 self.buffer.clone(),
17156 cx,
17157 )
17158 .detach();
17159 }
17160 }
17161 cx.emit(EditorEvent::ExcerptsAdded {
17162 buffer: buffer.clone(),
17163 predecessor: *predecessor,
17164 excerpts: excerpts.clone(),
17165 });
17166 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17167 }
17168 multi_buffer::Event::ExcerptsRemoved { ids } => {
17169 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17170 let buffer = self.buffer.read(cx);
17171 self.registered_buffers
17172 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17173 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17174 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
17175 }
17176 multi_buffer::Event::ExcerptsEdited {
17177 excerpt_ids,
17178 buffer_ids,
17179 } => {
17180 self.display_map.update(cx, |map, cx| {
17181 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17182 });
17183 cx.emit(EditorEvent::ExcerptsEdited {
17184 ids: excerpt_ids.clone(),
17185 })
17186 }
17187 multi_buffer::Event::ExcerptsExpanded { ids } => {
17188 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17189 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17190 }
17191 multi_buffer::Event::Reparsed(buffer_id) => {
17192 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17193 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17194
17195 cx.emit(EditorEvent::Reparsed(*buffer_id));
17196 }
17197 multi_buffer::Event::DiffHunksToggled => {
17198 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17199 }
17200 multi_buffer::Event::LanguageChanged(buffer_id) => {
17201 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17202 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17203 cx.emit(EditorEvent::Reparsed(*buffer_id));
17204 cx.notify();
17205 }
17206 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17207 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17208 multi_buffer::Event::FileHandleChanged
17209 | multi_buffer::Event::Reloaded
17210 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17211 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17212 multi_buffer::Event::DiagnosticsUpdated => {
17213 self.refresh_active_diagnostics(cx);
17214 self.refresh_inline_diagnostics(true, window, cx);
17215 self.scrollbar_marker_state.dirty = true;
17216 cx.notify();
17217 }
17218 _ => {}
17219 };
17220 }
17221
17222 fn on_display_map_changed(
17223 &mut self,
17224 _: Entity<DisplayMap>,
17225 _: &mut Window,
17226 cx: &mut Context<Self>,
17227 ) {
17228 cx.notify();
17229 }
17230
17231 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17232 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17233 self.update_edit_prediction_settings(cx);
17234 self.refresh_inline_completion(true, false, window, cx);
17235 self.refresh_inlay_hints(
17236 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17237 self.selections.newest_anchor().head(),
17238 &self.buffer.read(cx).snapshot(cx),
17239 cx,
17240 )),
17241 cx,
17242 );
17243
17244 let old_cursor_shape = self.cursor_shape;
17245
17246 {
17247 let editor_settings = EditorSettings::get_global(cx);
17248 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17249 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17250 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17251 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17252 }
17253
17254 if old_cursor_shape != self.cursor_shape {
17255 cx.emit(EditorEvent::CursorShapeChanged);
17256 }
17257
17258 let project_settings = ProjectSettings::get_global(cx);
17259 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17260
17261 if self.mode == EditorMode::Full {
17262 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17263 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17264 if self.show_inline_diagnostics != show_inline_diagnostics {
17265 self.show_inline_diagnostics = show_inline_diagnostics;
17266 self.refresh_inline_diagnostics(false, window, cx);
17267 }
17268
17269 if self.git_blame_inline_enabled != inline_blame_enabled {
17270 self.toggle_git_blame_inline_internal(false, window, cx);
17271 }
17272 }
17273
17274 cx.notify();
17275 }
17276
17277 pub fn set_searchable(&mut self, searchable: bool) {
17278 self.searchable = searchable;
17279 }
17280
17281 pub fn searchable(&self) -> bool {
17282 self.searchable
17283 }
17284
17285 fn open_proposed_changes_editor(
17286 &mut self,
17287 _: &OpenProposedChangesEditor,
17288 window: &mut Window,
17289 cx: &mut Context<Self>,
17290 ) {
17291 let Some(workspace) = self.workspace() else {
17292 cx.propagate();
17293 return;
17294 };
17295
17296 let selections = self.selections.all::<usize>(cx);
17297 let multi_buffer = self.buffer.read(cx);
17298 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17299 let mut new_selections_by_buffer = HashMap::default();
17300 for selection in selections {
17301 for (buffer, range, _) in
17302 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17303 {
17304 let mut range = range.to_point(buffer);
17305 range.start.column = 0;
17306 range.end.column = buffer.line_len(range.end.row);
17307 new_selections_by_buffer
17308 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17309 .or_insert(Vec::new())
17310 .push(range)
17311 }
17312 }
17313
17314 let proposed_changes_buffers = new_selections_by_buffer
17315 .into_iter()
17316 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17317 .collect::<Vec<_>>();
17318 let proposed_changes_editor = cx.new(|cx| {
17319 ProposedChangesEditor::new(
17320 "Proposed changes",
17321 proposed_changes_buffers,
17322 self.project.clone(),
17323 window,
17324 cx,
17325 )
17326 });
17327
17328 window.defer(cx, move |window, cx| {
17329 workspace.update(cx, |workspace, cx| {
17330 workspace.active_pane().update(cx, |pane, cx| {
17331 pane.add_item(
17332 Box::new(proposed_changes_editor),
17333 true,
17334 true,
17335 None,
17336 window,
17337 cx,
17338 );
17339 });
17340 });
17341 });
17342 }
17343
17344 pub fn open_excerpts_in_split(
17345 &mut self,
17346 _: &OpenExcerptsSplit,
17347 window: &mut Window,
17348 cx: &mut Context<Self>,
17349 ) {
17350 self.open_excerpts_common(None, true, window, cx)
17351 }
17352
17353 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17354 self.open_excerpts_common(None, false, window, cx)
17355 }
17356
17357 fn open_excerpts_common(
17358 &mut self,
17359 jump_data: Option<JumpData>,
17360 split: bool,
17361 window: &mut Window,
17362 cx: &mut Context<Self>,
17363 ) {
17364 let Some(workspace) = self.workspace() else {
17365 cx.propagate();
17366 return;
17367 };
17368
17369 if self.buffer.read(cx).is_singleton() {
17370 cx.propagate();
17371 return;
17372 }
17373
17374 let mut new_selections_by_buffer = HashMap::default();
17375 match &jump_data {
17376 Some(JumpData::MultiBufferPoint {
17377 excerpt_id,
17378 position,
17379 anchor,
17380 line_offset_from_top,
17381 }) => {
17382 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17383 if let Some(buffer) = multi_buffer_snapshot
17384 .buffer_id_for_excerpt(*excerpt_id)
17385 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17386 {
17387 let buffer_snapshot = buffer.read(cx).snapshot();
17388 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17389 language::ToPoint::to_point(anchor, &buffer_snapshot)
17390 } else {
17391 buffer_snapshot.clip_point(*position, Bias::Left)
17392 };
17393 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17394 new_selections_by_buffer.insert(
17395 buffer,
17396 (
17397 vec![jump_to_offset..jump_to_offset],
17398 Some(*line_offset_from_top),
17399 ),
17400 );
17401 }
17402 }
17403 Some(JumpData::MultiBufferRow {
17404 row,
17405 line_offset_from_top,
17406 }) => {
17407 let point = MultiBufferPoint::new(row.0, 0);
17408 if let Some((buffer, buffer_point, _)) =
17409 self.buffer.read(cx).point_to_buffer_point(point, cx)
17410 {
17411 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17412 new_selections_by_buffer
17413 .entry(buffer)
17414 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17415 .0
17416 .push(buffer_offset..buffer_offset)
17417 }
17418 }
17419 None => {
17420 let selections = self.selections.all::<usize>(cx);
17421 let multi_buffer = self.buffer.read(cx);
17422 for selection in selections {
17423 for (snapshot, range, _, anchor) in multi_buffer
17424 .snapshot(cx)
17425 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17426 {
17427 if let Some(anchor) = anchor {
17428 // selection is in a deleted hunk
17429 let Some(buffer_id) = anchor.buffer_id else {
17430 continue;
17431 };
17432 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17433 continue;
17434 };
17435 let offset = text::ToOffset::to_offset(
17436 &anchor.text_anchor,
17437 &buffer_handle.read(cx).snapshot(),
17438 );
17439 let range = offset..offset;
17440 new_selections_by_buffer
17441 .entry(buffer_handle)
17442 .or_insert((Vec::new(), None))
17443 .0
17444 .push(range)
17445 } else {
17446 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17447 else {
17448 continue;
17449 };
17450 new_selections_by_buffer
17451 .entry(buffer_handle)
17452 .or_insert((Vec::new(), None))
17453 .0
17454 .push(range)
17455 }
17456 }
17457 }
17458 }
17459 }
17460
17461 new_selections_by_buffer
17462 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
17463
17464 if new_selections_by_buffer.is_empty() {
17465 return;
17466 }
17467
17468 // We defer the pane interaction because we ourselves are a workspace item
17469 // and activating a new item causes the pane to call a method on us reentrantly,
17470 // which panics if we're on the stack.
17471 window.defer(cx, move |window, cx| {
17472 workspace.update(cx, |workspace, cx| {
17473 let pane = if split {
17474 workspace.adjacent_pane(window, cx)
17475 } else {
17476 workspace.active_pane().clone()
17477 };
17478
17479 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17480 let editor = buffer
17481 .read(cx)
17482 .file()
17483 .is_none()
17484 .then(|| {
17485 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17486 // so `workspace.open_project_item` will never find them, always opening a new editor.
17487 // Instead, we try to activate the existing editor in the pane first.
17488 let (editor, pane_item_index) =
17489 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17490 let editor = item.downcast::<Editor>()?;
17491 let singleton_buffer =
17492 editor.read(cx).buffer().read(cx).as_singleton()?;
17493 if singleton_buffer == buffer {
17494 Some((editor, i))
17495 } else {
17496 None
17497 }
17498 })?;
17499 pane.update(cx, |pane, cx| {
17500 pane.activate_item(pane_item_index, true, true, window, cx)
17501 });
17502 Some(editor)
17503 })
17504 .flatten()
17505 .unwrap_or_else(|| {
17506 workspace.open_project_item::<Self>(
17507 pane.clone(),
17508 buffer,
17509 true,
17510 true,
17511 window,
17512 cx,
17513 )
17514 });
17515
17516 editor.update(cx, |editor, cx| {
17517 let autoscroll = match scroll_offset {
17518 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17519 None => Autoscroll::newest(),
17520 };
17521 let nav_history = editor.nav_history.take();
17522 editor.change_selections(Some(autoscroll), window, cx, |s| {
17523 s.select_ranges(ranges);
17524 });
17525 editor.nav_history = nav_history;
17526 });
17527 }
17528 })
17529 });
17530 }
17531
17532 // For now, don't allow opening excerpts in buffers that aren't backed by
17533 // regular project files.
17534 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
17535 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
17536 }
17537
17538 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17539 let snapshot = self.buffer.read(cx).read(cx);
17540 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17541 Some(
17542 ranges
17543 .iter()
17544 .map(move |range| {
17545 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17546 })
17547 .collect(),
17548 )
17549 }
17550
17551 fn selection_replacement_ranges(
17552 &self,
17553 range: Range<OffsetUtf16>,
17554 cx: &mut App,
17555 ) -> Vec<Range<OffsetUtf16>> {
17556 let selections = self.selections.all::<OffsetUtf16>(cx);
17557 let newest_selection = selections
17558 .iter()
17559 .max_by_key(|selection| selection.id)
17560 .unwrap();
17561 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17562 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17563 let snapshot = self.buffer.read(cx).read(cx);
17564 selections
17565 .into_iter()
17566 .map(|mut selection| {
17567 selection.start.0 =
17568 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17569 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17570 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17571 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17572 })
17573 .collect()
17574 }
17575
17576 fn report_editor_event(
17577 &self,
17578 event_type: &'static str,
17579 file_extension: Option<String>,
17580 cx: &App,
17581 ) {
17582 if cfg!(any(test, feature = "test-support")) {
17583 return;
17584 }
17585
17586 let Some(project) = &self.project else { return };
17587
17588 // If None, we are in a file without an extension
17589 let file = self
17590 .buffer
17591 .read(cx)
17592 .as_singleton()
17593 .and_then(|b| b.read(cx).file());
17594 let file_extension = file_extension.or(file
17595 .as_ref()
17596 .and_then(|file| Path::new(file.file_name(cx)).extension())
17597 .and_then(|e| e.to_str())
17598 .map(|a| a.to_string()));
17599
17600 let vim_mode = cx
17601 .global::<SettingsStore>()
17602 .raw_user_settings()
17603 .get("vim_mode")
17604 == Some(&serde_json::Value::Bool(true));
17605
17606 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17607 let copilot_enabled = edit_predictions_provider
17608 == language::language_settings::EditPredictionProvider::Copilot;
17609 let copilot_enabled_for_language = self
17610 .buffer
17611 .read(cx)
17612 .language_settings(cx)
17613 .show_edit_predictions;
17614
17615 let project = project.read(cx);
17616 telemetry::event!(
17617 event_type,
17618 file_extension,
17619 vim_mode,
17620 copilot_enabled,
17621 copilot_enabled_for_language,
17622 edit_predictions_provider,
17623 is_via_ssh = project.is_via_ssh(),
17624 );
17625 }
17626
17627 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17628 /// with each line being an array of {text, highlight} objects.
17629 fn copy_highlight_json(
17630 &mut self,
17631 _: &CopyHighlightJson,
17632 window: &mut Window,
17633 cx: &mut Context<Self>,
17634 ) {
17635 #[derive(Serialize)]
17636 struct Chunk<'a> {
17637 text: String,
17638 highlight: Option<&'a str>,
17639 }
17640
17641 let snapshot = self.buffer.read(cx).snapshot(cx);
17642 let range = self
17643 .selected_text_range(false, window, cx)
17644 .and_then(|selection| {
17645 if selection.range.is_empty() {
17646 None
17647 } else {
17648 Some(selection.range)
17649 }
17650 })
17651 .unwrap_or_else(|| 0..snapshot.len());
17652
17653 let chunks = snapshot.chunks(range, true);
17654 let mut lines = Vec::new();
17655 let mut line: VecDeque<Chunk> = VecDeque::new();
17656
17657 let Some(style) = self.style.as_ref() else {
17658 return;
17659 };
17660
17661 for chunk in chunks {
17662 let highlight = chunk
17663 .syntax_highlight_id
17664 .and_then(|id| id.name(&style.syntax));
17665 let mut chunk_lines = chunk.text.split('\n').peekable();
17666 while let Some(text) = chunk_lines.next() {
17667 let mut merged_with_last_token = false;
17668 if let Some(last_token) = line.back_mut() {
17669 if last_token.highlight == highlight {
17670 last_token.text.push_str(text);
17671 merged_with_last_token = true;
17672 }
17673 }
17674
17675 if !merged_with_last_token {
17676 line.push_back(Chunk {
17677 text: text.into(),
17678 highlight,
17679 });
17680 }
17681
17682 if chunk_lines.peek().is_some() {
17683 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17684 line.pop_front();
17685 }
17686 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17687 line.pop_back();
17688 }
17689
17690 lines.push(mem::take(&mut line));
17691 }
17692 }
17693 }
17694
17695 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17696 return;
17697 };
17698 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17699 }
17700
17701 pub fn open_context_menu(
17702 &mut self,
17703 _: &OpenContextMenu,
17704 window: &mut Window,
17705 cx: &mut Context<Self>,
17706 ) {
17707 self.request_autoscroll(Autoscroll::newest(), cx);
17708 let position = self.selections.newest_display(cx).start;
17709 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17710 }
17711
17712 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17713 &self.inlay_hint_cache
17714 }
17715
17716 pub fn replay_insert_event(
17717 &mut self,
17718 text: &str,
17719 relative_utf16_range: Option<Range<isize>>,
17720 window: &mut Window,
17721 cx: &mut Context<Self>,
17722 ) {
17723 if !self.input_enabled {
17724 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17725 return;
17726 }
17727 if let Some(relative_utf16_range) = relative_utf16_range {
17728 let selections = self.selections.all::<OffsetUtf16>(cx);
17729 self.change_selections(None, window, cx, |s| {
17730 let new_ranges = selections.into_iter().map(|range| {
17731 let start = OffsetUtf16(
17732 range
17733 .head()
17734 .0
17735 .saturating_add_signed(relative_utf16_range.start),
17736 );
17737 let end = OffsetUtf16(
17738 range
17739 .head()
17740 .0
17741 .saturating_add_signed(relative_utf16_range.end),
17742 );
17743 start..end
17744 });
17745 s.select_ranges(new_ranges);
17746 });
17747 }
17748
17749 self.handle_input(text, window, cx);
17750 }
17751
17752 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17753 let Some(provider) = self.semantics_provider.as_ref() else {
17754 return false;
17755 };
17756
17757 let mut supports = false;
17758 self.buffer().update(cx, |this, cx| {
17759 this.for_each_buffer(|buffer| {
17760 supports |= provider.supports_inlay_hints(buffer, cx);
17761 });
17762 });
17763
17764 supports
17765 }
17766
17767 pub fn is_focused(&self, window: &Window) -> bool {
17768 self.focus_handle.is_focused(window)
17769 }
17770
17771 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17772 cx.emit(EditorEvent::Focused);
17773
17774 if let Some(descendant) = self
17775 .last_focused_descendant
17776 .take()
17777 .and_then(|descendant| descendant.upgrade())
17778 {
17779 window.focus(&descendant);
17780 } else {
17781 if let Some(blame) = self.blame.as_ref() {
17782 blame.update(cx, GitBlame::focus)
17783 }
17784
17785 self.blink_manager.update(cx, BlinkManager::enable);
17786 self.show_cursor_names(window, cx);
17787 self.buffer.update(cx, |buffer, cx| {
17788 buffer.finalize_last_transaction(cx);
17789 if self.leader_peer_id.is_none() {
17790 buffer.set_active_selections(
17791 &self.selections.disjoint_anchors(),
17792 self.selections.line_mode,
17793 self.cursor_shape,
17794 cx,
17795 );
17796 }
17797 });
17798 }
17799 }
17800
17801 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17802 cx.emit(EditorEvent::FocusedIn)
17803 }
17804
17805 fn handle_focus_out(
17806 &mut self,
17807 event: FocusOutEvent,
17808 _window: &mut Window,
17809 cx: &mut Context<Self>,
17810 ) {
17811 if event.blurred != self.focus_handle {
17812 self.last_focused_descendant = Some(event.blurred);
17813 }
17814 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17815 }
17816
17817 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17818 self.blink_manager.update(cx, BlinkManager::disable);
17819 self.buffer
17820 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17821
17822 if let Some(blame) = self.blame.as_ref() {
17823 blame.update(cx, GitBlame::blur)
17824 }
17825 if !self.hover_state.focused(window, cx) {
17826 hide_hover(self, cx);
17827 }
17828 if !self
17829 .context_menu
17830 .borrow()
17831 .as_ref()
17832 .is_some_and(|context_menu| context_menu.focused(window, cx))
17833 {
17834 self.hide_context_menu(window, cx);
17835 }
17836 self.discard_inline_completion(false, cx);
17837 cx.emit(EditorEvent::Blurred);
17838 cx.notify();
17839 }
17840
17841 pub fn register_action<A: Action>(
17842 &mut self,
17843 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17844 ) -> Subscription {
17845 let id = self.next_editor_action_id.post_inc();
17846 let listener = Arc::new(listener);
17847 self.editor_actions.borrow_mut().insert(
17848 id,
17849 Box::new(move |window, _| {
17850 let listener = listener.clone();
17851 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17852 let action = action.downcast_ref().unwrap();
17853 if phase == DispatchPhase::Bubble {
17854 listener(action, window, cx)
17855 }
17856 })
17857 }),
17858 );
17859
17860 let editor_actions = self.editor_actions.clone();
17861 Subscription::new(move || {
17862 editor_actions.borrow_mut().remove(&id);
17863 })
17864 }
17865
17866 pub fn file_header_size(&self) -> u32 {
17867 FILE_HEADER_HEIGHT
17868 }
17869
17870 pub fn restore(
17871 &mut self,
17872 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17873 window: &mut Window,
17874 cx: &mut Context<Self>,
17875 ) {
17876 let workspace = self.workspace();
17877 let project = self.project.as_ref();
17878 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17879 let mut tasks = Vec::new();
17880 for (buffer_id, changes) in revert_changes {
17881 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17882 buffer.update(cx, |buffer, cx| {
17883 buffer.edit(
17884 changes
17885 .into_iter()
17886 .map(|(range, text)| (range, text.to_string())),
17887 None,
17888 cx,
17889 );
17890 });
17891
17892 if let Some(project) =
17893 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17894 {
17895 project.update(cx, |project, cx| {
17896 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17897 })
17898 }
17899 }
17900 }
17901 tasks
17902 });
17903 cx.spawn_in(window, async move |_, cx| {
17904 for (buffer, task) in save_tasks {
17905 let result = task.await;
17906 if result.is_err() {
17907 let Some(path) = buffer
17908 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17909 .ok()
17910 else {
17911 continue;
17912 };
17913 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17914 let Some(task) = cx
17915 .update_window_entity(&workspace, |workspace, window, cx| {
17916 workspace
17917 .open_path_preview(path, None, false, false, false, window, cx)
17918 })
17919 .ok()
17920 else {
17921 continue;
17922 };
17923 task.await.log_err();
17924 }
17925 }
17926 }
17927 })
17928 .detach();
17929 self.change_selections(None, window, cx, |selections| selections.refresh());
17930 }
17931
17932 pub fn to_pixel_point(
17933 &self,
17934 source: multi_buffer::Anchor,
17935 editor_snapshot: &EditorSnapshot,
17936 window: &mut Window,
17937 ) -> Option<gpui::Point<Pixels>> {
17938 let source_point = source.to_display_point(editor_snapshot);
17939 self.display_to_pixel_point(source_point, editor_snapshot, window)
17940 }
17941
17942 pub fn display_to_pixel_point(
17943 &self,
17944 source: DisplayPoint,
17945 editor_snapshot: &EditorSnapshot,
17946 window: &mut Window,
17947 ) -> Option<gpui::Point<Pixels>> {
17948 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17949 let text_layout_details = self.text_layout_details(window);
17950 let scroll_top = text_layout_details
17951 .scroll_anchor
17952 .scroll_position(editor_snapshot)
17953 .y;
17954
17955 if source.row().as_f32() < scroll_top.floor() {
17956 return None;
17957 }
17958 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17959 let source_y = line_height * (source.row().as_f32() - scroll_top);
17960 Some(gpui::Point::new(source_x, source_y))
17961 }
17962
17963 pub fn has_visible_completions_menu(&self) -> bool {
17964 !self.edit_prediction_preview_is_active()
17965 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17966 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17967 })
17968 }
17969
17970 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17971 self.addons
17972 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17973 }
17974
17975 pub fn unregister_addon<T: Addon>(&mut self) {
17976 self.addons.remove(&std::any::TypeId::of::<T>());
17977 }
17978
17979 pub fn addon<T: Addon>(&self) -> Option<&T> {
17980 let type_id = std::any::TypeId::of::<T>();
17981 self.addons
17982 .get(&type_id)
17983 .and_then(|item| item.to_any().downcast_ref::<T>())
17984 }
17985
17986 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17987 let text_layout_details = self.text_layout_details(window);
17988 let style = &text_layout_details.editor_style;
17989 let font_id = window.text_system().resolve_font(&style.text.font());
17990 let font_size = style.text.font_size.to_pixels(window.rem_size());
17991 let line_height = style.text.line_height_in_pixels(window.rem_size());
17992 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17993
17994 gpui::Size::new(em_width, line_height)
17995 }
17996
17997 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17998 self.load_diff_task.clone()
17999 }
18000
18001 fn read_metadata_from_db(
18002 &mut self,
18003 item_id: u64,
18004 workspace_id: WorkspaceId,
18005 window: &mut Window,
18006 cx: &mut Context<Editor>,
18007 ) {
18008 if self.is_singleton(cx)
18009 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18010 {
18011 let buffer_snapshot = OnceCell::new();
18012
18013 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18014 if !folds.is_empty() {
18015 let snapshot =
18016 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18017 self.fold_ranges(
18018 folds
18019 .into_iter()
18020 .map(|(start, end)| {
18021 snapshot.clip_offset(start, Bias::Left)
18022 ..snapshot.clip_offset(end, Bias::Right)
18023 })
18024 .collect(),
18025 false,
18026 window,
18027 cx,
18028 );
18029 }
18030 }
18031
18032 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18033 if !selections.is_empty() {
18034 let snapshot =
18035 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18036 self.change_selections(None, window, cx, |s| {
18037 s.select_ranges(selections.into_iter().map(|(start, end)| {
18038 snapshot.clip_offset(start, Bias::Left)
18039 ..snapshot.clip_offset(end, Bias::Right)
18040 }));
18041 });
18042 }
18043 };
18044 }
18045
18046 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18047 }
18048}
18049
18050// Consider user intent and default settings
18051fn choose_completion_range(
18052 completion: &Completion,
18053 intent: CompletionIntent,
18054 buffer: &Entity<Buffer>,
18055 cx: &mut Context<Editor>,
18056) -> Range<usize> {
18057 fn should_replace(
18058 completion: &Completion,
18059 insert_range: &Range<text::Anchor>,
18060 intent: CompletionIntent,
18061 completion_mode_setting: LspInsertMode,
18062 buffer: &Buffer,
18063 ) -> bool {
18064 // specific actions take precedence over settings
18065 match intent {
18066 CompletionIntent::CompleteWithInsert => return false,
18067 CompletionIntent::CompleteWithReplace => return true,
18068 CompletionIntent::Complete | CompletionIntent::Compose => {}
18069 }
18070
18071 match completion_mode_setting {
18072 LspInsertMode::Insert => false,
18073 LspInsertMode::Replace => true,
18074 LspInsertMode::ReplaceSubsequence => {
18075 let mut text_to_replace = buffer.chars_for_range(
18076 buffer.anchor_before(completion.replace_range.start)
18077 ..buffer.anchor_after(completion.replace_range.end),
18078 );
18079 let mut completion_text = completion.new_text.chars();
18080
18081 // is `text_to_replace` a subsequence of `completion_text`
18082 text_to_replace
18083 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18084 }
18085 LspInsertMode::ReplaceSuffix => {
18086 let range_after_cursor = insert_range.end..completion.replace_range.end;
18087
18088 let text_after_cursor = buffer
18089 .text_for_range(
18090 buffer.anchor_before(range_after_cursor.start)
18091 ..buffer.anchor_after(range_after_cursor.end),
18092 )
18093 .collect::<String>();
18094 completion.new_text.ends_with(&text_after_cursor)
18095 }
18096 }
18097 }
18098
18099 let buffer = buffer.read(cx);
18100
18101 if let CompletionSource::Lsp {
18102 insert_range: Some(insert_range),
18103 ..
18104 } = &completion.source
18105 {
18106 let completion_mode_setting =
18107 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18108 .completions
18109 .lsp_insert_mode;
18110
18111 if !should_replace(
18112 completion,
18113 &insert_range,
18114 intent,
18115 completion_mode_setting,
18116 buffer,
18117 ) {
18118 return insert_range.to_offset(buffer);
18119 }
18120 }
18121
18122 completion.replace_range.to_offset(buffer)
18123}
18124
18125fn insert_extra_newline_brackets(
18126 buffer: &MultiBufferSnapshot,
18127 range: Range<usize>,
18128 language: &language::LanguageScope,
18129) -> bool {
18130 let leading_whitespace_len = buffer
18131 .reversed_chars_at(range.start)
18132 .take_while(|c| c.is_whitespace() && *c != '\n')
18133 .map(|c| c.len_utf8())
18134 .sum::<usize>();
18135 let trailing_whitespace_len = buffer
18136 .chars_at(range.end)
18137 .take_while(|c| c.is_whitespace() && *c != '\n')
18138 .map(|c| c.len_utf8())
18139 .sum::<usize>();
18140 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18141
18142 language.brackets().any(|(pair, enabled)| {
18143 let pair_start = pair.start.trim_end();
18144 let pair_end = pair.end.trim_start();
18145
18146 enabled
18147 && pair.newline
18148 && buffer.contains_str_at(range.end, pair_end)
18149 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18150 })
18151}
18152
18153fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18154 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18155 [(buffer, range, _)] => (*buffer, range.clone()),
18156 _ => return false,
18157 };
18158 let pair = {
18159 let mut result: Option<BracketMatch> = None;
18160
18161 for pair in buffer
18162 .all_bracket_ranges(range.clone())
18163 .filter(move |pair| {
18164 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18165 })
18166 {
18167 let len = pair.close_range.end - pair.open_range.start;
18168
18169 if let Some(existing) = &result {
18170 let existing_len = existing.close_range.end - existing.open_range.start;
18171 if len > existing_len {
18172 continue;
18173 }
18174 }
18175
18176 result = Some(pair);
18177 }
18178
18179 result
18180 };
18181 let Some(pair) = pair else {
18182 return false;
18183 };
18184 pair.newline_only
18185 && buffer
18186 .chars_for_range(pair.open_range.end..range.start)
18187 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18188 .all(|c| c.is_whitespace() && c != '\n')
18189}
18190
18191fn get_uncommitted_diff_for_buffer(
18192 project: &Entity<Project>,
18193 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18194 buffer: Entity<MultiBuffer>,
18195 cx: &mut App,
18196) -> Task<()> {
18197 let mut tasks = Vec::new();
18198 project.update(cx, |project, cx| {
18199 for buffer in buffers {
18200 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18201 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18202 }
18203 }
18204 });
18205 cx.spawn(async move |cx| {
18206 let diffs = future::join_all(tasks).await;
18207 buffer
18208 .update(cx, |buffer, cx| {
18209 for diff in diffs.into_iter().flatten() {
18210 buffer.add_diff(diff, cx);
18211 }
18212 })
18213 .ok();
18214 })
18215}
18216
18217fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18218 let tab_size = tab_size.get() as usize;
18219 let mut width = offset;
18220
18221 for ch in text.chars() {
18222 width += if ch == '\t' {
18223 tab_size - (width % tab_size)
18224 } else {
18225 1
18226 };
18227 }
18228
18229 width - offset
18230}
18231
18232#[cfg(test)]
18233mod tests {
18234 use super::*;
18235
18236 #[test]
18237 fn test_string_size_with_expanded_tabs() {
18238 let nz = |val| NonZeroU32::new(val).unwrap();
18239 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18240 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18241 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18242 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18243 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18244 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18245 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18246 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18247 }
18248}
18249
18250/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18251struct WordBreakingTokenizer<'a> {
18252 input: &'a str,
18253}
18254
18255impl<'a> WordBreakingTokenizer<'a> {
18256 fn new(input: &'a str) -> Self {
18257 Self { input }
18258 }
18259}
18260
18261fn is_char_ideographic(ch: char) -> bool {
18262 use unicode_script::Script::*;
18263 use unicode_script::UnicodeScript;
18264 matches!(ch.script(), Han | Tangut | Yi)
18265}
18266
18267fn is_grapheme_ideographic(text: &str) -> bool {
18268 text.chars().any(is_char_ideographic)
18269}
18270
18271fn is_grapheme_whitespace(text: &str) -> bool {
18272 text.chars().any(|x| x.is_whitespace())
18273}
18274
18275fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18276 text.chars().next().map_or(false, |ch| {
18277 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18278 })
18279}
18280
18281#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18282enum WordBreakToken<'a> {
18283 Word { token: &'a str, grapheme_len: usize },
18284 InlineWhitespace { token: &'a str, grapheme_len: usize },
18285 Newline,
18286}
18287
18288impl<'a> Iterator for WordBreakingTokenizer<'a> {
18289 /// Yields a span, the count of graphemes in the token, and whether it was
18290 /// whitespace. Note that it also breaks at word boundaries.
18291 type Item = WordBreakToken<'a>;
18292
18293 fn next(&mut self) -> Option<Self::Item> {
18294 use unicode_segmentation::UnicodeSegmentation;
18295 if self.input.is_empty() {
18296 return None;
18297 }
18298
18299 let mut iter = self.input.graphemes(true).peekable();
18300 let mut offset = 0;
18301 let mut grapheme_len = 0;
18302 if let Some(first_grapheme) = iter.next() {
18303 let is_newline = first_grapheme == "\n";
18304 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18305 offset += first_grapheme.len();
18306 grapheme_len += 1;
18307 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18308 if let Some(grapheme) = iter.peek().copied() {
18309 if should_stay_with_preceding_ideograph(grapheme) {
18310 offset += grapheme.len();
18311 grapheme_len += 1;
18312 }
18313 }
18314 } else {
18315 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18316 let mut next_word_bound = words.peek().copied();
18317 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18318 next_word_bound = words.next();
18319 }
18320 while let Some(grapheme) = iter.peek().copied() {
18321 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18322 break;
18323 };
18324 if is_grapheme_whitespace(grapheme) != is_whitespace
18325 || (grapheme == "\n") != is_newline
18326 {
18327 break;
18328 };
18329 offset += grapheme.len();
18330 grapheme_len += 1;
18331 iter.next();
18332 }
18333 }
18334 let token = &self.input[..offset];
18335 self.input = &self.input[offset..];
18336 if token == "\n" {
18337 Some(WordBreakToken::Newline)
18338 } else if is_whitespace {
18339 Some(WordBreakToken::InlineWhitespace {
18340 token,
18341 grapheme_len,
18342 })
18343 } else {
18344 Some(WordBreakToken::Word {
18345 token,
18346 grapheme_len,
18347 })
18348 }
18349 } else {
18350 None
18351 }
18352 }
18353}
18354
18355#[test]
18356fn test_word_breaking_tokenizer() {
18357 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18358 ("", &[]),
18359 (" ", &[whitespace(" ", 2)]),
18360 ("Ʒ", &[word("Ʒ", 1)]),
18361 ("Ǽ", &[word("Ǽ", 1)]),
18362 ("⋑", &[word("⋑", 1)]),
18363 ("⋑⋑", &[word("⋑⋑", 2)]),
18364 (
18365 "原理,进而",
18366 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18367 ),
18368 (
18369 "hello world",
18370 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18371 ),
18372 (
18373 "hello, world",
18374 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18375 ),
18376 (
18377 " hello world",
18378 &[
18379 whitespace(" ", 2),
18380 word("hello", 5),
18381 whitespace(" ", 1),
18382 word("world", 5),
18383 ],
18384 ),
18385 (
18386 "这是什么 \n 钢笔",
18387 &[
18388 word("这", 1),
18389 word("是", 1),
18390 word("什", 1),
18391 word("么", 1),
18392 whitespace(" ", 1),
18393 newline(),
18394 whitespace(" ", 1),
18395 word("钢", 1),
18396 word("笔", 1),
18397 ],
18398 ),
18399 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
18400 ];
18401
18402 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18403 WordBreakToken::Word {
18404 token,
18405 grapheme_len,
18406 }
18407 }
18408
18409 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18410 WordBreakToken::InlineWhitespace {
18411 token,
18412 grapheme_len,
18413 }
18414 }
18415
18416 fn newline() -> WordBreakToken<'static> {
18417 WordBreakToken::Newline
18418 }
18419
18420 for (input, result) in tests {
18421 assert_eq!(
18422 WordBreakingTokenizer::new(input)
18423 .collect::<Vec<_>>()
18424 .as_slice(),
18425 *result,
18426 );
18427 }
18428}
18429
18430fn wrap_with_prefix(
18431 line_prefix: String,
18432 unwrapped_text: String,
18433 wrap_column: usize,
18434 tab_size: NonZeroU32,
18435 preserve_existing_whitespace: bool,
18436) -> String {
18437 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18438 let mut wrapped_text = String::new();
18439 let mut current_line = line_prefix.clone();
18440
18441 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18442 let mut current_line_len = line_prefix_len;
18443 let mut in_whitespace = false;
18444 for token in tokenizer {
18445 let have_preceding_whitespace = in_whitespace;
18446 match token {
18447 WordBreakToken::Word {
18448 token,
18449 grapheme_len,
18450 } => {
18451 in_whitespace = false;
18452 if current_line_len + grapheme_len > wrap_column
18453 && current_line_len != line_prefix_len
18454 {
18455 wrapped_text.push_str(current_line.trim_end());
18456 wrapped_text.push('\n');
18457 current_line.truncate(line_prefix.len());
18458 current_line_len = line_prefix_len;
18459 }
18460 current_line.push_str(token);
18461 current_line_len += grapheme_len;
18462 }
18463 WordBreakToken::InlineWhitespace {
18464 mut token,
18465 mut grapheme_len,
18466 } => {
18467 in_whitespace = true;
18468 if have_preceding_whitespace && !preserve_existing_whitespace {
18469 continue;
18470 }
18471 if !preserve_existing_whitespace {
18472 token = " ";
18473 grapheme_len = 1;
18474 }
18475 if current_line_len + grapheme_len > wrap_column {
18476 wrapped_text.push_str(current_line.trim_end());
18477 wrapped_text.push('\n');
18478 current_line.truncate(line_prefix.len());
18479 current_line_len = line_prefix_len;
18480 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18481 current_line.push_str(token);
18482 current_line_len += grapheme_len;
18483 }
18484 }
18485 WordBreakToken::Newline => {
18486 in_whitespace = true;
18487 if preserve_existing_whitespace {
18488 wrapped_text.push_str(current_line.trim_end());
18489 wrapped_text.push('\n');
18490 current_line.truncate(line_prefix.len());
18491 current_line_len = line_prefix_len;
18492 } else if have_preceding_whitespace {
18493 continue;
18494 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18495 {
18496 wrapped_text.push_str(current_line.trim_end());
18497 wrapped_text.push('\n');
18498 current_line.truncate(line_prefix.len());
18499 current_line_len = line_prefix_len;
18500 } else if current_line_len != line_prefix_len {
18501 current_line.push(' ');
18502 current_line_len += 1;
18503 }
18504 }
18505 }
18506 }
18507
18508 if !current_line.is_empty() {
18509 wrapped_text.push_str(¤t_line);
18510 }
18511 wrapped_text
18512}
18513
18514#[test]
18515fn test_wrap_with_prefix() {
18516 assert_eq!(
18517 wrap_with_prefix(
18518 "# ".to_string(),
18519 "abcdefg".to_string(),
18520 4,
18521 NonZeroU32::new(4).unwrap(),
18522 false,
18523 ),
18524 "# abcdefg"
18525 );
18526 assert_eq!(
18527 wrap_with_prefix(
18528 "".to_string(),
18529 "\thello world".to_string(),
18530 8,
18531 NonZeroU32::new(4).unwrap(),
18532 false,
18533 ),
18534 "hello\nworld"
18535 );
18536 assert_eq!(
18537 wrap_with_prefix(
18538 "// ".to_string(),
18539 "xx \nyy zz aa bb cc".to_string(),
18540 12,
18541 NonZeroU32::new(4).unwrap(),
18542 false,
18543 ),
18544 "// xx yy zz\n// aa bb cc"
18545 );
18546 assert_eq!(
18547 wrap_with_prefix(
18548 String::new(),
18549 "这是什么 \n 钢笔".to_string(),
18550 3,
18551 NonZeroU32::new(4).unwrap(),
18552 false,
18553 ),
18554 "这是什\n么 钢\n笔"
18555 );
18556}
18557
18558pub trait CollaborationHub {
18559 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18560 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18561 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18562}
18563
18564impl CollaborationHub for Entity<Project> {
18565 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18566 self.read(cx).collaborators()
18567 }
18568
18569 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18570 self.read(cx).user_store().read(cx).participant_indices()
18571 }
18572
18573 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18574 let this = self.read(cx);
18575 let user_ids = this.collaborators().values().map(|c| c.user_id);
18576 this.user_store().read_with(cx, |user_store, cx| {
18577 user_store.participant_names(user_ids, cx)
18578 })
18579 }
18580}
18581
18582pub trait SemanticsProvider {
18583 fn hover(
18584 &self,
18585 buffer: &Entity<Buffer>,
18586 position: text::Anchor,
18587 cx: &mut App,
18588 ) -> Option<Task<Vec<project::Hover>>>;
18589
18590 fn inlay_hints(
18591 &self,
18592 buffer_handle: Entity<Buffer>,
18593 range: Range<text::Anchor>,
18594 cx: &mut App,
18595 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18596
18597 fn resolve_inlay_hint(
18598 &self,
18599 hint: InlayHint,
18600 buffer_handle: Entity<Buffer>,
18601 server_id: LanguageServerId,
18602 cx: &mut App,
18603 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18604
18605 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18606
18607 fn document_highlights(
18608 &self,
18609 buffer: &Entity<Buffer>,
18610 position: text::Anchor,
18611 cx: &mut App,
18612 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18613
18614 fn definitions(
18615 &self,
18616 buffer: &Entity<Buffer>,
18617 position: text::Anchor,
18618 kind: GotoDefinitionKind,
18619 cx: &mut App,
18620 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18621
18622 fn range_for_rename(
18623 &self,
18624 buffer: &Entity<Buffer>,
18625 position: text::Anchor,
18626 cx: &mut App,
18627 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18628
18629 fn perform_rename(
18630 &self,
18631 buffer: &Entity<Buffer>,
18632 position: text::Anchor,
18633 new_name: String,
18634 cx: &mut App,
18635 ) -> Option<Task<Result<ProjectTransaction>>>;
18636}
18637
18638pub trait CompletionProvider {
18639 fn completions(
18640 &self,
18641 excerpt_id: ExcerptId,
18642 buffer: &Entity<Buffer>,
18643 buffer_position: text::Anchor,
18644 trigger: CompletionContext,
18645 window: &mut Window,
18646 cx: &mut Context<Editor>,
18647 ) -> Task<Result<Option<Vec<Completion>>>>;
18648
18649 fn resolve_completions(
18650 &self,
18651 buffer: Entity<Buffer>,
18652 completion_indices: Vec<usize>,
18653 completions: Rc<RefCell<Box<[Completion]>>>,
18654 cx: &mut Context<Editor>,
18655 ) -> Task<Result<bool>>;
18656
18657 fn apply_additional_edits_for_completion(
18658 &self,
18659 _buffer: Entity<Buffer>,
18660 _completions: Rc<RefCell<Box<[Completion]>>>,
18661 _completion_index: usize,
18662 _push_to_history: bool,
18663 _cx: &mut Context<Editor>,
18664 ) -> Task<Result<Option<language::Transaction>>> {
18665 Task::ready(Ok(None))
18666 }
18667
18668 fn is_completion_trigger(
18669 &self,
18670 buffer: &Entity<Buffer>,
18671 position: language::Anchor,
18672 text: &str,
18673 trigger_in_words: bool,
18674 cx: &mut Context<Editor>,
18675 ) -> bool;
18676
18677 fn sort_completions(&self) -> bool {
18678 true
18679 }
18680
18681 fn filter_completions(&self) -> bool {
18682 true
18683 }
18684}
18685
18686pub trait CodeActionProvider {
18687 fn id(&self) -> Arc<str>;
18688
18689 fn code_actions(
18690 &self,
18691 buffer: &Entity<Buffer>,
18692 range: Range<text::Anchor>,
18693 window: &mut Window,
18694 cx: &mut App,
18695 ) -> Task<Result<Vec<CodeAction>>>;
18696
18697 fn apply_code_action(
18698 &self,
18699 buffer_handle: Entity<Buffer>,
18700 action: CodeAction,
18701 excerpt_id: ExcerptId,
18702 push_to_history: bool,
18703 window: &mut Window,
18704 cx: &mut App,
18705 ) -> Task<Result<ProjectTransaction>>;
18706}
18707
18708impl CodeActionProvider for Entity<Project> {
18709 fn id(&self) -> Arc<str> {
18710 "project".into()
18711 }
18712
18713 fn code_actions(
18714 &self,
18715 buffer: &Entity<Buffer>,
18716 range: Range<text::Anchor>,
18717 _window: &mut Window,
18718 cx: &mut App,
18719 ) -> Task<Result<Vec<CodeAction>>> {
18720 self.update(cx, |project, cx| {
18721 let code_lens = project.code_lens(buffer, range.clone(), cx);
18722 let code_actions = project.code_actions(buffer, range, None, cx);
18723 cx.background_spawn(async move {
18724 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18725 Ok(code_lens
18726 .context("code lens fetch")?
18727 .into_iter()
18728 .chain(code_actions.context("code action fetch")?)
18729 .collect())
18730 })
18731 })
18732 }
18733
18734 fn apply_code_action(
18735 &self,
18736 buffer_handle: Entity<Buffer>,
18737 action: CodeAction,
18738 _excerpt_id: ExcerptId,
18739 push_to_history: bool,
18740 _window: &mut Window,
18741 cx: &mut App,
18742 ) -> Task<Result<ProjectTransaction>> {
18743 self.update(cx, |project, cx| {
18744 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18745 })
18746 }
18747}
18748
18749fn snippet_completions(
18750 project: &Project,
18751 buffer: &Entity<Buffer>,
18752 buffer_position: text::Anchor,
18753 cx: &mut App,
18754) -> Task<Result<Vec<Completion>>> {
18755 let language = buffer.read(cx).language_at(buffer_position);
18756 let language_name = language.as_ref().map(|language| language.lsp_id());
18757 let snippet_store = project.snippets().read(cx);
18758 let snippets = snippet_store.snippets_for(language_name, cx);
18759
18760 if snippets.is_empty() {
18761 return Task::ready(Ok(vec![]));
18762 }
18763 let snapshot = buffer.read(cx).text_snapshot();
18764 let chars: String = snapshot
18765 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18766 .collect();
18767
18768 let scope = language.map(|language| language.default_scope());
18769 let executor = cx.background_executor().clone();
18770
18771 cx.background_spawn(async move {
18772 let classifier = CharClassifier::new(scope).for_completion(true);
18773 let mut last_word = chars
18774 .chars()
18775 .take_while(|c| classifier.is_word(*c))
18776 .collect::<String>();
18777 last_word = last_word.chars().rev().collect();
18778
18779 if last_word.is_empty() {
18780 return Ok(vec![]);
18781 }
18782
18783 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18784 let to_lsp = |point: &text::Anchor| {
18785 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18786 point_to_lsp(end)
18787 };
18788 let lsp_end = to_lsp(&buffer_position);
18789
18790 let candidates = snippets
18791 .iter()
18792 .enumerate()
18793 .flat_map(|(ix, snippet)| {
18794 snippet
18795 .prefix
18796 .iter()
18797 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18798 })
18799 .collect::<Vec<StringMatchCandidate>>();
18800
18801 let mut matches = fuzzy::match_strings(
18802 &candidates,
18803 &last_word,
18804 last_word.chars().any(|c| c.is_uppercase()),
18805 100,
18806 &Default::default(),
18807 executor,
18808 )
18809 .await;
18810
18811 // Remove all candidates where the query's start does not match the start of any word in the candidate
18812 if let Some(query_start) = last_word.chars().next() {
18813 matches.retain(|string_match| {
18814 split_words(&string_match.string).any(|word| {
18815 // Check that the first codepoint of the word as lowercase matches the first
18816 // codepoint of the query as lowercase
18817 word.chars()
18818 .flat_map(|codepoint| codepoint.to_lowercase())
18819 .zip(query_start.to_lowercase())
18820 .all(|(word_cp, query_cp)| word_cp == query_cp)
18821 })
18822 });
18823 }
18824
18825 let matched_strings = matches
18826 .into_iter()
18827 .map(|m| m.string)
18828 .collect::<HashSet<_>>();
18829
18830 let result: Vec<Completion> = snippets
18831 .into_iter()
18832 .filter_map(|snippet| {
18833 let matching_prefix = snippet
18834 .prefix
18835 .iter()
18836 .find(|prefix| matched_strings.contains(*prefix))?;
18837 let start = as_offset - last_word.len();
18838 let start = snapshot.anchor_before(start);
18839 let range = start..buffer_position;
18840 let lsp_start = to_lsp(&start);
18841 let lsp_range = lsp::Range {
18842 start: lsp_start,
18843 end: lsp_end,
18844 };
18845 Some(Completion {
18846 replace_range: range,
18847 new_text: snippet.body.clone(),
18848 source: CompletionSource::Lsp {
18849 insert_range: None,
18850 server_id: LanguageServerId(usize::MAX),
18851 resolved: true,
18852 lsp_completion: Box::new(lsp::CompletionItem {
18853 label: snippet.prefix.first().unwrap().clone(),
18854 kind: Some(CompletionItemKind::SNIPPET),
18855 label_details: snippet.description.as_ref().map(|description| {
18856 lsp::CompletionItemLabelDetails {
18857 detail: Some(description.clone()),
18858 description: None,
18859 }
18860 }),
18861 insert_text_format: Some(InsertTextFormat::SNIPPET),
18862 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18863 lsp::InsertReplaceEdit {
18864 new_text: snippet.body.clone(),
18865 insert: lsp_range,
18866 replace: lsp_range,
18867 },
18868 )),
18869 filter_text: Some(snippet.body.clone()),
18870 sort_text: Some(char::MAX.to_string()),
18871 ..lsp::CompletionItem::default()
18872 }),
18873 lsp_defaults: None,
18874 },
18875 label: CodeLabel {
18876 text: matching_prefix.clone(),
18877 runs: Vec::new(),
18878 filter_range: 0..matching_prefix.len(),
18879 },
18880 icon_path: None,
18881 documentation: snippet
18882 .description
18883 .clone()
18884 .map(|description| CompletionDocumentation::SingleLine(description.into())),
18885 insert_text_mode: None,
18886 confirm: None,
18887 })
18888 })
18889 .collect();
18890
18891 Ok(result)
18892 })
18893}
18894
18895impl CompletionProvider for Entity<Project> {
18896 fn completions(
18897 &self,
18898 _excerpt_id: ExcerptId,
18899 buffer: &Entity<Buffer>,
18900 buffer_position: text::Anchor,
18901 options: CompletionContext,
18902 _window: &mut Window,
18903 cx: &mut Context<Editor>,
18904 ) -> Task<Result<Option<Vec<Completion>>>> {
18905 self.update(cx, |project, cx| {
18906 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18907 let project_completions = project.completions(buffer, buffer_position, options, cx);
18908 cx.background_spawn(async move {
18909 let snippets_completions = snippets.await?;
18910 match project_completions.await? {
18911 Some(mut completions) => {
18912 completions.extend(snippets_completions);
18913 Ok(Some(completions))
18914 }
18915 None => {
18916 if snippets_completions.is_empty() {
18917 Ok(None)
18918 } else {
18919 Ok(Some(snippets_completions))
18920 }
18921 }
18922 }
18923 })
18924 })
18925 }
18926
18927 fn resolve_completions(
18928 &self,
18929 buffer: Entity<Buffer>,
18930 completion_indices: Vec<usize>,
18931 completions: Rc<RefCell<Box<[Completion]>>>,
18932 cx: &mut Context<Editor>,
18933 ) -> Task<Result<bool>> {
18934 self.update(cx, |project, cx| {
18935 project.lsp_store().update(cx, |lsp_store, cx| {
18936 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18937 })
18938 })
18939 }
18940
18941 fn apply_additional_edits_for_completion(
18942 &self,
18943 buffer: Entity<Buffer>,
18944 completions: Rc<RefCell<Box<[Completion]>>>,
18945 completion_index: usize,
18946 push_to_history: bool,
18947 cx: &mut Context<Editor>,
18948 ) -> Task<Result<Option<language::Transaction>>> {
18949 self.update(cx, |project, cx| {
18950 project.lsp_store().update(cx, |lsp_store, cx| {
18951 lsp_store.apply_additional_edits_for_completion(
18952 buffer,
18953 completions,
18954 completion_index,
18955 push_to_history,
18956 cx,
18957 )
18958 })
18959 })
18960 }
18961
18962 fn is_completion_trigger(
18963 &self,
18964 buffer: &Entity<Buffer>,
18965 position: language::Anchor,
18966 text: &str,
18967 trigger_in_words: bool,
18968 cx: &mut Context<Editor>,
18969 ) -> bool {
18970 let mut chars = text.chars();
18971 let char = if let Some(char) = chars.next() {
18972 char
18973 } else {
18974 return false;
18975 };
18976 if chars.next().is_some() {
18977 return false;
18978 }
18979
18980 let buffer = buffer.read(cx);
18981 let snapshot = buffer.snapshot();
18982 if !snapshot.settings_at(position, cx).show_completions_on_input {
18983 return false;
18984 }
18985 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18986 if trigger_in_words && classifier.is_word(char) {
18987 return true;
18988 }
18989
18990 buffer.completion_triggers().contains(text)
18991 }
18992}
18993
18994impl SemanticsProvider for Entity<Project> {
18995 fn hover(
18996 &self,
18997 buffer: &Entity<Buffer>,
18998 position: text::Anchor,
18999 cx: &mut App,
19000 ) -> Option<Task<Vec<project::Hover>>> {
19001 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19002 }
19003
19004 fn document_highlights(
19005 &self,
19006 buffer: &Entity<Buffer>,
19007 position: text::Anchor,
19008 cx: &mut App,
19009 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19010 Some(self.update(cx, |project, cx| {
19011 project.document_highlights(buffer, position, cx)
19012 }))
19013 }
19014
19015 fn definitions(
19016 &self,
19017 buffer: &Entity<Buffer>,
19018 position: text::Anchor,
19019 kind: GotoDefinitionKind,
19020 cx: &mut App,
19021 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19022 Some(self.update(cx, |project, cx| match kind {
19023 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19024 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19025 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19026 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19027 }))
19028 }
19029
19030 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19031 // TODO: make this work for remote projects
19032 self.update(cx, |this, cx| {
19033 buffer.update(cx, |buffer, cx| {
19034 this.any_language_server_supports_inlay_hints(buffer, cx)
19035 })
19036 })
19037 }
19038
19039 fn inlay_hints(
19040 &self,
19041 buffer_handle: Entity<Buffer>,
19042 range: Range<text::Anchor>,
19043 cx: &mut App,
19044 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19045 Some(self.update(cx, |project, cx| {
19046 project.inlay_hints(buffer_handle, range, cx)
19047 }))
19048 }
19049
19050 fn resolve_inlay_hint(
19051 &self,
19052 hint: InlayHint,
19053 buffer_handle: Entity<Buffer>,
19054 server_id: LanguageServerId,
19055 cx: &mut App,
19056 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19057 Some(self.update(cx, |project, cx| {
19058 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19059 }))
19060 }
19061
19062 fn range_for_rename(
19063 &self,
19064 buffer: &Entity<Buffer>,
19065 position: text::Anchor,
19066 cx: &mut App,
19067 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19068 Some(self.update(cx, |project, cx| {
19069 let buffer = buffer.clone();
19070 let task = project.prepare_rename(buffer.clone(), position, cx);
19071 cx.spawn(async move |_, cx| {
19072 Ok(match task.await? {
19073 PrepareRenameResponse::Success(range) => Some(range),
19074 PrepareRenameResponse::InvalidPosition => None,
19075 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19076 // Fallback on using TreeSitter info to determine identifier range
19077 buffer.update(cx, |buffer, _| {
19078 let snapshot = buffer.snapshot();
19079 let (range, kind) = snapshot.surrounding_word(position);
19080 if kind != Some(CharKind::Word) {
19081 return None;
19082 }
19083 Some(
19084 snapshot.anchor_before(range.start)
19085 ..snapshot.anchor_after(range.end),
19086 )
19087 })?
19088 }
19089 })
19090 })
19091 }))
19092 }
19093
19094 fn perform_rename(
19095 &self,
19096 buffer: &Entity<Buffer>,
19097 position: text::Anchor,
19098 new_name: String,
19099 cx: &mut App,
19100 ) -> Option<Task<Result<ProjectTransaction>>> {
19101 Some(self.update(cx, |project, cx| {
19102 project.perform_rename(buffer.clone(), position, new_name, cx)
19103 }))
19104 }
19105}
19106
19107fn inlay_hint_settings(
19108 location: Anchor,
19109 snapshot: &MultiBufferSnapshot,
19110 cx: &mut Context<Editor>,
19111) -> InlayHintSettings {
19112 let file = snapshot.file_at(location);
19113 let language = snapshot.language_at(location).map(|l| l.name());
19114 language_settings(language, file, cx).inlay_hints
19115}
19116
19117fn consume_contiguous_rows(
19118 contiguous_row_selections: &mut Vec<Selection<Point>>,
19119 selection: &Selection<Point>,
19120 display_map: &DisplaySnapshot,
19121 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19122) -> (MultiBufferRow, MultiBufferRow) {
19123 contiguous_row_selections.push(selection.clone());
19124 let start_row = MultiBufferRow(selection.start.row);
19125 let mut end_row = ending_row(selection, display_map);
19126
19127 while let Some(next_selection) = selections.peek() {
19128 if next_selection.start.row <= end_row.0 {
19129 end_row = ending_row(next_selection, display_map);
19130 contiguous_row_selections.push(selections.next().unwrap().clone());
19131 } else {
19132 break;
19133 }
19134 }
19135 (start_row, end_row)
19136}
19137
19138fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19139 if next_selection.end.column > 0 || next_selection.is_empty() {
19140 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19141 } else {
19142 MultiBufferRow(next_selection.end.row)
19143 }
19144}
19145
19146impl EditorSnapshot {
19147 pub fn remote_selections_in_range<'a>(
19148 &'a self,
19149 range: &'a Range<Anchor>,
19150 collaboration_hub: &dyn CollaborationHub,
19151 cx: &'a App,
19152 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19153 let participant_names = collaboration_hub.user_names(cx);
19154 let participant_indices = collaboration_hub.user_participant_indices(cx);
19155 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19156 let collaborators_by_replica_id = collaborators_by_peer_id
19157 .iter()
19158 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19159 .collect::<HashMap<_, _>>();
19160 self.buffer_snapshot
19161 .selections_in_range(range, false)
19162 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19163 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19164 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19165 let user_name = participant_names.get(&collaborator.user_id).cloned();
19166 Some(RemoteSelection {
19167 replica_id,
19168 selection,
19169 cursor_shape,
19170 line_mode,
19171 participant_index,
19172 peer_id: collaborator.peer_id,
19173 user_name,
19174 })
19175 })
19176 }
19177
19178 pub fn hunks_for_ranges(
19179 &self,
19180 ranges: impl IntoIterator<Item = Range<Point>>,
19181 ) -> Vec<MultiBufferDiffHunk> {
19182 let mut hunks = Vec::new();
19183 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19184 HashMap::default();
19185 for query_range in ranges {
19186 let query_rows =
19187 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19188 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19189 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19190 ) {
19191 // Include deleted hunks that are adjacent to the query range, because
19192 // otherwise they would be missed.
19193 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19194 if hunk.status().is_deleted() {
19195 intersects_range |= hunk.row_range.start == query_rows.end;
19196 intersects_range |= hunk.row_range.end == query_rows.start;
19197 }
19198 if intersects_range {
19199 if !processed_buffer_rows
19200 .entry(hunk.buffer_id)
19201 .or_default()
19202 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19203 {
19204 continue;
19205 }
19206 hunks.push(hunk);
19207 }
19208 }
19209 }
19210
19211 hunks
19212 }
19213
19214 fn display_diff_hunks_for_rows<'a>(
19215 &'a self,
19216 display_rows: Range<DisplayRow>,
19217 folded_buffers: &'a HashSet<BufferId>,
19218 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19219 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19220 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19221
19222 self.buffer_snapshot
19223 .diff_hunks_in_range(buffer_start..buffer_end)
19224 .filter_map(|hunk| {
19225 if folded_buffers.contains(&hunk.buffer_id) {
19226 return None;
19227 }
19228
19229 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19230 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19231
19232 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19233 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19234
19235 let display_hunk = if hunk_display_start.column() != 0 {
19236 DisplayDiffHunk::Folded {
19237 display_row: hunk_display_start.row(),
19238 }
19239 } else {
19240 let mut end_row = hunk_display_end.row();
19241 if hunk_display_end.column() > 0 {
19242 end_row.0 += 1;
19243 }
19244 let is_created_file = hunk.is_created_file();
19245 DisplayDiffHunk::Unfolded {
19246 status: hunk.status(),
19247 diff_base_byte_range: hunk.diff_base_byte_range,
19248 display_row_range: hunk_display_start.row()..end_row,
19249 multi_buffer_range: Anchor::range_in_buffer(
19250 hunk.excerpt_id,
19251 hunk.buffer_id,
19252 hunk.buffer_range,
19253 ),
19254 is_created_file,
19255 }
19256 };
19257
19258 Some(display_hunk)
19259 })
19260 }
19261
19262 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19263 self.display_snapshot.buffer_snapshot.language_at(position)
19264 }
19265
19266 pub fn is_focused(&self) -> bool {
19267 self.is_focused
19268 }
19269
19270 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19271 self.placeholder_text.as_ref()
19272 }
19273
19274 pub fn scroll_position(&self) -> gpui::Point<f32> {
19275 self.scroll_anchor.scroll_position(&self.display_snapshot)
19276 }
19277
19278 fn gutter_dimensions(
19279 &self,
19280 font_id: FontId,
19281 font_size: Pixels,
19282 max_line_number_width: Pixels,
19283 cx: &App,
19284 ) -> Option<GutterDimensions> {
19285 if !self.show_gutter {
19286 return None;
19287 }
19288
19289 let descent = cx.text_system().descent(font_id, font_size);
19290 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19291 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19292
19293 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19294 matches!(
19295 ProjectSettings::get_global(cx).git.git_gutter,
19296 Some(GitGutterSetting::TrackedFiles)
19297 )
19298 });
19299 let gutter_settings = EditorSettings::get_global(cx).gutter;
19300 let show_line_numbers = self
19301 .show_line_numbers
19302 .unwrap_or(gutter_settings.line_numbers);
19303 let line_gutter_width = if show_line_numbers {
19304 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19305 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19306 max_line_number_width.max(min_width_for_number_on_gutter)
19307 } else {
19308 0.0.into()
19309 };
19310
19311 let show_code_actions = self
19312 .show_code_actions
19313 .unwrap_or(gutter_settings.code_actions);
19314
19315 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19316 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19317
19318 let git_blame_entries_width =
19319 self.git_blame_gutter_max_author_length
19320 .map(|max_author_length| {
19321 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19322 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19323
19324 /// The number of characters to dedicate to gaps and margins.
19325 const SPACING_WIDTH: usize = 4;
19326
19327 let max_char_count = max_author_length.min(renderer.max_author_length())
19328 + ::git::SHORT_SHA_LENGTH
19329 + MAX_RELATIVE_TIMESTAMP.len()
19330 + SPACING_WIDTH;
19331
19332 em_advance * max_char_count
19333 });
19334
19335 let is_singleton = self.buffer_snapshot.is_singleton();
19336
19337 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19338 left_padding += if !is_singleton {
19339 em_width * 4.0
19340 } else if show_code_actions || show_runnables || show_breakpoints {
19341 em_width * 3.0
19342 } else if show_git_gutter && show_line_numbers {
19343 em_width * 2.0
19344 } else if show_git_gutter || show_line_numbers {
19345 em_width
19346 } else {
19347 px(0.)
19348 };
19349
19350 let shows_folds = is_singleton && gutter_settings.folds;
19351
19352 let right_padding = if shows_folds && show_line_numbers {
19353 em_width * 4.0
19354 } else if shows_folds || (!is_singleton && show_line_numbers) {
19355 em_width * 3.0
19356 } else if show_line_numbers {
19357 em_width
19358 } else {
19359 px(0.)
19360 };
19361
19362 Some(GutterDimensions {
19363 left_padding,
19364 right_padding,
19365 width: line_gutter_width + left_padding + right_padding,
19366 margin: -descent,
19367 git_blame_entries_width,
19368 })
19369 }
19370
19371 pub fn render_crease_toggle(
19372 &self,
19373 buffer_row: MultiBufferRow,
19374 row_contains_cursor: bool,
19375 editor: Entity<Editor>,
19376 window: &mut Window,
19377 cx: &mut App,
19378 ) -> Option<AnyElement> {
19379 let folded = self.is_line_folded(buffer_row);
19380 let mut is_foldable = false;
19381
19382 if let Some(crease) = self
19383 .crease_snapshot
19384 .query_row(buffer_row, &self.buffer_snapshot)
19385 {
19386 is_foldable = true;
19387 match crease {
19388 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
19389 if let Some(render_toggle) = render_toggle {
19390 let toggle_callback =
19391 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
19392 if folded {
19393 editor.update(cx, |editor, cx| {
19394 editor.fold_at(buffer_row, window, cx)
19395 });
19396 } else {
19397 editor.update(cx, |editor, cx| {
19398 editor.unfold_at(buffer_row, window, cx)
19399 });
19400 }
19401 });
19402 return Some((render_toggle)(
19403 buffer_row,
19404 folded,
19405 toggle_callback,
19406 window,
19407 cx,
19408 ));
19409 }
19410 }
19411 }
19412 }
19413
19414 is_foldable |= self.starts_indent(buffer_row);
19415
19416 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19417 Some(
19418 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19419 .toggle_state(folded)
19420 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19421 if folded {
19422 this.unfold_at(buffer_row, window, cx);
19423 } else {
19424 this.fold_at(buffer_row, window, cx);
19425 }
19426 }))
19427 .into_any_element(),
19428 )
19429 } else {
19430 None
19431 }
19432 }
19433
19434 pub fn render_crease_trailer(
19435 &self,
19436 buffer_row: MultiBufferRow,
19437 window: &mut Window,
19438 cx: &mut App,
19439 ) -> Option<AnyElement> {
19440 let folded = self.is_line_folded(buffer_row);
19441 if let Crease::Inline { render_trailer, .. } = self
19442 .crease_snapshot
19443 .query_row(buffer_row, &self.buffer_snapshot)?
19444 {
19445 let render_trailer = render_trailer.as_ref()?;
19446 Some(render_trailer(buffer_row, folded, window, cx))
19447 } else {
19448 None
19449 }
19450 }
19451}
19452
19453impl Deref for EditorSnapshot {
19454 type Target = DisplaySnapshot;
19455
19456 fn deref(&self) -> &Self::Target {
19457 &self.display_snapshot
19458 }
19459}
19460
19461#[derive(Clone, Debug, PartialEq, Eq)]
19462pub enum EditorEvent {
19463 InputIgnored {
19464 text: Arc<str>,
19465 },
19466 InputHandled {
19467 utf16_range_to_replace: Option<Range<isize>>,
19468 text: Arc<str>,
19469 },
19470 ExcerptsAdded {
19471 buffer: Entity<Buffer>,
19472 predecessor: ExcerptId,
19473 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19474 },
19475 ExcerptsRemoved {
19476 ids: Vec<ExcerptId>,
19477 },
19478 BufferFoldToggled {
19479 ids: Vec<ExcerptId>,
19480 folded: bool,
19481 },
19482 ExcerptsEdited {
19483 ids: Vec<ExcerptId>,
19484 },
19485 ExcerptsExpanded {
19486 ids: Vec<ExcerptId>,
19487 },
19488 BufferEdited,
19489 Edited {
19490 transaction_id: clock::Lamport,
19491 },
19492 Reparsed(BufferId),
19493 Focused,
19494 FocusedIn,
19495 Blurred,
19496 DirtyChanged,
19497 Saved,
19498 TitleChanged,
19499 DiffBaseChanged,
19500 SelectionsChanged {
19501 local: bool,
19502 },
19503 ScrollPositionChanged {
19504 local: bool,
19505 autoscroll: bool,
19506 },
19507 Closed,
19508 TransactionUndone {
19509 transaction_id: clock::Lamport,
19510 },
19511 TransactionBegun {
19512 transaction_id: clock::Lamport,
19513 },
19514 Reloaded,
19515 CursorShapeChanged,
19516 PushedToNavHistory {
19517 anchor: Anchor,
19518 is_deactivate: bool,
19519 },
19520}
19521
19522impl EventEmitter<EditorEvent> for Editor {}
19523
19524impl Focusable for Editor {
19525 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19526 self.focus_handle.clone()
19527 }
19528}
19529
19530impl Render for Editor {
19531 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19532 let settings = ThemeSettings::get_global(cx);
19533
19534 let mut text_style = match self.mode {
19535 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19536 color: cx.theme().colors().editor_foreground,
19537 font_family: settings.ui_font.family.clone(),
19538 font_features: settings.ui_font.features.clone(),
19539 font_fallbacks: settings.ui_font.fallbacks.clone(),
19540 font_size: rems(0.875).into(),
19541 font_weight: settings.ui_font.weight,
19542 line_height: relative(settings.buffer_line_height.value()),
19543 ..Default::default()
19544 },
19545 EditorMode::Full => TextStyle {
19546 color: cx.theme().colors().editor_foreground,
19547 font_family: settings.buffer_font.family.clone(),
19548 font_features: settings.buffer_font.features.clone(),
19549 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19550 font_size: settings.buffer_font_size(cx).into(),
19551 font_weight: settings.buffer_font.weight,
19552 line_height: relative(settings.buffer_line_height.value()),
19553 ..Default::default()
19554 },
19555 };
19556 if let Some(text_style_refinement) = &self.text_style_refinement {
19557 text_style.refine(text_style_refinement)
19558 }
19559
19560 let background = match self.mode {
19561 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19562 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19563 EditorMode::Full => cx.theme().colors().editor_background,
19564 };
19565
19566 EditorElement::new(
19567 &cx.entity(),
19568 EditorStyle {
19569 background,
19570 local_player: cx.theme().players().local(),
19571 text: text_style,
19572 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19573 syntax: cx.theme().syntax().clone(),
19574 status: cx.theme().status().clone(),
19575 inlay_hints_style: make_inlay_hints_style(cx),
19576 inline_completion_styles: make_suggestion_styles(cx),
19577 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19578 },
19579 )
19580 }
19581}
19582
19583impl EntityInputHandler for Editor {
19584 fn text_for_range(
19585 &mut self,
19586 range_utf16: Range<usize>,
19587 adjusted_range: &mut Option<Range<usize>>,
19588 _: &mut Window,
19589 cx: &mut Context<Self>,
19590 ) -> Option<String> {
19591 let snapshot = self.buffer.read(cx).read(cx);
19592 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19593 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19594 if (start.0..end.0) != range_utf16 {
19595 adjusted_range.replace(start.0..end.0);
19596 }
19597 Some(snapshot.text_for_range(start..end).collect())
19598 }
19599
19600 fn selected_text_range(
19601 &mut self,
19602 ignore_disabled_input: bool,
19603 _: &mut Window,
19604 cx: &mut Context<Self>,
19605 ) -> Option<UTF16Selection> {
19606 // Prevent the IME menu from appearing when holding down an alphabetic key
19607 // while input is disabled.
19608 if !ignore_disabled_input && !self.input_enabled {
19609 return None;
19610 }
19611
19612 let selection = self.selections.newest::<OffsetUtf16>(cx);
19613 let range = selection.range();
19614
19615 Some(UTF16Selection {
19616 range: range.start.0..range.end.0,
19617 reversed: selection.reversed,
19618 })
19619 }
19620
19621 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19622 let snapshot = self.buffer.read(cx).read(cx);
19623 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19624 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19625 }
19626
19627 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19628 self.clear_highlights::<InputComposition>(cx);
19629 self.ime_transaction.take();
19630 }
19631
19632 fn replace_text_in_range(
19633 &mut self,
19634 range_utf16: Option<Range<usize>>,
19635 text: &str,
19636 window: &mut Window,
19637 cx: &mut Context<Self>,
19638 ) {
19639 if !self.input_enabled {
19640 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19641 return;
19642 }
19643
19644 self.transact(window, cx, |this, window, cx| {
19645 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19646 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19647 Some(this.selection_replacement_ranges(range_utf16, cx))
19648 } else {
19649 this.marked_text_ranges(cx)
19650 };
19651
19652 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19653 let newest_selection_id = this.selections.newest_anchor().id;
19654 this.selections
19655 .all::<OffsetUtf16>(cx)
19656 .iter()
19657 .zip(ranges_to_replace.iter())
19658 .find_map(|(selection, range)| {
19659 if selection.id == newest_selection_id {
19660 Some(
19661 (range.start.0 as isize - selection.head().0 as isize)
19662 ..(range.end.0 as isize - selection.head().0 as isize),
19663 )
19664 } else {
19665 None
19666 }
19667 })
19668 });
19669
19670 cx.emit(EditorEvent::InputHandled {
19671 utf16_range_to_replace: range_to_replace,
19672 text: text.into(),
19673 });
19674
19675 if let Some(new_selected_ranges) = new_selected_ranges {
19676 this.change_selections(None, window, cx, |selections| {
19677 selections.select_ranges(new_selected_ranges)
19678 });
19679 this.backspace(&Default::default(), window, cx);
19680 }
19681
19682 this.handle_input(text, window, cx);
19683 });
19684
19685 if let Some(transaction) = self.ime_transaction {
19686 self.buffer.update(cx, |buffer, cx| {
19687 buffer.group_until_transaction(transaction, cx);
19688 });
19689 }
19690
19691 self.unmark_text(window, cx);
19692 }
19693
19694 fn replace_and_mark_text_in_range(
19695 &mut self,
19696 range_utf16: Option<Range<usize>>,
19697 text: &str,
19698 new_selected_range_utf16: Option<Range<usize>>,
19699 window: &mut Window,
19700 cx: &mut Context<Self>,
19701 ) {
19702 if !self.input_enabled {
19703 return;
19704 }
19705
19706 let transaction = self.transact(window, cx, |this, window, cx| {
19707 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19708 let snapshot = this.buffer.read(cx).read(cx);
19709 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19710 for marked_range in &mut marked_ranges {
19711 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19712 marked_range.start.0 += relative_range_utf16.start;
19713 marked_range.start =
19714 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19715 marked_range.end =
19716 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19717 }
19718 }
19719 Some(marked_ranges)
19720 } else if let Some(range_utf16) = range_utf16 {
19721 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19722 Some(this.selection_replacement_ranges(range_utf16, cx))
19723 } else {
19724 None
19725 };
19726
19727 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19728 let newest_selection_id = this.selections.newest_anchor().id;
19729 this.selections
19730 .all::<OffsetUtf16>(cx)
19731 .iter()
19732 .zip(ranges_to_replace.iter())
19733 .find_map(|(selection, range)| {
19734 if selection.id == newest_selection_id {
19735 Some(
19736 (range.start.0 as isize - selection.head().0 as isize)
19737 ..(range.end.0 as isize - selection.head().0 as isize),
19738 )
19739 } else {
19740 None
19741 }
19742 })
19743 });
19744
19745 cx.emit(EditorEvent::InputHandled {
19746 utf16_range_to_replace: range_to_replace,
19747 text: text.into(),
19748 });
19749
19750 if let Some(ranges) = ranges_to_replace {
19751 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19752 }
19753
19754 let marked_ranges = {
19755 let snapshot = this.buffer.read(cx).read(cx);
19756 this.selections
19757 .disjoint_anchors()
19758 .iter()
19759 .map(|selection| {
19760 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19761 })
19762 .collect::<Vec<_>>()
19763 };
19764
19765 if text.is_empty() {
19766 this.unmark_text(window, cx);
19767 } else {
19768 this.highlight_text::<InputComposition>(
19769 marked_ranges.clone(),
19770 HighlightStyle {
19771 underline: Some(UnderlineStyle {
19772 thickness: px(1.),
19773 color: None,
19774 wavy: false,
19775 }),
19776 ..Default::default()
19777 },
19778 cx,
19779 );
19780 }
19781
19782 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19783 let use_autoclose = this.use_autoclose;
19784 let use_auto_surround = this.use_auto_surround;
19785 this.set_use_autoclose(false);
19786 this.set_use_auto_surround(false);
19787 this.handle_input(text, window, cx);
19788 this.set_use_autoclose(use_autoclose);
19789 this.set_use_auto_surround(use_auto_surround);
19790
19791 if let Some(new_selected_range) = new_selected_range_utf16 {
19792 let snapshot = this.buffer.read(cx).read(cx);
19793 let new_selected_ranges = marked_ranges
19794 .into_iter()
19795 .map(|marked_range| {
19796 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19797 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19798 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19799 snapshot.clip_offset_utf16(new_start, Bias::Left)
19800 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19801 })
19802 .collect::<Vec<_>>();
19803
19804 drop(snapshot);
19805 this.change_selections(None, window, cx, |selections| {
19806 selections.select_ranges(new_selected_ranges)
19807 });
19808 }
19809 });
19810
19811 self.ime_transaction = self.ime_transaction.or(transaction);
19812 if let Some(transaction) = self.ime_transaction {
19813 self.buffer.update(cx, |buffer, cx| {
19814 buffer.group_until_transaction(transaction, cx);
19815 });
19816 }
19817
19818 if self.text_highlights::<InputComposition>(cx).is_none() {
19819 self.ime_transaction.take();
19820 }
19821 }
19822
19823 fn bounds_for_range(
19824 &mut self,
19825 range_utf16: Range<usize>,
19826 element_bounds: gpui::Bounds<Pixels>,
19827 window: &mut Window,
19828 cx: &mut Context<Self>,
19829 ) -> Option<gpui::Bounds<Pixels>> {
19830 let text_layout_details = self.text_layout_details(window);
19831 let gpui::Size {
19832 width: em_width,
19833 height: line_height,
19834 } = self.character_size(window);
19835
19836 let snapshot = self.snapshot(window, cx);
19837 let scroll_position = snapshot.scroll_position();
19838 let scroll_left = scroll_position.x * em_width;
19839
19840 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19841 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19842 + self.gutter_dimensions.width
19843 + self.gutter_dimensions.margin;
19844 let y = line_height * (start.row().as_f32() - scroll_position.y);
19845
19846 Some(Bounds {
19847 origin: element_bounds.origin + point(x, y),
19848 size: size(em_width, line_height),
19849 })
19850 }
19851
19852 fn character_index_for_point(
19853 &mut self,
19854 point: gpui::Point<Pixels>,
19855 _window: &mut Window,
19856 _cx: &mut Context<Self>,
19857 ) -> Option<usize> {
19858 let position_map = self.last_position_map.as_ref()?;
19859 if !position_map.text_hitbox.contains(&point) {
19860 return None;
19861 }
19862 let display_point = position_map.point_for_position(point).previous_valid;
19863 let anchor = position_map
19864 .snapshot
19865 .display_point_to_anchor(display_point, Bias::Left);
19866 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19867 Some(utf16_offset.0)
19868 }
19869}
19870
19871trait SelectionExt {
19872 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19873 fn spanned_rows(
19874 &self,
19875 include_end_if_at_line_start: bool,
19876 map: &DisplaySnapshot,
19877 ) -> Range<MultiBufferRow>;
19878}
19879
19880impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19881 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19882 let start = self
19883 .start
19884 .to_point(&map.buffer_snapshot)
19885 .to_display_point(map);
19886 let end = self
19887 .end
19888 .to_point(&map.buffer_snapshot)
19889 .to_display_point(map);
19890 if self.reversed {
19891 end..start
19892 } else {
19893 start..end
19894 }
19895 }
19896
19897 fn spanned_rows(
19898 &self,
19899 include_end_if_at_line_start: bool,
19900 map: &DisplaySnapshot,
19901 ) -> Range<MultiBufferRow> {
19902 let start = self.start.to_point(&map.buffer_snapshot);
19903 let mut end = self.end.to_point(&map.buffer_snapshot);
19904 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19905 end.row -= 1;
19906 }
19907
19908 let buffer_start = map.prev_line_boundary(start).0;
19909 let buffer_end = map.next_line_boundary(end).0;
19910 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19911 }
19912}
19913
19914impl<T: InvalidationRegion> InvalidationStack<T> {
19915 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19916 where
19917 S: Clone + ToOffset,
19918 {
19919 while let Some(region) = self.last() {
19920 let all_selections_inside_invalidation_ranges =
19921 if selections.len() == region.ranges().len() {
19922 selections
19923 .iter()
19924 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19925 .all(|(selection, invalidation_range)| {
19926 let head = selection.head().to_offset(buffer);
19927 invalidation_range.start <= head && invalidation_range.end >= head
19928 })
19929 } else {
19930 false
19931 };
19932
19933 if all_selections_inside_invalidation_ranges {
19934 break;
19935 } else {
19936 self.pop();
19937 }
19938 }
19939 }
19940}
19941
19942impl<T> Default for InvalidationStack<T> {
19943 fn default() -> Self {
19944 Self(Default::default())
19945 }
19946}
19947
19948impl<T> Deref for InvalidationStack<T> {
19949 type Target = Vec<T>;
19950
19951 fn deref(&self) -> &Self::Target {
19952 &self.0
19953 }
19954}
19955
19956impl<T> DerefMut for InvalidationStack<T> {
19957 fn deref_mut(&mut self) -> &mut Self::Target {
19958 &mut self.0
19959 }
19960}
19961
19962impl InvalidationRegion for SnippetState {
19963 fn ranges(&self) -> &[Range<Anchor>] {
19964 &self.ranges[self.active_index]
19965 }
19966}
19967
19968pub fn diagnostic_block_renderer(
19969 diagnostic: Diagnostic,
19970 max_message_rows: Option<u8>,
19971 allow_closing: bool,
19972) -> RenderBlock {
19973 let (text_without_backticks, code_ranges) =
19974 highlight_diagnostic_message(&diagnostic, max_message_rows);
19975
19976 Arc::new(move |cx: &mut BlockContext| {
19977 let group_id: SharedString = cx.block_id.to_string().into();
19978
19979 let mut text_style = cx.window.text_style().clone();
19980 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19981 let theme_settings = ThemeSettings::get_global(cx);
19982 text_style.font_family = theme_settings.buffer_font.family.clone();
19983 text_style.font_style = theme_settings.buffer_font.style;
19984 text_style.font_features = theme_settings.buffer_font.features.clone();
19985 text_style.font_weight = theme_settings.buffer_font.weight;
19986
19987 let multi_line_diagnostic = diagnostic.message.contains('\n');
19988
19989 let buttons = |diagnostic: &Diagnostic| {
19990 if multi_line_diagnostic {
19991 v_flex()
19992 } else {
19993 h_flex()
19994 }
19995 .when(allow_closing, |div| {
19996 div.children(diagnostic.is_primary.then(|| {
19997 IconButton::new("close-block", IconName::XCircle)
19998 .icon_color(Color::Muted)
19999 .size(ButtonSize::Compact)
20000 .style(ButtonStyle::Transparent)
20001 .visible_on_hover(group_id.clone())
20002 .on_click(move |_click, window, cx| {
20003 window.dispatch_action(Box::new(Cancel), cx)
20004 })
20005 .tooltip(|window, cx| {
20006 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
20007 })
20008 }))
20009 })
20010 .child(
20011 IconButton::new("copy-block", IconName::Copy)
20012 .icon_color(Color::Muted)
20013 .size(ButtonSize::Compact)
20014 .style(ButtonStyle::Transparent)
20015 .visible_on_hover(group_id.clone())
20016 .on_click({
20017 let message = diagnostic.message.clone();
20018 move |_click, _, cx| {
20019 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
20020 }
20021 })
20022 .tooltip(Tooltip::text("Copy diagnostic message")),
20023 )
20024 };
20025
20026 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
20027 AvailableSpace::min_size(),
20028 cx.window,
20029 cx.app,
20030 );
20031
20032 h_flex()
20033 .id(cx.block_id)
20034 .group(group_id.clone())
20035 .relative()
20036 .size_full()
20037 .block_mouse_down()
20038 .pl(cx.gutter_dimensions.width)
20039 .w(cx.max_width - cx.gutter_dimensions.full_width())
20040 .child(
20041 div()
20042 .flex()
20043 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
20044 .flex_shrink(),
20045 )
20046 .child(buttons(&diagnostic))
20047 .child(div().flex().flex_shrink_0().child(
20048 StyledText::new(text_without_backticks.clone()).with_default_highlights(
20049 &text_style,
20050 code_ranges.iter().map(|range| {
20051 (
20052 range.clone(),
20053 HighlightStyle {
20054 font_weight: Some(FontWeight::BOLD),
20055 ..Default::default()
20056 },
20057 )
20058 }),
20059 ),
20060 ))
20061 .into_any_element()
20062 })
20063}
20064
20065fn inline_completion_edit_text(
20066 current_snapshot: &BufferSnapshot,
20067 edits: &[(Range<Anchor>, String)],
20068 edit_preview: &EditPreview,
20069 include_deletions: bool,
20070 cx: &App,
20071) -> HighlightedText {
20072 let edits = edits
20073 .iter()
20074 .map(|(anchor, text)| {
20075 (
20076 anchor.start.text_anchor..anchor.end.text_anchor,
20077 text.clone(),
20078 )
20079 })
20080 .collect::<Vec<_>>();
20081
20082 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20083}
20084
20085pub fn highlight_diagnostic_message(
20086 diagnostic: &Diagnostic,
20087 mut max_message_rows: Option<u8>,
20088) -> (SharedString, Vec<Range<usize>>) {
20089 let mut text_without_backticks = String::new();
20090 let mut code_ranges = Vec::new();
20091
20092 if let Some(source) = &diagnostic.source {
20093 text_without_backticks.push_str(source);
20094 code_ranges.push(0..source.len());
20095 text_without_backticks.push_str(": ");
20096 }
20097
20098 let mut prev_offset = 0;
20099 let mut in_code_block = false;
20100 let has_row_limit = max_message_rows.is_some();
20101 let mut newline_indices = diagnostic
20102 .message
20103 .match_indices('\n')
20104 .filter(|_| has_row_limit)
20105 .map(|(ix, _)| ix)
20106 .fuse()
20107 .peekable();
20108
20109 for (quote_ix, _) in diagnostic
20110 .message
20111 .match_indices('`')
20112 .chain([(diagnostic.message.len(), "")])
20113 {
20114 let mut first_newline_ix = None;
20115 let mut last_newline_ix = None;
20116 while let Some(newline_ix) = newline_indices.peek() {
20117 if *newline_ix < quote_ix {
20118 if first_newline_ix.is_none() {
20119 first_newline_ix = Some(*newline_ix);
20120 }
20121 last_newline_ix = Some(*newline_ix);
20122
20123 if let Some(rows_left) = &mut max_message_rows {
20124 if *rows_left == 0 {
20125 break;
20126 } else {
20127 *rows_left -= 1;
20128 }
20129 }
20130 let _ = newline_indices.next();
20131 } else {
20132 break;
20133 }
20134 }
20135 let prev_len = text_without_backticks.len();
20136 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
20137 text_without_backticks.push_str(new_text);
20138 if in_code_block {
20139 code_ranges.push(prev_len..text_without_backticks.len());
20140 }
20141 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
20142 in_code_block = !in_code_block;
20143 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
20144 text_without_backticks.push_str("...");
20145 break;
20146 }
20147 }
20148
20149 (text_without_backticks.into(), code_ranges)
20150}
20151
20152fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20153 match severity {
20154 DiagnosticSeverity::ERROR => colors.error,
20155 DiagnosticSeverity::WARNING => colors.warning,
20156 DiagnosticSeverity::INFORMATION => colors.info,
20157 DiagnosticSeverity::HINT => colors.info,
20158 _ => colors.ignored,
20159 }
20160}
20161
20162pub fn styled_runs_for_code_label<'a>(
20163 label: &'a CodeLabel,
20164 syntax_theme: &'a theme::SyntaxTheme,
20165) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20166 let fade_out = HighlightStyle {
20167 fade_out: Some(0.35),
20168 ..Default::default()
20169 };
20170
20171 let mut prev_end = label.filter_range.end;
20172 label
20173 .runs
20174 .iter()
20175 .enumerate()
20176 .flat_map(move |(ix, (range, highlight_id))| {
20177 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20178 style
20179 } else {
20180 return Default::default();
20181 };
20182 let mut muted_style = style;
20183 muted_style.highlight(fade_out);
20184
20185 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20186 if range.start >= label.filter_range.end {
20187 if range.start > prev_end {
20188 runs.push((prev_end..range.start, fade_out));
20189 }
20190 runs.push((range.clone(), muted_style));
20191 } else if range.end <= label.filter_range.end {
20192 runs.push((range.clone(), style));
20193 } else {
20194 runs.push((range.start..label.filter_range.end, style));
20195 runs.push((label.filter_range.end..range.end, muted_style));
20196 }
20197 prev_end = cmp::max(prev_end, range.end);
20198
20199 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20200 runs.push((prev_end..label.text.len(), fade_out));
20201 }
20202
20203 runs
20204 })
20205}
20206
20207pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20208 let mut prev_index = 0;
20209 let mut prev_codepoint: Option<char> = None;
20210 text.char_indices()
20211 .chain([(text.len(), '\0')])
20212 .filter_map(move |(index, codepoint)| {
20213 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20214 let is_boundary = index == text.len()
20215 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20216 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20217 if is_boundary {
20218 let chunk = &text[prev_index..index];
20219 prev_index = index;
20220 Some(chunk)
20221 } else {
20222 None
20223 }
20224 })
20225}
20226
20227pub trait RangeToAnchorExt: Sized {
20228 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20229
20230 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20231 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20232 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20233 }
20234}
20235
20236impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20237 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20238 let start_offset = self.start.to_offset(snapshot);
20239 let end_offset = self.end.to_offset(snapshot);
20240 if start_offset == end_offset {
20241 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20242 } else {
20243 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20244 }
20245 }
20246}
20247
20248pub trait RowExt {
20249 fn as_f32(&self) -> f32;
20250
20251 fn next_row(&self) -> Self;
20252
20253 fn previous_row(&self) -> Self;
20254
20255 fn minus(&self, other: Self) -> u32;
20256}
20257
20258impl RowExt for DisplayRow {
20259 fn as_f32(&self) -> f32 {
20260 self.0 as f32
20261 }
20262
20263 fn next_row(&self) -> Self {
20264 Self(self.0 + 1)
20265 }
20266
20267 fn previous_row(&self) -> Self {
20268 Self(self.0.saturating_sub(1))
20269 }
20270
20271 fn minus(&self, other: Self) -> u32 {
20272 self.0 - other.0
20273 }
20274}
20275
20276impl RowExt for MultiBufferRow {
20277 fn as_f32(&self) -> f32 {
20278 self.0 as f32
20279 }
20280
20281 fn next_row(&self) -> Self {
20282 Self(self.0 + 1)
20283 }
20284
20285 fn previous_row(&self) -> Self {
20286 Self(self.0.saturating_sub(1))
20287 }
20288
20289 fn minus(&self, other: Self) -> u32 {
20290 self.0 - other.0
20291 }
20292}
20293
20294trait RowRangeExt {
20295 type Row;
20296
20297 fn len(&self) -> usize;
20298
20299 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20300}
20301
20302impl RowRangeExt for Range<MultiBufferRow> {
20303 type Row = MultiBufferRow;
20304
20305 fn len(&self) -> usize {
20306 (self.end.0 - self.start.0) as usize
20307 }
20308
20309 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20310 (self.start.0..self.end.0).map(MultiBufferRow)
20311 }
20312}
20313
20314impl RowRangeExt for Range<DisplayRow> {
20315 type Row = DisplayRow;
20316
20317 fn len(&self) -> usize {
20318 (self.end.0 - self.start.0) as usize
20319 }
20320
20321 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20322 (self.start.0..self.end.0).map(DisplayRow)
20323 }
20324}
20325
20326/// If select range has more than one line, we
20327/// just point the cursor to range.start.
20328fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20329 if range.start.row == range.end.row {
20330 range
20331 } else {
20332 range.start..range.start
20333 }
20334}
20335pub struct KillRing(ClipboardItem);
20336impl Global for KillRing {}
20337
20338const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20339
20340enum BreakpointPromptEditAction {
20341 Log,
20342 Condition,
20343 HitCondition,
20344}
20345
20346struct BreakpointPromptEditor {
20347 pub(crate) prompt: Entity<Editor>,
20348 editor: WeakEntity<Editor>,
20349 breakpoint_anchor: Anchor,
20350 breakpoint: Breakpoint,
20351 edit_action: BreakpointPromptEditAction,
20352 block_ids: HashSet<CustomBlockId>,
20353 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20354 _subscriptions: Vec<Subscription>,
20355}
20356
20357impl BreakpointPromptEditor {
20358 const MAX_LINES: u8 = 4;
20359
20360 fn new(
20361 editor: WeakEntity<Editor>,
20362 breakpoint_anchor: Anchor,
20363 breakpoint: Breakpoint,
20364 edit_action: BreakpointPromptEditAction,
20365 window: &mut Window,
20366 cx: &mut Context<Self>,
20367 ) -> Self {
20368 let base_text = match edit_action {
20369 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20370 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20371 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20372 }
20373 .map(|msg| msg.to_string())
20374 .unwrap_or_default();
20375
20376 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20377 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20378
20379 let prompt = cx.new(|cx| {
20380 let mut prompt = Editor::new(
20381 EditorMode::AutoHeight {
20382 max_lines: Self::MAX_LINES as usize,
20383 },
20384 buffer,
20385 None,
20386 window,
20387 cx,
20388 );
20389 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20390 prompt.set_show_cursor_when_unfocused(false, cx);
20391 prompt.set_placeholder_text(
20392 match edit_action {
20393 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20394 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20395 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20396 },
20397 cx,
20398 );
20399
20400 prompt
20401 });
20402
20403 Self {
20404 prompt,
20405 editor,
20406 breakpoint_anchor,
20407 breakpoint,
20408 edit_action,
20409 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20410 block_ids: Default::default(),
20411 _subscriptions: vec![],
20412 }
20413 }
20414
20415 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20416 self.block_ids.extend(block_ids)
20417 }
20418
20419 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20420 if let Some(editor) = self.editor.upgrade() {
20421 let message = self
20422 .prompt
20423 .read(cx)
20424 .buffer
20425 .read(cx)
20426 .as_singleton()
20427 .expect("A multi buffer in breakpoint prompt isn't possible")
20428 .read(cx)
20429 .as_rope()
20430 .to_string();
20431
20432 editor.update(cx, |editor, cx| {
20433 editor.edit_breakpoint_at_anchor(
20434 self.breakpoint_anchor,
20435 self.breakpoint.clone(),
20436 match self.edit_action {
20437 BreakpointPromptEditAction::Log => {
20438 BreakpointEditAction::EditLogMessage(message.into())
20439 }
20440 BreakpointPromptEditAction::Condition => {
20441 BreakpointEditAction::EditCondition(message.into())
20442 }
20443 BreakpointPromptEditAction::HitCondition => {
20444 BreakpointEditAction::EditHitCondition(message.into())
20445 }
20446 },
20447 cx,
20448 );
20449
20450 editor.remove_blocks(self.block_ids.clone(), None, cx);
20451 cx.focus_self(window);
20452 });
20453 }
20454 }
20455
20456 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20457 self.editor
20458 .update(cx, |editor, cx| {
20459 editor.remove_blocks(self.block_ids.clone(), None, cx);
20460 window.focus(&editor.focus_handle);
20461 })
20462 .log_err();
20463 }
20464
20465 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20466 let settings = ThemeSettings::get_global(cx);
20467 let text_style = TextStyle {
20468 color: if self.prompt.read(cx).read_only(cx) {
20469 cx.theme().colors().text_disabled
20470 } else {
20471 cx.theme().colors().text
20472 },
20473 font_family: settings.buffer_font.family.clone(),
20474 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20475 font_size: settings.buffer_font_size(cx).into(),
20476 font_weight: settings.buffer_font.weight,
20477 line_height: relative(settings.buffer_line_height.value()),
20478 ..Default::default()
20479 };
20480 EditorElement::new(
20481 &self.prompt,
20482 EditorStyle {
20483 background: cx.theme().colors().editor_background,
20484 local_player: cx.theme().players().local(),
20485 text: text_style,
20486 ..Default::default()
20487 },
20488 )
20489 }
20490}
20491
20492impl Render for BreakpointPromptEditor {
20493 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20494 let gutter_dimensions = *self.gutter_dimensions.lock();
20495 h_flex()
20496 .key_context("Editor")
20497 .bg(cx.theme().colors().editor_background)
20498 .border_y_1()
20499 .border_color(cx.theme().status().info_border)
20500 .size_full()
20501 .py(window.line_height() / 2.5)
20502 .on_action(cx.listener(Self::confirm))
20503 .on_action(cx.listener(Self::cancel))
20504 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20505 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20506 }
20507}
20508
20509impl Focusable for BreakpointPromptEditor {
20510 fn focus_handle(&self, cx: &App) -> FocusHandle {
20511 self.prompt.focus_handle(cx)
20512 }
20513}
20514
20515fn all_edits_insertions_or_deletions(
20516 edits: &Vec<(Range<Anchor>, String)>,
20517 snapshot: &MultiBufferSnapshot,
20518) -> bool {
20519 let mut all_insertions = true;
20520 let mut all_deletions = true;
20521
20522 for (range, new_text) in edits.iter() {
20523 let range_is_empty = range.to_offset(&snapshot).is_empty();
20524 let text_is_empty = new_text.is_empty();
20525
20526 if range_is_empty != text_is_empty {
20527 if range_is_empty {
20528 all_deletions = false;
20529 } else {
20530 all_insertions = false;
20531 }
20532 } else {
20533 return false;
20534 }
20535
20536 if !all_insertions && !all_deletions {
20537 return false;
20538 }
20539 }
20540 all_insertions || all_deletions
20541}
20542
20543struct MissingEditPredictionKeybindingTooltip;
20544
20545impl Render for MissingEditPredictionKeybindingTooltip {
20546 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20547 ui::tooltip_container(window, cx, |container, _, cx| {
20548 container
20549 .flex_shrink_0()
20550 .max_w_80()
20551 .min_h(rems_from_px(124.))
20552 .justify_between()
20553 .child(
20554 v_flex()
20555 .flex_1()
20556 .text_ui_sm(cx)
20557 .child(Label::new("Conflict with Accept Keybinding"))
20558 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20559 )
20560 .child(
20561 h_flex()
20562 .pb_1()
20563 .gap_1()
20564 .items_end()
20565 .w_full()
20566 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20567 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20568 }))
20569 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20570 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20571 })),
20572 )
20573 })
20574 }
20575}
20576
20577#[derive(Debug, Clone, Copy, PartialEq)]
20578pub struct LineHighlight {
20579 pub background: Background,
20580 pub border: Option<gpui::Hsla>,
20581}
20582
20583impl From<Hsla> for LineHighlight {
20584 fn from(hsla: Hsla) -> Self {
20585 Self {
20586 background: hsla.into(),
20587 border: None,
20588 }
20589 }
20590}
20591
20592impl From<Background> for LineHighlight {
20593 fn from(background: Background) -> Self {
20594 Self {
20595 background,
20596 border: None,
20597 }
20598 }
20599}
20600
20601fn render_diff_hunk_controls(
20602 row: u32,
20603 status: &DiffHunkStatus,
20604 hunk_range: Range<Anchor>,
20605 is_created_file: bool,
20606 line_height: Pixels,
20607 editor: &Entity<Editor>,
20608 _window: &mut Window,
20609 cx: &mut App,
20610) -> AnyElement {
20611 h_flex()
20612 .h(line_height)
20613 .mr_1()
20614 .gap_1()
20615 .px_0p5()
20616 .pb_1()
20617 .border_x_1()
20618 .border_b_1()
20619 .border_color(cx.theme().colors().border_variant)
20620 .rounded_b_lg()
20621 .bg(cx.theme().colors().editor_background)
20622 .gap_1()
20623 .occlude()
20624 .shadow_md()
20625 .child(if status.has_secondary_hunk() {
20626 Button::new(("stage", row as u64), "Stage")
20627 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20628 .tooltip({
20629 let focus_handle = editor.focus_handle(cx);
20630 move |window, cx| {
20631 Tooltip::for_action_in(
20632 "Stage Hunk",
20633 &::git::ToggleStaged,
20634 &focus_handle,
20635 window,
20636 cx,
20637 )
20638 }
20639 })
20640 .on_click({
20641 let editor = editor.clone();
20642 move |_event, _window, cx| {
20643 editor.update(cx, |editor, cx| {
20644 editor.stage_or_unstage_diff_hunks(
20645 true,
20646 vec![hunk_range.start..hunk_range.start],
20647 cx,
20648 );
20649 });
20650 }
20651 })
20652 } else {
20653 Button::new(("unstage", row as u64), "Unstage")
20654 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20655 .tooltip({
20656 let focus_handle = editor.focus_handle(cx);
20657 move |window, cx| {
20658 Tooltip::for_action_in(
20659 "Unstage Hunk",
20660 &::git::ToggleStaged,
20661 &focus_handle,
20662 window,
20663 cx,
20664 )
20665 }
20666 })
20667 .on_click({
20668 let editor = editor.clone();
20669 move |_event, _window, cx| {
20670 editor.update(cx, |editor, cx| {
20671 editor.stage_or_unstage_diff_hunks(
20672 false,
20673 vec![hunk_range.start..hunk_range.start],
20674 cx,
20675 );
20676 });
20677 }
20678 })
20679 })
20680 .child(
20681 Button::new(("restore", row as u64), "Restore")
20682 .tooltip({
20683 let focus_handle = editor.focus_handle(cx);
20684 move |window, cx| {
20685 Tooltip::for_action_in(
20686 "Restore Hunk",
20687 &::git::Restore,
20688 &focus_handle,
20689 window,
20690 cx,
20691 )
20692 }
20693 })
20694 .on_click({
20695 let editor = editor.clone();
20696 move |_event, window, cx| {
20697 editor.update(cx, |editor, cx| {
20698 let snapshot = editor.snapshot(window, cx);
20699 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20700 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20701 });
20702 }
20703 })
20704 .disabled(is_created_file),
20705 )
20706 .when(
20707 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20708 |el| {
20709 el.child(
20710 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20711 .shape(IconButtonShape::Square)
20712 .icon_size(IconSize::Small)
20713 // .disabled(!has_multiple_hunks)
20714 .tooltip({
20715 let focus_handle = editor.focus_handle(cx);
20716 move |window, cx| {
20717 Tooltip::for_action_in(
20718 "Next Hunk",
20719 &GoToHunk,
20720 &focus_handle,
20721 window,
20722 cx,
20723 )
20724 }
20725 })
20726 .on_click({
20727 let editor = editor.clone();
20728 move |_event, window, cx| {
20729 editor.update(cx, |editor, cx| {
20730 let snapshot = editor.snapshot(window, cx);
20731 let position =
20732 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20733 editor.go_to_hunk_before_or_after_position(
20734 &snapshot,
20735 position,
20736 Direction::Next,
20737 window,
20738 cx,
20739 );
20740 editor.expand_selected_diff_hunks(cx);
20741 });
20742 }
20743 }),
20744 )
20745 .child(
20746 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20747 .shape(IconButtonShape::Square)
20748 .icon_size(IconSize::Small)
20749 // .disabled(!has_multiple_hunks)
20750 .tooltip({
20751 let focus_handle = editor.focus_handle(cx);
20752 move |window, cx| {
20753 Tooltip::for_action_in(
20754 "Previous Hunk",
20755 &GoToPreviousHunk,
20756 &focus_handle,
20757 window,
20758 cx,
20759 )
20760 }
20761 })
20762 .on_click({
20763 let editor = editor.clone();
20764 move |_event, window, cx| {
20765 editor.update(cx, |editor, cx| {
20766 let snapshot = editor.snapshot(window, cx);
20767 let point =
20768 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20769 editor.go_to_hunk_before_or_after_position(
20770 &snapshot,
20771 point,
20772 Direction::Prev,
20773 window,
20774 cx,
20775 );
20776 editor.expand_selected_diff_hunks(cx);
20777 });
20778 }
20779 }),
20780 )
20781 },
20782 )
20783 .into_any_element()
20784}