1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod editor_tests;
44#[cfg(test)]
45mod inline_completion_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
52use aho_corasick::AhoCorasick;
53use anyhow::{Context as _, Result, anyhow};
54use blink_manager::BlinkManager;
55use buffer_diff::DiffHunkStatus;
56use client::{Collaborator, ParticipantIndex};
57use clock::ReplicaId;
58use collections::{BTreeMap, HashMap, HashSet, VecDeque};
59use convert_case::{Case, Casing};
60use display_map::*;
61pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
62use editor_settings::GoToDefinitionFallback;
63pub use editor_settings::{
64 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
65 ShowScrollbar,
66};
67pub use editor_settings_controls::*;
68use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
69pub use element::{
70 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
71};
72use feature_flags::{Debugger, FeatureFlagAppExt};
73use futures::{
74 FutureExt,
75 future::{self, Shared, join},
76};
77use fuzzy::StringMatchCandidate;
78
79use ::git::Restore;
80use code_context_menus::{
81 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
82 CompletionsMenu, ContextMenuOrigin,
83};
84use git::blame::{GitBlame, GlobalBlameRenderer};
85use gpui::{
86 Action, Animation, AnimationExt, AnyElement, AnyWeakEntity, App, AppContext,
87 AsyncWindowContext, AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry,
88 ClipboardItem, Context, DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter,
89 FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla,
90 KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render,
91 SharedString, Size, Stateful, Styled, StyledText, Subscription, Task, TextStyle,
92 TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity,
93 WeakFocusHandle, Window, div, impl_actions, point, prelude::*, pulsating_between, px, relative,
94 size,
95};
96use highlight_matching_bracket::refresh_matching_bracket_highlights;
97use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
98pub use hover_popover::hover_markdown_style;
99use hover_popover::{HoverState, hide_hover};
100use indent_guides::ActiveIndentGuidesState;
101use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
102pub use inline_completion::Direction;
103use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
104pub use items::MAX_TAB_TITLE_LEN;
105use itertools::Itertools;
106use language::{
107 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
108 CursorShape, Diagnostic, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
109 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
110 TransactionId, TreeSitterOptions, WordsQuery,
111 language_settings::{
112 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
113 all_language_settings, language_settings,
114 },
115 point_from_lsp, text_diff_with_options,
116};
117use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
118use linked_editing_ranges::refresh_linked_ranges;
119use mouse_context_menu::MouseContextMenu;
120use persistence::DB;
121use project::{
122 ProjectPath,
123 debugger::breakpoint_store::{
124 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
125 },
126};
127
128pub use git::blame::BlameRenderer;
129pub use proposed_changes_editor::{
130 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
131};
132use smallvec::smallvec;
133use std::{cell::OnceCell, iter::Peekable};
134use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
135
136pub use lsp::CompletionContext;
137use lsp::{
138 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
139 InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
140};
141
142use language::BufferSnapshot;
143pub use lsp_ext::lsp_tasks;
144use movement::TextLayoutDetails;
145pub use multi_buffer::{
146 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
147 ToOffset, ToPoint,
148};
149use multi_buffer::{
150 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
151 MultiOrSingleBufferOffsetRange, PathKey, ToOffsetUtf16,
152};
153use parking_lot::Mutex;
154use project::{
155 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
156 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
157 TaskSourceKind,
158 debugger::breakpoint_store::Breakpoint,
159 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
160 project_settings::{GitGutterSetting, ProjectSettings},
161};
162use rand::prelude::*;
163use rpc::{ErrorExt, proto::*};
164use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
165use selections_collection::{
166 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
167};
168use serde::{Deserialize, Serialize};
169use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
170use smallvec::SmallVec;
171use snippet::Snippet;
172use std::sync::Arc;
173use std::{
174 any::TypeId,
175 borrow::Cow,
176 cell::RefCell,
177 cmp::{self, Ordering, Reverse},
178 mem,
179 num::NonZeroU32,
180 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
181 path::{Path, PathBuf},
182 rc::Rc,
183 time::{Duration, Instant},
184};
185pub use sum_tree::Bias;
186use sum_tree::TreeMap;
187use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
188use theme::{
189 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
190 observe_buffer_font_size_adjustment,
191};
192use ui::{
193 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
194 IconSize, Key, Tooltip, h_flex, prelude::*,
195};
196use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
197use workspace::{
198 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
199 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
200 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
201 item::{ItemHandle, PreviewTabsSettings},
202 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
203 searchable::SearchEvent,
204};
205
206use crate::hover_links::{find_url, find_url_from_range};
207use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
208
209pub const FILE_HEADER_HEIGHT: u32 = 2;
210pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
211pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
212const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
213const MAX_LINE_LEN: usize = 1024;
214const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
215const MAX_SELECTION_HISTORY_LEN: usize = 1024;
216pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
217#[doc(hidden)]
218pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
219
220pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
221pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
222pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
223
224pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
225pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
226pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
227
228pub type RenderDiffHunkControlsFn = Arc<
229 dyn Fn(
230 u32,
231 &DiffHunkStatus,
232 Range<Anchor>,
233 bool,
234 Pixels,
235 &Entity<Editor>,
236 &mut Window,
237 &mut App,
238 ) -> AnyElement,
239>;
240
241const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
242 alt: true,
243 shift: true,
244 control: false,
245 platform: false,
246 function: false,
247};
248
249#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
250pub enum InlayId {
251 InlineCompletion(usize),
252 Hint(usize),
253}
254
255impl InlayId {
256 fn id(&self) -> usize {
257 match self {
258 Self::InlineCompletion(id) => *id,
259 Self::Hint(id) => *id,
260 }
261 }
262}
263
264pub enum DebugCurrentRowHighlight {}
265enum DocumentHighlightRead {}
266enum DocumentHighlightWrite {}
267enum InputComposition {}
268enum SelectedTextHighlight {}
269
270#[derive(Debug, Copy, Clone, PartialEq, Eq)]
271pub enum Navigated {
272 Yes,
273 No,
274}
275
276impl Navigated {
277 pub fn from_bool(yes: bool) -> Navigated {
278 if yes { Navigated::Yes } else { Navigated::No }
279 }
280}
281
282#[derive(Debug, Clone, PartialEq, Eq)]
283enum DisplayDiffHunk {
284 Folded {
285 display_row: DisplayRow,
286 },
287 Unfolded {
288 is_created_file: bool,
289 diff_base_byte_range: Range<usize>,
290 display_row_range: Range<DisplayRow>,
291 multi_buffer_range: Range<Anchor>,
292 status: DiffHunkStatus,
293 },
294}
295
296pub enum HideMouseCursorOrigin {
297 TypingAction,
298 MovementAction,
299}
300
301pub fn init_settings(cx: &mut App) {
302 EditorSettings::register(cx);
303}
304
305pub fn init(cx: &mut App) {
306 init_settings(cx);
307
308 cx.set_global(GlobalBlameRenderer(Arc::new(())));
309
310 workspace::register_project_item::<Editor>(cx);
311 workspace::FollowableViewRegistry::register::<Editor>(cx);
312 workspace::register_serializable_item::<Editor>(cx);
313
314 cx.observe_new(
315 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
316 workspace.register_action(Editor::new_file);
317 workspace.register_action(Editor::new_file_vertical);
318 workspace.register_action(Editor::new_file_horizontal);
319 workspace.register_action(Editor::cancel_language_server_work);
320 },
321 )
322 .detach();
323
324 cx.on_action(move |_: &workspace::NewFile, cx| {
325 let app_state = workspace::AppState::global(cx);
326 if let Some(app_state) = app_state.upgrade() {
327 workspace::open_new(
328 Default::default(),
329 app_state,
330 cx,
331 |workspace, window, cx| {
332 Editor::new_file(workspace, &Default::default(), window, cx)
333 },
334 )
335 .detach();
336 }
337 });
338 cx.on_action(move |_: &workspace::NewWindow, cx| {
339 let app_state = workspace::AppState::global(cx);
340 if let Some(app_state) = app_state.upgrade() {
341 workspace::open_new(
342 Default::default(),
343 app_state,
344 cx,
345 |workspace, window, cx| {
346 cx.activate(true);
347 Editor::new_file(workspace, &Default::default(), window, cx)
348 },
349 )
350 .detach();
351 }
352 });
353}
354
355pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
356 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
357}
358
359pub struct SearchWithinRange;
360
361trait InvalidationRegion {
362 fn ranges(&self) -> &[Range<Anchor>];
363}
364
365#[derive(Clone, Debug, PartialEq)]
366pub enum SelectPhase {
367 Begin {
368 position: DisplayPoint,
369 add: bool,
370 click_count: usize,
371 },
372 BeginColumnar {
373 position: DisplayPoint,
374 reset: bool,
375 goal_column: u32,
376 },
377 Extend {
378 position: DisplayPoint,
379 click_count: usize,
380 },
381 Update {
382 position: DisplayPoint,
383 goal_column: u32,
384 scroll_delta: gpui::Point<f32>,
385 },
386 End,
387}
388
389#[derive(Clone, Debug)]
390pub enum SelectMode {
391 Character,
392 Word(Range<Anchor>),
393 Line(Range<Anchor>),
394 All,
395}
396
397#[derive(Copy, Clone, PartialEq, Eq, Debug)]
398pub enum EditorMode {
399 SingleLine { auto_width: bool },
400 AutoHeight { max_lines: usize },
401 Full,
402}
403
404#[derive(Copy, Clone, Debug)]
405pub enum SoftWrap {
406 /// Prefer not to wrap at all.
407 ///
408 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
409 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
410 GitDiff,
411 /// Prefer a single line generally, unless an overly long line is encountered.
412 None,
413 /// Soft wrap lines that exceed the editor width.
414 EditorWidth,
415 /// Soft wrap lines at the preferred line length.
416 Column(u32),
417 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
418 Bounded(u32),
419}
420
421#[derive(Clone)]
422pub struct EditorStyle {
423 pub background: Hsla,
424 pub local_player: PlayerColor,
425 pub text: TextStyle,
426 pub scrollbar_width: Pixels,
427 pub syntax: Arc<SyntaxTheme>,
428 pub status: StatusColors,
429 pub inlay_hints_style: HighlightStyle,
430 pub inline_completion_styles: InlineCompletionStyles,
431 pub unnecessary_code_fade: f32,
432}
433
434impl Default for EditorStyle {
435 fn default() -> Self {
436 Self {
437 background: Hsla::default(),
438 local_player: PlayerColor::default(),
439 text: TextStyle::default(),
440 scrollbar_width: Pixels::default(),
441 syntax: Default::default(),
442 // HACK: Status colors don't have a real default.
443 // We should look into removing the status colors from the editor
444 // style and retrieve them directly from the theme.
445 status: StatusColors::dark(),
446 inlay_hints_style: HighlightStyle::default(),
447 inline_completion_styles: InlineCompletionStyles {
448 insertion: HighlightStyle::default(),
449 whitespace: HighlightStyle::default(),
450 },
451 unnecessary_code_fade: Default::default(),
452 }
453 }
454}
455
456pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
457 let show_background = language_settings::language_settings(None, None, cx)
458 .inlay_hints
459 .show_background;
460
461 HighlightStyle {
462 color: Some(cx.theme().status().hint),
463 background_color: show_background.then(|| cx.theme().status().hint_background),
464 ..HighlightStyle::default()
465 }
466}
467
468pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
469 InlineCompletionStyles {
470 insertion: HighlightStyle {
471 color: Some(cx.theme().status().predictive),
472 ..HighlightStyle::default()
473 },
474 whitespace: HighlightStyle {
475 background_color: Some(cx.theme().status().created_background),
476 ..HighlightStyle::default()
477 },
478 }
479}
480
481type CompletionId = usize;
482
483pub(crate) enum EditDisplayMode {
484 TabAccept,
485 DiffPopover,
486 Inline,
487}
488
489enum InlineCompletion {
490 Edit {
491 edits: Vec<(Range<Anchor>, String)>,
492 edit_preview: Option<EditPreview>,
493 display_mode: EditDisplayMode,
494 snapshot: BufferSnapshot,
495 },
496 Move {
497 target: Anchor,
498 snapshot: BufferSnapshot,
499 },
500}
501
502struct InlineCompletionState {
503 inlay_ids: Vec<InlayId>,
504 completion: InlineCompletion,
505 completion_id: Option<SharedString>,
506 invalidation_range: Range<Anchor>,
507}
508
509enum EditPredictionSettings {
510 Disabled,
511 Enabled {
512 show_in_menu: bool,
513 preview_requires_modifier: bool,
514 },
515}
516
517enum InlineCompletionHighlight {}
518
519#[derive(Debug, Clone)]
520struct InlineDiagnostic {
521 message: SharedString,
522 group_id: usize,
523 is_primary: bool,
524 start: Point,
525 severity: DiagnosticSeverity,
526}
527
528pub enum MenuInlineCompletionsPolicy {
529 Never,
530 ByProvider,
531}
532
533pub enum EditPredictionPreview {
534 /// Modifier is not pressed
535 Inactive { released_too_fast: bool },
536 /// Modifier pressed
537 Active {
538 since: Instant,
539 previous_scroll_position: Option<ScrollAnchor>,
540 },
541}
542
543impl EditPredictionPreview {
544 pub fn released_too_fast(&self) -> bool {
545 match self {
546 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
547 EditPredictionPreview::Active { .. } => false,
548 }
549 }
550
551 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
552 if let EditPredictionPreview::Active {
553 previous_scroll_position,
554 ..
555 } = self
556 {
557 *previous_scroll_position = scroll_position;
558 }
559 }
560}
561
562pub struct ContextMenuOptions {
563 pub min_entries_visible: usize,
564 pub max_entries_visible: usize,
565 pub placement: Option<ContextMenuPlacement>,
566}
567
568#[derive(Debug, Clone, PartialEq, Eq)]
569pub enum ContextMenuPlacement {
570 Above,
571 Below,
572}
573
574#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
575struct EditorActionId(usize);
576
577impl EditorActionId {
578 pub fn post_inc(&mut self) -> Self {
579 let answer = self.0;
580
581 *self = Self(answer + 1);
582
583 Self(answer)
584 }
585}
586
587// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
588// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
589
590type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
591type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
592
593#[derive(Default)]
594struct ScrollbarMarkerState {
595 scrollbar_size: Size<Pixels>,
596 dirty: bool,
597 markers: Arc<[PaintQuad]>,
598 pending_refresh: Option<Task<Result<()>>>,
599}
600
601impl ScrollbarMarkerState {
602 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
603 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
604 }
605}
606
607#[derive(Clone, Debug)]
608struct RunnableTasks {
609 templates: Vec<(TaskSourceKind, TaskTemplate)>,
610 offset: multi_buffer::Anchor,
611 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
612 column: u32,
613 // Values of all named captures, including those starting with '_'
614 extra_variables: HashMap<String, String>,
615 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
616 context_range: Range<BufferOffset>,
617}
618
619impl RunnableTasks {
620 fn resolve<'a>(
621 &'a self,
622 cx: &'a task::TaskContext,
623 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
624 self.templates.iter().filter_map(|(kind, template)| {
625 template
626 .resolve_task(&kind.to_id_base(), cx)
627 .map(|task| (kind.clone(), task))
628 })
629 }
630}
631
632#[derive(Clone)]
633struct ResolvedTasks {
634 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
635 position: Anchor,
636}
637
638#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
639struct BufferOffset(usize);
640
641// Addons allow storing per-editor state in other crates (e.g. Vim)
642pub trait Addon: 'static {
643 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
644
645 fn render_buffer_header_controls(
646 &self,
647 _: &ExcerptInfo,
648 _: &Window,
649 _: &App,
650 ) -> Option<AnyElement> {
651 None
652 }
653
654 fn to_any(&self) -> &dyn std::any::Any;
655}
656
657/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
658///
659/// See the [module level documentation](self) for more information.
660pub struct Editor {
661 focus_handle: FocusHandle,
662 last_focused_descendant: Option<WeakFocusHandle>,
663 /// The text buffer being edited
664 buffer: Entity<MultiBuffer>,
665 /// Map of how text in the buffer should be displayed.
666 /// Handles soft wraps, folds, fake inlay text insertions, etc.
667 pub display_map: Entity<DisplayMap>,
668 pub selections: SelectionsCollection,
669 pub scroll_manager: ScrollManager,
670 /// When inline assist editors are linked, they all render cursors because
671 /// typing enters text into each of them, even the ones that aren't focused.
672 pub(crate) show_cursor_when_unfocused: bool,
673 columnar_selection_tail: Option<Anchor>,
674 add_selections_state: Option<AddSelectionsState>,
675 select_next_state: Option<SelectNextState>,
676 select_prev_state: Option<SelectNextState>,
677 selection_history: SelectionHistory,
678 autoclose_regions: Vec<AutocloseRegion>,
679 snippet_stack: InvalidationStack<SnippetState>,
680 select_syntax_node_history: SelectSyntaxNodeHistory,
681 ime_transaction: Option<TransactionId>,
682 active_diagnostics: Option<ActiveDiagnosticGroup>,
683 show_inline_diagnostics: bool,
684 inline_diagnostics_update: Task<()>,
685 inline_diagnostics_enabled: bool,
686 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
687 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
688 hard_wrap: Option<usize>,
689
690 // TODO: make this a access method
691 pub project: Option<Entity<Project>>,
692 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
693 completion_provider: Option<Box<dyn CompletionProvider>>,
694 collaboration_hub: Option<Box<dyn CollaborationHub>>,
695 blink_manager: Entity<BlinkManager>,
696 show_cursor_names: bool,
697 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
698 pub show_local_selections: bool,
699 mode: EditorMode,
700 show_breadcrumbs: bool,
701 show_gutter: bool,
702 show_scrollbars: bool,
703 show_line_numbers: Option<bool>,
704 use_relative_line_numbers: Option<bool>,
705 show_git_diff_gutter: Option<bool>,
706 show_code_actions: Option<bool>,
707 show_runnables: Option<bool>,
708 show_breakpoints: Option<bool>,
709 show_wrap_guides: Option<bool>,
710 show_indent_guides: Option<bool>,
711 placeholder_text: Option<Arc<str>>,
712 highlight_order: usize,
713 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
714 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
715 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
716 scrollbar_marker_state: ScrollbarMarkerState,
717 active_indent_guides_state: ActiveIndentGuidesState,
718 nav_history: Option<ItemNavHistory>,
719 context_menu: RefCell<Option<CodeContextMenu>>,
720 context_menu_options: Option<ContextMenuOptions>,
721 mouse_context_menu: Option<MouseContextMenu>,
722 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
723 signature_help_state: SignatureHelpState,
724 auto_signature_help: Option<bool>,
725 find_all_references_task_sources: Vec<Anchor>,
726 next_completion_id: CompletionId,
727 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
728 code_actions_task: Option<Task<Result<()>>>,
729 selection_highlight_task: Option<Task<()>>,
730 document_highlights_task: Option<Task<()>>,
731 linked_editing_range_task: Option<Task<Option<()>>>,
732 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
733 pending_rename: Option<RenameState>,
734 searchable: bool,
735 cursor_shape: CursorShape,
736 current_line_highlight: Option<CurrentLineHighlight>,
737 collapse_matches: bool,
738 autoindent_mode: Option<AutoindentMode>,
739 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
740 input_enabled: bool,
741 use_modal_editing: bool,
742 read_only: bool,
743 leader_peer_id: Option<PeerId>,
744 remote_id: Option<ViewId>,
745 hover_state: HoverState,
746 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
747 gutter_hovered: bool,
748 hovered_link_state: Option<HoveredLinkState>,
749 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
750 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
751 active_inline_completion: Option<InlineCompletionState>,
752 /// Used to prevent flickering as the user types while the menu is open
753 stale_inline_completion_in_menu: Option<InlineCompletionState>,
754 edit_prediction_settings: EditPredictionSettings,
755 inline_completions_hidden_for_vim_mode: bool,
756 show_inline_completions_override: Option<bool>,
757 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
758 edit_prediction_preview: EditPredictionPreview,
759 edit_prediction_indent_conflict: bool,
760 edit_prediction_requires_modifier_in_indent_conflict: bool,
761 inlay_hint_cache: InlayHintCache,
762 next_inlay_id: usize,
763 _subscriptions: Vec<Subscription>,
764 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
765 gutter_dimensions: GutterDimensions,
766 style: Option<EditorStyle>,
767 text_style_refinement: Option<TextStyleRefinement>,
768 next_editor_action_id: EditorActionId,
769 editor_actions:
770 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
771 use_autoclose: bool,
772 use_auto_surround: bool,
773 auto_replace_emoji_shortcode: bool,
774 jsx_tag_auto_close_enabled_in_any_buffer: bool,
775 show_git_blame_gutter: bool,
776 show_git_blame_inline: bool,
777 show_git_blame_inline_delay_task: Option<Task<()>>,
778 pub git_blame_inline_tooltip: Option<AnyWeakEntity>,
779 git_blame_inline_enabled: bool,
780 render_diff_hunk_controls: RenderDiffHunkControlsFn,
781 serialize_dirty_buffers: bool,
782 show_selection_menu: Option<bool>,
783 blame: Option<Entity<GitBlame>>,
784 blame_subscription: Option<Subscription>,
785 custom_context_menu: Option<
786 Box<
787 dyn 'static
788 + Fn(
789 &mut Self,
790 DisplayPoint,
791 &mut Window,
792 &mut Context<Self>,
793 ) -> Option<Entity<ui::ContextMenu>>,
794 >,
795 >,
796 last_bounds: Option<Bounds<Pixels>>,
797 last_position_map: Option<Rc<PositionMap>>,
798 expect_bounds_change: Option<Bounds<Pixels>>,
799 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
800 tasks_update_task: Option<Task<()>>,
801 breakpoint_store: Option<Entity<BreakpointStore>>,
802 /// Allow's a user to create a breakpoint by selecting this indicator
803 /// It should be None while a user is not hovering over the gutter
804 /// Otherwise it represents the point that the breakpoint will be shown
805 gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
806 in_project_search: bool,
807 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
808 breadcrumb_header: Option<String>,
809 focused_block: Option<FocusedBlock>,
810 next_scroll_position: NextScrollCursorCenterTopBottom,
811 addons: HashMap<TypeId, Box<dyn Addon>>,
812 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
813 load_diff_task: Option<Shared<Task<()>>>,
814 selection_mark_mode: bool,
815 toggle_fold_multiple_buffers: Task<()>,
816 _scroll_cursor_center_top_bottom_task: Task<()>,
817 serialize_selections: Task<()>,
818 serialize_folds: Task<()>,
819 mouse_cursor_hidden: bool,
820 hide_mouse_mode: HideMouseMode,
821}
822
823#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
824enum NextScrollCursorCenterTopBottom {
825 #[default]
826 Center,
827 Top,
828 Bottom,
829}
830
831impl NextScrollCursorCenterTopBottom {
832 fn next(&self) -> Self {
833 match self {
834 Self::Center => Self::Top,
835 Self::Top => Self::Bottom,
836 Self::Bottom => Self::Center,
837 }
838 }
839}
840
841#[derive(Clone)]
842pub struct EditorSnapshot {
843 pub mode: EditorMode,
844 show_gutter: bool,
845 show_line_numbers: Option<bool>,
846 show_git_diff_gutter: Option<bool>,
847 show_code_actions: Option<bool>,
848 show_runnables: Option<bool>,
849 show_breakpoints: Option<bool>,
850 git_blame_gutter_max_author_length: Option<usize>,
851 pub display_snapshot: DisplaySnapshot,
852 pub placeholder_text: Option<Arc<str>>,
853 is_focused: bool,
854 scroll_anchor: ScrollAnchor,
855 ongoing_scroll: OngoingScroll,
856 current_line_highlight: CurrentLineHighlight,
857 gutter_hovered: bool,
858}
859
860#[derive(Default, Debug, Clone, Copy)]
861pub struct GutterDimensions {
862 pub left_padding: Pixels,
863 pub right_padding: Pixels,
864 pub width: Pixels,
865 pub margin: Pixels,
866 pub git_blame_entries_width: Option<Pixels>,
867}
868
869impl GutterDimensions {
870 /// The full width of the space taken up by the gutter.
871 pub fn full_width(&self) -> Pixels {
872 self.margin + self.width
873 }
874
875 /// The width of the space reserved for the fold indicators,
876 /// use alongside 'justify_end' and `gutter_width` to
877 /// right align content with the line numbers
878 pub fn fold_area_width(&self) -> Pixels {
879 self.margin + self.right_padding
880 }
881}
882
883#[derive(Debug)]
884pub struct RemoteSelection {
885 pub replica_id: ReplicaId,
886 pub selection: Selection<Anchor>,
887 pub cursor_shape: CursorShape,
888 pub peer_id: PeerId,
889 pub line_mode: bool,
890 pub participant_index: Option<ParticipantIndex>,
891 pub user_name: Option<SharedString>,
892}
893
894#[derive(Clone, Debug)]
895struct SelectionHistoryEntry {
896 selections: Arc<[Selection<Anchor>]>,
897 select_next_state: Option<SelectNextState>,
898 select_prev_state: Option<SelectNextState>,
899 add_selections_state: Option<AddSelectionsState>,
900}
901
902enum SelectionHistoryMode {
903 Normal,
904 Undoing,
905 Redoing,
906}
907
908#[derive(Clone, PartialEq, Eq, Hash)]
909struct HoveredCursor {
910 replica_id: u16,
911 selection_id: usize,
912}
913
914impl Default for SelectionHistoryMode {
915 fn default() -> Self {
916 Self::Normal
917 }
918}
919
920#[derive(Default)]
921struct SelectionHistory {
922 #[allow(clippy::type_complexity)]
923 selections_by_transaction:
924 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
925 mode: SelectionHistoryMode,
926 undo_stack: VecDeque<SelectionHistoryEntry>,
927 redo_stack: VecDeque<SelectionHistoryEntry>,
928}
929
930impl SelectionHistory {
931 fn insert_transaction(
932 &mut self,
933 transaction_id: TransactionId,
934 selections: Arc<[Selection<Anchor>]>,
935 ) {
936 self.selections_by_transaction
937 .insert(transaction_id, (selections, None));
938 }
939
940 #[allow(clippy::type_complexity)]
941 fn transaction(
942 &self,
943 transaction_id: TransactionId,
944 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
945 self.selections_by_transaction.get(&transaction_id)
946 }
947
948 #[allow(clippy::type_complexity)]
949 fn transaction_mut(
950 &mut self,
951 transaction_id: TransactionId,
952 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
953 self.selections_by_transaction.get_mut(&transaction_id)
954 }
955
956 fn push(&mut self, entry: SelectionHistoryEntry) {
957 if !entry.selections.is_empty() {
958 match self.mode {
959 SelectionHistoryMode::Normal => {
960 self.push_undo(entry);
961 self.redo_stack.clear();
962 }
963 SelectionHistoryMode::Undoing => self.push_redo(entry),
964 SelectionHistoryMode::Redoing => self.push_undo(entry),
965 }
966 }
967 }
968
969 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
970 if self
971 .undo_stack
972 .back()
973 .map_or(true, |e| e.selections != entry.selections)
974 {
975 self.undo_stack.push_back(entry);
976 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
977 self.undo_stack.pop_front();
978 }
979 }
980 }
981
982 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
983 if self
984 .redo_stack
985 .back()
986 .map_or(true, |e| e.selections != entry.selections)
987 {
988 self.redo_stack.push_back(entry);
989 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
990 self.redo_stack.pop_front();
991 }
992 }
993 }
994}
995
996struct RowHighlight {
997 index: usize,
998 range: Range<Anchor>,
999 color: Hsla,
1000 should_autoscroll: bool,
1001}
1002
1003#[derive(Clone, Debug)]
1004struct AddSelectionsState {
1005 above: bool,
1006 stack: Vec<usize>,
1007}
1008
1009#[derive(Clone)]
1010struct SelectNextState {
1011 query: AhoCorasick,
1012 wordwise: bool,
1013 done: bool,
1014}
1015
1016impl std::fmt::Debug for SelectNextState {
1017 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1018 f.debug_struct(std::any::type_name::<Self>())
1019 .field("wordwise", &self.wordwise)
1020 .field("done", &self.done)
1021 .finish()
1022 }
1023}
1024
1025#[derive(Debug)]
1026struct AutocloseRegion {
1027 selection_id: usize,
1028 range: Range<Anchor>,
1029 pair: BracketPair,
1030}
1031
1032#[derive(Debug)]
1033struct SnippetState {
1034 ranges: Vec<Vec<Range<Anchor>>>,
1035 active_index: usize,
1036 choices: Vec<Option<Vec<String>>>,
1037}
1038
1039#[doc(hidden)]
1040pub struct RenameState {
1041 pub range: Range<Anchor>,
1042 pub old_name: Arc<str>,
1043 pub editor: Entity<Editor>,
1044 block_id: CustomBlockId,
1045}
1046
1047struct InvalidationStack<T>(Vec<T>);
1048
1049struct RegisteredInlineCompletionProvider {
1050 provider: Arc<dyn InlineCompletionProviderHandle>,
1051 _subscription: Subscription,
1052}
1053
1054#[derive(Debug, PartialEq, Eq)]
1055struct ActiveDiagnosticGroup {
1056 primary_range: Range<Anchor>,
1057 primary_message: String,
1058 group_id: usize,
1059 blocks: HashMap<CustomBlockId, Diagnostic>,
1060 is_valid: bool,
1061}
1062
1063#[derive(Serialize, Deserialize, Clone, Debug)]
1064pub struct ClipboardSelection {
1065 /// The number of bytes in this selection.
1066 pub len: usize,
1067 /// Whether this was a full-line selection.
1068 pub is_entire_line: bool,
1069 /// The indentation of the first line when this content was originally copied.
1070 pub first_line_indent: u32,
1071}
1072
1073// selections, scroll behavior, was newest selection reversed
1074type SelectSyntaxNodeHistoryState = (
1075 Box<[Selection<usize>]>,
1076 SelectSyntaxNodeScrollBehavior,
1077 bool,
1078);
1079
1080#[derive(Default)]
1081struct SelectSyntaxNodeHistory {
1082 stack: Vec<SelectSyntaxNodeHistoryState>,
1083 // disable temporarily to allow changing selections without losing the stack
1084 pub disable_clearing: bool,
1085}
1086
1087impl SelectSyntaxNodeHistory {
1088 pub fn try_clear(&mut self) {
1089 if !self.disable_clearing {
1090 self.stack.clear();
1091 }
1092 }
1093
1094 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1095 self.stack.push(selection);
1096 }
1097
1098 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1099 self.stack.pop()
1100 }
1101}
1102
1103enum SelectSyntaxNodeScrollBehavior {
1104 CursorTop,
1105 FitSelection,
1106 CursorBottom,
1107}
1108
1109#[derive(Debug)]
1110pub(crate) struct NavigationData {
1111 cursor_anchor: Anchor,
1112 cursor_position: Point,
1113 scroll_anchor: ScrollAnchor,
1114 scroll_top_row: u32,
1115}
1116
1117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1118pub enum GotoDefinitionKind {
1119 Symbol,
1120 Declaration,
1121 Type,
1122 Implementation,
1123}
1124
1125#[derive(Debug, Clone)]
1126enum InlayHintRefreshReason {
1127 ModifiersChanged(bool),
1128 Toggle(bool),
1129 SettingsChange(InlayHintSettings),
1130 NewLinesShown,
1131 BufferEdited(HashSet<Arc<Language>>),
1132 RefreshRequested,
1133 ExcerptsRemoved(Vec<ExcerptId>),
1134}
1135
1136impl InlayHintRefreshReason {
1137 fn description(&self) -> &'static str {
1138 match self {
1139 Self::ModifiersChanged(_) => "modifiers changed",
1140 Self::Toggle(_) => "toggle",
1141 Self::SettingsChange(_) => "settings change",
1142 Self::NewLinesShown => "new lines shown",
1143 Self::BufferEdited(_) => "buffer edited",
1144 Self::RefreshRequested => "refresh requested",
1145 Self::ExcerptsRemoved(_) => "excerpts removed",
1146 }
1147 }
1148}
1149
1150pub enum FormatTarget {
1151 Buffers,
1152 Ranges(Vec<Range<MultiBufferPoint>>),
1153}
1154
1155pub(crate) struct FocusedBlock {
1156 id: BlockId,
1157 focus_handle: WeakFocusHandle,
1158}
1159
1160#[derive(Clone)]
1161enum JumpData {
1162 MultiBufferRow {
1163 row: MultiBufferRow,
1164 line_offset_from_top: u32,
1165 },
1166 MultiBufferPoint {
1167 excerpt_id: ExcerptId,
1168 position: Point,
1169 anchor: text::Anchor,
1170 line_offset_from_top: u32,
1171 },
1172}
1173
1174pub enum MultibufferSelectionMode {
1175 First,
1176 All,
1177}
1178
1179#[derive(Clone, Copy, Debug, Default)]
1180pub struct RewrapOptions {
1181 pub override_language_settings: bool,
1182 pub preserve_existing_whitespace: bool,
1183}
1184
1185impl Editor {
1186 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1187 let buffer = cx.new(|cx| Buffer::local("", cx));
1188 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1189 Self::new(
1190 EditorMode::SingleLine { auto_width: false },
1191 buffer,
1192 None,
1193 window,
1194 cx,
1195 )
1196 }
1197
1198 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1199 let buffer = cx.new(|cx| Buffer::local("", cx));
1200 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1201 Self::new(EditorMode::Full, buffer, None, window, cx)
1202 }
1203
1204 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1205 let buffer = cx.new(|cx| Buffer::local("", cx));
1206 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1207 Self::new(
1208 EditorMode::SingleLine { auto_width: true },
1209 buffer,
1210 None,
1211 window,
1212 cx,
1213 )
1214 }
1215
1216 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1217 let buffer = cx.new(|cx| Buffer::local("", cx));
1218 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1219 Self::new(
1220 EditorMode::AutoHeight { max_lines },
1221 buffer,
1222 None,
1223 window,
1224 cx,
1225 )
1226 }
1227
1228 pub fn for_buffer(
1229 buffer: Entity<Buffer>,
1230 project: Option<Entity<Project>>,
1231 window: &mut Window,
1232 cx: &mut Context<Self>,
1233 ) -> Self {
1234 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1235 Self::new(EditorMode::Full, buffer, project, window, cx)
1236 }
1237
1238 pub fn for_multibuffer(
1239 buffer: Entity<MultiBuffer>,
1240 project: Option<Entity<Project>>,
1241 window: &mut Window,
1242 cx: &mut Context<Self>,
1243 ) -> Self {
1244 Self::new(EditorMode::Full, buffer, project, window, cx)
1245 }
1246
1247 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1248 let mut clone = Self::new(
1249 self.mode,
1250 self.buffer.clone(),
1251 self.project.clone(),
1252 window,
1253 cx,
1254 );
1255 self.display_map.update(cx, |display_map, cx| {
1256 let snapshot = display_map.snapshot(cx);
1257 clone.display_map.update(cx, |display_map, cx| {
1258 display_map.set_state(&snapshot, cx);
1259 });
1260 });
1261 clone.folds_did_change(cx);
1262 clone.selections.clone_state(&self.selections);
1263 clone.scroll_manager.clone_state(&self.scroll_manager);
1264 clone.searchable = self.searchable;
1265 clone.read_only = self.read_only;
1266 clone
1267 }
1268
1269 pub fn new(
1270 mode: EditorMode,
1271 buffer: Entity<MultiBuffer>,
1272 project: Option<Entity<Project>>,
1273 window: &mut Window,
1274 cx: &mut Context<Self>,
1275 ) -> Self {
1276 let style = window.text_style();
1277 let font_size = style.font_size.to_pixels(window.rem_size());
1278 let editor = cx.entity().downgrade();
1279 let fold_placeholder = FoldPlaceholder {
1280 constrain_width: true,
1281 render: Arc::new(move |fold_id, fold_range, cx| {
1282 let editor = editor.clone();
1283 div()
1284 .id(fold_id)
1285 .bg(cx.theme().colors().ghost_element_background)
1286 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1287 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1288 .rounded_xs()
1289 .size_full()
1290 .cursor_pointer()
1291 .child("⋯")
1292 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1293 .on_click(move |_, _window, cx| {
1294 editor
1295 .update(cx, |editor, cx| {
1296 editor.unfold_ranges(
1297 &[fold_range.start..fold_range.end],
1298 true,
1299 false,
1300 cx,
1301 );
1302 cx.stop_propagation();
1303 })
1304 .ok();
1305 })
1306 .into_any()
1307 }),
1308 merge_adjacent: true,
1309 ..Default::default()
1310 };
1311 let display_map = cx.new(|cx| {
1312 DisplayMap::new(
1313 buffer.clone(),
1314 style.font(),
1315 font_size,
1316 None,
1317 FILE_HEADER_HEIGHT,
1318 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1319 fold_placeholder,
1320 cx,
1321 )
1322 });
1323
1324 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1325
1326 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1327
1328 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1329 .then(|| language_settings::SoftWrap::None);
1330
1331 let mut project_subscriptions = Vec::new();
1332 if mode == EditorMode::Full {
1333 if let Some(project) = project.as_ref() {
1334 project_subscriptions.push(cx.subscribe_in(
1335 project,
1336 window,
1337 |editor, _, event, window, cx| match event {
1338 project::Event::RefreshCodeLens => {
1339 // we always query lens with actions, without storing them, always refreshing them
1340 }
1341 project::Event::RefreshInlayHints => {
1342 editor
1343 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1344 }
1345 project::Event::SnippetEdit(id, snippet_edits) => {
1346 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1347 let focus_handle = editor.focus_handle(cx);
1348 if focus_handle.is_focused(window) {
1349 let snapshot = buffer.read(cx).snapshot();
1350 for (range, snippet) in snippet_edits {
1351 let editor_range =
1352 language::range_from_lsp(*range).to_offset(&snapshot);
1353 editor
1354 .insert_snippet(
1355 &[editor_range],
1356 snippet.clone(),
1357 window,
1358 cx,
1359 )
1360 .ok();
1361 }
1362 }
1363 }
1364 }
1365 _ => {}
1366 },
1367 ));
1368 if let Some(task_inventory) = project
1369 .read(cx)
1370 .task_store()
1371 .read(cx)
1372 .task_inventory()
1373 .cloned()
1374 {
1375 project_subscriptions.push(cx.observe_in(
1376 &task_inventory,
1377 window,
1378 |editor, _, window, cx| {
1379 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1380 },
1381 ));
1382 };
1383
1384 project_subscriptions.push(cx.subscribe_in(
1385 &project.read(cx).breakpoint_store(),
1386 window,
1387 |editor, _, event, window, cx| match event {
1388 BreakpointStoreEvent::ActiveDebugLineChanged => {
1389 if editor.go_to_active_debug_line(window, cx) {
1390 cx.stop_propagation();
1391 }
1392 }
1393 _ => {}
1394 },
1395 ));
1396 }
1397 }
1398
1399 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1400
1401 let inlay_hint_settings =
1402 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1403 let focus_handle = cx.focus_handle();
1404 cx.on_focus(&focus_handle, window, Self::handle_focus)
1405 .detach();
1406 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1407 .detach();
1408 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1409 .detach();
1410 cx.on_blur(&focus_handle, window, Self::handle_blur)
1411 .detach();
1412
1413 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1414 Some(false)
1415 } else {
1416 None
1417 };
1418
1419 let breakpoint_store = match (mode, project.as_ref()) {
1420 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1421 _ => None,
1422 };
1423
1424 let mut code_action_providers = Vec::new();
1425 let mut load_uncommitted_diff = None;
1426 if let Some(project) = project.clone() {
1427 load_uncommitted_diff = Some(
1428 get_uncommitted_diff_for_buffer(
1429 &project,
1430 buffer.read(cx).all_buffers(),
1431 buffer.clone(),
1432 cx,
1433 )
1434 .shared(),
1435 );
1436 code_action_providers.push(Rc::new(project) as Rc<_>);
1437 }
1438
1439 let mut this = Self {
1440 focus_handle,
1441 show_cursor_when_unfocused: false,
1442 last_focused_descendant: None,
1443 buffer: buffer.clone(),
1444 display_map: display_map.clone(),
1445 selections,
1446 scroll_manager: ScrollManager::new(cx),
1447 columnar_selection_tail: None,
1448 add_selections_state: None,
1449 select_next_state: None,
1450 select_prev_state: None,
1451 selection_history: Default::default(),
1452 autoclose_regions: Default::default(),
1453 snippet_stack: Default::default(),
1454 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1455 ime_transaction: Default::default(),
1456 active_diagnostics: None,
1457 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1458 inline_diagnostics_update: Task::ready(()),
1459 inline_diagnostics: Vec::new(),
1460 soft_wrap_mode_override,
1461 hard_wrap: None,
1462 completion_provider: project.clone().map(|project| Box::new(project) as _),
1463 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1464 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1465 project,
1466 blink_manager: blink_manager.clone(),
1467 show_local_selections: true,
1468 show_scrollbars: true,
1469 mode,
1470 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1471 show_gutter: mode == EditorMode::Full,
1472 show_line_numbers: None,
1473 use_relative_line_numbers: None,
1474 show_git_diff_gutter: None,
1475 show_code_actions: None,
1476 show_runnables: None,
1477 show_breakpoints: None,
1478 show_wrap_guides: None,
1479 show_indent_guides,
1480 placeholder_text: None,
1481 highlight_order: 0,
1482 highlighted_rows: HashMap::default(),
1483 background_highlights: Default::default(),
1484 gutter_highlights: TreeMap::default(),
1485 scrollbar_marker_state: ScrollbarMarkerState::default(),
1486 active_indent_guides_state: ActiveIndentGuidesState::default(),
1487 nav_history: None,
1488 context_menu: RefCell::new(None),
1489 context_menu_options: None,
1490 mouse_context_menu: None,
1491 completion_tasks: Default::default(),
1492 signature_help_state: SignatureHelpState::default(),
1493 auto_signature_help: None,
1494 find_all_references_task_sources: Vec::new(),
1495 next_completion_id: 0,
1496 next_inlay_id: 0,
1497 code_action_providers,
1498 available_code_actions: Default::default(),
1499 code_actions_task: Default::default(),
1500 selection_highlight_task: Default::default(),
1501 document_highlights_task: Default::default(),
1502 linked_editing_range_task: Default::default(),
1503 pending_rename: Default::default(),
1504 searchable: true,
1505 cursor_shape: EditorSettings::get_global(cx)
1506 .cursor_shape
1507 .unwrap_or_default(),
1508 current_line_highlight: None,
1509 autoindent_mode: Some(AutoindentMode::EachLine),
1510 collapse_matches: false,
1511 workspace: None,
1512 input_enabled: true,
1513 use_modal_editing: mode == EditorMode::Full,
1514 read_only: false,
1515 use_autoclose: true,
1516 use_auto_surround: true,
1517 auto_replace_emoji_shortcode: false,
1518 jsx_tag_auto_close_enabled_in_any_buffer: false,
1519 leader_peer_id: None,
1520 remote_id: None,
1521 hover_state: Default::default(),
1522 pending_mouse_down: None,
1523 hovered_link_state: Default::default(),
1524 edit_prediction_provider: None,
1525 active_inline_completion: None,
1526 stale_inline_completion_in_menu: None,
1527 edit_prediction_preview: EditPredictionPreview::Inactive {
1528 released_too_fast: false,
1529 },
1530 inline_diagnostics_enabled: mode == EditorMode::Full,
1531 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1532
1533 gutter_hovered: false,
1534 pixel_position_of_newest_cursor: None,
1535 last_bounds: None,
1536 last_position_map: None,
1537 expect_bounds_change: None,
1538 gutter_dimensions: GutterDimensions::default(),
1539 style: None,
1540 show_cursor_names: false,
1541 hovered_cursors: Default::default(),
1542 next_editor_action_id: EditorActionId::default(),
1543 editor_actions: Rc::default(),
1544 inline_completions_hidden_for_vim_mode: false,
1545 show_inline_completions_override: None,
1546 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1547 edit_prediction_settings: EditPredictionSettings::Disabled,
1548 edit_prediction_indent_conflict: false,
1549 edit_prediction_requires_modifier_in_indent_conflict: true,
1550 custom_context_menu: None,
1551 show_git_blame_gutter: false,
1552 show_git_blame_inline: false,
1553 show_selection_menu: None,
1554 show_git_blame_inline_delay_task: None,
1555 git_blame_inline_tooltip: None,
1556 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1557 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1558 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1559 .session
1560 .restore_unsaved_buffers,
1561 blame: None,
1562 blame_subscription: None,
1563 tasks: Default::default(),
1564
1565 breakpoint_store,
1566 gutter_breakpoint_indicator: (None, None),
1567 _subscriptions: vec![
1568 cx.observe(&buffer, Self::on_buffer_changed),
1569 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1570 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1571 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1572 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1573 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1574 cx.observe_window_activation(window, |editor, window, cx| {
1575 let active = window.is_window_active();
1576 editor.blink_manager.update(cx, |blink_manager, cx| {
1577 if active {
1578 blink_manager.enable(cx);
1579 } else {
1580 blink_manager.disable(cx);
1581 }
1582 });
1583 }),
1584 ],
1585 tasks_update_task: None,
1586 linked_edit_ranges: Default::default(),
1587 in_project_search: false,
1588 previous_search_ranges: None,
1589 breadcrumb_header: None,
1590 focused_block: None,
1591 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1592 addons: HashMap::default(),
1593 registered_buffers: HashMap::default(),
1594 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1595 selection_mark_mode: false,
1596 toggle_fold_multiple_buffers: Task::ready(()),
1597 serialize_selections: Task::ready(()),
1598 serialize_folds: Task::ready(()),
1599 text_style_refinement: None,
1600 load_diff_task: load_uncommitted_diff,
1601 mouse_cursor_hidden: false,
1602 hide_mouse_mode: EditorSettings::get_global(cx)
1603 .hide_mouse
1604 .unwrap_or_default(),
1605 };
1606 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1607 this._subscriptions
1608 .push(cx.observe(breakpoints, |_, _, cx| {
1609 cx.notify();
1610 }));
1611 }
1612 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1613 this._subscriptions.extend(project_subscriptions);
1614
1615 this._subscriptions.push(cx.subscribe_in(
1616 &cx.entity(),
1617 window,
1618 |editor, _, e: &EditorEvent, window, cx| {
1619 if let EditorEvent::SelectionsChanged { local } = e {
1620 if *local {
1621 let new_anchor = editor.scroll_manager.anchor();
1622 let snapshot = editor.snapshot(window, cx);
1623 editor.update_restoration_data(cx, move |data| {
1624 data.scroll_position = (
1625 new_anchor.top_row(&snapshot.buffer_snapshot),
1626 new_anchor.offset,
1627 );
1628 });
1629 }
1630 }
1631 },
1632 ));
1633
1634 this.end_selection(window, cx);
1635 this.scroll_manager.show_scrollbars(window, cx);
1636 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1637
1638 if mode == EditorMode::Full {
1639 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1640 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1641
1642 if this.git_blame_inline_enabled {
1643 this.git_blame_inline_enabled = true;
1644 this.start_git_blame_inline(false, window, cx);
1645 }
1646
1647 this.go_to_active_debug_line(window, cx);
1648
1649 if let Some(buffer) = buffer.read(cx).as_singleton() {
1650 if let Some(project) = this.project.as_ref() {
1651 let handle = project.update(cx, |project, cx| {
1652 project.register_buffer_with_language_servers(&buffer, cx)
1653 });
1654 this.registered_buffers
1655 .insert(buffer.read(cx).remote_id(), handle);
1656 }
1657 }
1658 }
1659
1660 this.report_editor_event("Editor Opened", None, cx);
1661 this
1662 }
1663
1664 pub fn deploy_mouse_context_menu(
1665 &mut self,
1666 position: gpui::Point<Pixels>,
1667 context_menu: Entity<ContextMenu>,
1668 window: &mut Window,
1669 cx: &mut Context<Self>,
1670 ) {
1671 self.mouse_context_menu = Some(MouseContextMenu::new(
1672 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1673 context_menu,
1674 window,
1675 cx,
1676 ));
1677 }
1678
1679 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1680 self.mouse_context_menu
1681 .as_ref()
1682 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1683 }
1684
1685 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1686 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1687 }
1688
1689 fn key_context_internal(
1690 &self,
1691 has_active_edit_prediction: bool,
1692 window: &Window,
1693 cx: &App,
1694 ) -> KeyContext {
1695 let mut key_context = KeyContext::new_with_defaults();
1696 key_context.add("Editor");
1697 let mode = match self.mode {
1698 EditorMode::SingleLine { .. } => "single_line",
1699 EditorMode::AutoHeight { .. } => "auto_height",
1700 EditorMode::Full => "full",
1701 };
1702
1703 if EditorSettings::jupyter_enabled(cx) {
1704 key_context.add("jupyter");
1705 }
1706
1707 key_context.set("mode", mode);
1708 if self.pending_rename.is_some() {
1709 key_context.add("renaming");
1710 }
1711
1712 match self.context_menu.borrow().as_ref() {
1713 Some(CodeContextMenu::Completions(_)) => {
1714 key_context.add("menu");
1715 key_context.add("showing_completions");
1716 }
1717 Some(CodeContextMenu::CodeActions(_)) => {
1718 key_context.add("menu");
1719 key_context.add("showing_code_actions")
1720 }
1721 None => {}
1722 }
1723
1724 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1725 if !self.focus_handle(cx).contains_focused(window, cx)
1726 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1727 {
1728 for addon in self.addons.values() {
1729 addon.extend_key_context(&mut key_context, cx)
1730 }
1731 }
1732
1733 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1734 if let Some(extension) = singleton_buffer
1735 .read(cx)
1736 .file()
1737 .and_then(|file| file.path().extension()?.to_str())
1738 {
1739 key_context.set("extension", extension.to_string());
1740 }
1741 } else {
1742 key_context.add("multibuffer");
1743 }
1744
1745 if has_active_edit_prediction {
1746 if self.edit_prediction_in_conflict() {
1747 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1748 } else {
1749 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1750 key_context.add("copilot_suggestion");
1751 }
1752 }
1753
1754 if self.selection_mark_mode {
1755 key_context.add("selection_mode");
1756 }
1757
1758 key_context
1759 }
1760
1761 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1762 self.mouse_cursor_hidden = match origin {
1763 HideMouseCursorOrigin::TypingAction => {
1764 matches!(
1765 self.hide_mouse_mode,
1766 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1767 )
1768 }
1769 HideMouseCursorOrigin::MovementAction => {
1770 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
1771 }
1772 };
1773 }
1774
1775 pub fn edit_prediction_in_conflict(&self) -> bool {
1776 if !self.show_edit_predictions_in_menu() {
1777 return false;
1778 }
1779
1780 let showing_completions = self
1781 .context_menu
1782 .borrow()
1783 .as_ref()
1784 .map_or(false, |context| {
1785 matches!(context, CodeContextMenu::Completions(_))
1786 });
1787
1788 showing_completions
1789 || self.edit_prediction_requires_modifier()
1790 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1791 // bindings to insert tab characters.
1792 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1793 }
1794
1795 pub fn accept_edit_prediction_keybind(
1796 &self,
1797 window: &Window,
1798 cx: &App,
1799 ) -> AcceptEditPredictionBinding {
1800 let key_context = self.key_context_internal(true, window, cx);
1801 let in_conflict = self.edit_prediction_in_conflict();
1802
1803 AcceptEditPredictionBinding(
1804 window
1805 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1806 .into_iter()
1807 .filter(|binding| {
1808 !in_conflict
1809 || binding
1810 .keystrokes()
1811 .first()
1812 .map_or(false, |keystroke| keystroke.modifiers.modified())
1813 })
1814 .rev()
1815 .min_by_key(|binding| {
1816 binding
1817 .keystrokes()
1818 .first()
1819 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1820 }),
1821 )
1822 }
1823
1824 pub fn new_file(
1825 workspace: &mut Workspace,
1826 _: &workspace::NewFile,
1827 window: &mut Window,
1828 cx: &mut Context<Workspace>,
1829 ) {
1830 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1831 "Failed to create buffer",
1832 window,
1833 cx,
1834 |e, _, _| match e.error_code() {
1835 ErrorCode::RemoteUpgradeRequired => Some(format!(
1836 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1837 e.error_tag("required").unwrap_or("the latest version")
1838 )),
1839 _ => None,
1840 },
1841 );
1842 }
1843
1844 pub fn new_in_workspace(
1845 workspace: &mut Workspace,
1846 window: &mut Window,
1847 cx: &mut Context<Workspace>,
1848 ) -> Task<Result<Entity<Editor>>> {
1849 let project = workspace.project().clone();
1850 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1851
1852 cx.spawn_in(window, async move |workspace, cx| {
1853 let buffer = create.await?;
1854 workspace.update_in(cx, |workspace, window, cx| {
1855 let editor =
1856 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1857 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1858 editor
1859 })
1860 })
1861 }
1862
1863 fn new_file_vertical(
1864 workspace: &mut Workspace,
1865 _: &workspace::NewFileSplitVertical,
1866 window: &mut Window,
1867 cx: &mut Context<Workspace>,
1868 ) {
1869 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1870 }
1871
1872 fn new_file_horizontal(
1873 workspace: &mut Workspace,
1874 _: &workspace::NewFileSplitHorizontal,
1875 window: &mut Window,
1876 cx: &mut Context<Workspace>,
1877 ) {
1878 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1879 }
1880
1881 fn new_file_in_direction(
1882 workspace: &mut Workspace,
1883 direction: SplitDirection,
1884 window: &mut Window,
1885 cx: &mut Context<Workspace>,
1886 ) {
1887 let project = workspace.project().clone();
1888 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1889
1890 cx.spawn_in(window, async move |workspace, cx| {
1891 let buffer = create.await?;
1892 workspace.update_in(cx, move |workspace, window, cx| {
1893 workspace.split_item(
1894 direction,
1895 Box::new(
1896 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1897 ),
1898 window,
1899 cx,
1900 )
1901 })?;
1902 anyhow::Ok(())
1903 })
1904 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1905 match e.error_code() {
1906 ErrorCode::RemoteUpgradeRequired => Some(format!(
1907 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1908 e.error_tag("required").unwrap_or("the latest version")
1909 )),
1910 _ => None,
1911 }
1912 });
1913 }
1914
1915 pub fn leader_peer_id(&self) -> Option<PeerId> {
1916 self.leader_peer_id
1917 }
1918
1919 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1920 &self.buffer
1921 }
1922
1923 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1924 self.workspace.as_ref()?.0.upgrade()
1925 }
1926
1927 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1928 self.buffer().read(cx).title(cx)
1929 }
1930
1931 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1932 let git_blame_gutter_max_author_length = self
1933 .render_git_blame_gutter(cx)
1934 .then(|| {
1935 if let Some(blame) = self.blame.as_ref() {
1936 let max_author_length =
1937 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1938 Some(max_author_length)
1939 } else {
1940 None
1941 }
1942 })
1943 .flatten();
1944
1945 EditorSnapshot {
1946 mode: self.mode,
1947 show_gutter: self.show_gutter,
1948 show_line_numbers: self.show_line_numbers,
1949 show_git_diff_gutter: self.show_git_diff_gutter,
1950 show_code_actions: self.show_code_actions,
1951 show_runnables: self.show_runnables,
1952 show_breakpoints: self.show_breakpoints,
1953 git_blame_gutter_max_author_length,
1954 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1955 scroll_anchor: self.scroll_manager.anchor(),
1956 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1957 placeholder_text: self.placeholder_text.clone(),
1958 is_focused: self.focus_handle.is_focused(window),
1959 current_line_highlight: self
1960 .current_line_highlight
1961 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1962 gutter_hovered: self.gutter_hovered,
1963 }
1964 }
1965
1966 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1967 self.buffer.read(cx).language_at(point, cx)
1968 }
1969
1970 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1971 self.buffer.read(cx).read(cx).file_at(point).cloned()
1972 }
1973
1974 pub fn active_excerpt(
1975 &self,
1976 cx: &App,
1977 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1978 self.buffer
1979 .read(cx)
1980 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1981 }
1982
1983 pub fn mode(&self) -> EditorMode {
1984 self.mode
1985 }
1986
1987 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1988 self.collaboration_hub.as_deref()
1989 }
1990
1991 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1992 self.collaboration_hub = Some(hub);
1993 }
1994
1995 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1996 self.in_project_search = in_project_search;
1997 }
1998
1999 pub fn set_custom_context_menu(
2000 &mut self,
2001 f: impl 'static
2002 + Fn(
2003 &mut Self,
2004 DisplayPoint,
2005 &mut Window,
2006 &mut Context<Self>,
2007 ) -> Option<Entity<ui::ContextMenu>>,
2008 ) {
2009 self.custom_context_menu = Some(Box::new(f))
2010 }
2011
2012 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2013 self.completion_provider = provider;
2014 }
2015
2016 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2017 self.semantics_provider.clone()
2018 }
2019
2020 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2021 self.semantics_provider = provider;
2022 }
2023
2024 pub fn set_edit_prediction_provider<T>(
2025 &mut self,
2026 provider: Option<Entity<T>>,
2027 window: &mut Window,
2028 cx: &mut Context<Self>,
2029 ) where
2030 T: EditPredictionProvider,
2031 {
2032 self.edit_prediction_provider =
2033 provider.map(|provider| RegisteredInlineCompletionProvider {
2034 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2035 if this.focus_handle.is_focused(window) {
2036 this.update_visible_inline_completion(window, cx);
2037 }
2038 }),
2039 provider: Arc::new(provider),
2040 });
2041 self.update_edit_prediction_settings(cx);
2042 self.refresh_inline_completion(false, false, window, cx);
2043 }
2044
2045 pub fn placeholder_text(&self) -> Option<&str> {
2046 self.placeholder_text.as_deref()
2047 }
2048
2049 pub fn set_placeholder_text(
2050 &mut self,
2051 placeholder_text: impl Into<Arc<str>>,
2052 cx: &mut Context<Self>,
2053 ) {
2054 let placeholder_text = Some(placeholder_text.into());
2055 if self.placeholder_text != placeholder_text {
2056 self.placeholder_text = placeholder_text;
2057 cx.notify();
2058 }
2059 }
2060
2061 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2062 self.cursor_shape = cursor_shape;
2063
2064 // Disrupt blink for immediate user feedback that the cursor shape has changed
2065 self.blink_manager.update(cx, BlinkManager::show_cursor);
2066
2067 cx.notify();
2068 }
2069
2070 pub fn set_current_line_highlight(
2071 &mut self,
2072 current_line_highlight: Option<CurrentLineHighlight>,
2073 ) {
2074 self.current_line_highlight = current_line_highlight;
2075 }
2076
2077 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2078 self.collapse_matches = collapse_matches;
2079 }
2080
2081 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2082 let buffers = self.buffer.read(cx).all_buffers();
2083 let Some(project) = self.project.as_ref() else {
2084 return;
2085 };
2086 project.update(cx, |project, cx| {
2087 for buffer in buffers {
2088 self.registered_buffers
2089 .entry(buffer.read(cx).remote_id())
2090 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2091 }
2092 })
2093 }
2094
2095 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2096 if self.collapse_matches {
2097 return range.start..range.start;
2098 }
2099 range.clone()
2100 }
2101
2102 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2103 if self.display_map.read(cx).clip_at_line_ends != clip {
2104 self.display_map
2105 .update(cx, |map, _| map.clip_at_line_ends = clip);
2106 }
2107 }
2108
2109 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2110 self.input_enabled = input_enabled;
2111 }
2112
2113 pub fn set_inline_completions_hidden_for_vim_mode(
2114 &mut self,
2115 hidden: bool,
2116 window: &mut Window,
2117 cx: &mut Context<Self>,
2118 ) {
2119 if hidden != self.inline_completions_hidden_for_vim_mode {
2120 self.inline_completions_hidden_for_vim_mode = hidden;
2121 if hidden {
2122 self.update_visible_inline_completion(window, cx);
2123 } else {
2124 self.refresh_inline_completion(true, false, window, cx);
2125 }
2126 }
2127 }
2128
2129 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2130 self.menu_inline_completions_policy = value;
2131 }
2132
2133 pub fn set_autoindent(&mut self, autoindent: bool) {
2134 if autoindent {
2135 self.autoindent_mode = Some(AutoindentMode::EachLine);
2136 } else {
2137 self.autoindent_mode = None;
2138 }
2139 }
2140
2141 pub fn read_only(&self, cx: &App) -> bool {
2142 self.read_only || self.buffer.read(cx).read_only()
2143 }
2144
2145 pub fn set_read_only(&mut self, read_only: bool) {
2146 self.read_only = read_only;
2147 }
2148
2149 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2150 self.use_autoclose = autoclose;
2151 }
2152
2153 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2154 self.use_auto_surround = auto_surround;
2155 }
2156
2157 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2158 self.auto_replace_emoji_shortcode = auto_replace;
2159 }
2160
2161 pub fn toggle_edit_predictions(
2162 &mut self,
2163 _: &ToggleEditPrediction,
2164 window: &mut Window,
2165 cx: &mut Context<Self>,
2166 ) {
2167 if self.show_inline_completions_override.is_some() {
2168 self.set_show_edit_predictions(None, window, cx);
2169 } else {
2170 let show_edit_predictions = !self.edit_predictions_enabled();
2171 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2172 }
2173 }
2174
2175 pub fn set_show_edit_predictions(
2176 &mut self,
2177 show_edit_predictions: Option<bool>,
2178 window: &mut Window,
2179 cx: &mut Context<Self>,
2180 ) {
2181 self.show_inline_completions_override = show_edit_predictions;
2182 self.update_edit_prediction_settings(cx);
2183
2184 if let Some(false) = show_edit_predictions {
2185 self.discard_inline_completion(false, cx);
2186 } else {
2187 self.refresh_inline_completion(false, true, window, cx);
2188 }
2189 }
2190
2191 fn inline_completions_disabled_in_scope(
2192 &self,
2193 buffer: &Entity<Buffer>,
2194 buffer_position: language::Anchor,
2195 cx: &App,
2196 ) -> bool {
2197 let snapshot = buffer.read(cx).snapshot();
2198 let settings = snapshot.settings_at(buffer_position, cx);
2199
2200 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2201 return false;
2202 };
2203
2204 scope.override_name().map_or(false, |scope_name| {
2205 settings
2206 .edit_predictions_disabled_in
2207 .iter()
2208 .any(|s| s == scope_name)
2209 })
2210 }
2211
2212 pub fn set_use_modal_editing(&mut self, to: bool) {
2213 self.use_modal_editing = to;
2214 }
2215
2216 pub fn use_modal_editing(&self) -> bool {
2217 self.use_modal_editing
2218 }
2219
2220 fn selections_did_change(
2221 &mut self,
2222 local: bool,
2223 old_cursor_position: &Anchor,
2224 show_completions: bool,
2225 window: &mut Window,
2226 cx: &mut Context<Self>,
2227 ) {
2228 window.invalidate_character_coordinates();
2229
2230 // Copy selections to primary selection buffer
2231 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2232 if local {
2233 let selections = self.selections.all::<usize>(cx);
2234 let buffer_handle = self.buffer.read(cx).read(cx);
2235
2236 let mut text = String::new();
2237 for (index, selection) in selections.iter().enumerate() {
2238 let text_for_selection = buffer_handle
2239 .text_for_range(selection.start..selection.end)
2240 .collect::<String>();
2241
2242 text.push_str(&text_for_selection);
2243 if index != selections.len() - 1 {
2244 text.push('\n');
2245 }
2246 }
2247
2248 if !text.is_empty() {
2249 cx.write_to_primary(ClipboardItem::new_string(text));
2250 }
2251 }
2252
2253 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2254 self.buffer.update(cx, |buffer, cx| {
2255 buffer.set_active_selections(
2256 &self.selections.disjoint_anchors(),
2257 self.selections.line_mode,
2258 self.cursor_shape,
2259 cx,
2260 )
2261 });
2262 }
2263 let display_map = self
2264 .display_map
2265 .update(cx, |display_map, cx| display_map.snapshot(cx));
2266 let buffer = &display_map.buffer_snapshot;
2267 self.add_selections_state = None;
2268 self.select_next_state = None;
2269 self.select_prev_state = None;
2270 self.select_syntax_node_history.try_clear();
2271 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2272 self.snippet_stack
2273 .invalidate(&self.selections.disjoint_anchors(), buffer);
2274 self.take_rename(false, window, cx);
2275
2276 let new_cursor_position = self.selections.newest_anchor().head();
2277
2278 self.push_to_nav_history(
2279 *old_cursor_position,
2280 Some(new_cursor_position.to_point(buffer)),
2281 false,
2282 cx,
2283 );
2284
2285 if local {
2286 let new_cursor_position = self.selections.newest_anchor().head();
2287 let mut context_menu = self.context_menu.borrow_mut();
2288 let completion_menu = match context_menu.as_ref() {
2289 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2290 _ => {
2291 *context_menu = None;
2292 None
2293 }
2294 };
2295 if let Some(buffer_id) = new_cursor_position.buffer_id {
2296 if !self.registered_buffers.contains_key(&buffer_id) {
2297 if let Some(project) = self.project.as_ref() {
2298 project.update(cx, |project, cx| {
2299 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2300 return;
2301 };
2302 self.registered_buffers.insert(
2303 buffer_id,
2304 project.register_buffer_with_language_servers(&buffer, cx),
2305 );
2306 })
2307 }
2308 }
2309 }
2310
2311 if let Some(completion_menu) = completion_menu {
2312 let cursor_position = new_cursor_position.to_offset(buffer);
2313 let (word_range, kind) =
2314 buffer.surrounding_word(completion_menu.initial_position, true);
2315 if kind == Some(CharKind::Word)
2316 && word_range.to_inclusive().contains(&cursor_position)
2317 {
2318 let mut completion_menu = completion_menu.clone();
2319 drop(context_menu);
2320
2321 let query = Self::completion_query(buffer, cursor_position);
2322 cx.spawn(async move |this, cx| {
2323 completion_menu
2324 .filter(query.as_deref(), cx.background_executor().clone())
2325 .await;
2326
2327 this.update(cx, |this, cx| {
2328 let mut context_menu = this.context_menu.borrow_mut();
2329 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2330 else {
2331 return;
2332 };
2333
2334 if menu.id > completion_menu.id {
2335 return;
2336 }
2337
2338 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2339 drop(context_menu);
2340 cx.notify();
2341 })
2342 })
2343 .detach();
2344
2345 if show_completions {
2346 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2347 }
2348 } else {
2349 drop(context_menu);
2350 self.hide_context_menu(window, cx);
2351 }
2352 } else {
2353 drop(context_menu);
2354 }
2355
2356 hide_hover(self, cx);
2357
2358 if old_cursor_position.to_display_point(&display_map).row()
2359 != new_cursor_position.to_display_point(&display_map).row()
2360 {
2361 self.available_code_actions.take();
2362 }
2363 self.refresh_code_actions(window, cx);
2364 self.refresh_document_highlights(cx);
2365 self.refresh_selected_text_highlights(window, cx);
2366 refresh_matching_bracket_highlights(self, window, cx);
2367 self.update_visible_inline_completion(window, cx);
2368 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2369 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2370 if self.git_blame_inline_enabled {
2371 self.start_inline_blame_timer(window, cx);
2372 }
2373 }
2374
2375 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2376 cx.emit(EditorEvent::SelectionsChanged { local });
2377
2378 let selections = &self.selections.disjoint;
2379 if selections.len() == 1 {
2380 cx.emit(SearchEvent::ActiveMatchChanged)
2381 }
2382 if local {
2383 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2384 let inmemory_selections = selections
2385 .iter()
2386 .map(|s| {
2387 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2388 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2389 })
2390 .collect();
2391 self.update_restoration_data(cx, |data| {
2392 data.selections = inmemory_selections;
2393 });
2394
2395 if WorkspaceSettings::get(None, cx).restore_on_startup
2396 != RestoreOnStartupBehavior::None
2397 {
2398 if let Some(workspace_id) =
2399 self.workspace.as_ref().and_then(|workspace| workspace.1)
2400 {
2401 let snapshot = self.buffer().read(cx).snapshot(cx);
2402 let selections = selections.clone();
2403 let background_executor = cx.background_executor().clone();
2404 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2405 self.serialize_selections = cx.background_spawn(async move {
2406 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2407 let db_selections = selections
2408 .iter()
2409 .map(|selection| {
2410 (
2411 selection.start.to_offset(&snapshot),
2412 selection.end.to_offset(&snapshot),
2413 )
2414 })
2415 .collect();
2416
2417 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2418 .await
2419 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2420 .log_err();
2421 });
2422 }
2423 }
2424 }
2425 }
2426
2427 cx.notify();
2428 }
2429
2430 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2431 use text::ToOffset as _;
2432 use text::ToPoint as _;
2433
2434 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2435 return;
2436 }
2437
2438 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2439 return;
2440 };
2441
2442 let snapshot = singleton.read(cx).snapshot();
2443 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2444 let display_snapshot = display_map.snapshot(cx);
2445
2446 display_snapshot
2447 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2448 .map(|fold| {
2449 fold.range.start.text_anchor.to_point(&snapshot)
2450 ..fold.range.end.text_anchor.to_point(&snapshot)
2451 })
2452 .collect()
2453 });
2454 self.update_restoration_data(cx, |data| {
2455 data.folds = inmemory_folds;
2456 });
2457
2458 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2459 return;
2460 };
2461 let background_executor = cx.background_executor().clone();
2462 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2463 let db_folds = self.display_map.update(cx, |display_map, cx| {
2464 display_map
2465 .snapshot(cx)
2466 .folds_in_range(0..snapshot.len())
2467 .map(|fold| {
2468 (
2469 fold.range.start.text_anchor.to_offset(&snapshot),
2470 fold.range.end.text_anchor.to_offset(&snapshot),
2471 )
2472 })
2473 .collect()
2474 });
2475 self.serialize_folds = cx.background_spawn(async move {
2476 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2477 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2478 .await
2479 .with_context(|| {
2480 format!(
2481 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2482 )
2483 })
2484 .log_err();
2485 });
2486 }
2487
2488 pub fn sync_selections(
2489 &mut self,
2490 other: Entity<Editor>,
2491 cx: &mut Context<Self>,
2492 ) -> gpui::Subscription {
2493 let other_selections = other.read(cx).selections.disjoint.to_vec();
2494 self.selections.change_with(cx, |selections| {
2495 selections.select_anchors(other_selections);
2496 });
2497
2498 let other_subscription =
2499 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2500 EditorEvent::SelectionsChanged { local: true } => {
2501 let other_selections = other.read(cx).selections.disjoint.to_vec();
2502 if other_selections.is_empty() {
2503 return;
2504 }
2505 this.selections.change_with(cx, |selections| {
2506 selections.select_anchors(other_selections);
2507 });
2508 }
2509 _ => {}
2510 });
2511
2512 let this_subscription =
2513 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2514 EditorEvent::SelectionsChanged { local: true } => {
2515 let these_selections = this.selections.disjoint.to_vec();
2516 if these_selections.is_empty() {
2517 return;
2518 }
2519 other.update(cx, |other_editor, cx| {
2520 other_editor.selections.change_with(cx, |selections| {
2521 selections.select_anchors(these_selections);
2522 })
2523 });
2524 }
2525 _ => {}
2526 });
2527
2528 Subscription::join(other_subscription, this_subscription)
2529 }
2530
2531 pub fn change_selections<R>(
2532 &mut self,
2533 autoscroll: Option<Autoscroll>,
2534 window: &mut Window,
2535 cx: &mut Context<Self>,
2536 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2537 ) -> R {
2538 self.change_selections_inner(autoscroll, true, window, cx, change)
2539 }
2540
2541 fn change_selections_inner<R>(
2542 &mut self,
2543 autoscroll: Option<Autoscroll>,
2544 request_completions: bool,
2545 window: &mut Window,
2546 cx: &mut Context<Self>,
2547 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2548 ) -> R {
2549 let old_cursor_position = self.selections.newest_anchor().head();
2550 self.push_to_selection_history();
2551
2552 let (changed, result) = self.selections.change_with(cx, change);
2553
2554 if changed {
2555 if let Some(autoscroll) = autoscroll {
2556 self.request_autoscroll(autoscroll, cx);
2557 }
2558 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2559
2560 if self.should_open_signature_help_automatically(
2561 &old_cursor_position,
2562 self.signature_help_state.backspace_pressed(),
2563 cx,
2564 ) {
2565 self.show_signature_help(&ShowSignatureHelp, window, cx);
2566 }
2567 self.signature_help_state.set_backspace_pressed(false);
2568 }
2569
2570 result
2571 }
2572
2573 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2574 where
2575 I: IntoIterator<Item = (Range<S>, T)>,
2576 S: ToOffset,
2577 T: Into<Arc<str>>,
2578 {
2579 if self.read_only(cx) {
2580 return;
2581 }
2582
2583 self.buffer
2584 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2585 }
2586
2587 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2588 where
2589 I: IntoIterator<Item = (Range<S>, T)>,
2590 S: ToOffset,
2591 T: Into<Arc<str>>,
2592 {
2593 if self.read_only(cx) {
2594 return;
2595 }
2596
2597 self.buffer.update(cx, |buffer, cx| {
2598 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2599 });
2600 }
2601
2602 pub fn edit_with_block_indent<I, S, T>(
2603 &mut self,
2604 edits: I,
2605 original_indent_columns: Vec<Option<u32>>,
2606 cx: &mut Context<Self>,
2607 ) where
2608 I: IntoIterator<Item = (Range<S>, T)>,
2609 S: ToOffset,
2610 T: Into<Arc<str>>,
2611 {
2612 if self.read_only(cx) {
2613 return;
2614 }
2615
2616 self.buffer.update(cx, |buffer, cx| {
2617 buffer.edit(
2618 edits,
2619 Some(AutoindentMode::Block {
2620 original_indent_columns,
2621 }),
2622 cx,
2623 )
2624 });
2625 }
2626
2627 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2628 self.hide_context_menu(window, cx);
2629
2630 match phase {
2631 SelectPhase::Begin {
2632 position,
2633 add,
2634 click_count,
2635 } => self.begin_selection(position, add, click_count, window, cx),
2636 SelectPhase::BeginColumnar {
2637 position,
2638 goal_column,
2639 reset,
2640 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2641 SelectPhase::Extend {
2642 position,
2643 click_count,
2644 } => self.extend_selection(position, click_count, window, cx),
2645 SelectPhase::Update {
2646 position,
2647 goal_column,
2648 scroll_delta,
2649 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2650 SelectPhase::End => self.end_selection(window, cx),
2651 }
2652 }
2653
2654 fn extend_selection(
2655 &mut self,
2656 position: DisplayPoint,
2657 click_count: usize,
2658 window: &mut Window,
2659 cx: &mut Context<Self>,
2660 ) {
2661 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2662 let tail = self.selections.newest::<usize>(cx).tail();
2663 self.begin_selection(position, false, click_count, window, cx);
2664
2665 let position = position.to_offset(&display_map, Bias::Left);
2666 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2667
2668 let mut pending_selection = self
2669 .selections
2670 .pending_anchor()
2671 .expect("extend_selection not called with pending selection");
2672 if position >= tail {
2673 pending_selection.start = tail_anchor;
2674 } else {
2675 pending_selection.end = tail_anchor;
2676 pending_selection.reversed = true;
2677 }
2678
2679 let mut pending_mode = self.selections.pending_mode().unwrap();
2680 match &mut pending_mode {
2681 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2682 _ => {}
2683 }
2684
2685 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2686 s.set_pending(pending_selection, pending_mode)
2687 });
2688 }
2689
2690 fn begin_selection(
2691 &mut self,
2692 position: DisplayPoint,
2693 add: bool,
2694 click_count: usize,
2695 window: &mut Window,
2696 cx: &mut Context<Self>,
2697 ) {
2698 if !self.focus_handle.is_focused(window) {
2699 self.last_focused_descendant = None;
2700 window.focus(&self.focus_handle);
2701 }
2702
2703 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2704 let buffer = &display_map.buffer_snapshot;
2705 let newest_selection = self.selections.newest_anchor().clone();
2706 let position = display_map.clip_point(position, Bias::Left);
2707
2708 let start;
2709 let end;
2710 let mode;
2711 let mut auto_scroll;
2712 match click_count {
2713 1 => {
2714 start = buffer.anchor_before(position.to_point(&display_map));
2715 end = start;
2716 mode = SelectMode::Character;
2717 auto_scroll = true;
2718 }
2719 2 => {
2720 let range = movement::surrounding_word(&display_map, position);
2721 start = buffer.anchor_before(range.start.to_point(&display_map));
2722 end = buffer.anchor_before(range.end.to_point(&display_map));
2723 mode = SelectMode::Word(start..end);
2724 auto_scroll = true;
2725 }
2726 3 => {
2727 let position = display_map
2728 .clip_point(position, Bias::Left)
2729 .to_point(&display_map);
2730 let line_start = display_map.prev_line_boundary(position).0;
2731 let next_line_start = buffer.clip_point(
2732 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2733 Bias::Left,
2734 );
2735 start = buffer.anchor_before(line_start);
2736 end = buffer.anchor_before(next_line_start);
2737 mode = SelectMode::Line(start..end);
2738 auto_scroll = true;
2739 }
2740 _ => {
2741 start = buffer.anchor_before(0);
2742 end = buffer.anchor_before(buffer.len());
2743 mode = SelectMode::All;
2744 auto_scroll = false;
2745 }
2746 }
2747 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2748
2749 let point_to_delete: Option<usize> = {
2750 let selected_points: Vec<Selection<Point>> =
2751 self.selections.disjoint_in_range(start..end, cx);
2752
2753 if !add || click_count > 1 {
2754 None
2755 } else if !selected_points.is_empty() {
2756 Some(selected_points[0].id)
2757 } else {
2758 let clicked_point_already_selected =
2759 self.selections.disjoint.iter().find(|selection| {
2760 selection.start.to_point(buffer) == start.to_point(buffer)
2761 || selection.end.to_point(buffer) == end.to_point(buffer)
2762 });
2763
2764 clicked_point_already_selected.map(|selection| selection.id)
2765 }
2766 };
2767
2768 let selections_count = self.selections.count();
2769
2770 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2771 if let Some(point_to_delete) = point_to_delete {
2772 s.delete(point_to_delete);
2773
2774 if selections_count == 1 {
2775 s.set_pending_anchor_range(start..end, mode);
2776 }
2777 } else {
2778 if !add {
2779 s.clear_disjoint();
2780 } else if click_count > 1 {
2781 s.delete(newest_selection.id)
2782 }
2783
2784 s.set_pending_anchor_range(start..end, mode);
2785 }
2786 });
2787 }
2788
2789 fn begin_columnar_selection(
2790 &mut self,
2791 position: DisplayPoint,
2792 goal_column: u32,
2793 reset: bool,
2794 window: &mut Window,
2795 cx: &mut Context<Self>,
2796 ) {
2797 if !self.focus_handle.is_focused(window) {
2798 self.last_focused_descendant = None;
2799 window.focus(&self.focus_handle);
2800 }
2801
2802 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2803
2804 if reset {
2805 let pointer_position = display_map
2806 .buffer_snapshot
2807 .anchor_before(position.to_point(&display_map));
2808
2809 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2810 s.clear_disjoint();
2811 s.set_pending_anchor_range(
2812 pointer_position..pointer_position,
2813 SelectMode::Character,
2814 );
2815 });
2816 }
2817
2818 let tail = self.selections.newest::<Point>(cx).tail();
2819 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2820
2821 if !reset {
2822 self.select_columns(
2823 tail.to_display_point(&display_map),
2824 position,
2825 goal_column,
2826 &display_map,
2827 window,
2828 cx,
2829 );
2830 }
2831 }
2832
2833 fn update_selection(
2834 &mut self,
2835 position: DisplayPoint,
2836 goal_column: u32,
2837 scroll_delta: gpui::Point<f32>,
2838 window: &mut Window,
2839 cx: &mut Context<Self>,
2840 ) {
2841 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2842
2843 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2844 let tail = tail.to_display_point(&display_map);
2845 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2846 } else if let Some(mut pending) = self.selections.pending_anchor() {
2847 let buffer = self.buffer.read(cx).snapshot(cx);
2848 let head;
2849 let tail;
2850 let mode = self.selections.pending_mode().unwrap();
2851 match &mode {
2852 SelectMode::Character => {
2853 head = position.to_point(&display_map);
2854 tail = pending.tail().to_point(&buffer);
2855 }
2856 SelectMode::Word(original_range) => {
2857 let original_display_range = original_range.start.to_display_point(&display_map)
2858 ..original_range.end.to_display_point(&display_map);
2859 let original_buffer_range = original_display_range.start.to_point(&display_map)
2860 ..original_display_range.end.to_point(&display_map);
2861 if movement::is_inside_word(&display_map, position)
2862 || original_display_range.contains(&position)
2863 {
2864 let word_range = movement::surrounding_word(&display_map, position);
2865 if word_range.start < original_display_range.start {
2866 head = word_range.start.to_point(&display_map);
2867 } else {
2868 head = word_range.end.to_point(&display_map);
2869 }
2870 } else {
2871 head = position.to_point(&display_map);
2872 }
2873
2874 if head <= original_buffer_range.start {
2875 tail = original_buffer_range.end;
2876 } else {
2877 tail = original_buffer_range.start;
2878 }
2879 }
2880 SelectMode::Line(original_range) => {
2881 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2882
2883 let position = display_map
2884 .clip_point(position, Bias::Left)
2885 .to_point(&display_map);
2886 let line_start = display_map.prev_line_boundary(position).0;
2887 let next_line_start = buffer.clip_point(
2888 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2889 Bias::Left,
2890 );
2891
2892 if line_start < original_range.start {
2893 head = line_start
2894 } else {
2895 head = next_line_start
2896 }
2897
2898 if head <= original_range.start {
2899 tail = original_range.end;
2900 } else {
2901 tail = original_range.start;
2902 }
2903 }
2904 SelectMode::All => {
2905 return;
2906 }
2907 };
2908
2909 if head < tail {
2910 pending.start = buffer.anchor_before(head);
2911 pending.end = buffer.anchor_before(tail);
2912 pending.reversed = true;
2913 } else {
2914 pending.start = buffer.anchor_before(tail);
2915 pending.end = buffer.anchor_before(head);
2916 pending.reversed = false;
2917 }
2918
2919 self.change_selections(None, window, cx, |s| {
2920 s.set_pending(pending, mode);
2921 });
2922 } else {
2923 log::error!("update_selection dispatched with no pending selection");
2924 return;
2925 }
2926
2927 self.apply_scroll_delta(scroll_delta, window, cx);
2928 cx.notify();
2929 }
2930
2931 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2932 self.columnar_selection_tail.take();
2933 if self.selections.pending_anchor().is_some() {
2934 let selections = self.selections.all::<usize>(cx);
2935 self.change_selections(None, window, cx, |s| {
2936 s.select(selections);
2937 s.clear_pending();
2938 });
2939 }
2940 }
2941
2942 fn select_columns(
2943 &mut self,
2944 tail: DisplayPoint,
2945 head: DisplayPoint,
2946 goal_column: u32,
2947 display_map: &DisplaySnapshot,
2948 window: &mut Window,
2949 cx: &mut Context<Self>,
2950 ) {
2951 let start_row = cmp::min(tail.row(), head.row());
2952 let end_row = cmp::max(tail.row(), head.row());
2953 let start_column = cmp::min(tail.column(), goal_column);
2954 let end_column = cmp::max(tail.column(), goal_column);
2955 let reversed = start_column < tail.column();
2956
2957 let selection_ranges = (start_row.0..=end_row.0)
2958 .map(DisplayRow)
2959 .filter_map(|row| {
2960 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2961 let start = display_map
2962 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2963 .to_point(display_map);
2964 let end = display_map
2965 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2966 .to_point(display_map);
2967 if reversed {
2968 Some(end..start)
2969 } else {
2970 Some(start..end)
2971 }
2972 } else {
2973 None
2974 }
2975 })
2976 .collect::<Vec<_>>();
2977
2978 self.change_selections(None, window, cx, |s| {
2979 s.select_ranges(selection_ranges);
2980 });
2981 cx.notify();
2982 }
2983
2984 pub fn has_pending_nonempty_selection(&self) -> bool {
2985 let pending_nonempty_selection = match self.selections.pending_anchor() {
2986 Some(Selection { start, end, .. }) => start != end,
2987 None => false,
2988 };
2989
2990 pending_nonempty_selection
2991 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2992 }
2993
2994 pub fn has_pending_selection(&self) -> bool {
2995 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2996 }
2997
2998 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2999 self.selection_mark_mode = false;
3000
3001 if self.clear_expanded_diff_hunks(cx) {
3002 cx.notify();
3003 return;
3004 }
3005 if self.dismiss_menus_and_popups(true, window, cx) {
3006 return;
3007 }
3008
3009 if self.mode == EditorMode::Full
3010 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3011 {
3012 return;
3013 }
3014
3015 cx.propagate();
3016 }
3017
3018 pub fn dismiss_menus_and_popups(
3019 &mut self,
3020 is_user_requested: bool,
3021 window: &mut Window,
3022 cx: &mut Context<Self>,
3023 ) -> bool {
3024 if self.take_rename(false, window, cx).is_some() {
3025 return true;
3026 }
3027
3028 if hide_hover(self, cx) {
3029 return true;
3030 }
3031
3032 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3033 return true;
3034 }
3035
3036 if self.hide_context_menu(window, cx).is_some() {
3037 return true;
3038 }
3039
3040 if self.mouse_context_menu.take().is_some() {
3041 return true;
3042 }
3043
3044 if is_user_requested && self.discard_inline_completion(true, cx) {
3045 return true;
3046 }
3047
3048 if self.snippet_stack.pop().is_some() {
3049 return true;
3050 }
3051
3052 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
3053 self.dismiss_diagnostics(cx);
3054 return true;
3055 }
3056
3057 false
3058 }
3059
3060 fn linked_editing_ranges_for(
3061 &self,
3062 selection: Range<text::Anchor>,
3063 cx: &App,
3064 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3065 if self.linked_edit_ranges.is_empty() {
3066 return None;
3067 }
3068 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3069 selection.end.buffer_id.and_then(|end_buffer_id| {
3070 if selection.start.buffer_id != Some(end_buffer_id) {
3071 return None;
3072 }
3073 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3074 let snapshot = buffer.read(cx).snapshot();
3075 self.linked_edit_ranges
3076 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3077 .map(|ranges| (ranges, snapshot, buffer))
3078 })?;
3079 use text::ToOffset as TO;
3080 // find offset from the start of current range to current cursor position
3081 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3082
3083 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3084 let start_difference = start_offset - start_byte_offset;
3085 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3086 let end_difference = end_offset - start_byte_offset;
3087 // Current range has associated linked ranges.
3088 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3089 for range in linked_ranges.iter() {
3090 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3091 let end_offset = start_offset + end_difference;
3092 let start_offset = start_offset + start_difference;
3093 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3094 continue;
3095 }
3096 if self.selections.disjoint_anchor_ranges().any(|s| {
3097 if s.start.buffer_id != selection.start.buffer_id
3098 || s.end.buffer_id != selection.end.buffer_id
3099 {
3100 return false;
3101 }
3102 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3103 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3104 }) {
3105 continue;
3106 }
3107 let start = buffer_snapshot.anchor_after(start_offset);
3108 let end = buffer_snapshot.anchor_after(end_offset);
3109 linked_edits
3110 .entry(buffer.clone())
3111 .or_default()
3112 .push(start..end);
3113 }
3114 Some(linked_edits)
3115 }
3116
3117 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3118 let text: Arc<str> = text.into();
3119
3120 if self.read_only(cx) {
3121 return;
3122 }
3123
3124 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3125
3126 let selections = self.selections.all_adjusted(cx);
3127 let mut bracket_inserted = false;
3128 let mut edits = Vec::new();
3129 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3130 let mut new_selections = Vec::with_capacity(selections.len());
3131 let mut new_autoclose_regions = Vec::new();
3132 let snapshot = self.buffer.read(cx).read(cx);
3133
3134 for (selection, autoclose_region) in
3135 self.selections_with_autoclose_regions(selections, &snapshot)
3136 {
3137 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3138 // Determine if the inserted text matches the opening or closing
3139 // bracket of any of this language's bracket pairs.
3140 let mut bracket_pair = None;
3141 let mut is_bracket_pair_start = false;
3142 let mut is_bracket_pair_end = false;
3143 if !text.is_empty() {
3144 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3145 // and they are removing the character that triggered IME popup.
3146 for (pair, enabled) in scope.brackets() {
3147 if !pair.close && !pair.surround {
3148 continue;
3149 }
3150
3151 if enabled && pair.start.ends_with(text.as_ref()) {
3152 let prefix_len = pair.start.len() - text.len();
3153 let preceding_text_matches_prefix = prefix_len == 0
3154 || (selection.start.column >= (prefix_len as u32)
3155 && snapshot.contains_str_at(
3156 Point::new(
3157 selection.start.row,
3158 selection.start.column - (prefix_len as u32),
3159 ),
3160 &pair.start[..prefix_len],
3161 ));
3162 if preceding_text_matches_prefix {
3163 bracket_pair = Some(pair.clone());
3164 is_bracket_pair_start = true;
3165 break;
3166 }
3167 }
3168 if pair.end.as_str() == text.as_ref() {
3169 bracket_pair = Some(pair.clone());
3170 is_bracket_pair_end = true;
3171 break;
3172 }
3173 }
3174 }
3175
3176 if let Some(bracket_pair) = bracket_pair {
3177 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3178 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3179 let auto_surround =
3180 self.use_auto_surround && snapshot_settings.use_auto_surround;
3181 if selection.is_empty() {
3182 if is_bracket_pair_start {
3183 // If the inserted text is a suffix of an opening bracket and the
3184 // selection is preceded by the rest of the opening bracket, then
3185 // insert the closing bracket.
3186 let following_text_allows_autoclose = snapshot
3187 .chars_at(selection.start)
3188 .next()
3189 .map_or(true, |c| scope.should_autoclose_before(c));
3190
3191 let preceding_text_allows_autoclose = selection.start.column == 0
3192 || snapshot.reversed_chars_at(selection.start).next().map_or(
3193 true,
3194 |c| {
3195 bracket_pair.start != bracket_pair.end
3196 || !snapshot
3197 .char_classifier_at(selection.start)
3198 .is_word(c)
3199 },
3200 );
3201
3202 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3203 && bracket_pair.start.len() == 1
3204 {
3205 let target = bracket_pair.start.chars().next().unwrap();
3206 let current_line_count = snapshot
3207 .reversed_chars_at(selection.start)
3208 .take_while(|&c| c != '\n')
3209 .filter(|&c| c == target)
3210 .count();
3211 current_line_count % 2 == 1
3212 } else {
3213 false
3214 };
3215
3216 if autoclose
3217 && bracket_pair.close
3218 && following_text_allows_autoclose
3219 && preceding_text_allows_autoclose
3220 && !is_closing_quote
3221 {
3222 let anchor = snapshot.anchor_before(selection.end);
3223 new_selections.push((selection.map(|_| anchor), text.len()));
3224 new_autoclose_regions.push((
3225 anchor,
3226 text.len(),
3227 selection.id,
3228 bracket_pair.clone(),
3229 ));
3230 edits.push((
3231 selection.range(),
3232 format!("{}{}", text, bracket_pair.end).into(),
3233 ));
3234 bracket_inserted = true;
3235 continue;
3236 }
3237 }
3238
3239 if let Some(region) = autoclose_region {
3240 // If the selection is followed by an auto-inserted closing bracket,
3241 // then don't insert that closing bracket again; just move the selection
3242 // past the closing bracket.
3243 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3244 && text.as_ref() == region.pair.end.as_str();
3245 if should_skip {
3246 let anchor = snapshot.anchor_after(selection.end);
3247 new_selections
3248 .push((selection.map(|_| anchor), region.pair.end.len()));
3249 continue;
3250 }
3251 }
3252
3253 let always_treat_brackets_as_autoclosed = snapshot
3254 .language_settings_at(selection.start, cx)
3255 .always_treat_brackets_as_autoclosed;
3256 if always_treat_brackets_as_autoclosed
3257 && is_bracket_pair_end
3258 && snapshot.contains_str_at(selection.end, text.as_ref())
3259 {
3260 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3261 // and the inserted text is a closing bracket and the selection is followed
3262 // by the closing bracket then move the selection past the closing bracket.
3263 let anchor = snapshot.anchor_after(selection.end);
3264 new_selections.push((selection.map(|_| anchor), text.len()));
3265 continue;
3266 }
3267 }
3268 // If an opening bracket is 1 character long and is typed while
3269 // text is selected, then surround that text with the bracket pair.
3270 else if auto_surround
3271 && bracket_pair.surround
3272 && is_bracket_pair_start
3273 && bracket_pair.start.chars().count() == 1
3274 {
3275 edits.push((selection.start..selection.start, text.clone()));
3276 edits.push((
3277 selection.end..selection.end,
3278 bracket_pair.end.as_str().into(),
3279 ));
3280 bracket_inserted = true;
3281 new_selections.push((
3282 Selection {
3283 id: selection.id,
3284 start: snapshot.anchor_after(selection.start),
3285 end: snapshot.anchor_before(selection.end),
3286 reversed: selection.reversed,
3287 goal: selection.goal,
3288 },
3289 0,
3290 ));
3291 continue;
3292 }
3293 }
3294 }
3295
3296 if self.auto_replace_emoji_shortcode
3297 && selection.is_empty()
3298 && text.as_ref().ends_with(':')
3299 {
3300 if let Some(possible_emoji_short_code) =
3301 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3302 {
3303 if !possible_emoji_short_code.is_empty() {
3304 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3305 let emoji_shortcode_start = Point::new(
3306 selection.start.row,
3307 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3308 );
3309
3310 // Remove shortcode from buffer
3311 edits.push((
3312 emoji_shortcode_start..selection.start,
3313 "".to_string().into(),
3314 ));
3315 new_selections.push((
3316 Selection {
3317 id: selection.id,
3318 start: snapshot.anchor_after(emoji_shortcode_start),
3319 end: snapshot.anchor_before(selection.start),
3320 reversed: selection.reversed,
3321 goal: selection.goal,
3322 },
3323 0,
3324 ));
3325
3326 // Insert emoji
3327 let selection_start_anchor = snapshot.anchor_after(selection.start);
3328 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3329 edits.push((selection.start..selection.end, emoji.to_string().into()));
3330
3331 continue;
3332 }
3333 }
3334 }
3335 }
3336
3337 // If not handling any auto-close operation, then just replace the selected
3338 // text with the given input and move the selection to the end of the
3339 // newly inserted text.
3340 let anchor = snapshot.anchor_after(selection.end);
3341 if !self.linked_edit_ranges.is_empty() {
3342 let start_anchor = snapshot.anchor_before(selection.start);
3343
3344 let is_word_char = text.chars().next().map_or(true, |char| {
3345 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3346 classifier.is_word(char)
3347 });
3348
3349 if is_word_char {
3350 if let Some(ranges) = self
3351 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3352 {
3353 for (buffer, edits) in ranges {
3354 linked_edits
3355 .entry(buffer.clone())
3356 .or_default()
3357 .extend(edits.into_iter().map(|range| (range, text.clone())));
3358 }
3359 }
3360 }
3361 }
3362
3363 new_selections.push((selection.map(|_| anchor), 0));
3364 edits.push((selection.start..selection.end, text.clone()));
3365 }
3366
3367 drop(snapshot);
3368
3369 self.transact(window, cx, |this, window, cx| {
3370 let initial_buffer_versions =
3371 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3372
3373 this.buffer.update(cx, |buffer, cx| {
3374 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3375 });
3376 for (buffer, edits) in linked_edits {
3377 buffer.update(cx, |buffer, cx| {
3378 let snapshot = buffer.snapshot();
3379 let edits = edits
3380 .into_iter()
3381 .map(|(range, text)| {
3382 use text::ToPoint as TP;
3383 let end_point = TP::to_point(&range.end, &snapshot);
3384 let start_point = TP::to_point(&range.start, &snapshot);
3385 (start_point..end_point, text)
3386 })
3387 .sorted_by_key(|(range, _)| range.start);
3388 buffer.edit(edits, None, cx);
3389 })
3390 }
3391 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3392 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3393 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3394 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3395 .zip(new_selection_deltas)
3396 .map(|(selection, delta)| Selection {
3397 id: selection.id,
3398 start: selection.start + delta,
3399 end: selection.end + delta,
3400 reversed: selection.reversed,
3401 goal: SelectionGoal::None,
3402 })
3403 .collect::<Vec<_>>();
3404
3405 let mut i = 0;
3406 for (position, delta, selection_id, pair) in new_autoclose_regions {
3407 let position = position.to_offset(&map.buffer_snapshot) + delta;
3408 let start = map.buffer_snapshot.anchor_before(position);
3409 let end = map.buffer_snapshot.anchor_after(position);
3410 while let Some(existing_state) = this.autoclose_regions.get(i) {
3411 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3412 Ordering::Less => i += 1,
3413 Ordering::Greater => break,
3414 Ordering::Equal => {
3415 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3416 Ordering::Less => i += 1,
3417 Ordering::Equal => break,
3418 Ordering::Greater => break,
3419 }
3420 }
3421 }
3422 }
3423 this.autoclose_regions.insert(
3424 i,
3425 AutocloseRegion {
3426 selection_id,
3427 range: start..end,
3428 pair,
3429 },
3430 );
3431 }
3432
3433 let had_active_inline_completion = this.has_active_inline_completion();
3434 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3435 s.select(new_selections)
3436 });
3437
3438 if !bracket_inserted {
3439 if let Some(on_type_format_task) =
3440 this.trigger_on_type_formatting(text.to_string(), window, cx)
3441 {
3442 on_type_format_task.detach_and_log_err(cx);
3443 }
3444 }
3445
3446 let editor_settings = EditorSettings::get_global(cx);
3447 if bracket_inserted
3448 && (editor_settings.auto_signature_help
3449 || editor_settings.show_signature_help_after_edits)
3450 {
3451 this.show_signature_help(&ShowSignatureHelp, window, cx);
3452 }
3453
3454 let trigger_in_words =
3455 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3456 if this.hard_wrap.is_some() {
3457 let latest: Range<Point> = this.selections.newest(cx).range();
3458 if latest.is_empty()
3459 && this
3460 .buffer()
3461 .read(cx)
3462 .snapshot(cx)
3463 .line_len(MultiBufferRow(latest.start.row))
3464 == latest.start.column
3465 {
3466 this.rewrap_impl(
3467 RewrapOptions {
3468 override_language_settings: true,
3469 preserve_existing_whitespace: true,
3470 },
3471 cx,
3472 )
3473 }
3474 }
3475 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3476 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3477 this.refresh_inline_completion(true, false, window, cx);
3478 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3479 });
3480 }
3481
3482 fn find_possible_emoji_shortcode_at_position(
3483 snapshot: &MultiBufferSnapshot,
3484 position: Point,
3485 ) -> Option<String> {
3486 let mut chars = Vec::new();
3487 let mut found_colon = false;
3488 for char in snapshot.reversed_chars_at(position).take(100) {
3489 // Found a possible emoji shortcode in the middle of the buffer
3490 if found_colon {
3491 if char.is_whitespace() {
3492 chars.reverse();
3493 return Some(chars.iter().collect());
3494 }
3495 // If the previous character is not a whitespace, we are in the middle of a word
3496 // and we only want to complete the shortcode if the word is made up of other emojis
3497 let mut containing_word = String::new();
3498 for ch in snapshot
3499 .reversed_chars_at(position)
3500 .skip(chars.len() + 1)
3501 .take(100)
3502 {
3503 if ch.is_whitespace() {
3504 break;
3505 }
3506 containing_word.push(ch);
3507 }
3508 let containing_word = containing_word.chars().rev().collect::<String>();
3509 if util::word_consists_of_emojis(containing_word.as_str()) {
3510 chars.reverse();
3511 return Some(chars.iter().collect());
3512 }
3513 }
3514
3515 if char.is_whitespace() || !char.is_ascii() {
3516 return None;
3517 }
3518 if char == ':' {
3519 found_colon = true;
3520 } else {
3521 chars.push(char);
3522 }
3523 }
3524 // Found a possible emoji shortcode at the beginning of the buffer
3525 chars.reverse();
3526 Some(chars.iter().collect())
3527 }
3528
3529 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3530 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3531 self.transact(window, cx, |this, window, cx| {
3532 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3533 let selections = this.selections.all::<usize>(cx);
3534 let multi_buffer = this.buffer.read(cx);
3535 let buffer = multi_buffer.snapshot(cx);
3536 selections
3537 .iter()
3538 .map(|selection| {
3539 let start_point = selection.start.to_point(&buffer);
3540 let mut indent =
3541 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3542 indent.len = cmp::min(indent.len, start_point.column);
3543 let start = selection.start;
3544 let end = selection.end;
3545 let selection_is_empty = start == end;
3546 let language_scope = buffer.language_scope_at(start);
3547 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3548 &language_scope
3549 {
3550 let insert_extra_newline =
3551 insert_extra_newline_brackets(&buffer, start..end, language)
3552 || insert_extra_newline_tree_sitter(&buffer, start..end);
3553
3554 // Comment extension on newline is allowed only for cursor selections
3555 let comment_delimiter = maybe!({
3556 if !selection_is_empty {
3557 return None;
3558 }
3559
3560 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3561 return None;
3562 }
3563
3564 let delimiters = language.line_comment_prefixes();
3565 let max_len_of_delimiter =
3566 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3567 let (snapshot, range) =
3568 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3569
3570 let mut index_of_first_non_whitespace = 0;
3571 let comment_candidate = snapshot
3572 .chars_for_range(range)
3573 .skip_while(|c| {
3574 let should_skip = c.is_whitespace();
3575 if should_skip {
3576 index_of_first_non_whitespace += 1;
3577 }
3578 should_skip
3579 })
3580 .take(max_len_of_delimiter)
3581 .collect::<String>();
3582 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3583 comment_candidate.starts_with(comment_prefix.as_ref())
3584 })?;
3585 let cursor_is_placed_after_comment_marker =
3586 index_of_first_non_whitespace + comment_prefix.len()
3587 <= start_point.column as usize;
3588 if cursor_is_placed_after_comment_marker {
3589 Some(comment_prefix.clone())
3590 } else {
3591 None
3592 }
3593 });
3594 (comment_delimiter, insert_extra_newline)
3595 } else {
3596 (None, false)
3597 };
3598
3599 let capacity_for_delimiter = comment_delimiter
3600 .as_deref()
3601 .map(str::len)
3602 .unwrap_or_default();
3603 let mut new_text =
3604 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3605 new_text.push('\n');
3606 new_text.extend(indent.chars());
3607 if let Some(delimiter) = &comment_delimiter {
3608 new_text.push_str(delimiter);
3609 }
3610 if insert_extra_newline {
3611 new_text = new_text.repeat(2);
3612 }
3613
3614 let anchor = buffer.anchor_after(end);
3615 let new_selection = selection.map(|_| anchor);
3616 (
3617 (start..end, new_text),
3618 (insert_extra_newline, new_selection),
3619 )
3620 })
3621 .unzip()
3622 };
3623
3624 this.edit_with_autoindent(edits, cx);
3625 let buffer = this.buffer.read(cx).snapshot(cx);
3626 let new_selections = selection_fixup_info
3627 .into_iter()
3628 .map(|(extra_newline_inserted, new_selection)| {
3629 let mut cursor = new_selection.end.to_point(&buffer);
3630 if extra_newline_inserted {
3631 cursor.row -= 1;
3632 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3633 }
3634 new_selection.map(|_| cursor)
3635 })
3636 .collect();
3637
3638 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3639 s.select(new_selections)
3640 });
3641 this.refresh_inline_completion(true, false, window, cx);
3642 });
3643 }
3644
3645 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3646 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3647
3648 let buffer = self.buffer.read(cx);
3649 let snapshot = buffer.snapshot(cx);
3650
3651 let mut edits = Vec::new();
3652 let mut rows = Vec::new();
3653
3654 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3655 let cursor = selection.head();
3656 let row = cursor.row;
3657
3658 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3659
3660 let newline = "\n".to_string();
3661 edits.push((start_of_line..start_of_line, newline));
3662
3663 rows.push(row + rows_inserted as u32);
3664 }
3665
3666 self.transact(window, cx, |editor, window, cx| {
3667 editor.edit(edits, cx);
3668
3669 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3670 let mut index = 0;
3671 s.move_cursors_with(|map, _, _| {
3672 let row = rows[index];
3673 index += 1;
3674
3675 let point = Point::new(row, 0);
3676 let boundary = map.next_line_boundary(point).1;
3677 let clipped = map.clip_point(boundary, Bias::Left);
3678
3679 (clipped, SelectionGoal::None)
3680 });
3681 });
3682
3683 let mut indent_edits = Vec::new();
3684 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3685 for row in rows {
3686 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3687 for (row, indent) in indents {
3688 if indent.len == 0 {
3689 continue;
3690 }
3691
3692 let text = match indent.kind {
3693 IndentKind::Space => " ".repeat(indent.len as usize),
3694 IndentKind::Tab => "\t".repeat(indent.len as usize),
3695 };
3696 let point = Point::new(row.0, 0);
3697 indent_edits.push((point..point, text));
3698 }
3699 }
3700 editor.edit(indent_edits, cx);
3701 });
3702 }
3703
3704 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3705 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3706
3707 let buffer = self.buffer.read(cx);
3708 let snapshot = buffer.snapshot(cx);
3709
3710 let mut edits = Vec::new();
3711 let mut rows = Vec::new();
3712 let mut rows_inserted = 0;
3713
3714 for selection in self.selections.all_adjusted(cx) {
3715 let cursor = selection.head();
3716 let row = cursor.row;
3717
3718 let point = Point::new(row + 1, 0);
3719 let start_of_line = snapshot.clip_point(point, Bias::Left);
3720
3721 let newline = "\n".to_string();
3722 edits.push((start_of_line..start_of_line, newline));
3723
3724 rows_inserted += 1;
3725 rows.push(row + rows_inserted);
3726 }
3727
3728 self.transact(window, cx, |editor, window, cx| {
3729 editor.edit(edits, cx);
3730
3731 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3732 let mut index = 0;
3733 s.move_cursors_with(|map, _, _| {
3734 let row = rows[index];
3735 index += 1;
3736
3737 let point = Point::new(row, 0);
3738 let boundary = map.next_line_boundary(point).1;
3739 let clipped = map.clip_point(boundary, Bias::Left);
3740
3741 (clipped, SelectionGoal::None)
3742 });
3743 });
3744
3745 let mut indent_edits = Vec::new();
3746 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3747 for row in rows {
3748 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3749 for (row, indent) in indents {
3750 if indent.len == 0 {
3751 continue;
3752 }
3753
3754 let text = match indent.kind {
3755 IndentKind::Space => " ".repeat(indent.len as usize),
3756 IndentKind::Tab => "\t".repeat(indent.len as usize),
3757 };
3758 let point = Point::new(row.0, 0);
3759 indent_edits.push((point..point, text));
3760 }
3761 }
3762 editor.edit(indent_edits, cx);
3763 });
3764 }
3765
3766 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3767 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3768 original_indent_columns: Vec::new(),
3769 });
3770 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3771 }
3772
3773 fn insert_with_autoindent_mode(
3774 &mut self,
3775 text: &str,
3776 autoindent_mode: Option<AutoindentMode>,
3777 window: &mut Window,
3778 cx: &mut Context<Self>,
3779 ) {
3780 if self.read_only(cx) {
3781 return;
3782 }
3783
3784 let text: Arc<str> = text.into();
3785 self.transact(window, cx, |this, window, cx| {
3786 let old_selections = this.selections.all_adjusted(cx);
3787 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3788 let anchors = {
3789 let snapshot = buffer.read(cx);
3790 old_selections
3791 .iter()
3792 .map(|s| {
3793 let anchor = snapshot.anchor_after(s.head());
3794 s.map(|_| anchor)
3795 })
3796 .collect::<Vec<_>>()
3797 };
3798 buffer.edit(
3799 old_selections
3800 .iter()
3801 .map(|s| (s.start..s.end, text.clone())),
3802 autoindent_mode,
3803 cx,
3804 );
3805 anchors
3806 });
3807
3808 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3809 s.select_anchors(selection_anchors);
3810 });
3811
3812 cx.notify();
3813 });
3814 }
3815
3816 fn trigger_completion_on_input(
3817 &mut self,
3818 text: &str,
3819 trigger_in_words: bool,
3820 window: &mut Window,
3821 cx: &mut Context<Self>,
3822 ) {
3823 let ignore_completion_provider = self
3824 .context_menu
3825 .borrow()
3826 .as_ref()
3827 .map(|menu| match menu {
3828 CodeContextMenu::Completions(completions_menu) => {
3829 completions_menu.ignore_completion_provider
3830 }
3831 CodeContextMenu::CodeActions(_) => false,
3832 })
3833 .unwrap_or(false);
3834
3835 if ignore_completion_provider {
3836 self.show_word_completions(&ShowWordCompletions, window, cx);
3837 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3838 self.show_completions(
3839 &ShowCompletions {
3840 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3841 },
3842 window,
3843 cx,
3844 );
3845 } else {
3846 self.hide_context_menu(window, cx);
3847 }
3848 }
3849
3850 fn is_completion_trigger(
3851 &self,
3852 text: &str,
3853 trigger_in_words: bool,
3854 cx: &mut Context<Self>,
3855 ) -> bool {
3856 let position = self.selections.newest_anchor().head();
3857 let multibuffer = self.buffer.read(cx);
3858 let Some(buffer) = position
3859 .buffer_id
3860 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3861 else {
3862 return false;
3863 };
3864
3865 if let Some(completion_provider) = &self.completion_provider {
3866 completion_provider.is_completion_trigger(
3867 &buffer,
3868 position.text_anchor,
3869 text,
3870 trigger_in_words,
3871 cx,
3872 )
3873 } else {
3874 false
3875 }
3876 }
3877
3878 /// If any empty selections is touching the start of its innermost containing autoclose
3879 /// region, expand it to select the brackets.
3880 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3881 let selections = self.selections.all::<usize>(cx);
3882 let buffer = self.buffer.read(cx).read(cx);
3883 let new_selections = self
3884 .selections_with_autoclose_regions(selections, &buffer)
3885 .map(|(mut selection, region)| {
3886 if !selection.is_empty() {
3887 return selection;
3888 }
3889
3890 if let Some(region) = region {
3891 let mut range = region.range.to_offset(&buffer);
3892 if selection.start == range.start && range.start >= region.pair.start.len() {
3893 range.start -= region.pair.start.len();
3894 if buffer.contains_str_at(range.start, ®ion.pair.start)
3895 && buffer.contains_str_at(range.end, ®ion.pair.end)
3896 {
3897 range.end += region.pair.end.len();
3898 selection.start = range.start;
3899 selection.end = range.end;
3900
3901 return selection;
3902 }
3903 }
3904 }
3905
3906 let always_treat_brackets_as_autoclosed = buffer
3907 .language_settings_at(selection.start, cx)
3908 .always_treat_brackets_as_autoclosed;
3909
3910 if !always_treat_brackets_as_autoclosed {
3911 return selection;
3912 }
3913
3914 if let Some(scope) = buffer.language_scope_at(selection.start) {
3915 for (pair, enabled) in scope.brackets() {
3916 if !enabled || !pair.close {
3917 continue;
3918 }
3919
3920 if buffer.contains_str_at(selection.start, &pair.end) {
3921 let pair_start_len = pair.start.len();
3922 if buffer.contains_str_at(
3923 selection.start.saturating_sub(pair_start_len),
3924 &pair.start,
3925 ) {
3926 selection.start -= pair_start_len;
3927 selection.end += pair.end.len();
3928
3929 return selection;
3930 }
3931 }
3932 }
3933 }
3934
3935 selection
3936 })
3937 .collect();
3938
3939 drop(buffer);
3940 self.change_selections(None, window, cx, |selections| {
3941 selections.select(new_selections)
3942 });
3943 }
3944
3945 /// Iterate the given selections, and for each one, find the smallest surrounding
3946 /// autoclose region. This uses the ordering of the selections and the autoclose
3947 /// regions to avoid repeated comparisons.
3948 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3949 &'a self,
3950 selections: impl IntoIterator<Item = Selection<D>>,
3951 buffer: &'a MultiBufferSnapshot,
3952 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3953 let mut i = 0;
3954 let mut regions = self.autoclose_regions.as_slice();
3955 selections.into_iter().map(move |selection| {
3956 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3957
3958 let mut enclosing = None;
3959 while let Some(pair_state) = regions.get(i) {
3960 if pair_state.range.end.to_offset(buffer) < range.start {
3961 regions = ®ions[i + 1..];
3962 i = 0;
3963 } else if pair_state.range.start.to_offset(buffer) > range.end {
3964 break;
3965 } else {
3966 if pair_state.selection_id == selection.id {
3967 enclosing = Some(pair_state);
3968 }
3969 i += 1;
3970 }
3971 }
3972
3973 (selection, enclosing)
3974 })
3975 }
3976
3977 /// Remove any autoclose regions that no longer contain their selection.
3978 fn invalidate_autoclose_regions(
3979 &mut self,
3980 mut selections: &[Selection<Anchor>],
3981 buffer: &MultiBufferSnapshot,
3982 ) {
3983 self.autoclose_regions.retain(|state| {
3984 let mut i = 0;
3985 while let Some(selection) = selections.get(i) {
3986 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3987 selections = &selections[1..];
3988 continue;
3989 }
3990 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3991 break;
3992 }
3993 if selection.id == state.selection_id {
3994 return true;
3995 } else {
3996 i += 1;
3997 }
3998 }
3999 false
4000 });
4001 }
4002
4003 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4004 let offset = position.to_offset(buffer);
4005 let (word_range, kind) = buffer.surrounding_word(offset, true);
4006 if offset > word_range.start && kind == Some(CharKind::Word) {
4007 Some(
4008 buffer
4009 .text_for_range(word_range.start..offset)
4010 .collect::<String>(),
4011 )
4012 } else {
4013 None
4014 }
4015 }
4016
4017 pub fn toggle_inlay_hints(
4018 &mut self,
4019 _: &ToggleInlayHints,
4020 _: &mut Window,
4021 cx: &mut Context<Self>,
4022 ) {
4023 self.refresh_inlay_hints(
4024 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4025 cx,
4026 );
4027 }
4028
4029 pub fn inlay_hints_enabled(&self) -> bool {
4030 self.inlay_hint_cache.enabled
4031 }
4032
4033 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4034 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
4035 return;
4036 }
4037
4038 let reason_description = reason.description();
4039 let ignore_debounce = matches!(
4040 reason,
4041 InlayHintRefreshReason::SettingsChange(_)
4042 | InlayHintRefreshReason::Toggle(_)
4043 | InlayHintRefreshReason::ExcerptsRemoved(_)
4044 | InlayHintRefreshReason::ModifiersChanged(_)
4045 );
4046 let (invalidate_cache, required_languages) = match reason {
4047 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4048 match self.inlay_hint_cache.modifiers_override(enabled) {
4049 Some(enabled) => {
4050 if enabled {
4051 (InvalidationStrategy::RefreshRequested, None)
4052 } else {
4053 self.splice_inlays(
4054 &self
4055 .visible_inlay_hints(cx)
4056 .iter()
4057 .map(|inlay| inlay.id)
4058 .collect::<Vec<InlayId>>(),
4059 Vec::new(),
4060 cx,
4061 );
4062 return;
4063 }
4064 }
4065 None => return,
4066 }
4067 }
4068 InlayHintRefreshReason::Toggle(enabled) => {
4069 if self.inlay_hint_cache.toggle(enabled) {
4070 if enabled {
4071 (InvalidationStrategy::RefreshRequested, None)
4072 } else {
4073 self.splice_inlays(
4074 &self
4075 .visible_inlay_hints(cx)
4076 .iter()
4077 .map(|inlay| inlay.id)
4078 .collect::<Vec<InlayId>>(),
4079 Vec::new(),
4080 cx,
4081 );
4082 return;
4083 }
4084 } else {
4085 return;
4086 }
4087 }
4088 InlayHintRefreshReason::SettingsChange(new_settings) => {
4089 match self.inlay_hint_cache.update_settings(
4090 &self.buffer,
4091 new_settings,
4092 self.visible_inlay_hints(cx),
4093 cx,
4094 ) {
4095 ControlFlow::Break(Some(InlaySplice {
4096 to_remove,
4097 to_insert,
4098 })) => {
4099 self.splice_inlays(&to_remove, to_insert, cx);
4100 return;
4101 }
4102 ControlFlow::Break(None) => return,
4103 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4104 }
4105 }
4106 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4107 if let Some(InlaySplice {
4108 to_remove,
4109 to_insert,
4110 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
4111 {
4112 self.splice_inlays(&to_remove, to_insert, cx);
4113 }
4114 return;
4115 }
4116 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4117 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4118 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4119 }
4120 InlayHintRefreshReason::RefreshRequested => {
4121 (InvalidationStrategy::RefreshRequested, None)
4122 }
4123 };
4124
4125 if let Some(InlaySplice {
4126 to_remove,
4127 to_insert,
4128 }) = self.inlay_hint_cache.spawn_hint_refresh(
4129 reason_description,
4130 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4131 invalidate_cache,
4132 ignore_debounce,
4133 cx,
4134 ) {
4135 self.splice_inlays(&to_remove, to_insert, cx);
4136 }
4137 }
4138
4139 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4140 self.display_map
4141 .read(cx)
4142 .current_inlays()
4143 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4144 .cloned()
4145 .collect()
4146 }
4147
4148 pub fn excerpts_for_inlay_hints_query(
4149 &self,
4150 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4151 cx: &mut Context<Editor>,
4152 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4153 let Some(project) = self.project.as_ref() else {
4154 return HashMap::default();
4155 };
4156 let project = project.read(cx);
4157 let multi_buffer = self.buffer().read(cx);
4158 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4159 let multi_buffer_visible_start = self
4160 .scroll_manager
4161 .anchor()
4162 .anchor
4163 .to_point(&multi_buffer_snapshot);
4164 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4165 multi_buffer_visible_start
4166 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4167 Bias::Left,
4168 );
4169 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4170 multi_buffer_snapshot
4171 .range_to_buffer_ranges(multi_buffer_visible_range)
4172 .into_iter()
4173 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4174 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4175 let buffer_file = project::File::from_dyn(buffer.file())?;
4176 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4177 let worktree_entry = buffer_worktree
4178 .read(cx)
4179 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4180 if worktree_entry.is_ignored {
4181 return None;
4182 }
4183
4184 let language = buffer.language()?;
4185 if let Some(restrict_to_languages) = restrict_to_languages {
4186 if !restrict_to_languages.contains(language) {
4187 return None;
4188 }
4189 }
4190 Some((
4191 excerpt_id,
4192 (
4193 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4194 buffer.version().clone(),
4195 excerpt_visible_range,
4196 ),
4197 ))
4198 })
4199 .collect()
4200 }
4201
4202 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4203 TextLayoutDetails {
4204 text_system: window.text_system().clone(),
4205 editor_style: self.style.clone().unwrap(),
4206 rem_size: window.rem_size(),
4207 scroll_anchor: self.scroll_manager.anchor(),
4208 visible_rows: self.visible_line_count(),
4209 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4210 }
4211 }
4212
4213 pub fn splice_inlays(
4214 &self,
4215 to_remove: &[InlayId],
4216 to_insert: Vec<Inlay>,
4217 cx: &mut Context<Self>,
4218 ) {
4219 self.display_map.update(cx, |display_map, cx| {
4220 display_map.splice_inlays(to_remove, to_insert, cx)
4221 });
4222 cx.notify();
4223 }
4224
4225 fn trigger_on_type_formatting(
4226 &self,
4227 input: String,
4228 window: &mut Window,
4229 cx: &mut Context<Self>,
4230 ) -> Option<Task<Result<()>>> {
4231 if input.len() != 1 {
4232 return None;
4233 }
4234
4235 let project = self.project.as_ref()?;
4236 let position = self.selections.newest_anchor().head();
4237 let (buffer, buffer_position) = self
4238 .buffer
4239 .read(cx)
4240 .text_anchor_for_position(position, cx)?;
4241
4242 let settings = language_settings::language_settings(
4243 buffer
4244 .read(cx)
4245 .language_at(buffer_position)
4246 .map(|l| l.name()),
4247 buffer.read(cx).file(),
4248 cx,
4249 );
4250 if !settings.use_on_type_format {
4251 return None;
4252 }
4253
4254 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4255 // hence we do LSP request & edit on host side only — add formats to host's history.
4256 let push_to_lsp_host_history = true;
4257 // If this is not the host, append its history with new edits.
4258 let push_to_client_history = project.read(cx).is_via_collab();
4259
4260 let on_type_formatting = project.update(cx, |project, cx| {
4261 project.on_type_format(
4262 buffer.clone(),
4263 buffer_position,
4264 input,
4265 push_to_lsp_host_history,
4266 cx,
4267 )
4268 });
4269 Some(cx.spawn_in(window, async move |editor, cx| {
4270 if let Some(transaction) = on_type_formatting.await? {
4271 if push_to_client_history {
4272 buffer
4273 .update(cx, |buffer, _| {
4274 buffer.push_transaction(transaction, Instant::now());
4275 buffer.finalize_last_transaction();
4276 })
4277 .ok();
4278 }
4279 editor.update(cx, |editor, cx| {
4280 editor.refresh_document_highlights(cx);
4281 })?;
4282 }
4283 Ok(())
4284 }))
4285 }
4286
4287 pub fn show_word_completions(
4288 &mut self,
4289 _: &ShowWordCompletions,
4290 window: &mut Window,
4291 cx: &mut Context<Self>,
4292 ) {
4293 self.open_completions_menu(true, None, window, cx);
4294 }
4295
4296 pub fn show_completions(
4297 &mut self,
4298 options: &ShowCompletions,
4299 window: &mut Window,
4300 cx: &mut Context<Self>,
4301 ) {
4302 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4303 }
4304
4305 fn open_completions_menu(
4306 &mut self,
4307 ignore_completion_provider: bool,
4308 trigger: Option<&str>,
4309 window: &mut Window,
4310 cx: &mut Context<Self>,
4311 ) {
4312 if self.pending_rename.is_some() {
4313 return;
4314 }
4315 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4316 return;
4317 }
4318
4319 let position = self.selections.newest_anchor().head();
4320 if position.diff_base_anchor.is_some() {
4321 return;
4322 }
4323 let (buffer, buffer_position) =
4324 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4325 output
4326 } else {
4327 return;
4328 };
4329 let buffer_snapshot = buffer.read(cx).snapshot();
4330 let show_completion_documentation = buffer_snapshot
4331 .settings_at(buffer_position, cx)
4332 .show_completion_documentation;
4333
4334 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4335
4336 let trigger_kind = match trigger {
4337 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4338 CompletionTriggerKind::TRIGGER_CHARACTER
4339 }
4340 _ => CompletionTriggerKind::INVOKED,
4341 };
4342 let completion_context = CompletionContext {
4343 trigger_character: trigger.and_then(|trigger| {
4344 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4345 Some(String::from(trigger))
4346 } else {
4347 None
4348 }
4349 }),
4350 trigger_kind,
4351 };
4352
4353 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4354 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4355 let word_to_exclude = buffer_snapshot
4356 .text_for_range(old_range.clone())
4357 .collect::<String>();
4358 (
4359 buffer_snapshot.anchor_before(old_range.start)
4360 ..buffer_snapshot.anchor_after(old_range.end),
4361 Some(word_to_exclude),
4362 )
4363 } else {
4364 (buffer_position..buffer_position, None)
4365 };
4366
4367 let completion_settings = language_settings(
4368 buffer_snapshot
4369 .language_at(buffer_position)
4370 .map(|language| language.name()),
4371 buffer_snapshot.file(),
4372 cx,
4373 )
4374 .completions;
4375
4376 // The document can be large, so stay in reasonable bounds when searching for words,
4377 // otherwise completion pop-up might be slow to appear.
4378 const WORD_LOOKUP_ROWS: u32 = 5_000;
4379 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4380 let min_word_search = buffer_snapshot.clip_point(
4381 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4382 Bias::Left,
4383 );
4384 let max_word_search = buffer_snapshot.clip_point(
4385 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4386 Bias::Right,
4387 );
4388 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4389 ..buffer_snapshot.point_to_offset(max_word_search);
4390
4391 let provider = self
4392 .completion_provider
4393 .as_ref()
4394 .filter(|_| !ignore_completion_provider);
4395 let skip_digits = query
4396 .as_ref()
4397 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4398
4399 let (mut words, provided_completions) = match provider {
4400 Some(provider) => {
4401 let completions = provider.completions(
4402 position.excerpt_id,
4403 &buffer,
4404 buffer_position,
4405 completion_context,
4406 window,
4407 cx,
4408 );
4409
4410 let words = match completion_settings.words {
4411 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4412 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4413 .background_spawn(async move {
4414 buffer_snapshot.words_in_range(WordsQuery {
4415 fuzzy_contents: None,
4416 range: word_search_range,
4417 skip_digits,
4418 })
4419 }),
4420 };
4421
4422 (words, completions)
4423 }
4424 None => (
4425 cx.background_spawn(async move {
4426 buffer_snapshot.words_in_range(WordsQuery {
4427 fuzzy_contents: None,
4428 range: word_search_range,
4429 skip_digits,
4430 })
4431 }),
4432 Task::ready(Ok(None)),
4433 ),
4434 };
4435
4436 let sort_completions = provider
4437 .as_ref()
4438 .map_or(false, |provider| provider.sort_completions());
4439
4440 let filter_completions = provider
4441 .as_ref()
4442 .map_or(true, |provider| provider.filter_completions());
4443
4444 let id = post_inc(&mut self.next_completion_id);
4445 let task = cx.spawn_in(window, async move |editor, cx| {
4446 async move {
4447 editor.update(cx, |this, _| {
4448 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4449 })?;
4450
4451 let mut completions = Vec::new();
4452 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4453 completions.extend(provided_completions);
4454 if completion_settings.words == WordsCompletionMode::Fallback {
4455 words = Task::ready(BTreeMap::default());
4456 }
4457 }
4458
4459 let mut words = words.await;
4460 if let Some(word_to_exclude) = &word_to_exclude {
4461 words.remove(word_to_exclude);
4462 }
4463 for lsp_completion in &completions {
4464 words.remove(&lsp_completion.new_text);
4465 }
4466 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4467 replace_range: old_range.clone(),
4468 new_text: word.clone(),
4469 label: CodeLabel::plain(word, None),
4470 icon_path: None,
4471 documentation: None,
4472 source: CompletionSource::BufferWord {
4473 word_range,
4474 resolved: false,
4475 },
4476 insert_text_mode: Some(InsertTextMode::AS_IS),
4477 confirm: None,
4478 }));
4479
4480 let menu = if completions.is_empty() {
4481 None
4482 } else {
4483 let mut menu = CompletionsMenu::new(
4484 id,
4485 sort_completions,
4486 show_completion_documentation,
4487 ignore_completion_provider,
4488 position,
4489 buffer.clone(),
4490 completions.into(),
4491 );
4492
4493 menu.filter(
4494 if filter_completions {
4495 query.as_deref()
4496 } else {
4497 None
4498 },
4499 cx.background_executor().clone(),
4500 )
4501 .await;
4502
4503 menu.visible().then_some(menu)
4504 };
4505
4506 editor.update_in(cx, |editor, window, cx| {
4507 match editor.context_menu.borrow().as_ref() {
4508 None => {}
4509 Some(CodeContextMenu::Completions(prev_menu)) => {
4510 if prev_menu.id > id {
4511 return;
4512 }
4513 }
4514 _ => return,
4515 }
4516
4517 if editor.focus_handle.is_focused(window) && menu.is_some() {
4518 let mut menu = menu.unwrap();
4519 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4520
4521 *editor.context_menu.borrow_mut() =
4522 Some(CodeContextMenu::Completions(menu));
4523
4524 if editor.show_edit_predictions_in_menu() {
4525 editor.update_visible_inline_completion(window, cx);
4526 } else {
4527 editor.discard_inline_completion(false, cx);
4528 }
4529
4530 cx.notify();
4531 } else if editor.completion_tasks.len() <= 1 {
4532 // If there are no more completion tasks and the last menu was
4533 // empty, we should hide it.
4534 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4535 // If it was already hidden and we don't show inline
4536 // completions in the menu, we should also show the
4537 // inline-completion when available.
4538 if was_hidden && editor.show_edit_predictions_in_menu() {
4539 editor.update_visible_inline_completion(window, cx);
4540 }
4541 }
4542 })?;
4543
4544 anyhow::Ok(())
4545 }
4546 .log_err()
4547 .await
4548 });
4549
4550 self.completion_tasks.push((id, task));
4551 }
4552
4553 #[cfg(feature = "test-support")]
4554 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4555 let menu = self.context_menu.borrow();
4556 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4557 let completions = menu.completions.borrow();
4558 Some(completions.to_vec())
4559 } else {
4560 None
4561 }
4562 }
4563
4564 pub fn confirm_completion(
4565 &mut self,
4566 action: &ConfirmCompletion,
4567 window: &mut Window,
4568 cx: &mut Context<Self>,
4569 ) -> Option<Task<Result<()>>> {
4570 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4571 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4572 }
4573
4574 pub fn confirm_completion_insert(
4575 &mut self,
4576 _: &ConfirmCompletionInsert,
4577 window: &mut Window,
4578 cx: &mut Context<Self>,
4579 ) -> Option<Task<Result<()>>> {
4580 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4581 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4582 }
4583
4584 pub fn confirm_completion_replace(
4585 &mut self,
4586 _: &ConfirmCompletionReplace,
4587 window: &mut Window,
4588 cx: &mut Context<Self>,
4589 ) -> Option<Task<Result<()>>> {
4590 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4591 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4592 }
4593
4594 pub fn compose_completion(
4595 &mut self,
4596 action: &ComposeCompletion,
4597 window: &mut Window,
4598 cx: &mut Context<Self>,
4599 ) -> Option<Task<Result<()>>> {
4600 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4601 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4602 }
4603
4604 fn do_completion(
4605 &mut self,
4606 item_ix: Option<usize>,
4607 intent: CompletionIntent,
4608 window: &mut Window,
4609 cx: &mut Context<Editor>,
4610 ) -> Option<Task<Result<()>>> {
4611 use language::ToOffset as _;
4612
4613 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4614 else {
4615 return None;
4616 };
4617
4618 let candidate_id = {
4619 let entries = completions_menu.entries.borrow();
4620 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4621 if self.show_edit_predictions_in_menu() {
4622 self.discard_inline_completion(true, cx);
4623 }
4624 mat.candidate_id
4625 };
4626
4627 let buffer_handle = completions_menu.buffer;
4628 let completion = completions_menu
4629 .completions
4630 .borrow()
4631 .get(candidate_id)?
4632 .clone();
4633 cx.stop_propagation();
4634
4635 let snippet;
4636 let new_text;
4637 if completion.is_snippet() {
4638 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4639 new_text = snippet.as_ref().unwrap().text.clone();
4640 } else {
4641 snippet = None;
4642 new_text = completion.new_text.clone();
4643 };
4644 let selections = self.selections.all::<usize>(cx);
4645
4646 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4647 let buffer = buffer_handle.read(cx);
4648 let old_text = buffer
4649 .text_for_range(replace_range.clone())
4650 .collect::<String>();
4651
4652 let newest_selection = self.selections.newest_anchor();
4653 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4654 return None;
4655 }
4656
4657 let lookbehind = newest_selection
4658 .start
4659 .text_anchor
4660 .to_offset(buffer)
4661 .saturating_sub(replace_range.start);
4662 let lookahead = replace_range
4663 .end
4664 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4665 let mut common_prefix_len = 0;
4666 for (a, b) in old_text.chars().zip(new_text.chars()) {
4667 if a == b {
4668 common_prefix_len += a.len_utf8();
4669 } else {
4670 break;
4671 }
4672 }
4673
4674 let snapshot = self.buffer.read(cx).snapshot(cx);
4675 let mut range_to_replace: Option<Range<usize>> = None;
4676 let mut ranges = Vec::new();
4677 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4678 for selection in &selections {
4679 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4680 let start = selection.start.saturating_sub(lookbehind);
4681 let end = selection.end + lookahead;
4682 if selection.id == newest_selection.id {
4683 range_to_replace = Some(start + common_prefix_len..end);
4684 }
4685 ranges.push(start + common_prefix_len..end);
4686 } else {
4687 common_prefix_len = 0;
4688 ranges.clear();
4689 ranges.extend(selections.iter().map(|s| {
4690 if s.id == newest_selection.id {
4691 range_to_replace = Some(replace_range.clone());
4692 replace_range.clone()
4693 } else {
4694 s.start..s.end
4695 }
4696 }));
4697 break;
4698 }
4699 if !self.linked_edit_ranges.is_empty() {
4700 let start_anchor = snapshot.anchor_before(selection.head());
4701 let end_anchor = snapshot.anchor_after(selection.tail());
4702 if let Some(ranges) = self
4703 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4704 {
4705 for (buffer, edits) in ranges {
4706 linked_edits.entry(buffer.clone()).or_default().extend(
4707 edits
4708 .into_iter()
4709 .map(|range| (range, new_text[common_prefix_len..].to_owned())),
4710 );
4711 }
4712 }
4713 }
4714 }
4715 let text = &new_text[common_prefix_len..];
4716
4717 let utf16_range_to_replace = range_to_replace.map(|range| {
4718 let newest_selection = self.selections.newest::<OffsetUtf16>(cx).range();
4719 let selection_start_utf16 = newest_selection.start.0 as isize;
4720
4721 range.start.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16
4722 ..range.end.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16
4723 });
4724 cx.emit(EditorEvent::InputHandled {
4725 utf16_range_to_replace,
4726 text: text.into(),
4727 });
4728
4729 self.transact(window, cx, |this, window, cx| {
4730 if let Some(mut snippet) = snippet {
4731 snippet.text = text.to_string();
4732 for tabstop in snippet
4733 .tabstops
4734 .iter_mut()
4735 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4736 {
4737 tabstop.start -= common_prefix_len as isize;
4738 tabstop.end -= common_prefix_len as isize;
4739 }
4740
4741 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4742 } else {
4743 this.buffer.update(cx, |buffer, cx| {
4744 let edits = ranges.iter().map(|range| (range.clone(), text));
4745 let auto_indent = if completion.insert_text_mode == Some(InsertTextMode::AS_IS)
4746 {
4747 None
4748 } else {
4749 this.autoindent_mode.clone()
4750 };
4751 buffer.edit(edits, auto_indent, cx);
4752 });
4753 }
4754 for (buffer, edits) in linked_edits {
4755 buffer.update(cx, |buffer, cx| {
4756 let snapshot = buffer.snapshot();
4757 let edits = edits
4758 .into_iter()
4759 .map(|(range, text)| {
4760 use text::ToPoint as TP;
4761 let end_point = TP::to_point(&range.end, &snapshot);
4762 let start_point = TP::to_point(&range.start, &snapshot);
4763 (start_point..end_point, text)
4764 })
4765 .sorted_by_key(|(range, _)| range.start);
4766 buffer.edit(edits, None, cx);
4767 })
4768 }
4769
4770 this.refresh_inline_completion(true, false, window, cx);
4771 });
4772
4773 let show_new_completions_on_confirm = completion
4774 .confirm
4775 .as_ref()
4776 .map_or(false, |confirm| confirm(intent, window, cx));
4777 if show_new_completions_on_confirm {
4778 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4779 }
4780
4781 let provider = self.completion_provider.as_ref()?;
4782 drop(completion);
4783 let apply_edits = provider.apply_additional_edits_for_completion(
4784 buffer_handle,
4785 completions_menu.completions.clone(),
4786 candidate_id,
4787 true,
4788 cx,
4789 );
4790
4791 let editor_settings = EditorSettings::get_global(cx);
4792 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4793 // After the code completion is finished, users often want to know what signatures are needed.
4794 // so we should automatically call signature_help
4795 self.show_signature_help(&ShowSignatureHelp, window, cx);
4796 }
4797
4798 Some(cx.foreground_executor().spawn(async move {
4799 apply_edits.await?;
4800 Ok(())
4801 }))
4802 }
4803
4804 pub fn toggle_code_actions(
4805 &mut self,
4806 action: &ToggleCodeActions,
4807 window: &mut Window,
4808 cx: &mut Context<Self>,
4809 ) {
4810 let mut context_menu = self.context_menu.borrow_mut();
4811 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4812 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4813 // Toggle if we're selecting the same one
4814 *context_menu = None;
4815 cx.notify();
4816 return;
4817 } else {
4818 // Otherwise, clear it and start a new one
4819 *context_menu = None;
4820 cx.notify();
4821 }
4822 }
4823 drop(context_menu);
4824 let snapshot = self.snapshot(window, cx);
4825 let deployed_from_indicator = action.deployed_from_indicator;
4826 let mut task = self.code_actions_task.take();
4827 let action = action.clone();
4828 cx.spawn_in(window, async move |editor, cx| {
4829 while let Some(prev_task) = task {
4830 prev_task.await.log_err();
4831 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4832 }
4833
4834 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4835 if editor.focus_handle.is_focused(window) {
4836 let multibuffer_point = action
4837 .deployed_from_indicator
4838 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4839 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4840 let (buffer, buffer_row) = snapshot
4841 .buffer_snapshot
4842 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4843 .and_then(|(buffer_snapshot, range)| {
4844 editor
4845 .buffer
4846 .read(cx)
4847 .buffer(buffer_snapshot.remote_id())
4848 .map(|buffer| (buffer, range.start.row))
4849 })?;
4850 let (_, code_actions) = editor
4851 .available_code_actions
4852 .clone()
4853 .and_then(|(location, code_actions)| {
4854 let snapshot = location.buffer.read(cx).snapshot();
4855 let point_range = location.range.to_point(&snapshot);
4856 let point_range = point_range.start.row..=point_range.end.row;
4857 if point_range.contains(&buffer_row) {
4858 Some((location, code_actions))
4859 } else {
4860 None
4861 }
4862 })
4863 .unzip();
4864 let buffer_id = buffer.read(cx).remote_id();
4865 let tasks = editor
4866 .tasks
4867 .get(&(buffer_id, buffer_row))
4868 .map(|t| Arc::new(t.to_owned()));
4869 if tasks.is_none() && code_actions.is_none() {
4870 return None;
4871 }
4872
4873 editor.completion_tasks.clear();
4874 editor.discard_inline_completion(false, cx);
4875 let task_context =
4876 tasks
4877 .as_ref()
4878 .zip(editor.project.clone())
4879 .map(|(tasks, project)| {
4880 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4881 });
4882
4883 let debugger_flag = cx.has_flag::<Debugger>();
4884
4885 Some(cx.spawn_in(window, async move |editor, cx| {
4886 let task_context = match task_context {
4887 Some(task_context) => task_context.await,
4888 None => None,
4889 };
4890 let resolved_tasks =
4891 tasks.zip(task_context).map(|(tasks, task_context)| {
4892 Rc::new(ResolvedTasks {
4893 templates: tasks.resolve(&task_context).collect(),
4894 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4895 multibuffer_point.row,
4896 tasks.column,
4897 )),
4898 })
4899 });
4900 let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
4901 tasks
4902 .templates
4903 .iter()
4904 .filter(|task| {
4905 if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
4906 debugger_flag
4907 } else {
4908 true
4909 }
4910 })
4911 .count()
4912 == 1
4913 }) && code_actions
4914 .as_ref()
4915 .map_or(true, |actions| actions.is_empty());
4916 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4917 *editor.context_menu.borrow_mut() =
4918 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4919 buffer,
4920 actions: CodeActionContents {
4921 tasks: resolved_tasks,
4922 actions: code_actions,
4923 },
4924 selected_item: Default::default(),
4925 scroll_handle: UniformListScrollHandle::default(),
4926 deployed_from_indicator,
4927 }));
4928 if spawn_straight_away {
4929 if let Some(task) = editor.confirm_code_action(
4930 &ConfirmCodeAction { item_ix: Some(0) },
4931 window,
4932 cx,
4933 ) {
4934 cx.notify();
4935 return task;
4936 }
4937 }
4938 cx.notify();
4939 Task::ready(Ok(()))
4940 }) {
4941 task.await
4942 } else {
4943 Ok(())
4944 }
4945 }))
4946 } else {
4947 Some(Task::ready(Ok(())))
4948 }
4949 })?;
4950 if let Some(task) = spawned_test_task {
4951 task.await?;
4952 }
4953
4954 Ok::<_, anyhow::Error>(())
4955 })
4956 .detach_and_log_err(cx);
4957 }
4958
4959 pub fn confirm_code_action(
4960 &mut self,
4961 action: &ConfirmCodeAction,
4962 window: &mut Window,
4963 cx: &mut Context<Self>,
4964 ) -> Option<Task<Result<()>>> {
4965 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4966
4967 let actions_menu =
4968 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4969 menu
4970 } else {
4971 return None;
4972 };
4973
4974 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4975 let action = actions_menu.actions.get(action_ix)?;
4976 let title = action.label();
4977 let buffer = actions_menu.buffer;
4978 let workspace = self.workspace()?;
4979
4980 match action {
4981 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4982 match resolved_task.task_type() {
4983 task::TaskType::Script => workspace.update(cx, |workspace, cx| {
4984 workspace::tasks::schedule_resolved_task(
4985 workspace,
4986 task_source_kind,
4987 resolved_task,
4988 false,
4989 cx,
4990 );
4991
4992 Some(Task::ready(Ok(())))
4993 }),
4994 task::TaskType::Debug(debug_args) => {
4995 if debug_args.locator.is_some() {
4996 workspace.update(cx, |workspace, cx| {
4997 workspace::tasks::schedule_resolved_task(
4998 workspace,
4999 task_source_kind,
5000 resolved_task,
5001 false,
5002 cx,
5003 );
5004 });
5005
5006 return Some(Task::ready(Ok(())));
5007 }
5008
5009 if let Some(project) = self.project.as_ref() {
5010 project
5011 .update(cx, |project, cx| {
5012 project.start_debug_session(
5013 resolved_task.resolved_debug_adapter_config().unwrap(),
5014 cx,
5015 )
5016 })
5017 .detach_and_log_err(cx);
5018 Some(Task::ready(Ok(())))
5019 } else {
5020 Some(Task::ready(Ok(())))
5021 }
5022 }
5023 }
5024 }
5025 CodeActionsItem::CodeAction {
5026 excerpt_id,
5027 action,
5028 provider,
5029 } => {
5030 let apply_code_action =
5031 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5032 let workspace = workspace.downgrade();
5033 Some(cx.spawn_in(window, async move |editor, cx| {
5034 let project_transaction = apply_code_action.await?;
5035 Self::open_project_transaction(
5036 &editor,
5037 workspace,
5038 project_transaction,
5039 title,
5040 cx,
5041 )
5042 .await
5043 }))
5044 }
5045 }
5046 }
5047
5048 pub async fn open_project_transaction(
5049 this: &WeakEntity<Editor>,
5050 workspace: WeakEntity<Workspace>,
5051 transaction: ProjectTransaction,
5052 title: String,
5053 cx: &mut AsyncWindowContext,
5054 ) -> Result<()> {
5055 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5056 cx.update(|_, cx| {
5057 entries.sort_unstable_by_key(|(buffer, _)| {
5058 buffer.read(cx).file().map(|f| f.path().clone())
5059 });
5060 })?;
5061
5062 // If the project transaction's edits are all contained within this editor, then
5063 // avoid opening a new editor to display them.
5064
5065 if let Some((buffer, transaction)) = entries.first() {
5066 if entries.len() == 1 {
5067 let excerpt = this.update(cx, |editor, cx| {
5068 editor
5069 .buffer()
5070 .read(cx)
5071 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5072 })?;
5073 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5074 if excerpted_buffer == *buffer {
5075 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5076 let excerpt_range = excerpt_range.to_offset(buffer);
5077 buffer
5078 .edited_ranges_for_transaction::<usize>(transaction)
5079 .all(|range| {
5080 excerpt_range.start <= range.start
5081 && excerpt_range.end >= range.end
5082 })
5083 })?;
5084
5085 if all_edits_within_excerpt {
5086 return Ok(());
5087 }
5088 }
5089 }
5090 }
5091 } else {
5092 return Ok(());
5093 }
5094
5095 let mut ranges_to_highlight = Vec::new();
5096 let excerpt_buffer = cx.new(|cx| {
5097 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5098 for (buffer_handle, transaction) in &entries {
5099 let edited_ranges = buffer_handle
5100 .read(cx)
5101 .edited_ranges_for_transaction::<Point>(transaction)
5102 .collect::<Vec<_>>();
5103 let (ranges, _) = multibuffer.set_excerpts_for_path(
5104 PathKey::for_buffer(buffer_handle, cx),
5105 buffer_handle.clone(),
5106 edited_ranges,
5107 DEFAULT_MULTIBUFFER_CONTEXT,
5108 cx,
5109 );
5110
5111 ranges_to_highlight.extend(ranges);
5112 }
5113 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5114 multibuffer
5115 })?;
5116
5117 workspace.update_in(cx, |workspace, window, cx| {
5118 let project = workspace.project().clone();
5119 let editor =
5120 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5121 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5122 editor.update(cx, |editor, cx| {
5123 editor.highlight_background::<Self>(
5124 &ranges_to_highlight,
5125 |theme| theme.editor_highlighted_line_background,
5126 cx,
5127 );
5128 });
5129 })?;
5130
5131 Ok(())
5132 }
5133
5134 pub fn clear_code_action_providers(&mut self) {
5135 self.code_action_providers.clear();
5136 self.available_code_actions.take();
5137 }
5138
5139 pub fn add_code_action_provider(
5140 &mut self,
5141 provider: Rc<dyn CodeActionProvider>,
5142 window: &mut Window,
5143 cx: &mut Context<Self>,
5144 ) {
5145 if self
5146 .code_action_providers
5147 .iter()
5148 .any(|existing_provider| existing_provider.id() == provider.id())
5149 {
5150 return;
5151 }
5152
5153 self.code_action_providers.push(provider);
5154 self.refresh_code_actions(window, cx);
5155 }
5156
5157 pub fn remove_code_action_provider(
5158 &mut self,
5159 id: Arc<str>,
5160 window: &mut Window,
5161 cx: &mut Context<Self>,
5162 ) {
5163 self.code_action_providers
5164 .retain(|provider| provider.id() != id);
5165 self.refresh_code_actions(window, cx);
5166 }
5167
5168 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5169 let buffer = self.buffer.read(cx);
5170 let newest_selection = self.selections.newest_anchor().clone();
5171 if newest_selection.head().diff_base_anchor.is_some() {
5172 return None;
5173 }
5174 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
5175 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
5176 if start_buffer != end_buffer {
5177 return None;
5178 }
5179
5180 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5181 cx.background_executor()
5182 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5183 .await;
5184
5185 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5186 let providers = this.code_action_providers.clone();
5187 let tasks = this
5188 .code_action_providers
5189 .iter()
5190 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5191 .collect::<Vec<_>>();
5192 (providers, tasks)
5193 })?;
5194
5195 let mut actions = Vec::new();
5196 for (provider, provider_actions) in
5197 providers.into_iter().zip(future::join_all(tasks).await)
5198 {
5199 if let Some(provider_actions) = provider_actions.log_err() {
5200 actions.extend(provider_actions.into_iter().map(|action| {
5201 AvailableCodeAction {
5202 excerpt_id: newest_selection.start.excerpt_id,
5203 action,
5204 provider: provider.clone(),
5205 }
5206 }));
5207 }
5208 }
5209
5210 this.update(cx, |this, cx| {
5211 this.available_code_actions = if actions.is_empty() {
5212 None
5213 } else {
5214 Some((
5215 Location {
5216 buffer: start_buffer,
5217 range: start..end,
5218 },
5219 actions.into(),
5220 ))
5221 };
5222 cx.notify();
5223 })
5224 }));
5225 None
5226 }
5227
5228 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5229 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5230 self.show_git_blame_inline = false;
5231
5232 self.show_git_blame_inline_delay_task =
5233 Some(cx.spawn_in(window, async move |this, cx| {
5234 cx.background_executor().timer(delay).await;
5235
5236 this.update(cx, |this, cx| {
5237 this.show_git_blame_inline = true;
5238 cx.notify();
5239 })
5240 .log_err();
5241 }));
5242 }
5243 }
5244
5245 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5246 if self.pending_rename.is_some() {
5247 return None;
5248 }
5249
5250 let provider = self.semantics_provider.clone()?;
5251 let buffer = self.buffer.read(cx);
5252 let newest_selection = self.selections.newest_anchor().clone();
5253 let cursor_position = newest_selection.head();
5254 let (cursor_buffer, cursor_buffer_position) =
5255 buffer.text_anchor_for_position(cursor_position, cx)?;
5256 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5257 if cursor_buffer != tail_buffer {
5258 return None;
5259 }
5260 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5261 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5262 cx.background_executor()
5263 .timer(Duration::from_millis(debounce))
5264 .await;
5265
5266 let highlights = if let Some(highlights) = cx
5267 .update(|cx| {
5268 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5269 })
5270 .ok()
5271 .flatten()
5272 {
5273 highlights.await.log_err()
5274 } else {
5275 None
5276 };
5277
5278 if let Some(highlights) = highlights {
5279 this.update(cx, |this, cx| {
5280 if this.pending_rename.is_some() {
5281 return;
5282 }
5283
5284 let buffer_id = cursor_position.buffer_id;
5285 let buffer = this.buffer.read(cx);
5286 if !buffer
5287 .text_anchor_for_position(cursor_position, cx)
5288 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5289 {
5290 return;
5291 }
5292
5293 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5294 let mut write_ranges = Vec::new();
5295 let mut read_ranges = Vec::new();
5296 for highlight in highlights {
5297 for (excerpt_id, excerpt_range) in
5298 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5299 {
5300 let start = highlight
5301 .range
5302 .start
5303 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5304 let end = highlight
5305 .range
5306 .end
5307 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5308 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5309 continue;
5310 }
5311
5312 let range = Anchor {
5313 buffer_id,
5314 excerpt_id,
5315 text_anchor: start,
5316 diff_base_anchor: None,
5317 }..Anchor {
5318 buffer_id,
5319 excerpt_id,
5320 text_anchor: end,
5321 diff_base_anchor: None,
5322 };
5323 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5324 write_ranges.push(range);
5325 } else {
5326 read_ranges.push(range);
5327 }
5328 }
5329 }
5330
5331 this.highlight_background::<DocumentHighlightRead>(
5332 &read_ranges,
5333 |theme| theme.editor_document_highlight_read_background,
5334 cx,
5335 );
5336 this.highlight_background::<DocumentHighlightWrite>(
5337 &write_ranges,
5338 |theme| theme.editor_document_highlight_write_background,
5339 cx,
5340 );
5341 cx.notify();
5342 })
5343 .log_err();
5344 }
5345 }));
5346 None
5347 }
5348
5349 pub fn refresh_selected_text_highlights(
5350 &mut self,
5351 window: &mut Window,
5352 cx: &mut Context<Editor>,
5353 ) {
5354 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5355 return;
5356 }
5357 self.selection_highlight_task.take();
5358 if !EditorSettings::get_global(cx).selection_highlight {
5359 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5360 return;
5361 }
5362 if self.selections.count() != 1 || self.selections.line_mode {
5363 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5364 return;
5365 }
5366 let selection = self.selections.newest::<Point>(cx);
5367 if selection.is_empty() || selection.start.row != selection.end.row {
5368 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5369 return;
5370 }
5371 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5372 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5373 cx.background_executor()
5374 .timer(Duration::from_millis(debounce))
5375 .await;
5376 let Some(Some(matches_task)) = editor
5377 .update_in(cx, |editor, _, cx| {
5378 if editor.selections.count() != 1 || editor.selections.line_mode {
5379 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5380 return None;
5381 }
5382 let selection = editor.selections.newest::<Point>(cx);
5383 if selection.is_empty() || selection.start.row != selection.end.row {
5384 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5385 return None;
5386 }
5387 let buffer = editor.buffer().read(cx).snapshot(cx);
5388 let query = buffer.text_for_range(selection.range()).collect::<String>();
5389 if query.trim().is_empty() {
5390 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5391 return None;
5392 }
5393 Some(cx.background_spawn(async move {
5394 let mut ranges = Vec::new();
5395 let selection_anchors = selection.range().to_anchors(&buffer);
5396 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5397 for (search_buffer, search_range, excerpt_id) in
5398 buffer.range_to_buffer_ranges(range)
5399 {
5400 ranges.extend(
5401 project::search::SearchQuery::text(
5402 query.clone(),
5403 false,
5404 false,
5405 false,
5406 Default::default(),
5407 Default::default(),
5408 None,
5409 )
5410 .unwrap()
5411 .search(search_buffer, Some(search_range.clone()))
5412 .await
5413 .into_iter()
5414 .filter_map(
5415 |match_range| {
5416 let start = search_buffer.anchor_after(
5417 search_range.start + match_range.start,
5418 );
5419 let end = search_buffer.anchor_before(
5420 search_range.start + match_range.end,
5421 );
5422 let range = Anchor::range_in_buffer(
5423 excerpt_id,
5424 search_buffer.remote_id(),
5425 start..end,
5426 );
5427 (range != selection_anchors).then_some(range)
5428 },
5429 ),
5430 );
5431 }
5432 }
5433 ranges
5434 }))
5435 })
5436 .log_err()
5437 else {
5438 return;
5439 };
5440 let matches = matches_task.await;
5441 editor
5442 .update_in(cx, |editor, _, cx| {
5443 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5444 if !matches.is_empty() {
5445 editor.highlight_background::<SelectedTextHighlight>(
5446 &matches,
5447 |theme| theme.editor_document_highlight_bracket_background,
5448 cx,
5449 )
5450 }
5451 })
5452 .log_err();
5453 }));
5454 }
5455
5456 pub fn refresh_inline_completion(
5457 &mut self,
5458 debounce: bool,
5459 user_requested: bool,
5460 window: &mut Window,
5461 cx: &mut Context<Self>,
5462 ) -> Option<()> {
5463 let provider = self.edit_prediction_provider()?;
5464 let cursor = self.selections.newest_anchor().head();
5465 let (buffer, cursor_buffer_position) =
5466 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5467
5468 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5469 self.discard_inline_completion(false, cx);
5470 return None;
5471 }
5472
5473 if !user_requested
5474 && (!self.should_show_edit_predictions()
5475 || !self.is_focused(window)
5476 || buffer.read(cx).is_empty())
5477 {
5478 self.discard_inline_completion(false, cx);
5479 return None;
5480 }
5481
5482 self.update_visible_inline_completion(window, cx);
5483 provider.refresh(
5484 self.project.clone(),
5485 buffer,
5486 cursor_buffer_position,
5487 debounce,
5488 cx,
5489 );
5490 Some(())
5491 }
5492
5493 fn show_edit_predictions_in_menu(&self) -> bool {
5494 match self.edit_prediction_settings {
5495 EditPredictionSettings::Disabled => false,
5496 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5497 }
5498 }
5499
5500 pub fn edit_predictions_enabled(&self) -> bool {
5501 match self.edit_prediction_settings {
5502 EditPredictionSettings::Disabled => false,
5503 EditPredictionSettings::Enabled { .. } => true,
5504 }
5505 }
5506
5507 fn edit_prediction_requires_modifier(&self) -> bool {
5508 match self.edit_prediction_settings {
5509 EditPredictionSettings::Disabled => false,
5510 EditPredictionSettings::Enabled {
5511 preview_requires_modifier,
5512 ..
5513 } => preview_requires_modifier,
5514 }
5515 }
5516
5517 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5518 if self.edit_prediction_provider.is_none() {
5519 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5520 } else {
5521 let selection = self.selections.newest_anchor();
5522 let cursor = selection.head();
5523
5524 if let Some((buffer, cursor_buffer_position)) =
5525 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5526 {
5527 self.edit_prediction_settings =
5528 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5529 }
5530 }
5531 }
5532
5533 fn edit_prediction_settings_at_position(
5534 &self,
5535 buffer: &Entity<Buffer>,
5536 buffer_position: language::Anchor,
5537 cx: &App,
5538 ) -> EditPredictionSettings {
5539 if self.mode != EditorMode::Full
5540 || !self.show_inline_completions_override.unwrap_or(true)
5541 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5542 {
5543 return EditPredictionSettings::Disabled;
5544 }
5545
5546 let buffer = buffer.read(cx);
5547
5548 let file = buffer.file();
5549
5550 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5551 return EditPredictionSettings::Disabled;
5552 };
5553
5554 let by_provider = matches!(
5555 self.menu_inline_completions_policy,
5556 MenuInlineCompletionsPolicy::ByProvider
5557 );
5558
5559 let show_in_menu = by_provider
5560 && self
5561 .edit_prediction_provider
5562 .as_ref()
5563 .map_or(false, |provider| {
5564 provider.provider.show_completions_in_menu()
5565 });
5566
5567 let preview_requires_modifier =
5568 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5569
5570 EditPredictionSettings::Enabled {
5571 show_in_menu,
5572 preview_requires_modifier,
5573 }
5574 }
5575
5576 fn should_show_edit_predictions(&self) -> bool {
5577 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5578 }
5579
5580 pub fn edit_prediction_preview_is_active(&self) -> bool {
5581 matches!(
5582 self.edit_prediction_preview,
5583 EditPredictionPreview::Active { .. }
5584 )
5585 }
5586
5587 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5588 let cursor = self.selections.newest_anchor().head();
5589 if let Some((buffer, cursor_position)) =
5590 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5591 {
5592 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5593 } else {
5594 false
5595 }
5596 }
5597
5598 fn edit_predictions_enabled_in_buffer(
5599 &self,
5600 buffer: &Entity<Buffer>,
5601 buffer_position: language::Anchor,
5602 cx: &App,
5603 ) -> bool {
5604 maybe!({
5605 if self.read_only(cx) {
5606 return Some(false);
5607 }
5608 let provider = self.edit_prediction_provider()?;
5609 if !provider.is_enabled(&buffer, buffer_position, cx) {
5610 return Some(false);
5611 }
5612 let buffer = buffer.read(cx);
5613 let Some(file) = buffer.file() else {
5614 return Some(true);
5615 };
5616 let settings = all_language_settings(Some(file), cx);
5617 Some(settings.edit_predictions_enabled_for_file(file, cx))
5618 })
5619 .unwrap_or(false)
5620 }
5621
5622 fn cycle_inline_completion(
5623 &mut self,
5624 direction: Direction,
5625 window: &mut Window,
5626 cx: &mut Context<Self>,
5627 ) -> Option<()> {
5628 let provider = self.edit_prediction_provider()?;
5629 let cursor = self.selections.newest_anchor().head();
5630 let (buffer, cursor_buffer_position) =
5631 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5632 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5633 return None;
5634 }
5635
5636 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5637 self.update_visible_inline_completion(window, cx);
5638
5639 Some(())
5640 }
5641
5642 pub fn show_inline_completion(
5643 &mut self,
5644 _: &ShowEditPrediction,
5645 window: &mut Window,
5646 cx: &mut Context<Self>,
5647 ) {
5648 if !self.has_active_inline_completion() {
5649 self.refresh_inline_completion(false, true, window, cx);
5650 return;
5651 }
5652
5653 self.update_visible_inline_completion(window, cx);
5654 }
5655
5656 pub fn display_cursor_names(
5657 &mut self,
5658 _: &DisplayCursorNames,
5659 window: &mut Window,
5660 cx: &mut Context<Self>,
5661 ) {
5662 self.show_cursor_names(window, cx);
5663 }
5664
5665 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5666 self.show_cursor_names = true;
5667 cx.notify();
5668 cx.spawn_in(window, async move |this, cx| {
5669 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5670 this.update(cx, |this, cx| {
5671 this.show_cursor_names = false;
5672 cx.notify()
5673 })
5674 .ok()
5675 })
5676 .detach();
5677 }
5678
5679 pub fn next_edit_prediction(
5680 &mut self,
5681 _: &NextEditPrediction,
5682 window: &mut Window,
5683 cx: &mut Context<Self>,
5684 ) {
5685 if self.has_active_inline_completion() {
5686 self.cycle_inline_completion(Direction::Next, window, cx);
5687 } else {
5688 let is_copilot_disabled = self
5689 .refresh_inline_completion(false, true, window, cx)
5690 .is_none();
5691 if is_copilot_disabled {
5692 cx.propagate();
5693 }
5694 }
5695 }
5696
5697 pub fn previous_edit_prediction(
5698 &mut self,
5699 _: &PreviousEditPrediction,
5700 window: &mut Window,
5701 cx: &mut Context<Self>,
5702 ) {
5703 if self.has_active_inline_completion() {
5704 self.cycle_inline_completion(Direction::Prev, window, cx);
5705 } else {
5706 let is_copilot_disabled = self
5707 .refresh_inline_completion(false, true, window, cx)
5708 .is_none();
5709 if is_copilot_disabled {
5710 cx.propagate();
5711 }
5712 }
5713 }
5714
5715 pub fn accept_edit_prediction(
5716 &mut self,
5717 _: &AcceptEditPrediction,
5718 window: &mut Window,
5719 cx: &mut Context<Self>,
5720 ) {
5721 if self.show_edit_predictions_in_menu() {
5722 self.hide_context_menu(window, cx);
5723 }
5724
5725 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5726 return;
5727 };
5728
5729 self.report_inline_completion_event(
5730 active_inline_completion.completion_id.clone(),
5731 true,
5732 cx,
5733 );
5734
5735 match &active_inline_completion.completion {
5736 InlineCompletion::Move { target, .. } => {
5737 let target = *target;
5738
5739 if let Some(position_map) = &self.last_position_map {
5740 if position_map
5741 .visible_row_range
5742 .contains(&target.to_display_point(&position_map.snapshot).row())
5743 || !self.edit_prediction_requires_modifier()
5744 {
5745 self.unfold_ranges(&[target..target], true, false, cx);
5746 // Note that this is also done in vim's handler of the Tab action.
5747 self.change_selections(
5748 Some(Autoscroll::newest()),
5749 window,
5750 cx,
5751 |selections| {
5752 selections.select_anchor_ranges([target..target]);
5753 },
5754 );
5755 self.clear_row_highlights::<EditPredictionPreview>();
5756
5757 self.edit_prediction_preview
5758 .set_previous_scroll_position(None);
5759 } else {
5760 self.edit_prediction_preview
5761 .set_previous_scroll_position(Some(
5762 position_map.snapshot.scroll_anchor,
5763 ));
5764
5765 self.highlight_rows::<EditPredictionPreview>(
5766 target..target,
5767 cx.theme().colors().editor_highlighted_line_background,
5768 true,
5769 cx,
5770 );
5771 self.request_autoscroll(Autoscroll::fit(), cx);
5772 }
5773 }
5774 }
5775 InlineCompletion::Edit { edits, .. } => {
5776 if let Some(provider) = self.edit_prediction_provider() {
5777 provider.accept(cx);
5778 }
5779
5780 let snapshot = self.buffer.read(cx).snapshot(cx);
5781 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5782
5783 self.buffer.update(cx, |buffer, cx| {
5784 buffer.edit(edits.iter().cloned(), None, cx)
5785 });
5786
5787 self.change_selections(None, window, cx, |s| {
5788 s.select_anchor_ranges([last_edit_end..last_edit_end])
5789 });
5790
5791 self.update_visible_inline_completion(window, cx);
5792 if self.active_inline_completion.is_none() {
5793 self.refresh_inline_completion(true, true, window, cx);
5794 }
5795
5796 cx.notify();
5797 }
5798 }
5799
5800 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5801 }
5802
5803 pub fn accept_partial_inline_completion(
5804 &mut self,
5805 _: &AcceptPartialEditPrediction,
5806 window: &mut Window,
5807 cx: &mut Context<Self>,
5808 ) {
5809 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5810 return;
5811 };
5812 if self.selections.count() != 1 {
5813 return;
5814 }
5815
5816 self.report_inline_completion_event(
5817 active_inline_completion.completion_id.clone(),
5818 true,
5819 cx,
5820 );
5821
5822 match &active_inline_completion.completion {
5823 InlineCompletion::Move { target, .. } => {
5824 let target = *target;
5825 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5826 selections.select_anchor_ranges([target..target]);
5827 });
5828 }
5829 InlineCompletion::Edit { edits, .. } => {
5830 // Find an insertion that starts at the cursor position.
5831 let snapshot = self.buffer.read(cx).snapshot(cx);
5832 let cursor_offset = self.selections.newest::<usize>(cx).head();
5833 let insertion = edits.iter().find_map(|(range, text)| {
5834 let range = range.to_offset(&snapshot);
5835 if range.is_empty() && range.start == cursor_offset {
5836 Some(text)
5837 } else {
5838 None
5839 }
5840 });
5841
5842 if let Some(text) = insertion {
5843 let mut partial_completion = text
5844 .chars()
5845 .by_ref()
5846 .take_while(|c| c.is_alphabetic())
5847 .collect::<String>();
5848 if partial_completion.is_empty() {
5849 partial_completion = text
5850 .chars()
5851 .by_ref()
5852 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5853 .collect::<String>();
5854 }
5855
5856 cx.emit(EditorEvent::InputHandled {
5857 utf16_range_to_replace: None,
5858 text: partial_completion.clone().into(),
5859 });
5860
5861 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5862
5863 self.refresh_inline_completion(true, true, window, cx);
5864 cx.notify();
5865 } else {
5866 self.accept_edit_prediction(&Default::default(), window, cx);
5867 }
5868 }
5869 }
5870 }
5871
5872 fn discard_inline_completion(
5873 &mut self,
5874 should_report_inline_completion_event: bool,
5875 cx: &mut Context<Self>,
5876 ) -> bool {
5877 if should_report_inline_completion_event {
5878 let completion_id = self
5879 .active_inline_completion
5880 .as_ref()
5881 .and_then(|active_completion| active_completion.completion_id.clone());
5882
5883 self.report_inline_completion_event(completion_id, false, cx);
5884 }
5885
5886 if let Some(provider) = self.edit_prediction_provider() {
5887 provider.discard(cx);
5888 }
5889
5890 self.take_active_inline_completion(cx)
5891 }
5892
5893 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5894 let Some(provider) = self.edit_prediction_provider() else {
5895 return;
5896 };
5897
5898 let Some((_, buffer, _)) = self
5899 .buffer
5900 .read(cx)
5901 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5902 else {
5903 return;
5904 };
5905
5906 let extension = buffer
5907 .read(cx)
5908 .file()
5909 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5910
5911 let event_type = match accepted {
5912 true => "Edit Prediction Accepted",
5913 false => "Edit Prediction Discarded",
5914 };
5915 telemetry::event!(
5916 event_type,
5917 provider = provider.name(),
5918 prediction_id = id,
5919 suggestion_accepted = accepted,
5920 file_extension = extension,
5921 );
5922 }
5923
5924 pub fn has_active_inline_completion(&self) -> bool {
5925 self.active_inline_completion.is_some()
5926 }
5927
5928 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5929 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5930 return false;
5931 };
5932
5933 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5934 self.clear_highlights::<InlineCompletionHighlight>(cx);
5935 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5936 true
5937 }
5938
5939 /// Returns true when we're displaying the edit prediction popover below the cursor
5940 /// like we are not previewing and the LSP autocomplete menu is visible
5941 /// or we are in `when_holding_modifier` mode.
5942 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5943 if self.edit_prediction_preview_is_active()
5944 || !self.show_edit_predictions_in_menu()
5945 || !self.edit_predictions_enabled()
5946 {
5947 return false;
5948 }
5949
5950 if self.has_visible_completions_menu() {
5951 return true;
5952 }
5953
5954 has_completion && self.edit_prediction_requires_modifier()
5955 }
5956
5957 fn handle_modifiers_changed(
5958 &mut self,
5959 modifiers: Modifiers,
5960 position_map: &PositionMap,
5961 window: &mut Window,
5962 cx: &mut Context<Self>,
5963 ) {
5964 if self.show_edit_predictions_in_menu() {
5965 self.update_edit_prediction_preview(&modifiers, window, cx);
5966 }
5967
5968 self.update_selection_mode(&modifiers, position_map, window, cx);
5969
5970 let mouse_position = window.mouse_position();
5971 if !position_map.text_hitbox.is_hovered(window) {
5972 return;
5973 }
5974
5975 self.update_hovered_link(
5976 position_map.point_for_position(mouse_position),
5977 &position_map.snapshot,
5978 modifiers,
5979 window,
5980 cx,
5981 )
5982 }
5983
5984 fn update_selection_mode(
5985 &mut self,
5986 modifiers: &Modifiers,
5987 position_map: &PositionMap,
5988 window: &mut Window,
5989 cx: &mut Context<Self>,
5990 ) {
5991 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
5992 return;
5993 }
5994
5995 let mouse_position = window.mouse_position();
5996 let point_for_position = position_map.point_for_position(mouse_position);
5997 let position = point_for_position.previous_valid;
5998
5999 self.select(
6000 SelectPhase::BeginColumnar {
6001 position,
6002 reset: false,
6003 goal_column: point_for_position.exact_unclipped.column(),
6004 },
6005 window,
6006 cx,
6007 );
6008 }
6009
6010 fn update_edit_prediction_preview(
6011 &mut self,
6012 modifiers: &Modifiers,
6013 window: &mut Window,
6014 cx: &mut Context<Self>,
6015 ) {
6016 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6017 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6018 return;
6019 };
6020
6021 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6022 if matches!(
6023 self.edit_prediction_preview,
6024 EditPredictionPreview::Inactive { .. }
6025 ) {
6026 self.edit_prediction_preview = EditPredictionPreview::Active {
6027 previous_scroll_position: None,
6028 since: Instant::now(),
6029 };
6030
6031 self.update_visible_inline_completion(window, cx);
6032 cx.notify();
6033 }
6034 } else if let EditPredictionPreview::Active {
6035 previous_scroll_position,
6036 since,
6037 } = self.edit_prediction_preview
6038 {
6039 if let (Some(previous_scroll_position), Some(position_map)) =
6040 (previous_scroll_position, self.last_position_map.as_ref())
6041 {
6042 self.set_scroll_position(
6043 previous_scroll_position
6044 .scroll_position(&position_map.snapshot.display_snapshot),
6045 window,
6046 cx,
6047 );
6048 }
6049
6050 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6051 released_too_fast: since.elapsed() < Duration::from_millis(200),
6052 };
6053 self.clear_row_highlights::<EditPredictionPreview>();
6054 self.update_visible_inline_completion(window, cx);
6055 cx.notify();
6056 }
6057 }
6058
6059 fn update_visible_inline_completion(
6060 &mut self,
6061 _window: &mut Window,
6062 cx: &mut Context<Self>,
6063 ) -> Option<()> {
6064 let selection = self.selections.newest_anchor();
6065 let cursor = selection.head();
6066 let multibuffer = self.buffer.read(cx).snapshot(cx);
6067 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6068 let excerpt_id = cursor.excerpt_id;
6069
6070 let show_in_menu = self.show_edit_predictions_in_menu();
6071 let completions_menu_has_precedence = !show_in_menu
6072 && (self.context_menu.borrow().is_some()
6073 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6074
6075 if completions_menu_has_precedence
6076 || !offset_selection.is_empty()
6077 || self
6078 .active_inline_completion
6079 .as_ref()
6080 .map_or(false, |completion| {
6081 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6082 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6083 !invalidation_range.contains(&offset_selection.head())
6084 })
6085 {
6086 self.discard_inline_completion(false, cx);
6087 return None;
6088 }
6089
6090 self.take_active_inline_completion(cx);
6091 let Some(provider) = self.edit_prediction_provider() else {
6092 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6093 return None;
6094 };
6095
6096 let (buffer, cursor_buffer_position) =
6097 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6098
6099 self.edit_prediction_settings =
6100 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6101
6102 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6103
6104 if self.edit_prediction_indent_conflict {
6105 let cursor_point = cursor.to_point(&multibuffer);
6106
6107 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6108
6109 if let Some((_, indent)) = indents.iter().next() {
6110 if indent.len == cursor_point.column {
6111 self.edit_prediction_indent_conflict = false;
6112 }
6113 }
6114 }
6115
6116 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6117 let edits = inline_completion
6118 .edits
6119 .into_iter()
6120 .flat_map(|(range, new_text)| {
6121 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6122 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6123 Some((start..end, new_text))
6124 })
6125 .collect::<Vec<_>>();
6126 if edits.is_empty() {
6127 return None;
6128 }
6129
6130 let first_edit_start = edits.first().unwrap().0.start;
6131 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6132 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6133
6134 let last_edit_end = edits.last().unwrap().0.end;
6135 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6136 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6137
6138 let cursor_row = cursor.to_point(&multibuffer).row;
6139
6140 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6141
6142 let mut inlay_ids = Vec::new();
6143 let invalidation_row_range;
6144 let move_invalidation_row_range = if cursor_row < edit_start_row {
6145 Some(cursor_row..edit_end_row)
6146 } else if cursor_row > edit_end_row {
6147 Some(edit_start_row..cursor_row)
6148 } else {
6149 None
6150 };
6151 let is_move =
6152 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6153 let completion = if is_move {
6154 invalidation_row_range =
6155 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6156 let target = first_edit_start;
6157 InlineCompletion::Move { target, snapshot }
6158 } else {
6159 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6160 && !self.inline_completions_hidden_for_vim_mode;
6161
6162 if show_completions_in_buffer {
6163 if edits
6164 .iter()
6165 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6166 {
6167 let mut inlays = Vec::new();
6168 for (range, new_text) in &edits {
6169 let inlay = Inlay::inline_completion(
6170 post_inc(&mut self.next_inlay_id),
6171 range.start,
6172 new_text.as_str(),
6173 );
6174 inlay_ids.push(inlay.id);
6175 inlays.push(inlay);
6176 }
6177
6178 self.splice_inlays(&[], inlays, cx);
6179 } else {
6180 let background_color = cx.theme().status().deleted_background;
6181 self.highlight_text::<InlineCompletionHighlight>(
6182 edits.iter().map(|(range, _)| range.clone()).collect(),
6183 HighlightStyle {
6184 background_color: Some(background_color),
6185 ..Default::default()
6186 },
6187 cx,
6188 );
6189 }
6190 }
6191
6192 invalidation_row_range = edit_start_row..edit_end_row;
6193
6194 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6195 if provider.show_tab_accept_marker() {
6196 EditDisplayMode::TabAccept
6197 } else {
6198 EditDisplayMode::Inline
6199 }
6200 } else {
6201 EditDisplayMode::DiffPopover
6202 };
6203
6204 InlineCompletion::Edit {
6205 edits,
6206 edit_preview: inline_completion.edit_preview,
6207 display_mode,
6208 snapshot,
6209 }
6210 };
6211
6212 let invalidation_range = multibuffer
6213 .anchor_before(Point::new(invalidation_row_range.start, 0))
6214 ..multibuffer.anchor_after(Point::new(
6215 invalidation_row_range.end,
6216 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6217 ));
6218
6219 self.stale_inline_completion_in_menu = None;
6220 self.active_inline_completion = Some(InlineCompletionState {
6221 inlay_ids,
6222 completion,
6223 completion_id: inline_completion.id,
6224 invalidation_range,
6225 });
6226
6227 cx.notify();
6228
6229 Some(())
6230 }
6231
6232 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6233 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6234 }
6235
6236 fn render_code_actions_indicator(
6237 &self,
6238 _style: &EditorStyle,
6239 row: DisplayRow,
6240 is_active: bool,
6241 breakpoint: Option<&(Anchor, Breakpoint)>,
6242 cx: &mut Context<Self>,
6243 ) -> Option<IconButton> {
6244 let color = Color::Muted;
6245 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6246 let show_tooltip = !self.context_menu_visible();
6247
6248 if self.available_code_actions.is_some() {
6249 Some(
6250 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6251 .shape(ui::IconButtonShape::Square)
6252 .icon_size(IconSize::XSmall)
6253 .icon_color(color)
6254 .toggle_state(is_active)
6255 .when(show_tooltip, |this| {
6256 this.tooltip({
6257 let focus_handle = self.focus_handle.clone();
6258 move |window, cx| {
6259 Tooltip::for_action_in(
6260 "Toggle Code Actions",
6261 &ToggleCodeActions {
6262 deployed_from_indicator: None,
6263 },
6264 &focus_handle,
6265 window,
6266 cx,
6267 )
6268 }
6269 })
6270 })
6271 .on_click(cx.listener(move |editor, _e, window, cx| {
6272 window.focus(&editor.focus_handle(cx));
6273 editor.toggle_code_actions(
6274 &ToggleCodeActions {
6275 deployed_from_indicator: Some(row),
6276 },
6277 window,
6278 cx,
6279 );
6280 }))
6281 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6282 editor.set_breakpoint_context_menu(
6283 row,
6284 position,
6285 event.down.position,
6286 window,
6287 cx,
6288 );
6289 })),
6290 )
6291 } else {
6292 None
6293 }
6294 }
6295
6296 fn clear_tasks(&mut self) {
6297 self.tasks.clear()
6298 }
6299
6300 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6301 if self.tasks.insert(key, value).is_some() {
6302 // This case should hopefully be rare, but just in case...
6303 log::error!(
6304 "multiple different run targets found on a single line, only the last target will be rendered"
6305 )
6306 }
6307 }
6308
6309 /// Get all display points of breakpoints that will be rendered within editor
6310 ///
6311 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6312 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6313 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6314 fn active_breakpoints(
6315 &self,
6316 range: Range<DisplayRow>,
6317 window: &mut Window,
6318 cx: &mut Context<Self>,
6319 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6320 let mut breakpoint_display_points = HashMap::default();
6321
6322 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6323 return breakpoint_display_points;
6324 };
6325
6326 let snapshot = self.snapshot(window, cx);
6327
6328 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6329 let Some(project) = self.project.as_ref() else {
6330 return breakpoint_display_points;
6331 };
6332
6333 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6334 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6335
6336 for (buffer_snapshot, range, excerpt_id) in
6337 multi_buffer_snapshot.range_to_buffer_ranges(range)
6338 {
6339 let Some(buffer) = project.read_with(cx, |this, cx| {
6340 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6341 }) else {
6342 continue;
6343 };
6344 let breakpoints = breakpoint_store.read(cx).breakpoints(
6345 &buffer,
6346 Some(
6347 buffer_snapshot.anchor_before(range.start)
6348 ..buffer_snapshot.anchor_after(range.end),
6349 ),
6350 buffer_snapshot,
6351 cx,
6352 );
6353 for (anchor, breakpoint) in breakpoints {
6354 let multi_buffer_anchor =
6355 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6356 let position = multi_buffer_anchor
6357 .to_point(&multi_buffer_snapshot)
6358 .to_display_point(&snapshot);
6359
6360 breakpoint_display_points
6361 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6362 }
6363 }
6364
6365 breakpoint_display_points
6366 }
6367
6368 fn breakpoint_context_menu(
6369 &self,
6370 anchor: Anchor,
6371 window: &mut Window,
6372 cx: &mut Context<Self>,
6373 ) -> Entity<ui::ContextMenu> {
6374 let weak_editor = cx.weak_entity();
6375 let focus_handle = self.focus_handle(cx);
6376
6377 let row = self
6378 .buffer
6379 .read(cx)
6380 .snapshot(cx)
6381 .summary_for_anchor::<Point>(&anchor)
6382 .row;
6383
6384 let breakpoint = self
6385 .breakpoint_at_row(row, window, cx)
6386 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6387
6388 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6389 "Edit Log Breakpoint"
6390 } else {
6391 "Set Log Breakpoint"
6392 };
6393
6394 let condition_breakpoint_msg = if breakpoint
6395 .as_ref()
6396 .is_some_and(|bp| bp.1.condition.is_some())
6397 {
6398 "Edit Condition Breakpoint"
6399 } else {
6400 "Set Condition Breakpoint"
6401 };
6402
6403 let hit_condition_breakpoint_msg = if breakpoint
6404 .as_ref()
6405 .is_some_and(|bp| bp.1.hit_condition.is_some())
6406 {
6407 "Edit Hit Condition Breakpoint"
6408 } else {
6409 "Set Hit Condition Breakpoint"
6410 };
6411
6412 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6413 "Unset Breakpoint"
6414 } else {
6415 "Set Breakpoint"
6416 };
6417
6418 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6419 BreakpointState::Enabled => Some("Disable"),
6420 BreakpointState::Disabled => Some("Enable"),
6421 });
6422
6423 let (anchor, breakpoint) =
6424 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6425
6426 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6427 menu.on_blur_subscription(Subscription::new(|| {}))
6428 .context(focus_handle)
6429 .when_some(toggle_state_msg, |this, msg| {
6430 this.entry(msg, None, {
6431 let weak_editor = weak_editor.clone();
6432 let breakpoint = breakpoint.clone();
6433 move |_window, cx| {
6434 weak_editor
6435 .update(cx, |this, cx| {
6436 this.edit_breakpoint_at_anchor(
6437 anchor,
6438 breakpoint.as_ref().clone(),
6439 BreakpointEditAction::InvertState,
6440 cx,
6441 );
6442 })
6443 .log_err();
6444 }
6445 })
6446 })
6447 .entry(set_breakpoint_msg, None, {
6448 let weak_editor = weak_editor.clone();
6449 let breakpoint = breakpoint.clone();
6450 move |_window, cx| {
6451 weak_editor
6452 .update(cx, |this, cx| {
6453 this.edit_breakpoint_at_anchor(
6454 anchor,
6455 breakpoint.as_ref().clone(),
6456 BreakpointEditAction::Toggle,
6457 cx,
6458 );
6459 })
6460 .log_err();
6461 }
6462 })
6463 .entry(log_breakpoint_msg, None, {
6464 let breakpoint = breakpoint.clone();
6465 let weak_editor = weak_editor.clone();
6466 move |window, cx| {
6467 weak_editor
6468 .update(cx, |this, cx| {
6469 this.add_edit_breakpoint_block(
6470 anchor,
6471 breakpoint.as_ref(),
6472 BreakpointPromptEditAction::Log,
6473 window,
6474 cx,
6475 );
6476 })
6477 .log_err();
6478 }
6479 })
6480 .entry(condition_breakpoint_msg, None, {
6481 let breakpoint = breakpoint.clone();
6482 let weak_editor = weak_editor.clone();
6483 move |window, cx| {
6484 weak_editor
6485 .update(cx, |this, cx| {
6486 this.add_edit_breakpoint_block(
6487 anchor,
6488 breakpoint.as_ref(),
6489 BreakpointPromptEditAction::Condition,
6490 window,
6491 cx,
6492 );
6493 })
6494 .log_err();
6495 }
6496 })
6497 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6498 weak_editor
6499 .update(cx, |this, cx| {
6500 this.add_edit_breakpoint_block(
6501 anchor,
6502 breakpoint.as_ref(),
6503 BreakpointPromptEditAction::HitCondition,
6504 window,
6505 cx,
6506 );
6507 })
6508 .log_err();
6509 })
6510 })
6511 }
6512
6513 fn render_breakpoint(
6514 &self,
6515 position: Anchor,
6516 row: DisplayRow,
6517 breakpoint: &Breakpoint,
6518 cx: &mut Context<Self>,
6519 ) -> IconButton {
6520 let (color, icon) = {
6521 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6522 (false, false) => ui::IconName::DebugBreakpoint,
6523 (true, false) => ui::IconName::DebugLogBreakpoint,
6524 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6525 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6526 };
6527
6528 let color = if self
6529 .gutter_breakpoint_indicator
6530 .0
6531 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6532 {
6533 Color::Hint
6534 } else {
6535 Color::Debugger
6536 };
6537
6538 (color, icon)
6539 };
6540
6541 let breakpoint = Arc::from(breakpoint.clone());
6542
6543 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6544 .icon_size(IconSize::XSmall)
6545 .size(ui::ButtonSize::None)
6546 .icon_color(color)
6547 .style(ButtonStyle::Transparent)
6548 .on_click(cx.listener({
6549 let breakpoint = breakpoint.clone();
6550
6551 move |editor, event: &ClickEvent, window, cx| {
6552 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6553 BreakpointEditAction::InvertState
6554 } else {
6555 BreakpointEditAction::Toggle
6556 };
6557
6558 window.focus(&editor.focus_handle(cx));
6559 editor.edit_breakpoint_at_anchor(
6560 position,
6561 breakpoint.as_ref().clone(),
6562 edit_action,
6563 cx,
6564 );
6565 }
6566 }))
6567 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6568 editor.set_breakpoint_context_menu(
6569 row,
6570 Some(position),
6571 event.down.position,
6572 window,
6573 cx,
6574 );
6575 }))
6576 }
6577
6578 fn build_tasks_context(
6579 project: &Entity<Project>,
6580 buffer: &Entity<Buffer>,
6581 buffer_row: u32,
6582 tasks: &Arc<RunnableTasks>,
6583 cx: &mut Context<Self>,
6584 ) -> Task<Option<task::TaskContext>> {
6585 let position = Point::new(buffer_row, tasks.column);
6586 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6587 let location = Location {
6588 buffer: buffer.clone(),
6589 range: range_start..range_start,
6590 };
6591 // Fill in the environmental variables from the tree-sitter captures
6592 let mut captured_task_variables = TaskVariables::default();
6593 for (capture_name, value) in tasks.extra_variables.clone() {
6594 captured_task_variables.insert(
6595 task::VariableName::Custom(capture_name.into()),
6596 value.clone(),
6597 );
6598 }
6599 project.update(cx, |project, cx| {
6600 project.task_store().update(cx, |task_store, cx| {
6601 task_store.task_context_for_location(captured_task_variables, location, cx)
6602 })
6603 })
6604 }
6605
6606 pub fn spawn_nearest_task(
6607 &mut self,
6608 action: &SpawnNearestTask,
6609 window: &mut Window,
6610 cx: &mut Context<Self>,
6611 ) {
6612 let Some((workspace, _)) = self.workspace.clone() else {
6613 return;
6614 };
6615 let Some(project) = self.project.clone() else {
6616 return;
6617 };
6618
6619 // Try to find a closest, enclosing node using tree-sitter that has a
6620 // task
6621 let Some((buffer, buffer_row, tasks)) = self
6622 .find_enclosing_node_task(cx)
6623 // Or find the task that's closest in row-distance.
6624 .or_else(|| self.find_closest_task(cx))
6625 else {
6626 return;
6627 };
6628
6629 let reveal_strategy = action.reveal;
6630 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6631 cx.spawn_in(window, async move |_, cx| {
6632 let context = task_context.await?;
6633 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6634
6635 let resolved = resolved_task.resolved.as_mut()?;
6636 resolved.reveal = reveal_strategy;
6637
6638 workspace
6639 .update(cx, |workspace, cx| {
6640 workspace::tasks::schedule_resolved_task(
6641 workspace,
6642 task_source_kind,
6643 resolved_task,
6644 false,
6645 cx,
6646 );
6647 })
6648 .ok()
6649 })
6650 .detach();
6651 }
6652
6653 fn find_closest_task(
6654 &mut self,
6655 cx: &mut Context<Self>,
6656 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6657 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6658
6659 let ((buffer_id, row), tasks) = self
6660 .tasks
6661 .iter()
6662 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6663
6664 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6665 let tasks = Arc::new(tasks.to_owned());
6666 Some((buffer, *row, tasks))
6667 }
6668
6669 fn find_enclosing_node_task(
6670 &mut self,
6671 cx: &mut Context<Self>,
6672 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6673 let snapshot = self.buffer.read(cx).snapshot(cx);
6674 let offset = self.selections.newest::<usize>(cx).head();
6675 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6676 let buffer_id = excerpt.buffer().remote_id();
6677
6678 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6679 let mut cursor = layer.node().walk();
6680
6681 while cursor.goto_first_child_for_byte(offset).is_some() {
6682 if cursor.node().end_byte() == offset {
6683 cursor.goto_next_sibling();
6684 }
6685 }
6686
6687 // Ascend to the smallest ancestor that contains the range and has a task.
6688 loop {
6689 let node = cursor.node();
6690 let node_range = node.byte_range();
6691 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6692
6693 // Check if this node contains our offset
6694 if node_range.start <= offset && node_range.end >= offset {
6695 // If it contains offset, check for task
6696 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6697 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6698 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6699 }
6700 }
6701
6702 if !cursor.goto_parent() {
6703 break;
6704 }
6705 }
6706 None
6707 }
6708
6709 fn render_run_indicator(
6710 &self,
6711 _style: &EditorStyle,
6712 is_active: bool,
6713 row: DisplayRow,
6714 breakpoint: Option<(Anchor, Breakpoint)>,
6715 cx: &mut Context<Self>,
6716 ) -> IconButton {
6717 let color = Color::Muted;
6718 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6719
6720 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6721 .shape(ui::IconButtonShape::Square)
6722 .icon_size(IconSize::XSmall)
6723 .icon_color(color)
6724 .toggle_state(is_active)
6725 .on_click(cx.listener(move |editor, _e, window, cx| {
6726 window.focus(&editor.focus_handle(cx));
6727 editor.toggle_code_actions(
6728 &ToggleCodeActions {
6729 deployed_from_indicator: Some(row),
6730 },
6731 window,
6732 cx,
6733 );
6734 }))
6735 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6736 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6737 }))
6738 }
6739
6740 pub fn context_menu_visible(&self) -> bool {
6741 !self.edit_prediction_preview_is_active()
6742 && self
6743 .context_menu
6744 .borrow()
6745 .as_ref()
6746 .map_or(false, |menu| menu.visible())
6747 }
6748
6749 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6750 self.context_menu
6751 .borrow()
6752 .as_ref()
6753 .map(|menu| menu.origin())
6754 }
6755
6756 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6757 self.context_menu_options = Some(options);
6758 }
6759
6760 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6761 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6762
6763 fn render_edit_prediction_popover(
6764 &mut self,
6765 text_bounds: &Bounds<Pixels>,
6766 content_origin: gpui::Point<Pixels>,
6767 editor_snapshot: &EditorSnapshot,
6768 visible_row_range: Range<DisplayRow>,
6769 scroll_top: f32,
6770 scroll_bottom: f32,
6771 line_layouts: &[LineWithInvisibles],
6772 line_height: Pixels,
6773 scroll_pixel_position: gpui::Point<Pixels>,
6774 newest_selection_head: Option<DisplayPoint>,
6775 editor_width: Pixels,
6776 style: &EditorStyle,
6777 window: &mut Window,
6778 cx: &mut App,
6779 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6780 let active_inline_completion = self.active_inline_completion.as_ref()?;
6781
6782 if self.edit_prediction_visible_in_cursor_popover(true) {
6783 return None;
6784 }
6785
6786 match &active_inline_completion.completion {
6787 InlineCompletion::Move { target, .. } => {
6788 let target_display_point = target.to_display_point(editor_snapshot);
6789
6790 if self.edit_prediction_requires_modifier() {
6791 if !self.edit_prediction_preview_is_active() {
6792 return None;
6793 }
6794
6795 self.render_edit_prediction_modifier_jump_popover(
6796 text_bounds,
6797 content_origin,
6798 visible_row_range,
6799 line_layouts,
6800 line_height,
6801 scroll_pixel_position,
6802 newest_selection_head,
6803 target_display_point,
6804 window,
6805 cx,
6806 )
6807 } else {
6808 self.render_edit_prediction_eager_jump_popover(
6809 text_bounds,
6810 content_origin,
6811 editor_snapshot,
6812 visible_row_range,
6813 scroll_top,
6814 scroll_bottom,
6815 line_height,
6816 scroll_pixel_position,
6817 target_display_point,
6818 editor_width,
6819 window,
6820 cx,
6821 )
6822 }
6823 }
6824 InlineCompletion::Edit {
6825 display_mode: EditDisplayMode::Inline,
6826 ..
6827 } => None,
6828 InlineCompletion::Edit {
6829 display_mode: EditDisplayMode::TabAccept,
6830 edits,
6831 ..
6832 } => {
6833 let range = &edits.first()?.0;
6834 let target_display_point = range.end.to_display_point(editor_snapshot);
6835
6836 self.render_edit_prediction_end_of_line_popover(
6837 "Accept",
6838 editor_snapshot,
6839 visible_row_range,
6840 target_display_point,
6841 line_height,
6842 scroll_pixel_position,
6843 content_origin,
6844 editor_width,
6845 window,
6846 cx,
6847 )
6848 }
6849 InlineCompletion::Edit {
6850 edits,
6851 edit_preview,
6852 display_mode: EditDisplayMode::DiffPopover,
6853 snapshot,
6854 } => self.render_edit_prediction_diff_popover(
6855 text_bounds,
6856 content_origin,
6857 editor_snapshot,
6858 visible_row_range,
6859 line_layouts,
6860 line_height,
6861 scroll_pixel_position,
6862 newest_selection_head,
6863 editor_width,
6864 style,
6865 edits,
6866 edit_preview,
6867 snapshot,
6868 window,
6869 cx,
6870 ),
6871 }
6872 }
6873
6874 fn render_edit_prediction_modifier_jump_popover(
6875 &mut self,
6876 text_bounds: &Bounds<Pixels>,
6877 content_origin: gpui::Point<Pixels>,
6878 visible_row_range: Range<DisplayRow>,
6879 line_layouts: &[LineWithInvisibles],
6880 line_height: Pixels,
6881 scroll_pixel_position: gpui::Point<Pixels>,
6882 newest_selection_head: Option<DisplayPoint>,
6883 target_display_point: DisplayPoint,
6884 window: &mut Window,
6885 cx: &mut App,
6886 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6887 let scrolled_content_origin =
6888 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6889
6890 const SCROLL_PADDING_Y: Pixels = px(12.);
6891
6892 if target_display_point.row() < visible_row_range.start {
6893 return self.render_edit_prediction_scroll_popover(
6894 |_| SCROLL_PADDING_Y,
6895 IconName::ArrowUp,
6896 visible_row_range,
6897 line_layouts,
6898 newest_selection_head,
6899 scrolled_content_origin,
6900 window,
6901 cx,
6902 );
6903 } else if target_display_point.row() >= visible_row_range.end {
6904 return self.render_edit_prediction_scroll_popover(
6905 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6906 IconName::ArrowDown,
6907 visible_row_range,
6908 line_layouts,
6909 newest_selection_head,
6910 scrolled_content_origin,
6911 window,
6912 cx,
6913 );
6914 }
6915
6916 const POLE_WIDTH: Pixels = px(2.);
6917
6918 let line_layout =
6919 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6920 let target_column = target_display_point.column() as usize;
6921
6922 let target_x = line_layout.x_for_index(target_column);
6923 let target_y =
6924 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6925
6926 let flag_on_right = target_x < text_bounds.size.width / 2.;
6927
6928 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6929 border_color.l += 0.001;
6930
6931 let mut element = v_flex()
6932 .items_end()
6933 .when(flag_on_right, |el| el.items_start())
6934 .child(if flag_on_right {
6935 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6936 .rounded_bl(px(0.))
6937 .rounded_tl(px(0.))
6938 .border_l_2()
6939 .border_color(border_color)
6940 } else {
6941 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6942 .rounded_br(px(0.))
6943 .rounded_tr(px(0.))
6944 .border_r_2()
6945 .border_color(border_color)
6946 })
6947 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6948 .into_any();
6949
6950 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6951
6952 let mut origin = scrolled_content_origin + point(target_x, target_y)
6953 - point(
6954 if flag_on_right {
6955 POLE_WIDTH
6956 } else {
6957 size.width - POLE_WIDTH
6958 },
6959 size.height - line_height,
6960 );
6961
6962 origin.x = origin.x.max(content_origin.x);
6963
6964 element.prepaint_at(origin, window, cx);
6965
6966 Some((element, origin))
6967 }
6968
6969 fn render_edit_prediction_scroll_popover(
6970 &mut self,
6971 to_y: impl Fn(Size<Pixels>) -> Pixels,
6972 scroll_icon: IconName,
6973 visible_row_range: Range<DisplayRow>,
6974 line_layouts: &[LineWithInvisibles],
6975 newest_selection_head: Option<DisplayPoint>,
6976 scrolled_content_origin: gpui::Point<Pixels>,
6977 window: &mut Window,
6978 cx: &mut App,
6979 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6980 let mut element = self
6981 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
6982 .into_any();
6983
6984 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6985
6986 let cursor = newest_selection_head?;
6987 let cursor_row_layout =
6988 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
6989 let cursor_column = cursor.column() as usize;
6990
6991 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
6992
6993 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
6994
6995 element.prepaint_at(origin, window, cx);
6996 Some((element, origin))
6997 }
6998
6999 fn render_edit_prediction_eager_jump_popover(
7000 &mut self,
7001 text_bounds: &Bounds<Pixels>,
7002 content_origin: gpui::Point<Pixels>,
7003 editor_snapshot: &EditorSnapshot,
7004 visible_row_range: Range<DisplayRow>,
7005 scroll_top: f32,
7006 scroll_bottom: f32,
7007 line_height: Pixels,
7008 scroll_pixel_position: gpui::Point<Pixels>,
7009 target_display_point: DisplayPoint,
7010 editor_width: Pixels,
7011 window: &mut Window,
7012 cx: &mut App,
7013 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7014 if target_display_point.row().as_f32() < scroll_top {
7015 let mut element = self
7016 .render_edit_prediction_line_popover(
7017 "Jump to Edit",
7018 Some(IconName::ArrowUp),
7019 window,
7020 cx,
7021 )?
7022 .into_any();
7023
7024 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7025 let offset = point(
7026 (text_bounds.size.width - size.width) / 2.,
7027 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7028 );
7029
7030 let origin = text_bounds.origin + offset;
7031 element.prepaint_at(origin, window, cx);
7032 Some((element, origin))
7033 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7034 let mut element = self
7035 .render_edit_prediction_line_popover(
7036 "Jump to Edit",
7037 Some(IconName::ArrowDown),
7038 window,
7039 cx,
7040 )?
7041 .into_any();
7042
7043 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7044 let offset = point(
7045 (text_bounds.size.width - size.width) / 2.,
7046 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7047 );
7048
7049 let origin = text_bounds.origin + offset;
7050 element.prepaint_at(origin, window, cx);
7051 Some((element, origin))
7052 } else {
7053 self.render_edit_prediction_end_of_line_popover(
7054 "Jump to Edit",
7055 editor_snapshot,
7056 visible_row_range,
7057 target_display_point,
7058 line_height,
7059 scroll_pixel_position,
7060 content_origin,
7061 editor_width,
7062 window,
7063 cx,
7064 )
7065 }
7066 }
7067
7068 fn render_edit_prediction_end_of_line_popover(
7069 self: &mut Editor,
7070 label: &'static str,
7071 editor_snapshot: &EditorSnapshot,
7072 visible_row_range: Range<DisplayRow>,
7073 target_display_point: DisplayPoint,
7074 line_height: Pixels,
7075 scroll_pixel_position: gpui::Point<Pixels>,
7076 content_origin: gpui::Point<Pixels>,
7077 editor_width: Pixels,
7078 window: &mut Window,
7079 cx: &mut App,
7080 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7081 let target_line_end = DisplayPoint::new(
7082 target_display_point.row(),
7083 editor_snapshot.line_len(target_display_point.row()),
7084 );
7085
7086 let mut element = self
7087 .render_edit_prediction_line_popover(label, None, window, cx)?
7088 .into_any();
7089
7090 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7091
7092 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7093
7094 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7095 let mut origin = start_point
7096 + line_origin
7097 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7098 origin.x = origin.x.max(content_origin.x);
7099
7100 let max_x = content_origin.x + editor_width - size.width;
7101
7102 if origin.x > max_x {
7103 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7104
7105 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7106 origin.y += offset;
7107 IconName::ArrowUp
7108 } else {
7109 origin.y -= offset;
7110 IconName::ArrowDown
7111 };
7112
7113 element = self
7114 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7115 .into_any();
7116
7117 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7118
7119 origin.x = content_origin.x + editor_width - size.width - px(2.);
7120 }
7121
7122 element.prepaint_at(origin, window, cx);
7123 Some((element, origin))
7124 }
7125
7126 fn render_edit_prediction_diff_popover(
7127 self: &Editor,
7128 text_bounds: &Bounds<Pixels>,
7129 content_origin: gpui::Point<Pixels>,
7130 editor_snapshot: &EditorSnapshot,
7131 visible_row_range: Range<DisplayRow>,
7132 line_layouts: &[LineWithInvisibles],
7133 line_height: Pixels,
7134 scroll_pixel_position: gpui::Point<Pixels>,
7135 newest_selection_head: Option<DisplayPoint>,
7136 editor_width: Pixels,
7137 style: &EditorStyle,
7138 edits: &Vec<(Range<Anchor>, String)>,
7139 edit_preview: &Option<language::EditPreview>,
7140 snapshot: &language::BufferSnapshot,
7141 window: &mut Window,
7142 cx: &mut App,
7143 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7144 let edit_start = edits
7145 .first()
7146 .unwrap()
7147 .0
7148 .start
7149 .to_display_point(editor_snapshot);
7150 let edit_end = edits
7151 .last()
7152 .unwrap()
7153 .0
7154 .end
7155 .to_display_point(editor_snapshot);
7156
7157 let is_visible = visible_row_range.contains(&edit_start.row())
7158 || visible_row_range.contains(&edit_end.row());
7159 if !is_visible {
7160 return None;
7161 }
7162
7163 let highlighted_edits =
7164 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7165
7166 let styled_text = highlighted_edits.to_styled_text(&style.text);
7167 let line_count = highlighted_edits.text.lines().count();
7168
7169 const BORDER_WIDTH: Pixels = px(1.);
7170
7171 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7172 let has_keybind = keybind.is_some();
7173
7174 let mut element = h_flex()
7175 .items_start()
7176 .child(
7177 h_flex()
7178 .bg(cx.theme().colors().editor_background)
7179 .border(BORDER_WIDTH)
7180 .shadow_sm()
7181 .border_color(cx.theme().colors().border)
7182 .rounded_l_lg()
7183 .when(line_count > 1, |el| el.rounded_br_lg())
7184 .pr_1()
7185 .child(styled_text),
7186 )
7187 .child(
7188 h_flex()
7189 .h(line_height + BORDER_WIDTH * 2.)
7190 .px_1p5()
7191 .gap_1()
7192 // Workaround: For some reason, there's a gap if we don't do this
7193 .ml(-BORDER_WIDTH)
7194 .shadow(smallvec![gpui::BoxShadow {
7195 color: gpui::black().opacity(0.05),
7196 offset: point(px(1.), px(1.)),
7197 blur_radius: px(2.),
7198 spread_radius: px(0.),
7199 }])
7200 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7201 .border(BORDER_WIDTH)
7202 .border_color(cx.theme().colors().border)
7203 .rounded_r_lg()
7204 .id("edit_prediction_diff_popover_keybind")
7205 .when(!has_keybind, |el| {
7206 let status_colors = cx.theme().status();
7207
7208 el.bg(status_colors.error_background)
7209 .border_color(status_colors.error.opacity(0.6))
7210 .child(Icon::new(IconName::Info).color(Color::Error))
7211 .cursor_default()
7212 .hoverable_tooltip(move |_window, cx| {
7213 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7214 })
7215 })
7216 .children(keybind),
7217 )
7218 .into_any();
7219
7220 let longest_row =
7221 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7222 let longest_line_width = if visible_row_range.contains(&longest_row) {
7223 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7224 } else {
7225 layout_line(
7226 longest_row,
7227 editor_snapshot,
7228 style,
7229 editor_width,
7230 |_| false,
7231 window,
7232 cx,
7233 )
7234 .width
7235 };
7236
7237 let viewport_bounds =
7238 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7239 right: -EditorElement::SCROLLBAR_WIDTH,
7240 ..Default::default()
7241 });
7242
7243 let x_after_longest =
7244 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7245 - scroll_pixel_position.x;
7246
7247 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7248
7249 // Fully visible if it can be displayed within the window (allow overlapping other
7250 // panes). However, this is only allowed if the popover starts within text_bounds.
7251 let can_position_to_the_right = x_after_longest < text_bounds.right()
7252 && x_after_longest + element_bounds.width < viewport_bounds.right();
7253
7254 let mut origin = if can_position_to_the_right {
7255 point(
7256 x_after_longest,
7257 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7258 - scroll_pixel_position.y,
7259 )
7260 } else {
7261 let cursor_row = newest_selection_head.map(|head| head.row());
7262 let above_edit = edit_start
7263 .row()
7264 .0
7265 .checked_sub(line_count as u32)
7266 .map(DisplayRow);
7267 let below_edit = Some(edit_end.row() + 1);
7268 let above_cursor =
7269 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7270 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7271
7272 // Place the edit popover adjacent to the edit if there is a location
7273 // available that is onscreen and does not obscure the cursor. Otherwise,
7274 // place it adjacent to the cursor.
7275 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7276 .into_iter()
7277 .flatten()
7278 .find(|&start_row| {
7279 let end_row = start_row + line_count as u32;
7280 visible_row_range.contains(&start_row)
7281 && visible_row_range.contains(&end_row)
7282 && cursor_row.map_or(true, |cursor_row| {
7283 !((start_row..end_row).contains(&cursor_row))
7284 })
7285 })?;
7286
7287 content_origin
7288 + point(
7289 -scroll_pixel_position.x,
7290 row_target.as_f32() * line_height - scroll_pixel_position.y,
7291 )
7292 };
7293
7294 origin.x -= BORDER_WIDTH;
7295
7296 window.defer_draw(element, origin, 1);
7297
7298 // Do not return an element, since it will already be drawn due to defer_draw.
7299 None
7300 }
7301
7302 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7303 px(30.)
7304 }
7305
7306 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7307 if self.read_only(cx) {
7308 cx.theme().players().read_only()
7309 } else {
7310 self.style.as_ref().unwrap().local_player
7311 }
7312 }
7313
7314 fn render_edit_prediction_accept_keybind(
7315 &self,
7316 window: &mut Window,
7317 cx: &App,
7318 ) -> Option<AnyElement> {
7319 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7320 let accept_keystroke = accept_binding.keystroke()?;
7321
7322 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7323
7324 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7325 Color::Accent
7326 } else {
7327 Color::Muted
7328 };
7329
7330 h_flex()
7331 .px_0p5()
7332 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7333 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7334 .text_size(TextSize::XSmall.rems(cx))
7335 .child(h_flex().children(ui::render_modifiers(
7336 &accept_keystroke.modifiers,
7337 PlatformStyle::platform(),
7338 Some(modifiers_color),
7339 Some(IconSize::XSmall.rems().into()),
7340 true,
7341 )))
7342 .when(is_platform_style_mac, |parent| {
7343 parent.child(accept_keystroke.key.clone())
7344 })
7345 .when(!is_platform_style_mac, |parent| {
7346 parent.child(
7347 Key::new(
7348 util::capitalize(&accept_keystroke.key),
7349 Some(Color::Default),
7350 )
7351 .size(Some(IconSize::XSmall.rems().into())),
7352 )
7353 })
7354 .into_any()
7355 .into()
7356 }
7357
7358 fn render_edit_prediction_line_popover(
7359 &self,
7360 label: impl Into<SharedString>,
7361 icon: Option<IconName>,
7362 window: &mut Window,
7363 cx: &App,
7364 ) -> Option<Stateful<Div>> {
7365 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7366
7367 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7368 let has_keybind = keybind.is_some();
7369
7370 let result = h_flex()
7371 .id("ep-line-popover")
7372 .py_0p5()
7373 .pl_1()
7374 .pr(padding_right)
7375 .gap_1()
7376 .rounded_md()
7377 .border_1()
7378 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7379 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7380 .shadow_sm()
7381 .when(!has_keybind, |el| {
7382 let status_colors = cx.theme().status();
7383
7384 el.bg(status_colors.error_background)
7385 .border_color(status_colors.error.opacity(0.6))
7386 .pl_2()
7387 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7388 .cursor_default()
7389 .hoverable_tooltip(move |_window, cx| {
7390 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7391 })
7392 })
7393 .children(keybind)
7394 .child(
7395 Label::new(label)
7396 .size(LabelSize::Small)
7397 .when(!has_keybind, |el| {
7398 el.color(cx.theme().status().error.into()).strikethrough()
7399 }),
7400 )
7401 .when(!has_keybind, |el| {
7402 el.child(
7403 h_flex().ml_1().child(
7404 Icon::new(IconName::Info)
7405 .size(IconSize::Small)
7406 .color(cx.theme().status().error.into()),
7407 ),
7408 )
7409 })
7410 .when_some(icon, |element, icon| {
7411 element.child(
7412 div()
7413 .mt(px(1.5))
7414 .child(Icon::new(icon).size(IconSize::Small)),
7415 )
7416 });
7417
7418 Some(result)
7419 }
7420
7421 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7422 let accent_color = cx.theme().colors().text_accent;
7423 let editor_bg_color = cx.theme().colors().editor_background;
7424 editor_bg_color.blend(accent_color.opacity(0.1))
7425 }
7426
7427 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7428 let accent_color = cx.theme().colors().text_accent;
7429 let editor_bg_color = cx.theme().colors().editor_background;
7430 editor_bg_color.blend(accent_color.opacity(0.6))
7431 }
7432
7433 fn render_edit_prediction_cursor_popover(
7434 &self,
7435 min_width: Pixels,
7436 max_width: Pixels,
7437 cursor_point: Point,
7438 style: &EditorStyle,
7439 accept_keystroke: Option<&gpui::Keystroke>,
7440 _window: &Window,
7441 cx: &mut Context<Editor>,
7442 ) -> Option<AnyElement> {
7443 let provider = self.edit_prediction_provider.as_ref()?;
7444
7445 if provider.provider.needs_terms_acceptance(cx) {
7446 return Some(
7447 h_flex()
7448 .min_w(min_width)
7449 .flex_1()
7450 .px_2()
7451 .py_1()
7452 .gap_3()
7453 .elevation_2(cx)
7454 .hover(|style| style.bg(cx.theme().colors().element_hover))
7455 .id("accept-terms")
7456 .cursor_pointer()
7457 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7458 .on_click(cx.listener(|this, _event, window, cx| {
7459 cx.stop_propagation();
7460 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7461 window.dispatch_action(
7462 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7463 cx,
7464 );
7465 }))
7466 .child(
7467 h_flex()
7468 .flex_1()
7469 .gap_2()
7470 .child(Icon::new(IconName::ZedPredict))
7471 .child(Label::new("Accept Terms of Service"))
7472 .child(div().w_full())
7473 .child(
7474 Icon::new(IconName::ArrowUpRight)
7475 .color(Color::Muted)
7476 .size(IconSize::Small),
7477 )
7478 .into_any_element(),
7479 )
7480 .into_any(),
7481 );
7482 }
7483
7484 let is_refreshing = provider.provider.is_refreshing(cx);
7485
7486 fn pending_completion_container() -> Div {
7487 h_flex()
7488 .h_full()
7489 .flex_1()
7490 .gap_2()
7491 .child(Icon::new(IconName::ZedPredict))
7492 }
7493
7494 let completion = match &self.active_inline_completion {
7495 Some(prediction) => {
7496 if !self.has_visible_completions_menu() {
7497 const RADIUS: Pixels = px(6.);
7498 const BORDER_WIDTH: Pixels = px(1.);
7499
7500 return Some(
7501 h_flex()
7502 .elevation_2(cx)
7503 .border(BORDER_WIDTH)
7504 .border_color(cx.theme().colors().border)
7505 .when(accept_keystroke.is_none(), |el| {
7506 el.border_color(cx.theme().status().error)
7507 })
7508 .rounded(RADIUS)
7509 .rounded_tl(px(0.))
7510 .overflow_hidden()
7511 .child(div().px_1p5().child(match &prediction.completion {
7512 InlineCompletion::Move { target, snapshot } => {
7513 use text::ToPoint as _;
7514 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7515 {
7516 Icon::new(IconName::ZedPredictDown)
7517 } else {
7518 Icon::new(IconName::ZedPredictUp)
7519 }
7520 }
7521 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7522 }))
7523 .child(
7524 h_flex()
7525 .gap_1()
7526 .py_1()
7527 .px_2()
7528 .rounded_r(RADIUS - BORDER_WIDTH)
7529 .border_l_1()
7530 .border_color(cx.theme().colors().border)
7531 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7532 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7533 el.child(
7534 Label::new("Hold")
7535 .size(LabelSize::Small)
7536 .when(accept_keystroke.is_none(), |el| {
7537 el.strikethrough()
7538 })
7539 .line_height_style(LineHeightStyle::UiLabel),
7540 )
7541 })
7542 .id("edit_prediction_cursor_popover_keybind")
7543 .when(accept_keystroke.is_none(), |el| {
7544 let status_colors = cx.theme().status();
7545
7546 el.bg(status_colors.error_background)
7547 .border_color(status_colors.error.opacity(0.6))
7548 .child(Icon::new(IconName::Info).color(Color::Error))
7549 .cursor_default()
7550 .hoverable_tooltip(move |_window, cx| {
7551 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7552 .into()
7553 })
7554 })
7555 .when_some(
7556 accept_keystroke.as_ref(),
7557 |el, accept_keystroke| {
7558 el.child(h_flex().children(ui::render_modifiers(
7559 &accept_keystroke.modifiers,
7560 PlatformStyle::platform(),
7561 Some(Color::Default),
7562 Some(IconSize::XSmall.rems().into()),
7563 false,
7564 )))
7565 },
7566 ),
7567 )
7568 .into_any(),
7569 );
7570 }
7571
7572 self.render_edit_prediction_cursor_popover_preview(
7573 prediction,
7574 cursor_point,
7575 style,
7576 cx,
7577 )?
7578 }
7579
7580 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7581 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7582 stale_completion,
7583 cursor_point,
7584 style,
7585 cx,
7586 )?,
7587
7588 None => {
7589 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7590 }
7591 },
7592
7593 None => pending_completion_container().child(Label::new("No Prediction")),
7594 };
7595
7596 let completion = if is_refreshing {
7597 completion
7598 .with_animation(
7599 "loading-completion",
7600 Animation::new(Duration::from_secs(2))
7601 .repeat()
7602 .with_easing(pulsating_between(0.4, 0.8)),
7603 |label, delta| label.opacity(delta),
7604 )
7605 .into_any_element()
7606 } else {
7607 completion.into_any_element()
7608 };
7609
7610 let has_completion = self.active_inline_completion.is_some();
7611
7612 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7613 Some(
7614 h_flex()
7615 .min_w(min_width)
7616 .max_w(max_width)
7617 .flex_1()
7618 .elevation_2(cx)
7619 .border_color(cx.theme().colors().border)
7620 .child(
7621 div()
7622 .flex_1()
7623 .py_1()
7624 .px_2()
7625 .overflow_hidden()
7626 .child(completion),
7627 )
7628 .when_some(accept_keystroke, |el, accept_keystroke| {
7629 if !accept_keystroke.modifiers.modified() {
7630 return el;
7631 }
7632
7633 el.child(
7634 h_flex()
7635 .h_full()
7636 .border_l_1()
7637 .rounded_r_lg()
7638 .border_color(cx.theme().colors().border)
7639 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7640 .gap_1()
7641 .py_1()
7642 .px_2()
7643 .child(
7644 h_flex()
7645 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7646 .when(is_platform_style_mac, |parent| parent.gap_1())
7647 .child(h_flex().children(ui::render_modifiers(
7648 &accept_keystroke.modifiers,
7649 PlatformStyle::platform(),
7650 Some(if !has_completion {
7651 Color::Muted
7652 } else {
7653 Color::Default
7654 }),
7655 None,
7656 false,
7657 ))),
7658 )
7659 .child(Label::new("Preview").into_any_element())
7660 .opacity(if has_completion { 1.0 } else { 0.4 }),
7661 )
7662 })
7663 .into_any(),
7664 )
7665 }
7666
7667 fn render_edit_prediction_cursor_popover_preview(
7668 &self,
7669 completion: &InlineCompletionState,
7670 cursor_point: Point,
7671 style: &EditorStyle,
7672 cx: &mut Context<Editor>,
7673 ) -> Option<Div> {
7674 use text::ToPoint as _;
7675
7676 fn render_relative_row_jump(
7677 prefix: impl Into<String>,
7678 current_row: u32,
7679 target_row: u32,
7680 ) -> Div {
7681 let (row_diff, arrow) = if target_row < current_row {
7682 (current_row - target_row, IconName::ArrowUp)
7683 } else {
7684 (target_row - current_row, IconName::ArrowDown)
7685 };
7686
7687 h_flex()
7688 .child(
7689 Label::new(format!("{}{}", prefix.into(), row_diff))
7690 .color(Color::Muted)
7691 .size(LabelSize::Small),
7692 )
7693 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7694 }
7695
7696 match &completion.completion {
7697 InlineCompletion::Move {
7698 target, snapshot, ..
7699 } => Some(
7700 h_flex()
7701 .px_2()
7702 .gap_2()
7703 .flex_1()
7704 .child(
7705 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7706 Icon::new(IconName::ZedPredictDown)
7707 } else {
7708 Icon::new(IconName::ZedPredictUp)
7709 },
7710 )
7711 .child(Label::new("Jump to Edit")),
7712 ),
7713
7714 InlineCompletion::Edit {
7715 edits,
7716 edit_preview,
7717 snapshot,
7718 display_mode: _,
7719 } => {
7720 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7721
7722 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7723 &snapshot,
7724 &edits,
7725 edit_preview.as_ref()?,
7726 true,
7727 cx,
7728 )
7729 .first_line_preview();
7730
7731 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7732 .with_default_highlights(&style.text, highlighted_edits.highlights);
7733
7734 let preview = h_flex()
7735 .gap_1()
7736 .min_w_16()
7737 .child(styled_text)
7738 .when(has_more_lines, |parent| parent.child("…"));
7739
7740 let left = if first_edit_row != cursor_point.row {
7741 render_relative_row_jump("", cursor_point.row, first_edit_row)
7742 .into_any_element()
7743 } else {
7744 Icon::new(IconName::ZedPredict).into_any_element()
7745 };
7746
7747 Some(
7748 h_flex()
7749 .h_full()
7750 .flex_1()
7751 .gap_2()
7752 .pr_1()
7753 .overflow_x_hidden()
7754 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7755 .child(left)
7756 .child(preview),
7757 )
7758 }
7759 }
7760 }
7761
7762 fn render_context_menu(
7763 &self,
7764 style: &EditorStyle,
7765 max_height_in_lines: u32,
7766 window: &mut Window,
7767 cx: &mut Context<Editor>,
7768 ) -> Option<AnyElement> {
7769 let menu = self.context_menu.borrow();
7770 let menu = menu.as_ref()?;
7771 if !menu.visible() {
7772 return None;
7773 };
7774 Some(menu.render(style, max_height_in_lines, window, cx))
7775 }
7776
7777 fn render_context_menu_aside(
7778 &mut self,
7779 max_size: Size<Pixels>,
7780 window: &mut Window,
7781 cx: &mut Context<Editor>,
7782 ) -> Option<AnyElement> {
7783 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7784 if menu.visible() {
7785 menu.render_aside(self, max_size, window, cx)
7786 } else {
7787 None
7788 }
7789 })
7790 }
7791
7792 fn hide_context_menu(
7793 &mut self,
7794 window: &mut Window,
7795 cx: &mut Context<Self>,
7796 ) -> Option<CodeContextMenu> {
7797 cx.notify();
7798 self.completion_tasks.clear();
7799 let context_menu = self.context_menu.borrow_mut().take();
7800 self.stale_inline_completion_in_menu.take();
7801 self.update_visible_inline_completion(window, cx);
7802 context_menu
7803 }
7804
7805 fn show_snippet_choices(
7806 &mut self,
7807 choices: &Vec<String>,
7808 selection: Range<Anchor>,
7809 cx: &mut Context<Self>,
7810 ) {
7811 if selection.start.buffer_id.is_none() {
7812 return;
7813 }
7814 let buffer_id = selection.start.buffer_id.unwrap();
7815 let buffer = self.buffer().read(cx).buffer(buffer_id);
7816 let id = post_inc(&mut self.next_completion_id);
7817
7818 if let Some(buffer) = buffer {
7819 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7820 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7821 ));
7822 }
7823 }
7824
7825 pub fn insert_snippet(
7826 &mut self,
7827 insertion_ranges: &[Range<usize>],
7828 snippet: Snippet,
7829 window: &mut Window,
7830 cx: &mut Context<Self>,
7831 ) -> Result<()> {
7832 struct Tabstop<T> {
7833 is_end_tabstop: bool,
7834 ranges: Vec<Range<T>>,
7835 choices: Option<Vec<String>>,
7836 }
7837
7838 let tabstops = self.buffer.update(cx, |buffer, cx| {
7839 let snippet_text: Arc<str> = snippet.text.clone().into();
7840 let edits = insertion_ranges
7841 .iter()
7842 .cloned()
7843 .map(|range| (range, snippet_text.clone()));
7844 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
7845
7846 let snapshot = &*buffer.read(cx);
7847 let snippet = &snippet;
7848 snippet
7849 .tabstops
7850 .iter()
7851 .map(|tabstop| {
7852 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7853 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7854 });
7855 let mut tabstop_ranges = tabstop
7856 .ranges
7857 .iter()
7858 .flat_map(|tabstop_range| {
7859 let mut delta = 0_isize;
7860 insertion_ranges.iter().map(move |insertion_range| {
7861 let insertion_start = insertion_range.start as isize + delta;
7862 delta +=
7863 snippet.text.len() as isize - insertion_range.len() as isize;
7864
7865 let start = ((insertion_start + tabstop_range.start) as usize)
7866 .min(snapshot.len());
7867 let end = ((insertion_start + tabstop_range.end) as usize)
7868 .min(snapshot.len());
7869 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7870 })
7871 })
7872 .collect::<Vec<_>>();
7873 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7874
7875 Tabstop {
7876 is_end_tabstop,
7877 ranges: tabstop_ranges,
7878 choices: tabstop.choices.clone(),
7879 }
7880 })
7881 .collect::<Vec<_>>()
7882 });
7883 if let Some(tabstop) = tabstops.first() {
7884 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7885 s.select_ranges(tabstop.ranges.iter().cloned());
7886 });
7887
7888 if let Some(choices) = &tabstop.choices {
7889 if let Some(selection) = tabstop.ranges.first() {
7890 self.show_snippet_choices(choices, selection.clone(), cx)
7891 }
7892 }
7893
7894 // If we're already at the last tabstop and it's at the end of the snippet,
7895 // we're done, we don't need to keep the state around.
7896 if !tabstop.is_end_tabstop {
7897 let choices = tabstops
7898 .iter()
7899 .map(|tabstop| tabstop.choices.clone())
7900 .collect();
7901
7902 let ranges = tabstops
7903 .into_iter()
7904 .map(|tabstop| tabstop.ranges)
7905 .collect::<Vec<_>>();
7906
7907 self.snippet_stack.push(SnippetState {
7908 active_index: 0,
7909 ranges,
7910 choices,
7911 });
7912 }
7913
7914 // Check whether the just-entered snippet ends with an auto-closable bracket.
7915 if self.autoclose_regions.is_empty() {
7916 let snapshot = self.buffer.read(cx).snapshot(cx);
7917 for selection in &mut self.selections.all::<Point>(cx) {
7918 let selection_head = selection.head();
7919 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7920 continue;
7921 };
7922
7923 let mut bracket_pair = None;
7924 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7925 let prev_chars = snapshot
7926 .reversed_chars_at(selection_head)
7927 .collect::<String>();
7928 for (pair, enabled) in scope.brackets() {
7929 if enabled
7930 && pair.close
7931 && prev_chars.starts_with(pair.start.as_str())
7932 && next_chars.starts_with(pair.end.as_str())
7933 {
7934 bracket_pair = Some(pair.clone());
7935 break;
7936 }
7937 }
7938 if let Some(pair) = bracket_pair {
7939 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
7940 let autoclose_enabled =
7941 self.use_autoclose && snapshot_settings.use_autoclose;
7942 if autoclose_enabled {
7943 let start = snapshot.anchor_after(selection_head);
7944 let end = snapshot.anchor_after(selection_head);
7945 self.autoclose_regions.push(AutocloseRegion {
7946 selection_id: selection.id,
7947 range: start..end,
7948 pair,
7949 });
7950 }
7951 }
7952 }
7953 }
7954 }
7955 Ok(())
7956 }
7957
7958 pub fn move_to_next_snippet_tabstop(
7959 &mut self,
7960 window: &mut Window,
7961 cx: &mut Context<Self>,
7962 ) -> bool {
7963 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7964 }
7965
7966 pub fn move_to_prev_snippet_tabstop(
7967 &mut self,
7968 window: &mut Window,
7969 cx: &mut Context<Self>,
7970 ) -> bool {
7971 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7972 }
7973
7974 pub fn move_to_snippet_tabstop(
7975 &mut self,
7976 bias: Bias,
7977 window: &mut Window,
7978 cx: &mut Context<Self>,
7979 ) -> bool {
7980 if let Some(mut snippet) = self.snippet_stack.pop() {
7981 match bias {
7982 Bias::Left => {
7983 if snippet.active_index > 0 {
7984 snippet.active_index -= 1;
7985 } else {
7986 self.snippet_stack.push(snippet);
7987 return false;
7988 }
7989 }
7990 Bias::Right => {
7991 if snippet.active_index + 1 < snippet.ranges.len() {
7992 snippet.active_index += 1;
7993 } else {
7994 self.snippet_stack.push(snippet);
7995 return false;
7996 }
7997 }
7998 }
7999 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8000 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8001 s.select_anchor_ranges(current_ranges.iter().cloned())
8002 });
8003
8004 if let Some(choices) = &snippet.choices[snippet.active_index] {
8005 if let Some(selection) = current_ranges.first() {
8006 self.show_snippet_choices(&choices, selection.clone(), cx);
8007 }
8008 }
8009
8010 // If snippet state is not at the last tabstop, push it back on the stack
8011 if snippet.active_index + 1 < snippet.ranges.len() {
8012 self.snippet_stack.push(snippet);
8013 }
8014 return true;
8015 }
8016 }
8017
8018 false
8019 }
8020
8021 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8022 self.transact(window, cx, |this, window, cx| {
8023 this.select_all(&SelectAll, window, cx);
8024 this.insert("", window, cx);
8025 });
8026 }
8027
8028 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8029 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8030 self.transact(window, cx, |this, window, cx| {
8031 this.select_autoclose_pair(window, cx);
8032 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8033 if !this.linked_edit_ranges.is_empty() {
8034 let selections = this.selections.all::<MultiBufferPoint>(cx);
8035 let snapshot = this.buffer.read(cx).snapshot(cx);
8036
8037 for selection in selections.iter() {
8038 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8039 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8040 if selection_start.buffer_id != selection_end.buffer_id {
8041 continue;
8042 }
8043 if let Some(ranges) =
8044 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8045 {
8046 for (buffer, entries) in ranges {
8047 linked_ranges.entry(buffer).or_default().extend(entries);
8048 }
8049 }
8050 }
8051 }
8052
8053 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8054 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8055 for selection in &mut selections {
8056 if selection.is_empty() {
8057 let old_head = selection.head();
8058 let mut new_head =
8059 movement::left(&display_map, old_head.to_display_point(&display_map))
8060 .to_point(&display_map);
8061 if let Some((buffer, line_buffer_range)) = display_map
8062 .buffer_snapshot
8063 .buffer_line_for_row(MultiBufferRow(old_head.row))
8064 {
8065 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8066 let indent_len = match indent_size.kind {
8067 IndentKind::Space => {
8068 buffer.settings_at(line_buffer_range.start, cx).tab_size
8069 }
8070 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8071 };
8072 if old_head.column <= indent_size.len && old_head.column > 0 {
8073 let indent_len = indent_len.get();
8074 new_head = cmp::min(
8075 new_head,
8076 MultiBufferPoint::new(
8077 old_head.row,
8078 ((old_head.column - 1) / indent_len) * indent_len,
8079 ),
8080 );
8081 }
8082 }
8083
8084 selection.set_head(new_head, SelectionGoal::None);
8085 }
8086 }
8087
8088 this.signature_help_state.set_backspace_pressed(true);
8089 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8090 s.select(selections)
8091 });
8092 this.insert("", window, cx);
8093 let empty_str: Arc<str> = Arc::from("");
8094 for (buffer, edits) in linked_ranges {
8095 let snapshot = buffer.read(cx).snapshot();
8096 use text::ToPoint as TP;
8097
8098 let edits = edits
8099 .into_iter()
8100 .map(|range| {
8101 let end_point = TP::to_point(&range.end, &snapshot);
8102 let mut start_point = TP::to_point(&range.start, &snapshot);
8103
8104 if end_point == start_point {
8105 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8106 .saturating_sub(1);
8107 start_point =
8108 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8109 };
8110
8111 (start_point..end_point, empty_str.clone())
8112 })
8113 .sorted_by_key(|(range, _)| range.start)
8114 .collect::<Vec<_>>();
8115 buffer.update(cx, |this, cx| {
8116 this.edit(edits, None, cx);
8117 })
8118 }
8119 this.refresh_inline_completion(true, false, window, cx);
8120 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8121 });
8122 }
8123
8124 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8125 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8126 self.transact(window, cx, |this, window, cx| {
8127 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8128 s.move_with(|map, selection| {
8129 if selection.is_empty() {
8130 let cursor = movement::right(map, selection.head());
8131 selection.end = cursor;
8132 selection.reversed = true;
8133 selection.goal = SelectionGoal::None;
8134 }
8135 })
8136 });
8137 this.insert("", window, cx);
8138 this.refresh_inline_completion(true, false, window, cx);
8139 });
8140 }
8141
8142 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8143 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8144 if self.move_to_prev_snippet_tabstop(window, cx) {
8145 return;
8146 }
8147 self.outdent(&Outdent, window, cx);
8148 }
8149
8150 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8151 if self.move_to_next_snippet_tabstop(window, cx) {
8152 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8153 return;
8154 }
8155 if self.read_only(cx) {
8156 return;
8157 }
8158 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8159 let mut selections = self.selections.all_adjusted(cx);
8160 let buffer = self.buffer.read(cx);
8161 let snapshot = buffer.snapshot(cx);
8162 let rows_iter = selections.iter().map(|s| s.head().row);
8163 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8164
8165 let mut edits = Vec::new();
8166 let mut prev_edited_row = 0;
8167 let mut row_delta = 0;
8168 for selection in &mut selections {
8169 if selection.start.row != prev_edited_row {
8170 row_delta = 0;
8171 }
8172 prev_edited_row = selection.end.row;
8173
8174 // If the selection is non-empty, then increase the indentation of the selected lines.
8175 if !selection.is_empty() {
8176 row_delta =
8177 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8178 continue;
8179 }
8180
8181 // If the selection is empty and the cursor is in the leading whitespace before the
8182 // suggested indentation, then auto-indent the line.
8183 let cursor = selection.head();
8184 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8185 if let Some(suggested_indent) =
8186 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8187 {
8188 if cursor.column < suggested_indent.len
8189 && cursor.column <= current_indent.len
8190 && current_indent.len <= suggested_indent.len
8191 {
8192 selection.start = Point::new(cursor.row, suggested_indent.len);
8193 selection.end = selection.start;
8194 if row_delta == 0 {
8195 edits.extend(Buffer::edit_for_indent_size_adjustment(
8196 cursor.row,
8197 current_indent,
8198 suggested_indent,
8199 ));
8200 row_delta = suggested_indent.len - current_indent.len;
8201 }
8202 continue;
8203 }
8204 }
8205
8206 // Otherwise, insert a hard or soft tab.
8207 let settings = buffer.language_settings_at(cursor, cx);
8208 let tab_size = if settings.hard_tabs {
8209 IndentSize::tab()
8210 } else {
8211 let tab_size = settings.tab_size.get();
8212 let char_column = snapshot
8213 .text_for_range(Point::new(cursor.row, 0)..cursor)
8214 .flat_map(str::chars)
8215 .count()
8216 + row_delta as usize;
8217 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
8218 IndentSize::spaces(chars_to_next_tab_stop)
8219 };
8220 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8221 selection.end = selection.start;
8222 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8223 row_delta += tab_size.len;
8224 }
8225
8226 self.transact(window, cx, |this, window, cx| {
8227 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8228 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8229 s.select(selections)
8230 });
8231 this.refresh_inline_completion(true, false, window, cx);
8232 });
8233 }
8234
8235 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8236 if self.read_only(cx) {
8237 return;
8238 }
8239 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8240 let mut selections = self.selections.all::<Point>(cx);
8241 let mut prev_edited_row = 0;
8242 let mut row_delta = 0;
8243 let mut edits = Vec::new();
8244 let buffer = self.buffer.read(cx);
8245 let snapshot = buffer.snapshot(cx);
8246 for selection in &mut selections {
8247 if selection.start.row != prev_edited_row {
8248 row_delta = 0;
8249 }
8250 prev_edited_row = selection.end.row;
8251
8252 row_delta =
8253 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8254 }
8255
8256 self.transact(window, cx, |this, window, cx| {
8257 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8258 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8259 s.select(selections)
8260 });
8261 });
8262 }
8263
8264 fn indent_selection(
8265 buffer: &MultiBuffer,
8266 snapshot: &MultiBufferSnapshot,
8267 selection: &mut Selection<Point>,
8268 edits: &mut Vec<(Range<Point>, String)>,
8269 delta_for_start_row: u32,
8270 cx: &App,
8271 ) -> u32 {
8272 let settings = buffer.language_settings_at(selection.start, cx);
8273 let tab_size = settings.tab_size.get();
8274 let indent_kind = if settings.hard_tabs {
8275 IndentKind::Tab
8276 } else {
8277 IndentKind::Space
8278 };
8279 let mut start_row = selection.start.row;
8280 let mut end_row = selection.end.row + 1;
8281
8282 // If a selection ends at the beginning of a line, don't indent
8283 // that last line.
8284 if selection.end.column == 0 && selection.end.row > selection.start.row {
8285 end_row -= 1;
8286 }
8287
8288 // Avoid re-indenting a row that has already been indented by a
8289 // previous selection, but still update this selection's column
8290 // to reflect that indentation.
8291 if delta_for_start_row > 0 {
8292 start_row += 1;
8293 selection.start.column += delta_for_start_row;
8294 if selection.end.row == selection.start.row {
8295 selection.end.column += delta_for_start_row;
8296 }
8297 }
8298
8299 let mut delta_for_end_row = 0;
8300 let has_multiple_rows = start_row + 1 != end_row;
8301 for row in start_row..end_row {
8302 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8303 let indent_delta = match (current_indent.kind, indent_kind) {
8304 (IndentKind::Space, IndentKind::Space) => {
8305 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8306 IndentSize::spaces(columns_to_next_tab_stop)
8307 }
8308 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8309 (_, IndentKind::Tab) => IndentSize::tab(),
8310 };
8311
8312 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8313 0
8314 } else {
8315 selection.start.column
8316 };
8317 let row_start = Point::new(row, start);
8318 edits.push((
8319 row_start..row_start,
8320 indent_delta.chars().collect::<String>(),
8321 ));
8322
8323 // Update this selection's endpoints to reflect the indentation.
8324 if row == selection.start.row {
8325 selection.start.column += indent_delta.len;
8326 }
8327 if row == selection.end.row {
8328 selection.end.column += indent_delta.len;
8329 delta_for_end_row = indent_delta.len;
8330 }
8331 }
8332
8333 if selection.start.row == selection.end.row {
8334 delta_for_start_row + delta_for_end_row
8335 } else {
8336 delta_for_end_row
8337 }
8338 }
8339
8340 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8341 if self.read_only(cx) {
8342 return;
8343 }
8344 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8346 let selections = self.selections.all::<Point>(cx);
8347 let mut deletion_ranges = Vec::new();
8348 let mut last_outdent = None;
8349 {
8350 let buffer = self.buffer.read(cx);
8351 let snapshot = buffer.snapshot(cx);
8352 for selection in &selections {
8353 let settings = buffer.language_settings_at(selection.start, cx);
8354 let tab_size = settings.tab_size.get();
8355 let mut rows = selection.spanned_rows(false, &display_map);
8356
8357 // Avoid re-outdenting a row that has already been outdented by a
8358 // previous selection.
8359 if let Some(last_row) = last_outdent {
8360 if last_row == rows.start {
8361 rows.start = rows.start.next_row();
8362 }
8363 }
8364 let has_multiple_rows = rows.len() > 1;
8365 for row in rows.iter_rows() {
8366 let indent_size = snapshot.indent_size_for_line(row);
8367 if indent_size.len > 0 {
8368 let deletion_len = match indent_size.kind {
8369 IndentKind::Space => {
8370 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8371 if columns_to_prev_tab_stop == 0 {
8372 tab_size
8373 } else {
8374 columns_to_prev_tab_stop
8375 }
8376 }
8377 IndentKind::Tab => 1,
8378 };
8379 let start = if has_multiple_rows
8380 || deletion_len > selection.start.column
8381 || indent_size.len < selection.start.column
8382 {
8383 0
8384 } else {
8385 selection.start.column - deletion_len
8386 };
8387 deletion_ranges.push(
8388 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8389 );
8390 last_outdent = Some(row);
8391 }
8392 }
8393 }
8394 }
8395
8396 self.transact(window, cx, |this, window, cx| {
8397 this.buffer.update(cx, |buffer, cx| {
8398 let empty_str: Arc<str> = Arc::default();
8399 buffer.edit(
8400 deletion_ranges
8401 .into_iter()
8402 .map(|range| (range, empty_str.clone())),
8403 None,
8404 cx,
8405 );
8406 });
8407 let selections = this.selections.all::<usize>(cx);
8408 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8409 s.select(selections)
8410 });
8411 });
8412 }
8413
8414 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8415 if self.read_only(cx) {
8416 return;
8417 }
8418 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8419 let selections = self
8420 .selections
8421 .all::<usize>(cx)
8422 .into_iter()
8423 .map(|s| s.range());
8424
8425 self.transact(window, cx, |this, window, cx| {
8426 this.buffer.update(cx, |buffer, cx| {
8427 buffer.autoindent_ranges(selections, cx);
8428 });
8429 let selections = this.selections.all::<usize>(cx);
8430 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8431 s.select(selections)
8432 });
8433 });
8434 }
8435
8436 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8437 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8438 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8439 let selections = self.selections.all::<Point>(cx);
8440
8441 let mut new_cursors = Vec::new();
8442 let mut edit_ranges = Vec::new();
8443 let mut selections = selections.iter().peekable();
8444 while let Some(selection) = selections.next() {
8445 let mut rows = selection.spanned_rows(false, &display_map);
8446 let goal_display_column = selection.head().to_display_point(&display_map).column();
8447
8448 // Accumulate contiguous regions of rows that we want to delete.
8449 while let Some(next_selection) = selections.peek() {
8450 let next_rows = next_selection.spanned_rows(false, &display_map);
8451 if next_rows.start <= rows.end {
8452 rows.end = next_rows.end;
8453 selections.next().unwrap();
8454 } else {
8455 break;
8456 }
8457 }
8458
8459 let buffer = &display_map.buffer_snapshot;
8460 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8461 let edit_end;
8462 let cursor_buffer_row;
8463 if buffer.max_point().row >= rows.end.0 {
8464 // If there's a line after the range, delete the \n from the end of the row range
8465 // and position the cursor on the next line.
8466 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8467 cursor_buffer_row = rows.end;
8468 } else {
8469 // If there isn't a line after the range, delete the \n from the line before the
8470 // start of the row range and position the cursor there.
8471 edit_start = edit_start.saturating_sub(1);
8472 edit_end = buffer.len();
8473 cursor_buffer_row = rows.start.previous_row();
8474 }
8475
8476 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8477 *cursor.column_mut() =
8478 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8479
8480 new_cursors.push((
8481 selection.id,
8482 buffer.anchor_after(cursor.to_point(&display_map)),
8483 ));
8484 edit_ranges.push(edit_start..edit_end);
8485 }
8486
8487 self.transact(window, cx, |this, window, cx| {
8488 let buffer = this.buffer.update(cx, |buffer, cx| {
8489 let empty_str: Arc<str> = Arc::default();
8490 buffer.edit(
8491 edit_ranges
8492 .into_iter()
8493 .map(|range| (range, empty_str.clone())),
8494 None,
8495 cx,
8496 );
8497 buffer.snapshot(cx)
8498 });
8499 let new_selections = new_cursors
8500 .into_iter()
8501 .map(|(id, cursor)| {
8502 let cursor = cursor.to_point(&buffer);
8503 Selection {
8504 id,
8505 start: cursor,
8506 end: cursor,
8507 reversed: false,
8508 goal: SelectionGoal::None,
8509 }
8510 })
8511 .collect();
8512
8513 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8514 s.select(new_selections);
8515 });
8516 });
8517 }
8518
8519 pub fn join_lines_impl(
8520 &mut self,
8521 insert_whitespace: bool,
8522 window: &mut Window,
8523 cx: &mut Context<Self>,
8524 ) {
8525 if self.read_only(cx) {
8526 return;
8527 }
8528 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8529 for selection in self.selections.all::<Point>(cx) {
8530 let start = MultiBufferRow(selection.start.row);
8531 // Treat single line selections as if they include the next line. Otherwise this action
8532 // would do nothing for single line selections individual cursors.
8533 let end = if selection.start.row == selection.end.row {
8534 MultiBufferRow(selection.start.row + 1)
8535 } else {
8536 MultiBufferRow(selection.end.row)
8537 };
8538
8539 if let Some(last_row_range) = row_ranges.last_mut() {
8540 if start <= last_row_range.end {
8541 last_row_range.end = end;
8542 continue;
8543 }
8544 }
8545 row_ranges.push(start..end);
8546 }
8547
8548 let snapshot = self.buffer.read(cx).snapshot(cx);
8549 let mut cursor_positions = Vec::new();
8550 for row_range in &row_ranges {
8551 let anchor = snapshot.anchor_before(Point::new(
8552 row_range.end.previous_row().0,
8553 snapshot.line_len(row_range.end.previous_row()),
8554 ));
8555 cursor_positions.push(anchor..anchor);
8556 }
8557
8558 self.transact(window, cx, |this, window, cx| {
8559 for row_range in row_ranges.into_iter().rev() {
8560 for row in row_range.iter_rows().rev() {
8561 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8562 let next_line_row = row.next_row();
8563 let indent = snapshot.indent_size_for_line(next_line_row);
8564 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8565
8566 let replace =
8567 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8568 " "
8569 } else {
8570 ""
8571 };
8572
8573 this.buffer.update(cx, |buffer, cx| {
8574 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8575 });
8576 }
8577 }
8578
8579 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8580 s.select_anchor_ranges(cursor_positions)
8581 });
8582 });
8583 }
8584
8585 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8586 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8587 self.join_lines_impl(true, window, cx);
8588 }
8589
8590 pub fn sort_lines_case_sensitive(
8591 &mut self,
8592 _: &SortLinesCaseSensitive,
8593 window: &mut Window,
8594 cx: &mut Context<Self>,
8595 ) {
8596 self.manipulate_lines(window, cx, |lines| lines.sort())
8597 }
8598
8599 pub fn sort_lines_case_insensitive(
8600 &mut self,
8601 _: &SortLinesCaseInsensitive,
8602 window: &mut Window,
8603 cx: &mut Context<Self>,
8604 ) {
8605 self.manipulate_lines(window, cx, |lines| {
8606 lines.sort_by_key(|line| line.to_lowercase())
8607 })
8608 }
8609
8610 pub fn unique_lines_case_insensitive(
8611 &mut self,
8612 _: &UniqueLinesCaseInsensitive,
8613 window: &mut Window,
8614 cx: &mut Context<Self>,
8615 ) {
8616 self.manipulate_lines(window, cx, |lines| {
8617 let mut seen = HashSet::default();
8618 lines.retain(|line| seen.insert(line.to_lowercase()));
8619 })
8620 }
8621
8622 pub fn unique_lines_case_sensitive(
8623 &mut self,
8624 _: &UniqueLinesCaseSensitive,
8625 window: &mut Window,
8626 cx: &mut Context<Self>,
8627 ) {
8628 self.manipulate_lines(window, cx, |lines| {
8629 let mut seen = HashSet::default();
8630 lines.retain(|line| seen.insert(*line));
8631 })
8632 }
8633
8634 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8635 let Some(project) = self.project.clone() else {
8636 return;
8637 };
8638 self.reload(project, window, cx)
8639 .detach_and_notify_err(window, cx);
8640 }
8641
8642 pub fn restore_file(
8643 &mut self,
8644 _: &::git::RestoreFile,
8645 window: &mut Window,
8646 cx: &mut Context<Self>,
8647 ) {
8648 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8649 let mut buffer_ids = HashSet::default();
8650 let snapshot = self.buffer().read(cx).snapshot(cx);
8651 for selection in self.selections.all::<usize>(cx) {
8652 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8653 }
8654
8655 let buffer = self.buffer().read(cx);
8656 let ranges = buffer_ids
8657 .into_iter()
8658 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8659 .collect::<Vec<_>>();
8660
8661 self.restore_hunks_in_ranges(ranges, window, cx);
8662 }
8663
8664 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8665 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8666 let selections = self
8667 .selections
8668 .all(cx)
8669 .into_iter()
8670 .map(|s| s.range())
8671 .collect();
8672 self.restore_hunks_in_ranges(selections, window, cx);
8673 }
8674
8675 pub fn restore_hunks_in_ranges(
8676 &mut self,
8677 ranges: Vec<Range<Point>>,
8678 window: &mut Window,
8679 cx: &mut Context<Editor>,
8680 ) {
8681 let mut revert_changes = HashMap::default();
8682 let chunk_by = self
8683 .snapshot(window, cx)
8684 .hunks_for_ranges(ranges)
8685 .into_iter()
8686 .chunk_by(|hunk| hunk.buffer_id);
8687 for (buffer_id, hunks) in &chunk_by {
8688 let hunks = hunks.collect::<Vec<_>>();
8689 for hunk in &hunks {
8690 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8691 }
8692 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8693 }
8694 drop(chunk_by);
8695 if !revert_changes.is_empty() {
8696 self.transact(window, cx, |editor, window, cx| {
8697 editor.restore(revert_changes, window, cx);
8698 });
8699 }
8700 }
8701
8702 pub fn open_active_item_in_terminal(
8703 &mut self,
8704 _: &OpenInTerminal,
8705 window: &mut Window,
8706 cx: &mut Context<Self>,
8707 ) {
8708 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8709 let project_path = buffer.read(cx).project_path(cx)?;
8710 let project = self.project.as_ref()?.read(cx);
8711 let entry = project.entry_for_path(&project_path, cx)?;
8712 let parent = match &entry.canonical_path {
8713 Some(canonical_path) => canonical_path.to_path_buf(),
8714 None => project.absolute_path(&project_path, cx)?,
8715 }
8716 .parent()?
8717 .to_path_buf();
8718 Some(parent)
8719 }) {
8720 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8721 }
8722 }
8723
8724 fn set_breakpoint_context_menu(
8725 &mut self,
8726 display_row: DisplayRow,
8727 position: Option<Anchor>,
8728 clicked_point: gpui::Point<Pixels>,
8729 window: &mut Window,
8730 cx: &mut Context<Self>,
8731 ) {
8732 if !cx.has_flag::<Debugger>() {
8733 return;
8734 }
8735 let source = self
8736 .buffer
8737 .read(cx)
8738 .snapshot(cx)
8739 .anchor_before(Point::new(display_row.0, 0u32));
8740
8741 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8742
8743 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8744 self,
8745 source,
8746 clicked_point,
8747 context_menu,
8748 window,
8749 cx,
8750 );
8751 }
8752
8753 fn add_edit_breakpoint_block(
8754 &mut self,
8755 anchor: Anchor,
8756 breakpoint: &Breakpoint,
8757 edit_action: BreakpointPromptEditAction,
8758 window: &mut Window,
8759 cx: &mut Context<Self>,
8760 ) {
8761 let weak_editor = cx.weak_entity();
8762 let bp_prompt = cx.new(|cx| {
8763 BreakpointPromptEditor::new(
8764 weak_editor,
8765 anchor,
8766 breakpoint.clone(),
8767 edit_action,
8768 window,
8769 cx,
8770 )
8771 });
8772
8773 let height = bp_prompt.update(cx, |this, cx| {
8774 this.prompt
8775 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8776 });
8777 let cloned_prompt = bp_prompt.clone();
8778 let blocks = vec![BlockProperties {
8779 style: BlockStyle::Sticky,
8780 placement: BlockPlacement::Above(anchor),
8781 height: Some(height),
8782 render: Arc::new(move |cx| {
8783 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8784 cloned_prompt.clone().into_any_element()
8785 }),
8786 priority: 0,
8787 }];
8788
8789 let focus_handle = bp_prompt.focus_handle(cx);
8790 window.focus(&focus_handle);
8791
8792 let block_ids = self.insert_blocks(blocks, None, cx);
8793 bp_prompt.update(cx, |prompt, _| {
8794 prompt.add_block_ids(block_ids);
8795 });
8796 }
8797
8798 fn breakpoint_at_cursor_head(
8799 &self,
8800 window: &mut Window,
8801 cx: &mut Context<Self>,
8802 ) -> Option<(Anchor, Breakpoint)> {
8803 let cursor_position: Point = self.selections.newest(cx).head();
8804 self.breakpoint_at_row(cursor_position.row, window, cx)
8805 }
8806
8807 pub(crate) fn breakpoint_at_row(
8808 &self,
8809 row: u32,
8810 window: &mut Window,
8811 cx: &mut Context<Self>,
8812 ) -> Option<(Anchor, Breakpoint)> {
8813 let snapshot = self.snapshot(window, cx);
8814 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
8815
8816 let project = self.project.clone()?;
8817
8818 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
8819 snapshot
8820 .buffer_snapshot
8821 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
8822 })?;
8823
8824 let enclosing_excerpt = breakpoint_position.excerpt_id;
8825 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8826 let buffer_snapshot = buffer.read(cx).snapshot();
8827
8828 let row = buffer_snapshot
8829 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
8830 .row;
8831
8832 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
8833 let anchor_end = snapshot
8834 .buffer_snapshot
8835 .anchor_after(Point::new(row, line_len));
8836
8837 let bp = self
8838 .breakpoint_store
8839 .as_ref()?
8840 .read_with(cx, |breakpoint_store, cx| {
8841 breakpoint_store
8842 .breakpoints(
8843 &buffer,
8844 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
8845 &buffer_snapshot,
8846 cx,
8847 )
8848 .next()
8849 .and_then(|(anchor, bp)| {
8850 let breakpoint_row = buffer_snapshot
8851 .summary_for_anchor::<text::PointUtf16>(anchor)
8852 .row;
8853
8854 if breakpoint_row == row {
8855 snapshot
8856 .buffer_snapshot
8857 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8858 .map(|anchor| (anchor, bp.clone()))
8859 } else {
8860 None
8861 }
8862 })
8863 });
8864 bp
8865 }
8866
8867 pub fn edit_log_breakpoint(
8868 &mut self,
8869 _: &EditLogBreakpoint,
8870 window: &mut Window,
8871 cx: &mut Context<Self>,
8872 ) {
8873 let (anchor, bp) = self
8874 .breakpoint_at_cursor_head(window, cx)
8875 .unwrap_or_else(|| {
8876 let cursor_position: Point = self.selections.newest(cx).head();
8877
8878 let breakpoint_position = self
8879 .snapshot(window, cx)
8880 .display_snapshot
8881 .buffer_snapshot
8882 .anchor_after(Point::new(cursor_position.row, 0));
8883
8884 (
8885 breakpoint_position,
8886 Breakpoint {
8887 message: None,
8888 state: BreakpointState::Enabled,
8889 condition: None,
8890 hit_condition: None,
8891 },
8892 )
8893 });
8894
8895 self.add_edit_breakpoint_block(anchor, &bp, BreakpointPromptEditAction::Log, window, cx);
8896 }
8897
8898 pub fn enable_breakpoint(
8899 &mut self,
8900 _: &crate::actions::EnableBreakpoint,
8901 window: &mut Window,
8902 cx: &mut Context<Self>,
8903 ) {
8904 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8905 if breakpoint.is_disabled() {
8906 self.edit_breakpoint_at_anchor(
8907 anchor,
8908 breakpoint,
8909 BreakpointEditAction::InvertState,
8910 cx,
8911 );
8912 }
8913 }
8914 }
8915
8916 pub fn disable_breakpoint(
8917 &mut self,
8918 _: &crate::actions::DisableBreakpoint,
8919 window: &mut Window,
8920 cx: &mut Context<Self>,
8921 ) {
8922 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8923 if breakpoint.is_enabled() {
8924 self.edit_breakpoint_at_anchor(
8925 anchor,
8926 breakpoint,
8927 BreakpointEditAction::InvertState,
8928 cx,
8929 );
8930 }
8931 }
8932 }
8933
8934 pub fn toggle_breakpoint(
8935 &mut self,
8936 _: &crate::actions::ToggleBreakpoint,
8937 window: &mut Window,
8938 cx: &mut Context<Self>,
8939 ) {
8940 let edit_action = BreakpointEditAction::Toggle;
8941
8942 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8943 self.edit_breakpoint_at_anchor(anchor, breakpoint, edit_action, cx);
8944 } else {
8945 let cursor_position: Point = self.selections.newest(cx).head();
8946
8947 let breakpoint_position = self
8948 .snapshot(window, cx)
8949 .display_snapshot
8950 .buffer_snapshot
8951 .anchor_after(Point::new(cursor_position.row, 0));
8952
8953 self.edit_breakpoint_at_anchor(
8954 breakpoint_position,
8955 Breakpoint::new_standard(),
8956 edit_action,
8957 cx,
8958 );
8959 }
8960 }
8961
8962 pub fn edit_breakpoint_at_anchor(
8963 &mut self,
8964 breakpoint_position: Anchor,
8965 breakpoint: Breakpoint,
8966 edit_action: BreakpointEditAction,
8967 cx: &mut Context<Self>,
8968 ) {
8969 let Some(breakpoint_store) = &self.breakpoint_store else {
8970 return;
8971 };
8972
8973 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8974 if breakpoint_position == Anchor::min() {
8975 self.buffer()
8976 .read(cx)
8977 .excerpt_buffer_ids()
8978 .into_iter()
8979 .next()
8980 } else {
8981 None
8982 }
8983 }) else {
8984 return;
8985 };
8986
8987 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8988 return;
8989 };
8990
8991 breakpoint_store.update(cx, |breakpoint_store, cx| {
8992 breakpoint_store.toggle_breakpoint(
8993 buffer,
8994 (breakpoint_position.text_anchor, breakpoint),
8995 edit_action,
8996 cx,
8997 );
8998 });
8999
9000 cx.notify();
9001 }
9002
9003 #[cfg(any(test, feature = "test-support"))]
9004 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9005 self.breakpoint_store.clone()
9006 }
9007
9008 pub fn prepare_restore_change(
9009 &self,
9010 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9011 hunk: &MultiBufferDiffHunk,
9012 cx: &mut App,
9013 ) -> Option<()> {
9014 if hunk.is_created_file() {
9015 return None;
9016 }
9017 let buffer = self.buffer.read(cx);
9018 let diff = buffer.diff_for(hunk.buffer_id)?;
9019 let buffer = buffer.buffer(hunk.buffer_id)?;
9020 let buffer = buffer.read(cx);
9021 let original_text = diff
9022 .read(cx)
9023 .base_text()
9024 .as_rope()
9025 .slice(hunk.diff_base_byte_range.clone());
9026 let buffer_snapshot = buffer.snapshot();
9027 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9028 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9029 probe
9030 .0
9031 .start
9032 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9033 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9034 }) {
9035 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9036 Some(())
9037 } else {
9038 None
9039 }
9040 }
9041
9042 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9043 self.manipulate_lines(window, cx, |lines| lines.reverse())
9044 }
9045
9046 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9047 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9048 }
9049
9050 fn manipulate_lines<Fn>(
9051 &mut self,
9052 window: &mut Window,
9053 cx: &mut Context<Self>,
9054 mut callback: Fn,
9055 ) where
9056 Fn: FnMut(&mut Vec<&str>),
9057 {
9058 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9059
9060 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9061 let buffer = self.buffer.read(cx).snapshot(cx);
9062
9063 let mut edits = Vec::new();
9064
9065 let selections = self.selections.all::<Point>(cx);
9066 let mut selections = selections.iter().peekable();
9067 let mut contiguous_row_selections = Vec::new();
9068 let mut new_selections = Vec::new();
9069 let mut added_lines = 0;
9070 let mut removed_lines = 0;
9071
9072 while let Some(selection) = selections.next() {
9073 let (start_row, end_row) = consume_contiguous_rows(
9074 &mut contiguous_row_selections,
9075 selection,
9076 &display_map,
9077 &mut selections,
9078 );
9079
9080 let start_point = Point::new(start_row.0, 0);
9081 let end_point = Point::new(
9082 end_row.previous_row().0,
9083 buffer.line_len(end_row.previous_row()),
9084 );
9085 let text = buffer
9086 .text_for_range(start_point..end_point)
9087 .collect::<String>();
9088
9089 let mut lines = text.split('\n').collect_vec();
9090
9091 let lines_before = lines.len();
9092 callback(&mut lines);
9093 let lines_after = lines.len();
9094
9095 edits.push((start_point..end_point, lines.join("\n")));
9096
9097 // Selections must change based on added and removed line count
9098 let start_row =
9099 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9100 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9101 new_selections.push(Selection {
9102 id: selection.id,
9103 start: start_row,
9104 end: end_row,
9105 goal: SelectionGoal::None,
9106 reversed: selection.reversed,
9107 });
9108
9109 if lines_after > lines_before {
9110 added_lines += lines_after - lines_before;
9111 } else if lines_before > lines_after {
9112 removed_lines += lines_before - lines_after;
9113 }
9114 }
9115
9116 self.transact(window, cx, |this, window, cx| {
9117 let buffer = this.buffer.update(cx, |buffer, cx| {
9118 buffer.edit(edits, None, cx);
9119 buffer.snapshot(cx)
9120 });
9121
9122 // Recalculate offsets on newly edited buffer
9123 let new_selections = new_selections
9124 .iter()
9125 .map(|s| {
9126 let start_point = Point::new(s.start.0, 0);
9127 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9128 Selection {
9129 id: s.id,
9130 start: buffer.point_to_offset(start_point),
9131 end: buffer.point_to_offset(end_point),
9132 goal: s.goal,
9133 reversed: s.reversed,
9134 }
9135 })
9136 .collect();
9137
9138 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9139 s.select(new_selections);
9140 });
9141
9142 this.request_autoscroll(Autoscroll::fit(), cx);
9143 });
9144 }
9145
9146 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9147 self.manipulate_text(window, cx, |text| {
9148 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9149 if has_upper_case_characters {
9150 text.to_lowercase()
9151 } else {
9152 text.to_uppercase()
9153 }
9154 })
9155 }
9156
9157 pub fn convert_to_upper_case(
9158 &mut self,
9159 _: &ConvertToUpperCase,
9160 window: &mut Window,
9161 cx: &mut Context<Self>,
9162 ) {
9163 self.manipulate_text(window, cx, |text| text.to_uppercase())
9164 }
9165
9166 pub fn convert_to_lower_case(
9167 &mut self,
9168 _: &ConvertToLowerCase,
9169 window: &mut Window,
9170 cx: &mut Context<Self>,
9171 ) {
9172 self.manipulate_text(window, cx, |text| text.to_lowercase())
9173 }
9174
9175 pub fn convert_to_title_case(
9176 &mut self,
9177 _: &ConvertToTitleCase,
9178 window: &mut Window,
9179 cx: &mut Context<Self>,
9180 ) {
9181 self.manipulate_text(window, cx, |text| {
9182 text.split('\n')
9183 .map(|line| line.to_case(Case::Title))
9184 .join("\n")
9185 })
9186 }
9187
9188 pub fn convert_to_snake_case(
9189 &mut self,
9190 _: &ConvertToSnakeCase,
9191 window: &mut Window,
9192 cx: &mut Context<Self>,
9193 ) {
9194 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9195 }
9196
9197 pub fn convert_to_kebab_case(
9198 &mut self,
9199 _: &ConvertToKebabCase,
9200 window: &mut Window,
9201 cx: &mut Context<Self>,
9202 ) {
9203 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9204 }
9205
9206 pub fn convert_to_upper_camel_case(
9207 &mut self,
9208 _: &ConvertToUpperCamelCase,
9209 window: &mut Window,
9210 cx: &mut Context<Self>,
9211 ) {
9212 self.manipulate_text(window, cx, |text| {
9213 text.split('\n')
9214 .map(|line| line.to_case(Case::UpperCamel))
9215 .join("\n")
9216 })
9217 }
9218
9219 pub fn convert_to_lower_camel_case(
9220 &mut self,
9221 _: &ConvertToLowerCamelCase,
9222 window: &mut Window,
9223 cx: &mut Context<Self>,
9224 ) {
9225 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9226 }
9227
9228 pub fn convert_to_opposite_case(
9229 &mut self,
9230 _: &ConvertToOppositeCase,
9231 window: &mut Window,
9232 cx: &mut Context<Self>,
9233 ) {
9234 self.manipulate_text(window, cx, |text| {
9235 text.chars()
9236 .fold(String::with_capacity(text.len()), |mut t, c| {
9237 if c.is_uppercase() {
9238 t.extend(c.to_lowercase());
9239 } else {
9240 t.extend(c.to_uppercase());
9241 }
9242 t
9243 })
9244 })
9245 }
9246
9247 pub fn convert_to_rot13(
9248 &mut self,
9249 _: &ConvertToRot13,
9250 window: &mut Window,
9251 cx: &mut Context<Self>,
9252 ) {
9253 self.manipulate_text(window, cx, |text| {
9254 text.chars()
9255 .map(|c| match c {
9256 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9257 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9258 _ => c,
9259 })
9260 .collect()
9261 })
9262 }
9263
9264 pub fn convert_to_rot47(
9265 &mut self,
9266 _: &ConvertToRot47,
9267 window: &mut Window,
9268 cx: &mut Context<Self>,
9269 ) {
9270 self.manipulate_text(window, cx, |text| {
9271 text.chars()
9272 .map(|c| {
9273 let code_point = c as u32;
9274 if code_point >= 33 && code_point <= 126 {
9275 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9276 }
9277 c
9278 })
9279 .collect()
9280 })
9281 }
9282
9283 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9284 where
9285 Fn: FnMut(&str) -> String,
9286 {
9287 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9288 let buffer = self.buffer.read(cx).snapshot(cx);
9289
9290 let mut new_selections = Vec::new();
9291 let mut edits = Vec::new();
9292 let mut selection_adjustment = 0i32;
9293
9294 for selection in self.selections.all::<usize>(cx) {
9295 let selection_is_empty = selection.is_empty();
9296
9297 let (start, end) = if selection_is_empty {
9298 let word_range = movement::surrounding_word(
9299 &display_map,
9300 selection.start.to_display_point(&display_map),
9301 );
9302 let start = word_range.start.to_offset(&display_map, Bias::Left);
9303 let end = word_range.end.to_offset(&display_map, Bias::Left);
9304 (start, end)
9305 } else {
9306 (selection.start, selection.end)
9307 };
9308
9309 let text = buffer.text_for_range(start..end).collect::<String>();
9310 let old_length = text.len() as i32;
9311 let text = callback(&text);
9312
9313 new_selections.push(Selection {
9314 start: (start as i32 - selection_adjustment) as usize,
9315 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9316 goal: SelectionGoal::None,
9317 ..selection
9318 });
9319
9320 selection_adjustment += old_length - text.len() as i32;
9321
9322 edits.push((start..end, text));
9323 }
9324
9325 self.transact(window, cx, |this, window, cx| {
9326 this.buffer.update(cx, |buffer, cx| {
9327 buffer.edit(edits, None, cx);
9328 });
9329
9330 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9331 s.select(new_selections);
9332 });
9333
9334 this.request_autoscroll(Autoscroll::fit(), cx);
9335 });
9336 }
9337
9338 pub fn duplicate(
9339 &mut self,
9340 upwards: bool,
9341 whole_lines: bool,
9342 window: &mut Window,
9343 cx: &mut Context<Self>,
9344 ) {
9345 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9346
9347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9348 let buffer = &display_map.buffer_snapshot;
9349 let selections = self.selections.all::<Point>(cx);
9350
9351 let mut edits = Vec::new();
9352 let mut selections_iter = selections.iter().peekable();
9353 while let Some(selection) = selections_iter.next() {
9354 let mut rows = selection.spanned_rows(false, &display_map);
9355 // duplicate line-wise
9356 if whole_lines || selection.start == selection.end {
9357 // Avoid duplicating the same lines twice.
9358 while let Some(next_selection) = selections_iter.peek() {
9359 let next_rows = next_selection.spanned_rows(false, &display_map);
9360 if next_rows.start < rows.end {
9361 rows.end = next_rows.end;
9362 selections_iter.next().unwrap();
9363 } else {
9364 break;
9365 }
9366 }
9367
9368 // Copy the text from the selected row region and splice it either at the start
9369 // or end of the region.
9370 let start = Point::new(rows.start.0, 0);
9371 let end = Point::new(
9372 rows.end.previous_row().0,
9373 buffer.line_len(rows.end.previous_row()),
9374 );
9375 let text = buffer
9376 .text_for_range(start..end)
9377 .chain(Some("\n"))
9378 .collect::<String>();
9379 let insert_location = if upwards {
9380 Point::new(rows.end.0, 0)
9381 } else {
9382 start
9383 };
9384 edits.push((insert_location..insert_location, text));
9385 } else {
9386 // duplicate character-wise
9387 let start = selection.start;
9388 let end = selection.end;
9389 let text = buffer.text_for_range(start..end).collect::<String>();
9390 edits.push((selection.end..selection.end, text));
9391 }
9392 }
9393
9394 self.transact(window, cx, |this, _, cx| {
9395 this.buffer.update(cx, |buffer, cx| {
9396 buffer.edit(edits, None, cx);
9397 });
9398
9399 this.request_autoscroll(Autoscroll::fit(), cx);
9400 });
9401 }
9402
9403 pub fn duplicate_line_up(
9404 &mut self,
9405 _: &DuplicateLineUp,
9406 window: &mut Window,
9407 cx: &mut Context<Self>,
9408 ) {
9409 self.duplicate(true, true, window, cx);
9410 }
9411
9412 pub fn duplicate_line_down(
9413 &mut self,
9414 _: &DuplicateLineDown,
9415 window: &mut Window,
9416 cx: &mut Context<Self>,
9417 ) {
9418 self.duplicate(false, true, window, cx);
9419 }
9420
9421 pub fn duplicate_selection(
9422 &mut self,
9423 _: &DuplicateSelection,
9424 window: &mut Window,
9425 cx: &mut Context<Self>,
9426 ) {
9427 self.duplicate(false, false, window, cx);
9428 }
9429
9430 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9431 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9432
9433 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9434 let buffer = self.buffer.read(cx).snapshot(cx);
9435
9436 let mut edits = Vec::new();
9437 let mut unfold_ranges = Vec::new();
9438 let mut refold_creases = Vec::new();
9439
9440 let selections = self.selections.all::<Point>(cx);
9441 let mut selections = selections.iter().peekable();
9442 let mut contiguous_row_selections = Vec::new();
9443 let mut new_selections = Vec::new();
9444
9445 while let Some(selection) = selections.next() {
9446 // Find all the selections that span a contiguous row range
9447 let (start_row, end_row) = consume_contiguous_rows(
9448 &mut contiguous_row_selections,
9449 selection,
9450 &display_map,
9451 &mut selections,
9452 );
9453
9454 // Move the text spanned by the row range to be before the line preceding the row range
9455 if start_row.0 > 0 {
9456 let range_to_move = Point::new(
9457 start_row.previous_row().0,
9458 buffer.line_len(start_row.previous_row()),
9459 )
9460 ..Point::new(
9461 end_row.previous_row().0,
9462 buffer.line_len(end_row.previous_row()),
9463 );
9464 let insertion_point = display_map
9465 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9466 .0;
9467
9468 // Don't move lines across excerpts
9469 if buffer
9470 .excerpt_containing(insertion_point..range_to_move.end)
9471 .is_some()
9472 {
9473 let text = buffer
9474 .text_for_range(range_to_move.clone())
9475 .flat_map(|s| s.chars())
9476 .skip(1)
9477 .chain(['\n'])
9478 .collect::<String>();
9479
9480 edits.push((
9481 buffer.anchor_after(range_to_move.start)
9482 ..buffer.anchor_before(range_to_move.end),
9483 String::new(),
9484 ));
9485 let insertion_anchor = buffer.anchor_after(insertion_point);
9486 edits.push((insertion_anchor..insertion_anchor, text));
9487
9488 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9489
9490 // Move selections up
9491 new_selections.extend(contiguous_row_selections.drain(..).map(
9492 |mut selection| {
9493 selection.start.row -= row_delta;
9494 selection.end.row -= row_delta;
9495 selection
9496 },
9497 ));
9498
9499 // Move folds up
9500 unfold_ranges.push(range_to_move.clone());
9501 for fold in display_map.folds_in_range(
9502 buffer.anchor_before(range_to_move.start)
9503 ..buffer.anchor_after(range_to_move.end),
9504 ) {
9505 let mut start = fold.range.start.to_point(&buffer);
9506 let mut end = fold.range.end.to_point(&buffer);
9507 start.row -= row_delta;
9508 end.row -= row_delta;
9509 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9510 }
9511 }
9512 }
9513
9514 // If we didn't move line(s), preserve the existing selections
9515 new_selections.append(&mut contiguous_row_selections);
9516 }
9517
9518 self.transact(window, cx, |this, window, cx| {
9519 this.unfold_ranges(&unfold_ranges, true, true, cx);
9520 this.buffer.update(cx, |buffer, cx| {
9521 for (range, text) in edits {
9522 buffer.edit([(range, text)], None, cx);
9523 }
9524 });
9525 this.fold_creases(refold_creases, true, window, cx);
9526 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9527 s.select(new_selections);
9528 })
9529 });
9530 }
9531
9532 pub fn move_line_down(
9533 &mut self,
9534 _: &MoveLineDown,
9535 window: &mut Window,
9536 cx: &mut Context<Self>,
9537 ) {
9538 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9539
9540 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9541 let buffer = self.buffer.read(cx).snapshot(cx);
9542
9543 let mut edits = Vec::new();
9544 let mut unfold_ranges = Vec::new();
9545 let mut refold_creases = Vec::new();
9546
9547 let selections = self.selections.all::<Point>(cx);
9548 let mut selections = selections.iter().peekable();
9549 let mut contiguous_row_selections = Vec::new();
9550 let mut new_selections = Vec::new();
9551
9552 while let Some(selection) = selections.next() {
9553 // Find all the selections that span a contiguous row range
9554 let (start_row, end_row) = consume_contiguous_rows(
9555 &mut contiguous_row_selections,
9556 selection,
9557 &display_map,
9558 &mut selections,
9559 );
9560
9561 // Move the text spanned by the row range to be after the last line of the row range
9562 if end_row.0 <= buffer.max_point().row {
9563 let range_to_move =
9564 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9565 let insertion_point = display_map
9566 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9567 .0;
9568
9569 // Don't move lines across excerpt boundaries
9570 if buffer
9571 .excerpt_containing(range_to_move.start..insertion_point)
9572 .is_some()
9573 {
9574 let mut text = String::from("\n");
9575 text.extend(buffer.text_for_range(range_to_move.clone()));
9576 text.pop(); // Drop trailing newline
9577 edits.push((
9578 buffer.anchor_after(range_to_move.start)
9579 ..buffer.anchor_before(range_to_move.end),
9580 String::new(),
9581 ));
9582 let insertion_anchor = buffer.anchor_after(insertion_point);
9583 edits.push((insertion_anchor..insertion_anchor, text));
9584
9585 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9586
9587 // Move selections down
9588 new_selections.extend(contiguous_row_selections.drain(..).map(
9589 |mut selection| {
9590 selection.start.row += row_delta;
9591 selection.end.row += row_delta;
9592 selection
9593 },
9594 ));
9595
9596 // Move folds down
9597 unfold_ranges.push(range_to_move.clone());
9598 for fold in display_map.folds_in_range(
9599 buffer.anchor_before(range_to_move.start)
9600 ..buffer.anchor_after(range_to_move.end),
9601 ) {
9602 let mut start = fold.range.start.to_point(&buffer);
9603 let mut end = fold.range.end.to_point(&buffer);
9604 start.row += row_delta;
9605 end.row += row_delta;
9606 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9607 }
9608 }
9609 }
9610
9611 // If we didn't move line(s), preserve the existing selections
9612 new_selections.append(&mut contiguous_row_selections);
9613 }
9614
9615 self.transact(window, cx, |this, window, cx| {
9616 this.unfold_ranges(&unfold_ranges, true, true, cx);
9617 this.buffer.update(cx, |buffer, cx| {
9618 for (range, text) in edits {
9619 buffer.edit([(range, text)], None, cx);
9620 }
9621 });
9622 this.fold_creases(refold_creases, true, window, cx);
9623 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9624 s.select(new_selections)
9625 });
9626 });
9627 }
9628
9629 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9630 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9631 let text_layout_details = &self.text_layout_details(window);
9632 self.transact(window, cx, |this, window, cx| {
9633 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9634 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9635 s.move_with(|display_map, selection| {
9636 if !selection.is_empty() {
9637 return;
9638 }
9639
9640 let mut head = selection.head();
9641 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9642 if head.column() == display_map.line_len(head.row()) {
9643 transpose_offset = display_map
9644 .buffer_snapshot
9645 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9646 }
9647
9648 if transpose_offset == 0 {
9649 return;
9650 }
9651
9652 *head.column_mut() += 1;
9653 head = display_map.clip_point(head, Bias::Right);
9654 let goal = SelectionGoal::HorizontalPosition(
9655 display_map
9656 .x_for_display_point(head, text_layout_details)
9657 .into(),
9658 );
9659 selection.collapse_to(head, goal);
9660
9661 let transpose_start = display_map
9662 .buffer_snapshot
9663 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9664 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9665 let transpose_end = display_map
9666 .buffer_snapshot
9667 .clip_offset(transpose_offset + 1, Bias::Right);
9668 if let Some(ch) =
9669 display_map.buffer_snapshot.chars_at(transpose_start).next()
9670 {
9671 edits.push((transpose_start..transpose_offset, String::new()));
9672 edits.push((transpose_end..transpose_end, ch.to_string()));
9673 }
9674 }
9675 });
9676 edits
9677 });
9678 this.buffer
9679 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9680 let selections = this.selections.all::<usize>(cx);
9681 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9682 s.select(selections);
9683 });
9684 });
9685 }
9686
9687 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9688 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9689 self.rewrap_impl(RewrapOptions::default(), cx)
9690 }
9691
9692 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9693 let buffer = self.buffer.read(cx).snapshot(cx);
9694 let selections = self.selections.all::<Point>(cx);
9695 let mut selections = selections.iter().peekable();
9696
9697 let mut edits = Vec::new();
9698 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9699
9700 while let Some(selection) = selections.next() {
9701 let mut start_row = selection.start.row;
9702 let mut end_row = selection.end.row;
9703
9704 // Skip selections that overlap with a range that has already been rewrapped.
9705 let selection_range = start_row..end_row;
9706 if rewrapped_row_ranges
9707 .iter()
9708 .any(|range| range.overlaps(&selection_range))
9709 {
9710 continue;
9711 }
9712
9713 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9714
9715 // Since not all lines in the selection may be at the same indent
9716 // level, choose the indent size that is the most common between all
9717 // of the lines.
9718 //
9719 // If there is a tie, we use the deepest indent.
9720 let (indent_size, indent_end) = {
9721 let mut indent_size_occurrences = HashMap::default();
9722 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9723
9724 for row in start_row..=end_row {
9725 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9726 rows_by_indent_size.entry(indent).or_default().push(row);
9727 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9728 }
9729
9730 let indent_size = indent_size_occurrences
9731 .into_iter()
9732 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9733 .map(|(indent, _)| indent)
9734 .unwrap_or_default();
9735 let row = rows_by_indent_size[&indent_size][0];
9736 let indent_end = Point::new(row, indent_size.len);
9737
9738 (indent_size, indent_end)
9739 };
9740
9741 let mut line_prefix = indent_size.chars().collect::<String>();
9742
9743 let mut inside_comment = false;
9744 if let Some(comment_prefix) =
9745 buffer
9746 .language_scope_at(selection.head())
9747 .and_then(|language| {
9748 language
9749 .line_comment_prefixes()
9750 .iter()
9751 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9752 .cloned()
9753 })
9754 {
9755 line_prefix.push_str(&comment_prefix);
9756 inside_comment = true;
9757 }
9758
9759 let language_settings = buffer.language_settings_at(selection.head(), cx);
9760 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9761 RewrapBehavior::InComments => inside_comment,
9762 RewrapBehavior::InSelections => !selection.is_empty(),
9763 RewrapBehavior::Anywhere => true,
9764 };
9765
9766 let should_rewrap = options.override_language_settings
9767 || allow_rewrap_based_on_language
9768 || self.hard_wrap.is_some();
9769 if !should_rewrap {
9770 continue;
9771 }
9772
9773 if selection.is_empty() {
9774 'expand_upwards: while start_row > 0 {
9775 let prev_row = start_row - 1;
9776 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9777 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9778 {
9779 start_row = prev_row;
9780 } else {
9781 break 'expand_upwards;
9782 }
9783 }
9784
9785 'expand_downwards: while end_row < buffer.max_point().row {
9786 let next_row = end_row + 1;
9787 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9788 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9789 {
9790 end_row = next_row;
9791 } else {
9792 break 'expand_downwards;
9793 }
9794 }
9795 }
9796
9797 let start = Point::new(start_row, 0);
9798 let start_offset = start.to_offset(&buffer);
9799 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9800 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9801 let Some(lines_without_prefixes) = selection_text
9802 .lines()
9803 .map(|line| {
9804 line.strip_prefix(&line_prefix)
9805 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9806 .ok_or_else(|| {
9807 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9808 })
9809 })
9810 .collect::<Result<Vec<_>, _>>()
9811 .log_err()
9812 else {
9813 continue;
9814 };
9815
9816 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9817 buffer
9818 .language_settings_at(Point::new(start_row, 0), cx)
9819 .preferred_line_length as usize
9820 });
9821 let wrapped_text = wrap_with_prefix(
9822 line_prefix,
9823 lines_without_prefixes.join("\n"),
9824 wrap_column,
9825 tab_size,
9826 options.preserve_existing_whitespace,
9827 );
9828
9829 // TODO: should always use char-based diff while still supporting cursor behavior that
9830 // matches vim.
9831 let mut diff_options = DiffOptions::default();
9832 if options.override_language_settings {
9833 diff_options.max_word_diff_len = 0;
9834 diff_options.max_word_diff_line_count = 0;
9835 } else {
9836 diff_options.max_word_diff_len = usize::MAX;
9837 diff_options.max_word_diff_line_count = usize::MAX;
9838 }
9839
9840 for (old_range, new_text) in
9841 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9842 {
9843 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9844 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9845 edits.push((edit_start..edit_end, new_text));
9846 }
9847
9848 rewrapped_row_ranges.push(start_row..=end_row);
9849 }
9850
9851 self.buffer
9852 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9853 }
9854
9855 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9856 let mut text = String::new();
9857 let buffer = self.buffer.read(cx).snapshot(cx);
9858 let mut selections = self.selections.all::<Point>(cx);
9859 let mut clipboard_selections = Vec::with_capacity(selections.len());
9860 {
9861 let max_point = buffer.max_point();
9862 let mut is_first = true;
9863 for selection in &mut selections {
9864 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9865 if is_entire_line {
9866 selection.start = Point::new(selection.start.row, 0);
9867 if !selection.is_empty() && selection.end.column == 0 {
9868 selection.end = cmp::min(max_point, selection.end);
9869 } else {
9870 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9871 }
9872 selection.goal = SelectionGoal::None;
9873 }
9874 if is_first {
9875 is_first = false;
9876 } else {
9877 text += "\n";
9878 }
9879 let mut len = 0;
9880 for chunk in buffer.text_for_range(selection.start..selection.end) {
9881 text.push_str(chunk);
9882 len += chunk.len();
9883 }
9884 clipboard_selections.push(ClipboardSelection {
9885 len,
9886 is_entire_line,
9887 first_line_indent: buffer
9888 .indent_size_for_line(MultiBufferRow(selection.start.row))
9889 .len,
9890 });
9891 }
9892 }
9893
9894 self.transact(window, cx, |this, window, cx| {
9895 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9896 s.select(selections);
9897 });
9898 this.insert("", window, cx);
9899 });
9900 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9901 }
9902
9903 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9904 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9905 let item = self.cut_common(window, cx);
9906 cx.write_to_clipboard(item);
9907 }
9908
9909 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9910 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9911 self.change_selections(None, window, cx, |s| {
9912 s.move_with(|snapshot, sel| {
9913 if sel.is_empty() {
9914 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9915 }
9916 });
9917 });
9918 let item = self.cut_common(window, cx);
9919 cx.set_global(KillRing(item))
9920 }
9921
9922 pub fn kill_ring_yank(
9923 &mut self,
9924 _: &KillRingYank,
9925 window: &mut Window,
9926 cx: &mut Context<Self>,
9927 ) {
9928 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9929 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9930 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9931 (kill_ring.text().to_string(), kill_ring.metadata_json())
9932 } else {
9933 return;
9934 }
9935 } else {
9936 return;
9937 };
9938 self.do_paste(&text, metadata, false, window, cx);
9939 }
9940
9941 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9942 self.do_copy(true, cx);
9943 }
9944
9945 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9946 self.do_copy(false, cx);
9947 }
9948
9949 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9950 let selections = self.selections.all::<Point>(cx);
9951 let buffer = self.buffer.read(cx).read(cx);
9952 let mut text = String::new();
9953
9954 let mut clipboard_selections = Vec::with_capacity(selections.len());
9955 {
9956 let max_point = buffer.max_point();
9957 let mut is_first = true;
9958 for selection in &selections {
9959 let mut start = selection.start;
9960 let mut end = selection.end;
9961 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9962 if is_entire_line {
9963 start = Point::new(start.row, 0);
9964 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9965 }
9966
9967 let mut trimmed_selections = Vec::new();
9968 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
9969 let row = MultiBufferRow(start.row);
9970 let first_indent = buffer.indent_size_for_line(row);
9971 if first_indent.len == 0 || start.column > first_indent.len {
9972 trimmed_selections.push(start..end);
9973 } else {
9974 trimmed_selections.push(
9975 Point::new(row.0, first_indent.len)
9976 ..Point::new(row.0, buffer.line_len(row)),
9977 );
9978 for row in start.row + 1..=end.row {
9979 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
9980 if row_indent_size.len >= first_indent.len {
9981 trimmed_selections.push(
9982 Point::new(row, first_indent.len)
9983 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
9984 );
9985 } else {
9986 trimmed_selections.clear();
9987 trimmed_selections.push(start..end);
9988 break;
9989 }
9990 }
9991 }
9992 } else {
9993 trimmed_selections.push(start..end);
9994 }
9995
9996 for trimmed_range in trimmed_selections {
9997 if is_first {
9998 is_first = false;
9999 } else {
10000 text += "\n";
10001 }
10002 let mut len = 0;
10003 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10004 text.push_str(chunk);
10005 len += chunk.len();
10006 }
10007 clipboard_selections.push(ClipboardSelection {
10008 len,
10009 is_entire_line,
10010 first_line_indent: buffer
10011 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10012 .len,
10013 });
10014 }
10015 }
10016 }
10017
10018 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10019 text,
10020 clipboard_selections,
10021 ));
10022 }
10023
10024 pub fn do_paste(
10025 &mut self,
10026 text: &String,
10027 clipboard_selections: Option<Vec<ClipboardSelection>>,
10028 handle_entire_lines: bool,
10029 window: &mut Window,
10030 cx: &mut Context<Self>,
10031 ) {
10032 if self.read_only(cx) {
10033 return;
10034 }
10035
10036 let clipboard_text = Cow::Borrowed(text);
10037
10038 self.transact(window, cx, |this, window, cx| {
10039 if let Some(mut clipboard_selections) = clipboard_selections {
10040 let old_selections = this.selections.all::<usize>(cx);
10041 let all_selections_were_entire_line =
10042 clipboard_selections.iter().all(|s| s.is_entire_line);
10043 let first_selection_indent_column =
10044 clipboard_selections.first().map(|s| s.first_line_indent);
10045 if clipboard_selections.len() != old_selections.len() {
10046 clipboard_selections.drain(..);
10047 }
10048 let cursor_offset = this.selections.last::<usize>(cx).head();
10049 let mut auto_indent_on_paste = true;
10050
10051 this.buffer.update(cx, |buffer, cx| {
10052 let snapshot = buffer.read(cx);
10053 auto_indent_on_paste = snapshot
10054 .language_settings_at(cursor_offset, cx)
10055 .auto_indent_on_paste;
10056
10057 let mut start_offset = 0;
10058 let mut edits = Vec::new();
10059 let mut original_indent_columns = Vec::new();
10060 for (ix, selection) in old_selections.iter().enumerate() {
10061 let to_insert;
10062 let entire_line;
10063 let original_indent_column;
10064 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10065 let end_offset = start_offset + clipboard_selection.len;
10066 to_insert = &clipboard_text[start_offset..end_offset];
10067 entire_line = clipboard_selection.is_entire_line;
10068 start_offset = end_offset + 1;
10069 original_indent_column = Some(clipboard_selection.first_line_indent);
10070 } else {
10071 to_insert = clipboard_text.as_str();
10072 entire_line = all_selections_were_entire_line;
10073 original_indent_column = first_selection_indent_column
10074 }
10075
10076 // If the corresponding selection was empty when this slice of the
10077 // clipboard text was written, then the entire line containing the
10078 // selection was copied. If this selection is also currently empty,
10079 // then paste the line before the current line of the buffer.
10080 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10081 let column = selection.start.to_point(&snapshot).column as usize;
10082 let line_start = selection.start - column;
10083 line_start..line_start
10084 } else {
10085 selection.range()
10086 };
10087
10088 edits.push((range, to_insert));
10089 original_indent_columns.push(original_indent_column);
10090 }
10091 drop(snapshot);
10092
10093 buffer.edit(
10094 edits,
10095 if auto_indent_on_paste {
10096 Some(AutoindentMode::Block {
10097 original_indent_columns,
10098 })
10099 } else {
10100 None
10101 },
10102 cx,
10103 );
10104 });
10105
10106 let selections = this.selections.all::<usize>(cx);
10107 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10108 s.select(selections)
10109 });
10110 } else {
10111 this.insert(&clipboard_text, window, cx);
10112 }
10113 });
10114 }
10115
10116 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10117 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10118 if let Some(item) = cx.read_from_clipboard() {
10119 let entries = item.entries();
10120
10121 match entries.first() {
10122 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10123 // of all the pasted entries.
10124 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10125 .do_paste(
10126 clipboard_string.text(),
10127 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10128 true,
10129 window,
10130 cx,
10131 ),
10132 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10133 }
10134 }
10135 }
10136
10137 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10138 if self.read_only(cx) {
10139 return;
10140 }
10141
10142 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10143
10144 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10145 if let Some((selections, _)) =
10146 self.selection_history.transaction(transaction_id).cloned()
10147 {
10148 self.change_selections(None, window, cx, |s| {
10149 s.select_anchors(selections.to_vec());
10150 });
10151 } else {
10152 log::error!(
10153 "No entry in selection_history found for undo. \
10154 This may correspond to a bug where undo does not update the selection. \
10155 If this is occurring, please add details to \
10156 https://github.com/zed-industries/zed/issues/22692"
10157 );
10158 }
10159 self.request_autoscroll(Autoscroll::fit(), cx);
10160 self.unmark_text(window, cx);
10161 self.refresh_inline_completion(true, false, window, cx);
10162 cx.emit(EditorEvent::Edited { transaction_id });
10163 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10164 }
10165 }
10166
10167 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10168 if self.read_only(cx) {
10169 return;
10170 }
10171
10172 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10173
10174 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10175 if let Some((_, Some(selections))) =
10176 self.selection_history.transaction(transaction_id).cloned()
10177 {
10178 self.change_selections(None, window, cx, |s| {
10179 s.select_anchors(selections.to_vec());
10180 });
10181 } else {
10182 log::error!(
10183 "No entry in selection_history found for redo. \
10184 This may correspond to a bug where undo does not update the selection. \
10185 If this is occurring, please add details to \
10186 https://github.com/zed-industries/zed/issues/22692"
10187 );
10188 }
10189 self.request_autoscroll(Autoscroll::fit(), cx);
10190 self.unmark_text(window, cx);
10191 self.refresh_inline_completion(true, false, window, cx);
10192 cx.emit(EditorEvent::Edited { transaction_id });
10193 }
10194 }
10195
10196 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10197 self.buffer
10198 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10199 }
10200
10201 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10202 self.buffer
10203 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10204 }
10205
10206 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10207 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10208 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10209 s.move_with(|map, selection| {
10210 let cursor = if selection.is_empty() {
10211 movement::left(map, selection.start)
10212 } else {
10213 selection.start
10214 };
10215 selection.collapse_to(cursor, SelectionGoal::None);
10216 });
10217 })
10218 }
10219
10220 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10221 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10222 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10223 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10224 })
10225 }
10226
10227 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10228 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10229 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10230 s.move_with(|map, selection| {
10231 let cursor = if selection.is_empty() {
10232 movement::right(map, selection.end)
10233 } else {
10234 selection.end
10235 };
10236 selection.collapse_to(cursor, SelectionGoal::None)
10237 });
10238 })
10239 }
10240
10241 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10242 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10243 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10244 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10245 })
10246 }
10247
10248 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10249 if self.take_rename(true, window, cx).is_some() {
10250 return;
10251 }
10252
10253 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10254 cx.propagate();
10255 return;
10256 }
10257
10258 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10259
10260 let text_layout_details = &self.text_layout_details(window);
10261 let selection_count = self.selections.count();
10262 let first_selection = self.selections.first_anchor();
10263
10264 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10265 s.move_with(|map, selection| {
10266 if !selection.is_empty() {
10267 selection.goal = SelectionGoal::None;
10268 }
10269 let (cursor, goal) = movement::up(
10270 map,
10271 selection.start,
10272 selection.goal,
10273 false,
10274 text_layout_details,
10275 );
10276 selection.collapse_to(cursor, goal);
10277 });
10278 });
10279
10280 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10281 {
10282 cx.propagate();
10283 }
10284 }
10285
10286 pub fn move_up_by_lines(
10287 &mut self,
10288 action: &MoveUpByLines,
10289 window: &mut Window,
10290 cx: &mut Context<Self>,
10291 ) {
10292 if self.take_rename(true, window, cx).is_some() {
10293 return;
10294 }
10295
10296 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10297 cx.propagate();
10298 return;
10299 }
10300
10301 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10302
10303 let text_layout_details = &self.text_layout_details(window);
10304
10305 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10306 s.move_with(|map, selection| {
10307 if !selection.is_empty() {
10308 selection.goal = SelectionGoal::None;
10309 }
10310 let (cursor, goal) = movement::up_by_rows(
10311 map,
10312 selection.start,
10313 action.lines,
10314 selection.goal,
10315 false,
10316 text_layout_details,
10317 );
10318 selection.collapse_to(cursor, goal);
10319 });
10320 })
10321 }
10322
10323 pub fn move_down_by_lines(
10324 &mut self,
10325 action: &MoveDownByLines,
10326 window: &mut Window,
10327 cx: &mut Context<Self>,
10328 ) {
10329 if self.take_rename(true, window, cx).is_some() {
10330 return;
10331 }
10332
10333 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10334 cx.propagate();
10335 return;
10336 }
10337
10338 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10339
10340 let text_layout_details = &self.text_layout_details(window);
10341
10342 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10343 s.move_with(|map, selection| {
10344 if !selection.is_empty() {
10345 selection.goal = SelectionGoal::None;
10346 }
10347 let (cursor, goal) = movement::down_by_rows(
10348 map,
10349 selection.start,
10350 action.lines,
10351 selection.goal,
10352 false,
10353 text_layout_details,
10354 );
10355 selection.collapse_to(cursor, goal);
10356 });
10357 })
10358 }
10359
10360 pub fn select_down_by_lines(
10361 &mut self,
10362 action: &SelectDownByLines,
10363 window: &mut Window,
10364 cx: &mut Context<Self>,
10365 ) {
10366 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10367 let text_layout_details = &self.text_layout_details(window);
10368 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10369 s.move_heads_with(|map, head, goal| {
10370 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10371 })
10372 })
10373 }
10374
10375 pub fn select_up_by_lines(
10376 &mut self,
10377 action: &SelectUpByLines,
10378 window: &mut Window,
10379 cx: &mut Context<Self>,
10380 ) {
10381 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10382 let text_layout_details = &self.text_layout_details(window);
10383 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10384 s.move_heads_with(|map, head, goal| {
10385 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10386 })
10387 })
10388 }
10389
10390 pub fn select_page_up(
10391 &mut self,
10392 _: &SelectPageUp,
10393 window: &mut Window,
10394 cx: &mut Context<Self>,
10395 ) {
10396 let Some(row_count) = self.visible_row_count() else {
10397 return;
10398 };
10399
10400 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10401
10402 let text_layout_details = &self.text_layout_details(window);
10403
10404 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10405 s.move_heads_with(|map, head, goal| {
10406 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10407 })
10408 })
10409 }
10410
10411 pub fn move_page_up(
10412 &mut self,
10413 action: &MovePageUp,
10414 window: &mut Window,
10415 cx: &mut Context<Self>,
10416 ) {
10417 if self.take_rename(true, window, cx).is_some() {
10418 return;
10419 }
10420
10421 if self
10422 .context_menu
10423 .borrow_mut()
10424 .as_mut()
10425 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10426 .unwrap_or(false)
10427 {
10428 return;
10429 }
10430
10431 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10432 cx.propagate();
10433 return;
10434 }
10435
10436 let Some(row_count) = self.visible_row_count() else {
10437 return;
10438 };
10439
10440 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10441
10442 let autoscroll = if action.center_cursor {
10443 Autoscroll::center()
10444 } else {
10445 Autoscroll::fit()
10446 };
10447
10448 let text_layout_details = &self.text_layout_details(window);
10449
10450 self.change_selections(Some(autoscroll), window, cx, |s| {
10451 s.move_with(|map, selection| {
10452 if !selection.is_empty() {
10453 selection.goal = SelectionGoal::None;
10454 }
10455 let (cursor, goal) = movement::up_by_rows(
10456 map,
10457 selection.end,
10458 row_count,
10459 selection.goal,
10460 false,
10461 text_layout_details,
10462 );
10463 selection.collapse_to(cursor, goal);
10464 });
10465 });
10466 }
10467
10468 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10469 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10470 let text_layout_details = &self.text_layout_details(window);
10471 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10472 s.move_heads_with(|map, head, goal| {
10473 movement::up(map, head, goal, false, text_layout_details)
10474 })
10475 })
10476 }
10477
10478 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10479 self.take_rename(true, window, cx);
10480
10481 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10482 cx.propagate();
10483 return;
10484 }
10485
10486 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10487
10488 let text_layout_details = &self.text_layout_details(window);
10489 let selection_count = self.selections.count();
10490 let first_selection = self.selections.first_anchor();
10491
10492 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10493 s.move_with(|map, selection| {
10494 if !selection.is_empty() {
10495 selection.goal = SelectionGoal::None;
10496 }
10497 let (cursor, goal) = movement::down(
10498 map,
10499 selection.end,
10500 selection.goal,
10501 false,
10502 text_layout_details,
10503 );
10504 selection.collapse_to(cursor, goal);
10505 });
10506 });
10507
10508 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10509 {
10510 cx.propagate();
10511 }
10512 }
10513
10514 pub fn select_page_down(
10515 &mut self,
10516 _: &SelectPageDown,
10517 window: &mut Window,
10518 cx: &mut Context<Self>,
10519 ) {
10520 let Some(row_count) = self.visible_row_count() else {
10521 return;
10522 };
10523
10524 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10525
10526 let text_layout_details = &self.text_layout_details(window);
10527
10528 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10529 s.move_heads_with(|map, head, goal| {
10530 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10531 })
10532 })
10533 }
10534
10535 pub fn move_page_down(
10536 &mut self,
10537 action: &MovePageDown,
10538 window: &mut Window,
10539 cx: &mut Context<Self>,
10540 ) {
10541 if self.take_rename(true, window, cx).is_some() {
10542 return;
10543 }
10544
10545 if self
10546 .context_menu
10547 .borrow_mut()
10548 .as_mut()
10549 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10550 .unwrap_or(false)
10551 {
10552 return;
10553 }
10554
10555 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10556 cx.propagate();
10557 return;
10558 }
10559
10560 let Some(row_count) = self.visible_row_count() else {
10561 return;
10562 };
10563
10564 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10565
10566 let autoscroll = if action.center_cursor {
10567 Autoscroll::center()
10568 } else {
10569 Autoscroll::fit()
10570 };
10571
10572 let text_layout_details = &self.text_layout_details(window);
10573 self.change_selections(Some(autoscroll), window, cx, |s| {
10574 s.move_with(|map, selection| {
10575 if !selection.is_empty() {
10576 selection.goal = SelectionGoal::None;
10577 }
10578 let (cursor, goal) = movement::down_by_rows(
10579 map,
10580 selection.end,
10581 row_count,
10582 selection.goal,
10583 false,
10584 text_layout_details,
10585 );
10586 selection.collapse_to(cursor, goal);
10587 });
10588 });
10589 }
10590
10591 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10592 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10593 let text_layout_details = &self.text_layout_details(window);
10594 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10595 s.move_heads_with(|map, head, goal| {
10596 movement::down(map, head, goal, false, text_layout_details)
10597 })
10598 });
10599 }
10600
10601 pub fn context_menu_first(
10602 &mut self,
10603 _: &ContextMenuFirst,
10604 _window: &mut Window,
10605 cx: &mut Context<Self>,
10606 ) {
10607 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10608 context_menu.select_first(self.completion_provider.as_deref(), cx);
10609 }
10610 }
10611
10612 pub fn context_menu_prev(
10613 &mut self,
10614 _: &ContextMenuPrevious,
10615 _window: &mut Window,
10616 cx: &mut Context<Self>,
10617 ) {
10618 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10619 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10620 }
10621 }
10622
10623 pub fn context_menu_next(
10624 &mut self,
10625 _: &ContextMenuNext,
10626 _window: &mut Window,
10627 cx: &mut Context<Self>,
10628 ) {
10629 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10630 context_menu.select_next(self.completion_provider.as_deref(), cx);
10631 }
10632 }
10633
10634 pub fn context_menu_last(
10635 &mut self,
10636 _: &ContextMenuLast,
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_last(self.completion_provider.as_deref(), cx);
10642 }
10643 }
10644
10645 pub fn move_to_previous_word_start(
10646 &mut self,
10647 _: &MoveToPreviousWordStart,
10648 window: &mut Window,
10649 cx: &mut Context<Self>,
10650 ) {
10651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10652 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10653 s.move_cursors_with(|map, head, _| {
10654 (
10655 movement::previous_word_start(map, head),
10656 SelectionGoal::None,
10657 )
10658 });
10659 })
10660 }
10661
10662 pub fn move_to_previous_subword_start(
10663 &mut self,
10664 _: &MoveToPreviousSubwordStart,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10669 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10670 s.move_cursors_with(|map, head, _| {
10671 (
10672 movement::previous_subword_start(map, head),
10673 SelectionGoal::None,
10674 )
10675 });
10676 })
10677 }
10678
10679 pub fn select_to_previous_word_start(
10680 &mut self,
10681 _: &SelectToPreviousWordStart,
10682 window: &mut Window,
10683 cx: &mut Context<Self>,
10684 ) {
10685 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10686 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10687 s.move_heads_with(|map, head, _| {
10688 (
10689 movement::previous_word_start(map, head),
10690 SelectionGoal::None,
10691 )
10692 });
10693 })
10694 }
10695
10696 pub fn select_to_previous_subword_start(
10697 &mut self,
10698 _: &SelectToPreviousSubwordStart,
10699 window: &mut Window,
10700 cx: &mut Context<Self>,
10701 ) {
10702 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10703 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10704 s.move_heads_with(|map, head, _| {
10705 (
10706 movement::previous_subword_start(map, head),
10707 SelectionGoal::None,
10708 )
10709 });
10710 })
10711 }
10712
10713 pub fn delete_to_previous_word_start(
10714 &mut self,
10715 action: &DeleteToPreviousWordStart,
10716 window: &mut Window,
10717 cx: &mut Context<Self>,
10718 ) {
10719 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10720 self.transact(window, cx, |this, window, cx| {
10721 this.select_autoclose_pair(window, cx);
10722 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10723 s.move_with(|map, selection| {
10724 if selection.is_empty() {
10725 let cursor = if action.ignore_newlines {
10726 movement::previous_word_start(map, selection.head())
10727 } else {
10728 movement::previous_word_start_or_newline(map, selection.head())
10729 };
10730 selection.set_head(cursor, SelectionGoal::None);
10731 }
10732 });
10733 });
10734 this.insert("", window, cx);
10735 });
10736 }
10737
10738 pub fn delete_to_previous_subword_start(
10739 &mut self,
10740 _: &DeleteToPreviousSubwordStart,
10741 window: &mut Window,
10742 cx: &mut Context<Self>,
10743 ) {
10744 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10745 self.transact(window, cx, |this, window, cx| {
10746 this.select_autoclose_pair(window, cx);
10747 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10748 s.move_with(|map, selection| {
10749 if selection.is_empty() {
10750 let cursor = movement::previous_subword_start(map, selection.head());
10751 selection.set_head(cursor, SelectionGoal::None);
10752 }
10753 });
10754 });
10755 this.insert("", window, cx);
10756 });
10757 }
10758
10759 pub fn move_to_next_word_end(
10760 &mut self,
10761 _: &MoveToNextWordEnd,
10762 window: &mut Window,
10763 cx: &mut Context<Self>,
10764 ) {
10765 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10766 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10767 s.move_cursors_with(|map, head, _| {
10768 (movement::next_word_end(map, head), SelectionGoal::None)
10769 });
10770 })
10771 }
10772
10773 pub fn move_to_next_subword_end(
10774 &mut self,
10775 _: &MoveToNextSubwordEnd,
10776 window: &mut Window,
10777 cx: &mut Context<Self>,
10778 ) {
10779 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10780 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10781 s.move_cursors_with(|map, head, _| {
10782 (movement::next_subword_end(map, head), SelectionGoal::None)
10783 });
10784 })
10785 }
10786
10787 pub fn select_to_next_word_end(
10788 &mut self,
10789 _: &SelectToNextWordEnd,
10790 window: &mut Window,
10791 cx: &mut Context<Self>,
10792 ) {
10793 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10794 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10795 s.move_heads_with(|map, head, _| {
10796 (movement::next_word_end(map, head), SelectionGoal::None)
10797 });
10798 })
10799 }
10800
10801 pub fn select_to_next_subword_end(
10802 &mut self,
10803 _: &SelectToNextSubwordEnd,
10804 window: &mut Window,
10805 cx: &mut Context<Self>,
10806 ) {
10807 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10808 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10809 s.move_heads_with(|map, head, _| {
10810 (movement::next_subword_end(map, head), SelectionGoal::None)
10811 });
10812 })
10813 }
10814
10815 pub fn delete_to_next_word_end(
10816 &mut self,
10817 action: &DeleteToNextWordEnd,
10818 window: &mut Window,
10819 cx: &mut Context<Self>,
10820 ) {
10821 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10822 self.transact(window, cx, |this, window, cx| {
10823 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10824 s.move_with(|map, selection| {
10825 if selection.is_empty() {
10826 let cursor = if action.ignore_newlines {
10827 movement::next_word_end(map, selection.head())
10828 } else {
10829 movement::next_word_end_or_newline(map, selection.head())
10830 };
10831 selection.set_head(cursor, SelectionGoal::None);
10832 }
10833 });
10834 });
10835 this.insert("", window, cx);
10836 });
10837 }
10838
10839 pub fn delete_to_next_subword_end(
10840 &mut self,
10841 _: &DeleteToNextSubwordEnd,
10842 window: &mut Window,
10843 cx: &mut Context<Self>,
10844 ) {
10845 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10846 self.transact(window, cx, |this, window, cx| {
10847 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10848 s.move_with(|map, selection| {
10849 if selection.is_empty() {
10850 let cursor = movement::next_subword_end(map, selection.head());
10851 selection.set_head(cursor, SelectionGoal::None);
10852 }
10853 });
10854 });
10855 this.insert("", window, cx);
10856 });
10857 }
10858
10859 pub fn move_to_beginning_of_line(
10860 &mut self,
10861 action: &MoveToBeginningOfLine,
10862 window: &mut Window,
10863 cx: &mut Context<Self>,
10864 ) {
10865 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10866 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10867 s.move_cursors_with(|map, head, _| {
10868 (
10869 movement::indented_line_beginning(
10870 map,
10871 head,
10872 action.stop_at_soft_wraps,
10873 action.stop_at_indent,
10874 ),
10875 SelectionGoal::None,
10876 )
10877 });
10878 })
10879 }
10880
10881 pub fn select_to_beginning_of_line(
10882 &mut self,
10883 action: &SelectToBeginningOfLine,
10884 window: &mut Window,
10885 cx: &mut Context<Self>,
10886 ) {
10887 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10888 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10889 s.move_heads_with(|map, head, _| {
10890 (
10891 movement::indented_line_beginning(
10892 map,
10893 head,
10894 action.stop_at_soft_wraps,
10895 action.stop_at_indent,
10896 ),
10897 SelectionGoal::None,
10898 )
10899 });
10900 });
10901 }
10902
10903 pub fn delete_to_beginning_of_line(
10904 &mut self,
10905 action: &DeleteToBeginningOfLine,
10906 window: &mut Window,
10907 cx: &mut Context<Self>,
10908 ) {
10909 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10910 self.transact(window, cx, |this, window, cx| {
10911 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10912 s.move_with(|_, selection| {
10913 selection.reversed = true;
10914 });
10915 });
10916
10917 this.select_to_beginning_of_line(
10918 &SelectToBeginningOfLine {
10919 stop_at_soft_wraps: false,
10920 stop_at_indent: action.stop_at_indent,
10921 },
10922 window,
10923 cx,
10924 );
10925 this.backspace(&Backspace, window, cx);
10926 });
10927 }
10928
10929 pub fn move_to_end_of_line(
10930 &mut self,
10931 action: &MoveToEndOfLine,
10932 window: &mut Window,
10933 cx: &mut Context<Self>,
10934 ) {
10935 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10936 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10937 s.move_cursors_with(|map, head, _| {
10938 (
10939 movement::line_end(map, head, action.stop_at_soft_wraps),
10940 SelectionGoal::None,
10941 )
10942 });
10943 })
10944 }
10945
10946 pub fn select_to_end_of_line(
10947 &mut self,
10948 action: &SelectToEndOfLine,
10949 window: &mut Window,
10950 cx: &mut Context<Self>,
10951 ) {
10952 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10953 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10954 s.move_heads_with(|map, head, _| {
10955 (
10956 movement::line_end(map, head, action.stop_at_soft_wraps),
10957 SelectionGoal::None,
10958 )
10959 });
10960 })
10961 }
10962
10963 pub fn delete_to_end_of_line(
10964 &mut self,
10965 _: &DeleteToEndOfLine,
10966 window: &mut Window,
10967 cx: &mut Context<Self>,
10968 ) {
10969 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10970 self.transact(window, cx, |this, window, cx| {
10971 this.select_to_end_of_line(
10972 &SelectToEndOfLine {
10973 stop_at_soft_wraps: false,
10974 },
10975 window,
10976 cx,
10977 );
10978 this.delete(&Delete, window, cx);
10979 });
10980 }
10981
10982 pub fn cut_to_end_of_line(
10983 &mut self,
10984 _: &CutToEndOfLine,
10985 window: &mut Window,
10986 cx: &mut Context<Self>,
10987 ) {
10988 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10989 self.transact(window, cx, |this, window, cx| {
10990 this.select_to_end_of_line(
10991 &SelectToEndOfLine {
10992 stop_at_soft_wraps: false,
10993 },
10994 window,
10995 cx,
10996 );
10997 this.cut(&Cut, window, cx);
10998 });
10999 }
11000
11001 pub fn move_to_start_of_paragraph(
11002 &mut self,
11003 _: &MoveToStartOfParagraph,
11004 window: &mut Window,
11005 cx: &mut Context<Self>,
11006 ) {
11007 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11008 cx.propagate();
11009 return;
11010 }
11011 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11012 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11013 s.move_with(|map, selection| {
11014 selection.collapse_to(
11015 movement::start_of_paragraph(map, selection.head(), 1),
11016 SelectionGoal::None,
11017 )
11018 });
11019 })
11020 }
11021
11022 pub fn move_to_end_of_paragraph(
11023 &mut self,
11024 _: &MoveToEndOfParagraph,
11025 window: &mut Window,
11026 cx: &mut Context<Self>,
11027 ) {
11028 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11029 cx.propagate();
11030 return;
11031 }
11032 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11033 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11034 s.move_with(|map, selection| {
11035 selection.collapse_to(
11036 movement::end_of_paragraph(map, selection.head(), 1),
11037 SelectionGoal::None,
11038 )
11039 });
11040 })
11041 }
11042
11043 pub fn select_to_start_of_paragraph(
11044 &mut self,
11045 _: &SelectToStartOfParagraph,
11046 window: &mut Window,
11047 cx: &mut Context<Self>,
11048 ) {
11049 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11050 cx.propagate();
11051 return;
11052 }
11053 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11054 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11055 s.move_heads_with(|map, head, _| {
11056 (
11057 movement::start_of_paragraph(map, head, 1),
11058 SelectionGoal::None,
11059 )
11060 });
11061 })
11062 }
11063
11064 pub fn select_to_end_of_paragraph(
11065 &mut self,
11066 _: &SelectToEndOfParagraph,
11067 window: &mut Window,
11068 cx: &mut Context<Self>,
11069 ) {
11070 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11071 cx.propagate();
11072 return;
11073 }
11074 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11075 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11076 s.move_heads_with(|map, head, _| {
11077 (
11078 movement::end_of_paragraph(map, head, 1),
11079 SelectionGoal::None,
11080 )
11081 });
11082 })
11083 }
11084
11085 pub fn move_to_start_of_excerpt(
11086 &mut self,
11087 _: &MoveToStartOfExcerpt,
11088 window: &mut Window,
11089 cx: &mut Context<Self>,
11090 ) {
11091 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11092 cx.propagate();
11093 return;
11094 }
11095 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11096 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11097 s.move_with(|map, selection| {
11098 selection.collapse_to(
11099 movement::start_of_excerpt(
11100 map,
11101 selection.head(),
11102 workspace::searchable::Direction::Prev,
11103 ),
11104 SelectionGoal::None,
11105 )
11106 });
11107 })
11108 }
11109
11110 pub fn move_to_start_of_next_excerpt(
11111 &mut self,
11112 _: &MoveToStartOfNextExcerpt,
11113 window: &mut Window,
11114 cx: &mut Context<Self>,
11115 ) {
11116 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11117 cx.propagate();
11118 return;
11119 }
11120
11121 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11122 s.move_with(|map, selection| {
11123 selection.collapse_to(
11124 movement::start_of_excerpt(
11125 map,
11126 selection.head(),
11127 workspace::searchable::Direction::Next,
11128 ),
11129 SelectionGoal::None,
11130 )
11131 });
11132 })
11133 }
11134
11135 pub fn move_to_end_of_excerpt(
11136 &mut self,
11137 _: &MoveToEndOfExcerpt,
11138 window: &mut Window,
11139 cx: &mut Context<Self>,
11140 ) {
11141 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11142 cx.propagate();
11143 return;
11144 }
11145 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11146 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11147 s.move_with(|map, selection| {
11148 selection.collapse_to(
11149 movement::end_of_excerpt(
11150 map,
11151 selection.head(),
11152 workspace::searchable::Direction::Next,
11153 ),
11154 SelectionGoal::None,
11155 )
11156 });
11157 })
11158 }
11159
11160 pub fn move_to_end_of_previous_excerpt(
11161 &mut self,
11162 _: &MoveToEndOfPreviousExcerpt,
11163 window: &mut Window,
11164 cx: &mut Context<Self>,
11165 ) {
11166 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11167 cx.propagate();
11168 return;
11169 }
11170 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11171 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11172 s.move_with(|map, selection| {
11173 selection.collapse_to(
11174 movement::end_of_excerpt(
11175 map,
11176 selection.head(),
11177 workspace::searchable::Direction::Prev,
11178 ),
11179 SelectionGoal::None,
11180 )
11181 });
11182 })
11183 }
11184
11185 pub fn select_to_start_of_excerpt(
11186 &mut self,
11187 _: &SelectToStartOfExcerpt,
11188 window: &mut Window,
11189 cx: &mut Context<Self>,
11190 ) {
11191 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11192 cx.propagate();
11193 return;
11194 }
11195 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11196 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11197 s.move_heads_with(|map, head, _| {
11198 (
11199 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11200 SelectionGoal::None,
11201 )
11202 });
11203 })
11204 }
11205
11206 pub fn select_to_start_of_next_excerpt(
11207 &mut self,
11208 _: &SelectToStartOfNextExcerpt,
11209 window: &mut Window,
11210 cx: &mut Context<Self>,
11211 ) {
11212 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11213 cx.propagate();
11214 return;
11215 }
11216 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11217 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11218 s.move_heads_with(|map, head, _| {
11219 (
11220 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11221 SelectionGoal::None,
11222 )
11223 });
11224 })
11225 }
11226
11227 pub fn select_to_end_of_excerpt(
11228 &mut self,
11229 _: &SelectToEndOfExcerpt,
11230 window: &mut Window,
11231 cx: &mut Context<Self>,
11232 ) {
11233 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11234 cx.propagate();
11235 return;
11236 }
11237 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11238 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11239 s.move_heads_with(|map, head, _| {
11240 (
11241 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11242 SelectionGoal::None,
11243 )
11244 });
11245 })
11246 }
11247
11248 pub fn select_to_end_of_previous_excerpt(
11249 &mut self,
11250 _: &SelectToEndOfPreviousExcerpt,
11251 window: &mut Window,
11252 cx: &mut Context<Self>,
11253 ) {
11254 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11255 cx.propagate();
11256 return;
11257 }
11258 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11259 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11260 s.move_heads_with(|map, head, _| {
11261 (
11262 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11263 SelectionGoal::None,
11264 )
11265 });
11266 })
11267 }
11268
11269 pub fn move_to_beginning(
11270 &mut self,
11271 _: &MoveToBeginning,
11272 window: &mut Window,
11273 cx: &mut Context<Self>,
11274 ) {
11275 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11276 cx.propagate();
11277 return;
11278 }
11279 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11280 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11281 s.select_ranges(vec![0..0]);
11282 });
11283 }
11284
11285 pub fn select_to_beginning(
11286 &mut self,
11287 _: &SelectToBeginning,
11288 window: &mut Window,
11289 cx: &mut Context<Self>,
11290 ) {
11291 let mut selection = self.selections.last::<Point>(cx);
11292 selection.set_head(Point::zero(), SelectionGoal::None);
11293 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11294 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11295 s.select(vec![selection]);
11296 });
11297 }
11298
11299 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11300 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11301 cx.propagate();
11302 return;
11303 }
11304 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11305 let cursor = self.buffer.read(cx).read(cx).len();
11306 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11307 s.select_ranges(vec![cursor..cursor])
11308 });
11309 }
11310
11311 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11312 self.nav_history = nav_history;
11313 }
11314
11315 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11316 self.nav_history.as_ref()
11317 }
11318
11319 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11320 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11321 }
11322
11323 fn push_to_nav_history(
11324 &mut self,
11325 cursor_anchor: Anchor,
11326 new_position: Option<Point>,
11327 is_deactivate: bool,
11328 cx: &mut Context<Self>,
11329 ) {
11330 if let Some(nav_history) = self.nav_history.as_mut() {
11331 let buffer = self.buffer.read(cx).read(cx);
11332 let cursor_position = cursor_anchor.to_point(&buffer);
11333 let scroll_state = self.scroll_manager.anchor();
11334 let scroll_top_row = scroll_state.top_row(&buffer);
11335 drop(buffer);
11336
11337 if let Some(new_position) = new_position {
11338 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11339 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11340 return;
11341 }
11342 }
11343
11344 nav_history.push(
11345 Some(NavigationData {
11346 cursor_anchor,
11347 cursor_position,
11348 scroll_anchor: scroll_state,
11349 scroll_top_row,
11350 }),
11351 cx,
11352 );
11353 cx.emit(EditorEvent::PushedToNavHistory {
11354 anchor: cursor_anchor,
11355 is_deactivate,
11356 })
11357 }
11358 }
11359
11360 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11361 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11362 let buffer = self.buffer.read(cx).snapshot(cx);
11363 let mut selection = self.selections.first::<usize>(cx);
11364 selection.set_head(buffer.len(), SelectionGoal::None);
11365 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11366 s.select(vec![selection]);
11367 });
11368 }
11369
11370 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11371 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11372 let end = self.buffer.read(cx).read(cx).len();
11373 self.change_selections(None, window, cx, |s| {
11374 s.select_ranges(vec![0..end]);
11375 });
11376 }
11377
11378 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11379 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11380 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11381 let mut selections = self.selections.all::<Point>(cx);
11382 let max_point = display_map.buffer_snapshot.max_point();
11383 for selection in &mut selections {
11384 let rows = selection.spanned_rows(true, &display_map);
11385 selection.start = Point::new(rows.start.0, 0);
11386 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11387 selection.reversed = false;
11388 }
11389 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11390 s.select(selections);
11391 });
11392 }
11393
11394 pub fn split_selection_into_lines(
11395 &mut self,
11396 _: &SplitSelectionIntoLines,
11397 window: &mut Window,
11398 cx: &mut Context<Self>,
11399 ) {
11400 let selections = self
11401 .selections
11402 .all::<Point>(cx)
11403 .into_iter()
11404 .map(|selection| selection.start..selection.end)
11405 .collect::<Vec<_>>();
11406 self.unfold_ranges(&selections, true, true, cx);
11407
11408 let mut new_selection_ranges = Vec::new();
11409 {
11410 let buffer = self.buffer.read(cx).read(cx);
11411 for selection in selections {
11412 for row in selection.start.row..selection.end.row {
11413 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11414 new_selection_ranges.push(cursor..cursor);
11415 }
11416
11417 let is_multiline_selection = selection.start.row != selection.end.row;
11418 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11419 // so this action feels more ergonomic when paired with other selection operations
11420 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11421 if !should_skip_last {
11422 new_selection_ranges.push(selection.end..selection.end);
11423 }
11424 }
11425 }
11426 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11427 s.select_ranges(new_selection_ranges);
11428 });
11429 }
11430
11431 pub fn add_selection_above(
11432 &mut self,
11433 _: &AddSelectionAbove,
11434 window: &mut Window,
11435 cx: &mut Context<Self>,
11436 ) {
11437 self.add_selection(true, window, cx);
11438 }
11439
11440 pub fn add_selection_below(
11441 &mut self,
11442 _: &AddSelectionBelow,
11443 window: &mut Window,
11444 cx: &mut Context<Self>,
11445 ) {
11446 self.add_selection(false, window, cx);
11447 }
11448
11449 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11450 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11451
11452 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11453 let mut selections = self.selections.all::<Point>(cx);
11454 let text_layout_details = self.text_layout_details(window);
11455 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11456 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11457 let range = oldest_selection.display_range(&display_map).sorted();
11458
11459 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11460 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11461 let positions = start_x.min(end_x)..start_x.max(end_x);
11462
11463 selections.clear();
11464 let mut stack = Vec::new();
11465 for row in range.start.row().0..=range.end.row().0 {
11466 if let Some(selection) = self.selections.build_columnar_selection(
11467 &display_map,
11468 DisplayRow(row),
11469 &positions,
11470 oldest_selection.reversed,
11471 &text_layout_details,
11472 ) {
11473 stack.push(selection.id);
11474 selections.push(selection);
11475 }
11476 }
11477
11478 if above {
11479 stack.reverse();
11480 }
11481
11482 AddSelectionsState { above, stack }
11483 });
11484
11485 let last_added_selection = *state.stack.last().unwrap();
11486 let mut new_selections = Vec::new();
11487 if above == state.above {
11488 let end_row = if above {
11489 DisplayRow(0)
11490 } else {
11491 display_map.max_point().row()
11492 };
11493
11494 'outer: for selection in selections {
11495 if selection.id == last_added_selection {
11496 let range = selection.display_range(&display_map).sorted();
11497 debug_assert_eq!(range.start.row(), range.end.row());
11498 let mut row = range.start.row();
11499 let positions =
11500 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11501 px(start)..px(end)
11502 } else {
11503 let start_x =
11504 display_map.x_for_display_point(range.start, &text_layout_details);
11505 let end_x =
11506 display_map.x_for_display_point(range.end, &text_layout_details);
11507 start_x.min(end_x)..start_x.max(end_x)
11508 };
11509
11510 while row != end_row {
11511 if above {
11512 row.0 -= 1;
11513 } else {
11514 row.0 += 1;
11515 }
11516
11517 if let Some(new_selection) = self.selections.build_columnar_selection(
11518 &display_map,
11519 row,
11520 &positions,
11521 selection.reversed,
11522 &text_layout_details,
11523 ) {
11524 state.stack.push(new_selection.id);
11525 if above {
11526 new_selections.push(new_selection);
11527 new_selections.push(selection);
11528 } else {
11529 new_selections.push(selection);
11530 new_selections.push(new_selection);
11531 }
11532
11533 continue 'outer;
11534 }
11535 }
11536 }
11537
11538 new_selections.push(selection);
11539 }
11540 } else {
11541 new_selections = selections;
11542 new_selections.retain(|s| s.id != last_added_selection);
11543 state.stack.pop();
11544 }
11545
11546 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11547 s.select(new_selections);
11548 });
11549 if state.stack.len() > 1 {
11550 self.add_selections_state = Some(state);
11551 }
11552 }
11553
11554 pub fn select_next_match_internal(
11555 &mut self,
11556 display_map: &DisplaySnapshot,
11557 replace_newest: bool,
11558 autoscroll: Option<Autoscroll>,
11559 window: &mut Window,
11560 cx: &mut Context<Self>,
11561 ) -> Result<()> {
11562 fn select_next_match_ranges(
11563 this: &mut Editor,
11564 range: Range<usize>,
11565 replace_newest: bool,
11566 auto_scroll: Option<Autoscroll>,
11567 window: &mut Window,
11568 cx: &mut Context<Editor>,
11569 ) {
11570 this.unfold_ranges(&[range.clone()], false, true, cx);
11571 this.change_selections(auto_scroll, window, cx, |s| {
11572 if replace_newest {
11573 s.delete(s.newest_anchor().id);
11574 }
11575 s.insert_range(range.clone());
11576 });
11577 }
11578
11579 let buffer = &display_map.buffer_snapshot;
11580 let mut selections = self.selections.all::<usize>(cx);
11581 if let Some(mut select_next_state) = self.select_next_state.take() {
11582 let query = &select_next_state.query;
11583 if !select_next_state.done {
11584 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11585 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11586 let mut next_selected_range = None;
11587
11588 let bytes_after_last_selection =
11589 buffer.bytes_in_range(last_selection.end..buffer.len());
11590 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11591 let query_matches = query
11592 .stream_find_iter(bytes_after_last_selection)
11593 .map(|result| (last_selection.end, result))
11594 .chain(
11595 query
11596 .stream_find_iter(bytes_before_first_selection)
11597 .map(|result| (0, result)),
11598 );
11599
11600 for (start_offset, query_match) in query_matches {
11601 let query_match = query_match.unwrap(); // can only fail due to I/O
11602 let offset_range =
11603 start_offset + query_match.start()..start_offset + query_match.end();
11604 let display_range = offset_range.start.to_display_point(display_map)
11605 ..offset_range.end.to_display_point(display_map);
11606
11607 if !select_next_state.wordwise
11608 || (!movement::is_inside_word(display_map, display_range.start)
11609 && !movement::is_inside_word(display_map, display_range.end))
11610 {
11611 // TODO: This is n^2, because we might check all the selections
11612 if !selections
11613 .iter()
11614 .any(|selection| selection.range().overlaps(&offset_range))
11615 {
11616 next_selected_range = Some(offset_range);
11617 break;
11618 }
11619 }
11620 }
11621
11622 if let Some(next_selected_range) = next_selected_range {
11623 select_next_match_ranges(
11624 self,
11625 next_selected_range,
11626 replace_newest,
11627 autoscroll,
11628 window,
11629 cx,
11630 );
11631 } else {
11632 select_next_state.done = true;
11633 }
11634 }
11635
11636 self.select_next_state = Some(select_next_state);
11637 } else {
11638 let mut only_carets = true;
11639 let mut same_text_selected = true;
11640 let mut selected_text = None;
11641
11642 let mut selections_iter = selections.iter().peekable();
11643 while let Some(selection) = selections_iter.next() {
11644 if selection.start != selection.end {
11645 only_carets = false;
11646 }
11647
11648 if same_text_selected {
11649 if selected_text.is_none() {
11650 selected_text =
11651 Some(buffer.text_for_range(selection.range()).collect::<String>());
11652 }
11653
11654 if let Some(next_selection) = selections_iter.peek() {
11655 if next_selection.range().len() == selection.range().len() {
11656 let next_selected_text = buffer
11657 .text_for_range(next_selection.range())
11658 .collect::<String>();
11659 if Some(next_selected_text) != selected_text {
11660 same_text_selected = false;
11661 selected_text = None;
11662 }
11663 } else {
11664 same_text_selected = false;
11665 selected_text = None;
11666 }
11667 }
11668 }
11669 }
11670
11671 if only_carets {
11672 for selection in &mut selections {
11673 let word_range = movement::surrounding_word(
11674 display_map,
11675 selection.start.to_display_point(display_map),
11676 );
11677 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11678 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11679 selection.goal = SelectionGoal::None;
11680 selection.reversed = false;
11681 select_next_match_ranges(
11682 self,
11683 selection.start..selection.end,
11684 replace_newest,
11685 autoscroll,
11686 window,
11687 cx,
11688 );
11689 }
11690
11691 if selections.len() == 1 {
11692 let selection = selections
11693 .last()
11694 .expect("ensured that there's only one selection");
11695 let query = buffer
11696 .text_for_range(selection.start..selection.end)
11697 .collect::<String>();
11698 let is_empty = query.is_empty();
11699 let select_state = SelectNextState {
11700 query: AhoCorasick::new(&[query])?,
11701 wordwise: true,
11702 done: is_empty,
11703 };
11704 self.select_next_state = Some(select_state);
11705 } else {
11706 self.select_next_state = None;
11707 }
11708 } else if let Some(selected_text) = selected_text {
11709 self.select_next_state = Some(SelectNextState {
11710 query: AhoCorasick::new(&[selected_text])?,
11711 wordwise: false,
11712 done: false,
11713 });
11714 self.select_next_match_internal(
11715 display_map,
11716 replace_newest,
11717 autoscroll,
11718 window,
11719 cx,
11720 )?;
11721 }
11722 }
11723 Ok(())
11724 }
11725
11726 pub fn select_all_matches(
11727 &mut self,
11728 _action: &SelectAllMatches,
11729 window: &mut Window,
11730 cx: &mut Context<Self>,
11731 ) -> Result<()> {
11732 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11733
11734 self.push_to_selection_history();
11735 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11736
11737 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11738 let Some(select_next_state) = self.select_next_state.as_mut() else {
11739 return Ok(());
11740 };
11741 if select_next_state.done {
11742 return Ok(());
11743 }
11744
11745 let mut new_selections = self.selections.all::<usize>(cx);
11746
11747 let buffer = &display_map.buffer_snapshot;
11748 let query_matches = select_next_state
11749 .query
11750 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11751
11752 for query_match in query_matches {
11753 let query_match = query_match.unwrap(); // can only fail due to I/O
11754 let offset_range = query_match.start()..query_match.end();
11755 let display_range = offset_range.start.to_display_point(&display_map)
11756 ..offset_range.end.to_display_point(&display_map);
11757
11758 if !select_next_state.wordwise
11759 || (!movement::is_inside_word(&display_map, display_range.start)
11760 && !movement::is_inside_word(&display_map, display_range.end))
11761 {
11762 self.selections.change_with(cx, |selections| {
11763 new_selections.push(Selection {
11764 id: selections.new_selection_id(),
11765 start: offset_range.start,
11766 end: offset_range.end,
11767 reversed: false,
11768 goal: SelectionGoal::None,
11769 });
11770 });
11771 }
11772 }
11773
11774 new_selections.sort_by_key(|selection| selection.start);
11775 let mut ix = 0;
11776 while ix + 1 < new_selections.len() {
11777 let current_selection = &new_selections[ix];
11778 let next_selection = &new_selections[ix + 1];
11779 if current_selection.range().overlaps(&next_selection.range()) {
11780 if current_selection.id < next_selection.id {
11781 new_selections.remove(ix + 1);
11782 } else {
11783 new_selections.remove(ix);
11784 }
11785 } else {
11786 ix += 1;
11787 }
11788 }
11789
11790 let reversed = self.selections.oldest::<usize>(cx).reversed;
11791
11792 for selection in new_selections.iter_mut() {
11793 selection.reversed = reversed;
11794 }
11795
11796 select_next_state.done = true;
11797 self.unfold_ranges(
11798 &new_selections
11799 .iter()
11800 .map(|selection| selection.range())
11801 .collect::<Vec<_>>(),
11802 false,
11803 false,
11804 cx,
11805 );
11806 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11807 selections.select(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 mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14188 let format = project.update(cx, |project, cx| {
14189 project.format(buffers, target, true, trigger, cx)
14190 });
14191
14192 cx.spawn_in(window, async move |_, cx| {
14193 let transaction = futures::select_biased! {
14194 transaction = format.log_err().fuse() => transaction,
14195 () = timeout => {
14196 log::warn!("timed out waiting for formatting");
14197 None
14198 }
14199 };
14200
14201 buffer
14202 .update(cx, |buffer, cx| {
14203 if let Some(transaction) = transaction {
14204 if !buffer.is_singleton() {
14205 buffer.push_transaction(&transaction.0, cx);
14206 }
14207 }
14208 cx.notify();
14209 })
14210 .ok();
14211
14212 Ok(())
14213 })
14214 }
14215
14216 fn organize_imports(
14217 &mut self,
14218 _: &OrganizeImports,
14219 window: &mut Window,
14220 cx: &mut Context<Self>,
14221 ) -> Option<Task<Result<()>>> {
14222 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14223 let project = match &self.project {
14224 Some(project) => project.clone(),
14225 None => return None,
14226 };
14227 Some(self.perform_code_action_kind(
14228 project,
14229 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14230 window,
14231 cx,
14232 ))
14233 }
14234
14235 fn perform_code_action_kind(
14236 &mut self,
14237 project: Entity<Project>,
14238 kind: CodeActionKind,
14239 window: &mut Window,
14240 cx: &mut Context<Self>,
14241 ) -> Task<Result<()>> {
14242 let buffer = self.buffer.clone();
14243 let buffers = buffer.read(cx).all_buffers();
14244 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14245 let apply_action = project.update(cx, |project, cx| {
14246 project.apply_code_action_kind(buffers, kind, true, cx)
14247 });
14248 cx.spawn_in(window, async move |_, cx| {
14249 let transaction = futures::select_biased! {
14250 () = timeout => {
14251 log::warn!("timed out waiting for executing code action");
14252 None
14253 }
14254 transaction = apply_action.log_err().fuse() => transaction,
14255 };
14256 buffer
14257 .update(cx, |buffer, cx| {
14258 // check if we need this
14259 if let Some(transaction) = transaction {
14260 if !buffer.is_singleton() {
14261 buffer.push_transaction(&transaction.0, cx);
14262 }
14263 }
14264 cx.notify();
14265 })
14266 .ok();
14267 Ok(())
14268 })
14269 }
14270
14271 fn restart_language_server(
14272 &mut self,
14273 _: &RestartLanguageServer,
14274 _: &mut Window,
14275 cx: &mut Context<Self>,
14276 ) {
14277 if let Some(project) = self.project.clone() {
14278 self.buffer.update(cx, |multi_buffer, cx| {
14279 project.update(cx, |project, cx| {
14280 project.restart_language_servers_for_buffers(
14281 multi_buffer.all_buffers().into_iter().collect(),
14282 cx,
14283 );
14284 });
14285 })
14286 }
14287 }
14288
14289 fn stop_language_server(
14290 &mut self,
14291 _: &StopLanguageServer,
14292 _: &mut Window,
14293 cx: &mut Context<Self>,
14294 ) {
14295 if let Some(project) = self.project.clone() {
14296 self.buffer.update(cx, |multi_buffer, cx| {
14297 project.update(cx, |project, cx| {
14298 project.stop_language_servers_for_buffers(
14299 multi_buffer.all_buffers().into_iter().collect(),
14300 cx,
14301 );
14302 cx.emit(project::Event::RefreshInlayHints);
14303 });
14304 });
14305 }
14306 }
14307
14308 fn cancel_language_server_work(
14309 workspace: &mut Workspace,
14310 _: &actions::CancelLanguageServerWork,
14311 _: &mut Window,
14312 cx: &mut Context<Workspace>,
14313 ) {
14314 let project = workspace.project();
14315 let buffers = workspace
14316 .active_item(cx)
14317 .and_then(|item| item.act_as::<Editor>(cx))
14318 .map_or(HashSet::default(), |editor| {
14319 editor.read(cx).buffer.read(cx).all_buffers()
14320 });
14321 project.update(cx, |project, cx| {
14322 project.cancel_language_server_work_for_buffers(buffers, cx);
14323 });
14324 }
14325
14326 fn show_character_palette(
14327 &mut self,
14328 _: &ShowCharacterPalette,
14329 window: &mut Window,
14330 _: &mut Context<Self>,
14331 ) {
14332 window.show_character_palette();
14333 }
14334
14335 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14336 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
14337 let buffer = self.buffer.read(cx).snapshot(cx);
14338 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
14339 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
14340 let is_valid = buffer
14341 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14342 .any(|entry| {
14343 entry.diagnostic.is_primary
14344 && !entry.range.is_empty()
14345 && entry.range.start == primary_range_start
14346 && entry.diagnostic.message == active_diagnostics.primary_message
14347 });
14348
14349 if is_valid != active_diagnostics.is_valid {
14350 active_diagnostics.is_valid = is_valid;
14351 if is_valid {
14352 let mut new_styles = HashMap::default();
14353 for (block_id, diagnostic) in &active_diagnostics.blocks {
14354 new_styles.insert(
14355 *block_id,
14356 diagnostic_block_renderer(diagnostic.clone(), None, true),
14357 );
14358 }
14359 self.display_map.update(cx, |display_map, _cx| {
14360 display_map.replace_blocks(new_styles);
14361 });
14362 } else {
14363 self.dismiss_diagnostics(cx);
14364 }
14365 }
14366 }
14367 }
14368
14369 fn activate_diagnostics(
14370 &mut self,
14371 buffer_id: BufferId,
14372 group_id: usize,
14373 window: &mut Window,
14374 cx: &mut Context<Self>,
14375 ) {
14376 self.dismiss_diagnostics(cx);
14377 let snapshot = self.snapshot(window, cx);
14378 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
14379 let buffer = self.buffer.read(cx).snapshot(cx);
14380
14381 let mut primary_range = None;
14382 let mut primary_message = None;
14383 let diagnostic_group = buffer
14384 .diagnostic_group(buffer_id, group_id)
14385 .filter_map(|entry| {
14386 let start = entry.range.start;
14387 let end = entry.range.end;
14388 if snapshot.is_line_folded(MultiBufferRow(start.row))
14389 && (start.row == end.row
14390 || snapshot.is_line_folded(MultiBufferRow(end.row)))
14391 {
14392 return None;
14393 }
14394 if entry.diagnostic.is_primary {
14395 primary_range = Some(entry.range.clone());
14396 primary_message = Some(entry.diagnostic.message.clone());
14397 }
14398 Some(entry)
14399 })
14400 .collect::<Vec<_>>();
14401 let primary_range = primary_range?;
14402 let primary_message = primary_message?;
14403
14404 let blocks = display_map
14405 .insert_blocks(
14406 diagnostic_group.iter().map(|entry| {
14407 let diagnostic = entry.diagnostic.clone();
14408 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
14409 BlockProperties {
14410 style: BlockStyle::Fixed,
14411 placement: BlockPlacement::Below(
14412 buffer.anchor_after(entry.range.start),
14413 ),
14414 height: Some(message_height),
14415 render: diagnostic_block_renderer(diagnostic, None, true),
14416 priority: 0,
14417 }
14418 }),
14419 cx,
14420 )
14421 .into_iter()
14422 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
14423 .collect();
14424
14425 Some(ActiveDiagnosticGroup {
14426 primary_range: buffer.anchor_before(primary_range.start)
14427 ..buffer.anchor_after(primary_range.end),
14428 primary_message,
14429 group_id,
14430 blocks,
14431 is_valid: true,
14432 })
14433 });
14434 }
14435
14436 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14437 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
14438 self.display_map.update(cx, |display_map, cx| {
14439 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
14440 });
14441 cx.notify();
14442 }
14443 }
14444
14445 /// Disable inline diagnostics rendering for this editor.
14446 pub fn disable_inline_diagnostics(&mut self) {
14447 self.inline_diagnostics_enabled = false;
14448 self.inline_diagnostics_update = Task::ready(());
14449 self.inline_diagnostics.clear();
14450 }
14451
14452 pub fn inline_diagnostics_enabled(&self) -> bool {
14453 self.inline_diagnostics_enabled
14454 }
14455
14456 pub fn show_inline_diagnostics(&self) -> bool {
14457 self.show_inline_diagnostics
14458 }
14459
14460 pub fn toggle_inline_diagnostics(
14461 &mut self,
14462 _: &ToggleInlineDiagnostics,
14463 window: &mut Window,
14464 cx: &mut Context<Editor>,
14465 ) {
14466 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14467 self.refresh_inline_diagnostics(false, window, cx);
14468 }
14469
14470 fn refresh_inline_diagnostics(
14471 &mut self,
14472 debounce: bool,
14473 window: &mut Window,
14474 cx: &mut Context<Self>,
14475 ) {
14476 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14477 self.inline_diagnostics_update = Task::ready(());
14478 self.inline_diagnostics.clear();
14479 return;
14480 }
14481
14482 let debounce_ms = ProjectSettings::get_global(cx)
14483 .diagnostics
14484 .inline
14485 .update_debounce_ms;
14486 let debounce = if debounce && debounce_ms > 0 {
14487 Some(Duration::from_millis(debounce_ms))
14488 } else {
14489 None
14490 };
14491 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14492 if let Some(debounce) = debounce {
14493 cx.background_executor().timer(debounce).await;
14494 }
14495 let Some(snapshot) = editor
14496 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14497 .ok()
14498 else {
14499 return;
14500 };
14501
14502 let new_inline_diagnostics = cx
14503 .background_spawn(async move {
14504 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14505 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14506 let message = diagnostic_entry
14507 .diagnostic
14508 .message
14509 .split_once('\n')
14510 .map(|(line, _)| line)
14511 .map(SharedString::new)
14512 .unwrap_or_else(|| {
14513 SharedString::from(diagnostic_entry.diagnostic.message)
14514 });
14515 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14516 let (Ok(i) | Err(i)) = inline_diagnostics
14517 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14518 inline_diagnostics.insert(
14519 i,
14520 (
14521 start_anchor,
14522 InlineDiagnostic {
14523 message,
14524 group_id: diagnostic_entry.diagnostic.group_id,
14525 start: diagnostic_entry.range.start.to_point(&snapshot),
14526 is_primary: diagnostic_entry.diagnostic.is_primary,
14527 severity: diagnostic_entry.diagnostic.severity,
14528 },
14529 ),
14530 );
14531 }
14532 inline_diagnostics
14533 })
14534 .await;
14535
14536 editor
14537 .update(cx, |editor, cx| {
14538 editor.inline_diagnostics = new_inline_diagnostics;
14539 cx.notify();
14540 })
14541 .ok();
14542 });
14543 }
14544
14545 pub fn set_selections_from_remote(
14546 &mut self,
14547 selections: Vec<Selection<Anchor>>,
14548 pending_selection: Option<Selection<Anchor>>,
14549 window: &mut Window,
14550 cx: &mut Context<Self>,
14551 ) {
14552 let old_cursor_position = self.selections.newest_anchor().head();
14553 self.selections.change_with(cx, |s| {
14554 s.select_anchors(selections);
14555 if let Some(pending_selection) = pending_selection {
14556 s.set_pending(pending_selection, SelectMode::Character);
14557 } else {
14558 s.clear_pending();
14559 }
14560 });
14561 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14562 }
14563
14564 fn push_to_selection_history(&mut self) {
14565 self.selection_history.push(SelectionHistoryEntry {
14566 selections: self.selections.disjoint_anchors(),
14567 select_next_state: self.select_next_state.clone(),
14568 select_prev_state: self.select_prev_state.clone(),
14569 add_selections_state: self.add_selections_state.clone(),
14570 });
14571 }
14572
14573 pub fn transact(
14574 &mut self,
14575 window: &mut Window,
14576 cx: &mut Context<Self>,
14577 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14578 ) -> Option<TransactionId> {
14579 self.start_transaction_at(Instant::now(), window, cx);
14580 update(self, window, cx);
14581 self.end_transaction_at(Instant::now(), cx)
14582 }
14583
14584 pub fn start_transaction_at(
14585 &mut self,
14586 now: Instant,
14587 window: &mut Window,
14588 cx: &mut Context<Self>,
14589 ) {
14590 self.end_selection(window, cx);
14591 if let Some(tx_id) = self
14592 .buffer
14593 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14594 {
14595 self.selection_history
14596 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14597 cx.emit(EditorEvent::TransactionBegun {
14598 transaction_id: tx_id,
14599 })
14600 }
14601 }
14602
14603 pub fn end_transaction_at(
14604 &mut self,
14605 now: Instant,
14606 cx: &mut Context<Self>,
14607 ) -> Option<TransactionId> {
14608 if let Some(transaction_id) = self
14609 .buffer
14610 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14611 {
14612 if let Some((_, end_selections)) =
14613 self.selection_history.transaction_mut(transaction_id)
14614 {
14615 *end_selections = Some(self.selections.disjoint_anchors());
14616 } else {
14617 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14618 }
14619
14620 cx.emit(EditorEvent::Edited { transaction_id });
14621 Some(transaction_id)
14622 } else {
14623 None
14624 }
14625 }
14626
14627 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14628 if self.selection_mark_mode {
14629 self.change_selections(None, window, cx, |s| {
14630 s.move_with(|_, sel| {
14631 sel.collapse_to(sel.head(), SelectionGoal::None);
14632 });
14633 })
14634 }
14635 self.selection_mark_mode = true;
14636 cx.notify();
14637 }
14638
14639 pub fn swap_selection_ends(
14640 &mut self,
14641 _: &actions::SwapSelectionEnds,
14642 window: &mut Window,
14643 cx: &mut Context<Self>,
14644 ) {
14645 self.change_selections(None, window, cx, |s| {
14646 s.move_with(|_, sel| {
14647 if sel.start != sel.end {
14648 sel.reversed = !sel.reversed
14649 }
14650 });
14651 });
14652 self.request_autoscroll(Autoscroll::newest(), cx);
14653 cx.notify();
14654 }
14655
14656 pub fn toggle_fold(
14657 &mut self,
14658 _: &actions::ToggleFold,
14659 window: &mut Window,
14660 cx: &mut Context<Self>,
14661 ) {
14662 if self.is_singleton(cx) {
14663 let selection = self.selections.newest::<Point>(cx);
14664
14665 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14666 let range = if selection.is_empty() {
14667 let point = selection.head().to_display_point(&display_map);
14668 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14669 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14670 .to_point(&display_map);
14671 start..end
14672 } else {
14673 selection.range()
14674 };
14675 if display_map.folds_in_range(range).next().is_some() {
14676 self.unfold_lines(&Default::default(), window, cx)
14677 } else {
14678 self.fold(&Default::default(), window, cx)
14679 }
14680 } else {
14681 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14682 let buffer_ids: HashSet<_> = self
14683 .selections
14684 .disjoint_anchor_ranges()
14685 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14686 .collect();
14687
14688 let should_unfold = buffer_ids
14689 .iter()
14690 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14691
14692 for buffer_id in buffer_ids {
14693 if should_unfold {
14694 self.unfold_buffer(buffer_id, cx);
14695 } else {
14696 self.fold_buffer(buffer_id, cx);
14697 }
14698 }
14699 }
14700 }
14701
14702 pub fn toggle_fold_recursive(
14703 &mut self,
14704 _: &actions::ToggleFoldRecursive,
14705 window: &mut Window,
14706 cx: &mut Context<Self>,
14707 ) {
14708 let selection = self.selections.newest::<Point>(cx);
14709
14710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14711 let range = if selection.is_empty() {
14712 let point = selection.head().to_display_point(&display_map);
14713 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14714 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14715 .to_point(&display_map);
14716 start..end
14717 } else {
14718 selection.range()
14719 };
14720 if display_map.folds_in_range(range).next().is_some() {
14721 self.unfold_recursive(&Default::default(), window, cx)
14722 } else {
14723 self.fold_recursive(&Default::default(), window, cx)
14724 }
14725 }
14726
14727 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14728 if self.is_singleton(cx) {
14729 let mut to_fold = Vec::new();
14730 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14731 let selections = self.selections.all_adjusted(cx);
14732
14733 for selection in selections {
14734 let range = selection.range().sorted();
14735 let buffer_start_row = range.start.row;
14736
14737 if range.start.row != range.end.row {
14738 let mut found = false;
14739 let mut row = range.start.row;
14740 while row <= range.end.row {
14741 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14742 {
14743 found = true;
14744 row = crease.range().end.row + 1;
14745 to_fold.push(crease);
14746 } else {
14747 row += 1
14748 }
14749 }
14750 if found {
14751 continue;
14752 }
14753 }
14754
14755 for row in (0..=range.start.row).rev() {
14756 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14757 if crease.range().end.row >= buffer_start_row {
14758 to_fold.push(crease);
14759 if row <= range.start.row {
14760 break;
14761 }
14762 }
14763 }
14764 }
14765 }
14766
14767 self.fold_creases(to_fold, true, window, cx);
14768 } else {
14769 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14770 let buffer_ids = self
14771 .selections
14772 .disjoint_anchor_ranges()
14773 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14774 .collect::<HashSet<_>>();
14775 for buffer_id in buffer_ids {
14776 self.fold_buffer(buffer_id, cx);
14777 }
14778 }
14779 }
14780
14781 fn fold_at_level(
14782 &mut self,
14783 fold_at: &FoldAtLevel,
14784 window: &mut Window,
14785 cx: &mut Context<Self>,
14786 ) {
14787 if !self.buffer.read(cx).is_singleton() {
14788 return;
14789 }
14790
14791 let fold_at_level = fold_at.0;
14792 let snapshot = self.buffer.read(cx).snapshot(cx);
14793 let mut to_fold = Vec::new();
14794 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14795
14796 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14797 while start_row < end_row {
14798 match self
14799 .snapshot(window, cx)
14800 .crease_for_buffer_row(MultiBufferRow(start_row))
14801 {
14802 Some(crease) => {
14803 let nested_start_row = crease.range().start.row + 1;
14804 let nested_end_row = crease.range().end.row;
14805
14806 if current_level < fold_at_level {
14807 stack.push((nested_start_row, nested_end_row, current_level + 1));
14808 } else if current_level == fold_at_level {
14809 to_fold.push(crease);
14810 }
14811
14812 start_row = nested_end_row + 1;
14813 }
14814 None => start_row += 1,
14815 }
14816 }
14817 }
14818
14819 self.fold_creases(to_fold, true, window, cx);
14820 }
14821
14822 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14823 if self.buffer.read(cx).is_singleton() {
14824 let mut fold_ranges = Vec::new();
14825 let snapshot = self.buffer.read(cx).snapshot(cx);
14826
14827 for row in 0..snapshot.max_row().0 {
14828 if let Some(foldable_range) = self
14829 .snapshot(window, cx)
14830 .crease_for_buffer_row(MultiBufferRow(row))
14831 {
14832 fold_ranges.push(foldable_range);
14833 }
14834 }
14835
14836 self.fold_creases(fold_ranges, true, window, cx);
14837 } else {
14838 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14839 editor
14840 .update_in(cx, |editor, _, cx| {
14841 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14842 editor.fold_buffer(buffer_id, cx);
14843 }
14844 })
14845 .ok();
14846 });
14847 }
14848 }
14849
14850 pub fn fold_function_bodies(
14851 &mut self,
14852 _: &actions::FoldFunctionBodies,
14853 window: &mut Window,
14854 cx: &mut Context<Self>,
14855 ) {
14856 let snapshot = self.buffer.read(cx).snapshot(cx);
14857
14858 let ranges = snapshot
14859 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14860 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14861 .collect::<Vec<_>>();
14862
14863 let creases = ranges
14864 .into_iter()
14865 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14866 .collect();
14867
14868 self.fold_creases(creases, true, window, cx);
14869 }
14870
14871 pub fn fold_recursive(
14872 &mut self,
14873 _: &actions::FoldRecursive,
14874 window: &mut Window,
14875 cx: &mut Context<Self>,
14876 ) {
14877 let mut to_fold = Vec::new();
14878 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14879 let selections = self.selections.all_adjusted(cx);
14880
14881 for selection in selections {
14882 let range = selection.range().sorted();
14883 let buffer_start_row = range.start.row;
14884
14885 if range.start.row != range.end.row {
14886 let mut found = false;
14887 for row in range.start.row..=range.end.row {
14888 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14889 found = true;
14890 to_fold.push(crease);
14891 }
14892 }
14893 if found {
14894 continue;
14895 }
14896 }
14897
14898 for row in (0..=range.start.row).rev() {
14899 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14900 if crease.range().end.row >= buffer_start_row {
14901 to_fold.push(crease);
14902 } else {
14903 break;
14904 }
14905 }
14906 }
14907 }
14908
14909 self.fold_creases(to_fold, true, window, cx);
14910 }
14911
14912 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14913 let buffer_row = fold_at.buffer_row;
14914 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14915
14916 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14917 let autoscroll = self
14918 .selections
14919 .all::<Point>(cx)
14920 .iter()
14921 .any(|selection| crease.range().overlaps(&selection.range()));
14922
14923 self.fold_creases(vec![crease], autoscroll, window, cx);
14924 }
14925 }
14926
14927 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14928 if self.is_singleton(cx) {
14929 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14930 let buffer = &display_map.buffer_snapshot;
14931 let selections = self.selections.all::<Point>(cx);
14932 let ranges = selections
14933 .iter()
14934 .map(|s| {
14935 let range = s.display_range(&display_map).sorted();
14936 let mut start = range.start.to_point(&display_map);
14937 let mut end = range.end.to_point(&display_map);
14938 start.column = 0;
14939 end.column = buffer.line_len(MultiBufferRow(end.row));
14940 start..end
14941 })
14942 .collect::<Vec<_>>();
14943
14944 self.unfold_ranges(&ranges, true, true, cx);
14945 } else {
14946 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14947 let buffer_ids = self
14948 .selections
14949 .disjoint_anchor_ranges()
14950 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14951 .collect::<HashSet<_>>();
14952 for buffer_id in buffer_ids {
14953 self.unfold_buffer(buffer_id, cx);
14954 }
14955 }
14956 }
14957
14958 pub fn unfold_recursive(
14959 &mut self,
14960 _: &UnfoldRecursive,
14961 _window: &mut Window,
14962 cx: &mut Context<Self>,
14963 ) {
14964 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14965 let selections = self.selections.all::<Point>(cx);
14966 let ranges = selections
14967 .iter()
14968 .map(|s| {
14969 let mut range = s.display_range(&display_map).sorted();
14970 *range.start.column_mut() = 0;
14971 *range.end.column_mut() = display_map.line_len(range.end.row());
14972 let start = range.start.to_point(&display_map);
14973 let end = range.end.to_point(&display_map);
14974 start..end
14975 })
14976 .collect::<Vec<_>>();
14977
14978 self.unfold_ranges(&ranges, true, true, cx);
14979 }
14980
14981 pub fn unfold_at(
14982 &mut self,
14983 unfold_at: &UnfoldAt,
14984 _window: &mut Window,
14985 cx: &mut Context<Self>,
14986 ) {
14987 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14988
14989 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14990 ..Point::new(
14991 unfold_at.buffer_row.0,
14992 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14993 );
14994
14995 let autoscroll = self
14996 .selections
14997 .all::<Point>(cx)
14998 .iter()
14999 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15000
15001 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15002 }
15003
15004 pub fn unfold_all(
15005 &mut self,
15006 _: &actions::UnfoldAll,
15007 _window: &mut Window,
15008 cx: &mut Context<Self>,
15009 ) {
15010 if self.buffer.read(cx).is_singleton() {
15011 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15012 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15013 } else {
15014 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15015 editor
15016 .update(cx, |editor, cx| {
15017 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15018 editor.unfold_buffer(buffer_id, cx);
15019 }
15020 })
15021 .ok();
15022 });
15023 }
15024 }
15025
15026 pub fn fold_selected_ranges(
15027 &mut self,
15028 _: &FoldSelectedRanges,
15029 window: &mut Window,
15030 cx: &mut Context<Self>,
15031 ) {
15032 let selections = self.selections.all_adjusted(cx);
15033 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15034 let ranges = selections
15035 .into_iter()
15036 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15037 .collect::<Vec<_>>();
15038 self.fold_creases(ranges, true, window, cx);
15039 }
15040
15041 pub fn fold_ranges<T: ToOffset + Clone>(
15042 &mut self,
15043 ranges: Vec<Range<T>>,
15044 auto_scroll: bool,
15045 window: &mut Window,
15046 cx: &mut Context<Self>,
15047 ) {
15048 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15049 let ranges = ranges
15050 .into_iter()
15051 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15052 .collect::<Vec<_>>();
15053 self.fold_creases(ranges, auto_scroll, window, cx);
15054 }
15055
15056 pub fn fold_creases<T: ToOffset + Clone>(
15057 &mut self,
15058 creases: Vec<Crease<T>>,
15059 auto_scroll: bool,
15060 window: &mut Window,
15061 cx: &mut Context<Self>,
15062 ) {
15063 if creases.is_empty() {
15064 return;
15065 }
15066
15067 let mut buffers_affected = HashSet::default();
15068 let multi_buffer = self.buffer().read(cx);
15069 for crease in &creases {
15070 if let Some((_, buffer, _)) =
15071 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15072 {
15073 buffers_affected.insert(buffer.read(cx).remote_id());
15074 };
15075 }
15076
15077 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15078
15079 if auto_scroll {
15080 self.request_autoscroll(Autoscroll::fit(), cx);
15081 }
15082
15083 cx.notify();
15084
15085 if let Some(active_diagnostics) = self.active_diagnostics.take() {
15086 // Clear diagnostics block when folding a range that contains it.
15087 let snapshot = self.snapshot(window, cx);
15088 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
15089 drop(snapshot);
15090 self.active_diagnostics = Some(active_diagnostics);
15091 self.dismiss_diagnostics(cx);
15092 } else {
15093 self.active_diagnostics = Some(active_diagnostics);
15094 }
15095 }
15096
15097 self.scrollbar_marker_state.dirty = true;
15098 self.folds_did_change(cx);
15099 }
15100
15101 /// Removes any folds whose ranges intersect any of the given ranges.
15102 pub fn unfold_ranges<T: ToOffset + Clone>(
15103 &mut self,
15104 ranges: &[Range<T>],
15105 inclusive: bool,
15106 auto_scroll: bool,
15107 cx: &mut Context<Self>,
15108 ) {
15109 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15110 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15111 });
15112 self.folds_did_change(cx);
15113 }
15114
15115 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15116 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15117 return;
15118 }
15119 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15120 self.display_map.update(cx, |display_map, cx| {
15121 display_map.fold_buffers([buffer_id], cx)
15122 });
15123 cx.emit(EditorEvent::BufferFoldToggled {
15124 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15125 folded: true,
15126 });
15127 cx.notify();
15128 }
15129
15130 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15131 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15132 return;
15133 }
15134 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15135 self.display_map.update(cx, |display_map, cx| {
15136 display_map.unfold_buffers([buffer_id], cx);
15137 });
15138 cx.emit(EditorEvent::BufferFoldToggled {
15139 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15140 folded: false,
15141 });
15142 cx.notify();
15143 }
15144
15145 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15146 self.display_map.read(cx).is_buffer_folded(buffer)
15147 }
15148
15149 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15150 self.display_map.read(cx).folded_buffers()
15151 }
15152
15153 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15154 self.display_map.update(cx, |display_map, cx| {
15155 display_map.disable_header_for_buffer(buffer_id, cx);
15156 });
15157 cx.notify();
15158 }
15159
15160 /// Removes any folds with the given ranges.
15161 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15162 &mut self,
15163 ranges: &[Range<T>],
15164 type_id: TypeId,
15165 auto_scroll: bool,
15166 cx: &mut Context<Self>,
15167 ) {
15168 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15169 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15170 });
15171 self.folds_did_change(cx);
15172 }
15173
15174 fn remove_folds_with<T: ToOffset + Clone>(
15175 &mut self,
15176 ranges: &[Range<T>],
15177 auto_scroll: bool,
15178 cx: &mut Context<Self>,
15179 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15180 ) {
15181 if ranges.is_empty() {
15182 return;
15183 }
15184
15185 let mut buffers_affected = HashSet::default();
15186 let multi_buffer = self.buffer().read(cx);
15187 for range in ranges {
15188 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15189 buffers_affected.insert(buffer.read(cx).remote_id());
15190 };
15191 }
15192
15193 self.display_map.update(cx, update);
15194
15195 if auto_scroll {
15196 self.request_autoscroll(Autoscroll::fit(), cx);
15197 }
15198
15199 cx.notify();
15200 self.scrollbar_marker_state.dirty = true;
15201 self.active_indent_guides_state.dirty = true;
15202 }
15203
15204 pub fn update_fold_widths(
15205 &mut self,
15206 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15207 cx: &mut Context<Self>,
15208 ) -> bool {
15209 self.display_map
15210 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15211 }
15212
15213 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15214 self.display_map.read(cx).fold_placeholder.clone()
15215 }
15216
15217 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15218 self.buffer.update(cx, |buffer, cx| {
15219 buffer.set_all_diff_hunks_expanded(cx);
15220 });
15221 }
15222
15223 pub fn expand_all_diff_hunks(
15224 &mut self,
15225 _: &ExpandAllDiffHunks,
15226 _window: &mut Window,
15227 cx: &mut Context<Self>,
15228 ) {
15229 self.buffer.update(cx, |buffer, cx| {
15230 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15231 });
15232 }
15233
15234 pub fn toggle_selected_diff_hunks(
15235 &mut self,
15236 _: &ToggleSelectedDiffHunks,
15237 _window: &mut Window,
15238 cx: &mut Context<Self>,
15239 ) {
15240 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15241 self.toggle_diff_hunks_in_ranges(ranges, cx);
15242 }
15243
15244 pub fn diff_hunks_in_ranges<'a>(
15245 &'a self,
15246 ranges: &'a [Range<Anchor>],
15247 buffer: &'a MultiBufferSnapshot,
15248 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15249 ranges.iter().flat_map(move |range| {
15250 let end_excerpt_id = range.end.excerpt_id;
15251 let range = range.to_point(buffer);
15252 let mut peek_end = range.end;
15253 if range.end.row < buffer.max_row().0 {
15254 peek_end = Point::new(range.end.row + 1, 0);
15255 }
15256 buffer
15257 .diff_hunks_in_range(range.start..peek_end)
15258 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15259 })
15260 }
15261
15262 pub fn has_stageable_diff_hunks_in_ranges(
15263 &self,
15264 ranges: &[Range<Anchor>],
15265 snapshot: &MultiBufferSnapshot,
15266 ) -> bool {
15267 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15268 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15269 }
15270
15271 pub fn toggle_staged_selected_diff_hunks(
15272 &mut self,
15273 _: &::git::ToggleStaged,
15274 _: &mut Window,
15275 cx: &mut Context<Self>,
15276 ) {
15277 let snapshot = self.buffer.read(cx).snapshot(cx);
15278 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15279 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15280 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15281 }
15282
15283 pub fn set_render_diff_hunk_controls(
15284 &mut self,
15285 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15286 cx: &mut Context<Self>,
15287 ) {
15288 self.render_diff_hunk_controls = render_diff_hunk_controls;
15289 cx.notify();
15290 }
15291
15292 pub fn stage_and_next(
15293 &mut self,
15294 _: &::git::StageAndNext,
15295 window: &mut Window,
15296 cx: &mut Context<Self>,
15297 ) {
15298 self.do_stage_or_unstage_and_next(true, window, cx);
15299 }
15300
15301 pub fn unstage_and_next(
15302 &mut self,
15303 _: &::git::UnstageAndNext,
15304 window: &mut Window,
15305 cx: &mut Context<Self>,
15306 ) {
15307 self.do_stage_or_unstage_and_next(false, window, cx);
15308 }
15309
15310 pub fn stage_or_unstage_diff_hunks(
15311 &mut self,
15312 stage: bool,
15313 ranges: Vec<Range<Anchor>>,
15314 cx: &mut Context<Self>,
15315 ) {
15316 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15317 cx.spawn(async move |this, cx| {
15318 task.await?;
15319 this.update(cx, |this, cx| {
15320 let snapshot = this.buffer.read(cx).snapshot(cx);
15321 let chunk_by = this
15322 .diff_hunks_in_ranges(&ranges, &snapshot)
15323 .chunk_by(|hunk| hunk.buffer_id);
15324 for (buffer_id, hunks) in &chunk_by {
15325 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15326 }
15327 })
15328 })
15329 .detach_and_log_err(cx);
15330 }
15331
15332 fn save_buffers_for_ranges_if_needed(
15333 &mut self,
15334 ranges: &[Range<Anchor>],
15335 cx: &mut Context<Editor>,
15336 ) -> Task<Result<()>> {
15337 let multibuffer = self.buffer.read(cx);
15338 let snapshot = multibuffer.read(cx);
15339 let buffer_ids: HashSet<_> = ranges
15340 .iter()
15341 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15342 .collect();
15343 drop(snapshot);
15344
15345 let mut buffers = HashSet::default();
15346 for buffer_id in buffer_ids {
15347 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15348 let buffer = buffer_entity.read(cx);
15349 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15350 {
15351 buffers.insert(buffer_entity);
15352 }
15353 }
15354 }
15355
15356 if let Some(project) = &self.project {
15357 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15358 } else {
15359 Task::ready(Ok(()))
15360 }
15361 }
15362
15363 fn do_stage_or_unstage_and_next(
15364 &mut self,
15365 stage: bool,
15366 window: &mut Window,
15367 cx: &mut Context<Self>,
15368 ) {
15369 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15370
15371 if ranges.iter().any(|range| range.start != range.end) {
15372 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15373 return;
15374 }
15375
15376 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15377 let snapshot = self.snapshot(window, cx);
15378 let position = self.selections.newest::<Point>(cx).head();
15379 let mut row = snapshot
15380 .buffer_snapshot
15381 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15382 .find(|hunk| hunk.row_range.start.0 > position.row)
15383 .map(|hunk| hunk.row_range.start);
15384
15385 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15386 // Outside of the project diff editor, wrap around to the beginning.
15387 if !all_diff_hunks_expanded {
15388 row = row.or_else(|| {
15389 snapshot
15390 .buffer_snapshot
15391 .diff_hunks_in_range(Point::zero()..position)
15392 .find(|hunk| hunk.row_range.end.0 < position.row)
15393 .map(|hunk| hunk.row_range.start)
15394 });
15395 }
15396
15397 if let Some(row) = row {
15398 let destination = Point::new(row.0, 0);
15399 let autoscroll = Autoscroll::center();
15400
15401 self.unfold_ranges(&[destination..destination], false, false, cx);
15402 self.change_selections(Some(autoscroll), window, cx, |s| {
15403 s.select_ranges([destination..destination]);
15404 });
15405 }
15406 }
15407
15408 fn do_stage_or_unstage(
15409 &self,
15410 stage: bool,
15411 buffer_id: BufferId,
15412 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15413 cx: &mut App,
15414 ) -> Option<()> {
15415 let project = self.project.as_ref()?;
15416 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15417 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15418 let buffer_snapshot = buffer.read(cx).snapshot();
15419 let file_exists = buffer_snapshot
15420 .file()
15421 .is_some_and(|file| file.disk_state().exists());
15422 diff.update(cx, |diff, cx| {
15423 diff.stage_or_unstage_hunks(
15424 stage,
15425 &hunks
15426 .map(|hunk| buffer_diff::DiffHunk {
15427 buffer_range: hunk.buffer_range,
15428 diff_base_byte_range: hunk.diff_base_byte_range,
15429 secondary_status: hunk.secondary_status,
15430 range: Point::zero()..Point::zero(), // unused
15431 })
15432 .collect::<Vec<_>>(),
15433 &buffer_snapshot,
15434 file_exists,
15435 cx,
15436 )
15437 });
15438 None
15439 }
15440
15441 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15442 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15443 self.buffer
15444 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15445 }
15446
15447 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15448 self.buffer.update(cx, |buffer, cx| {
15449 let ranges = vec![Anchor::min()..Anchor::max()];
15450 if !buffer.all_diff_hunks_expanded()
15451 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15452 {
15453 buffer.collapse_diff_hunks(ranges, cx);
15454 true
15455 } else {
15456 false
15457 }
15458 })
15459 }
15460
15461 fn toggle_diff_hunks_in_ranges(
15462 &mut self,
15463 ranges: Vec<Range<Anchor>>,
15464 cx: &mut Context<Editor>,
15465 ) {
15466 self.buffer.update(cx, |buffer, cx| {
15467 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15468 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15469 })
15470 }
15471
15472 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15473 self.buffer.update(cx, |buffer, cx| {
15474 let snapshot = buffer.snapshot(cx);
15475 let excerpt_id = range.end.excerpt_id;
15476 let point_range = range.to_point(&snapshot);
15477 let expand = !buffer.single_hunk_is_expanded(range, cx);
15478 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15479 })
15480 }
15481
15482 pub(crate) fn apply_all_diff_hunks(
15483 &mut self,
15484 _: &ApplyAllDiffHunks,
15485 window: &mut Window,
15486 cx: &mut Context<Self>,
15487 ) {
15488 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15489
15490 let buffers = self.buffer.read(cx).all_buffers();
15491 for branch_buffer in buffers {
15492 branch_buffer.update(cx, |branch_buffer, cx| {
15493 branch_buffer.merge_into_base(Vec::new(), cx);
15494 });
15495 }
15496
15497 if let Some(project) = self.project.clone() {
15498 self.save(true, project, window, cx).detach_and_log_err(cx);
15499 }
15500 }
15501
15502 pub(crate) fn apply_selected_diff_hunks(
15503 &mut self,
15504 _: &ApplyDiffHunk,
15505 window: &mut Window,
15506 cx: &mut Context<Self>,
15507 ) {
15508 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15509 let snapshot = self.snapshot(window, cx);
15510 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15511 let mut ranges_by_buffer = HashMap::default();
15512 self.transact(window, cx, |editor, _window, cx| {
15513 for hunk in hunks {
15514 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15515 ranges_by_buffer
15516 .entry(buffer.clone())
15517 .or_insert_with(Vec::new)
15518 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15519 }
15520 }
15521
15522 for (buffer, ranges) in ranges_by_buffer {
15523 buffer.update(cx, |buffer, cx| {
15524 buffer.merge_into_base(ranges, cx);
15525 });
15526 }
15527 });
15528
15529 if let Some(project) = self.project.clone() {
15530 self.save(true, project, window, cx).detach_and_log_err(cx);
15531 }
15532 }
15533
15534 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15535 if hovered != self.gutter_hovered {
15536 self.gutter_hovered = hovered;
15537 cx.notify();
15538 }
15539 }
15540
15541 pub fn insert_blocks(
15542 &mut self,
15543 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15544 autoscroll: Option<Autoscroll>,
15545 cx: &mut Context<Self>,
15546 ) -> Vec<CustomBlockId> {
15547 let blocks = self
15548 .display_map
15549 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15550 if let Some(autoscroll) = autoscroll {
15551 self.request_autoscroll(autoscroll, cx);
15552 }
15553 cx.notify();
15554 blocks
15555 }
15556
15557 pub fn resize_blocks(
15558 &mut self,
15559 heights: HashMap<CustomBlockId, u32>,
15560 autoscroll: Option<Autoscroll>,
15561 cx: &mut Context<Self>,
15562 ) {
15563 self.display_map
15564 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15565 if let Some(autoscroll) = autoscroll {
15566 self.request_autoscroll(autoscroll, cx);
15567 }
15568 cx.notify();
15569 }
15570
15571 pub fn replace_blocks(
15572 &mut self,
15573 renderers: HashMap<CustomBlockId, RenderBlock>,
15574 autoscroll: Option<Autoscroll>,
15575 cx: &mut Context<Self>,
15576 ) {
15577 self.display_map
15578 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15579 if let Some(autoscroll) = autoscroll {
15580 self.request_autoscroll(autoscroll, cx);
15581 }
15582 cx.notify();
15583 }
15584
15585 pub fn remove_blocks(
15586 &mut self,
15587 block_ids: HashSet<CustomBlockId>,
15588 autoscroll: Option<Autoscroll>,
15589 cx: &mut Context<Self>,
15590 ) {
15591 self.display_map.update(cx, |display_map, cx| {
15592 display_map.remove_blocks(block_ids, cx)
15593 });
15594 if let Some(autoscroll) = autoscroll {
15595 self.request_autoscroll(autoscroll, cx);
15596 }
15597 cx.notify();
15598 }
15599
15600 pub fn row_for_block(
15601 &self,
15602 block_id: CustomBlockId,
15603 cx: &mut Context<Self>,
15604 ) -> Option<DisplayRow> {
15605 self.display_map
15606 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15607 }
15608
15609 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15610 self.focused_block = Some(focused_block);
15611 }
15612
15613 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15614 self.focused_block.take()
15615 }
15616
15617 pub fn insert_creases(
15618 &mut self,
15619 creases: impl IntoIterator<Item = Crease<Anchor>>,
15620 cx: &mut Context<Self>,
15621 ) -> Vec<CreaseId> {
15622 self.display_map
15623 .update(cx, |map, cx| map.insert_creases(creases, cx))
15624 }
15625
15626 pub fn remove_creases(
15627 &mut self,
15628 ids: impl IntoIterator<Item = CreaseId>,
15629 cx: &mut Context<Self>,
15630 ) {
15631 self.display_map
15632 .update(cx, |map, cx| map.remove_creases(ids, cx));
15633 }
15634
15635 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15636 self.display_map
15637 .update(cx, |map, cx| map.snapshot(cx))
15638 .longest_row()
15639 }
15640
15641 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15642 self.display_map
15643 .update(cx, |map, cx| map.snapshot(cx))
15644 .max_point()
15645 }
15646
15647 pub fn text(&self, cx: &App) -> String {
15648 self.buffer.read(cx).read(cx).text()
15649 }
15650
15651 pub fn is_empty(&self, cx: &App) -> bool {
15652 self.buffer.read(cx).read(cx).is_empty()
15653 }
15654
15655 pub fn text_option(&self, cx: &App) -> Option<String> {
15656 let text = self.text(cx);
15657 let text = text.trim();
15658
15659 if text.is_empty() {
15660 return None;
15661 }
15662
15663 Some(text.to_string())
15664 }
15665
15666 pub fn set_text(
15667 &mut self,
15668 text: impl Into<Arc<str>>,
15669 window: &mut Window,
15670 cx: &mut Context<Self>,
15671 ) {
15672 self.transact(window, cx, |this, _, cx| {
15673 this.buffer
15674 .read(cx)
15675 .as_singleton()
15676 .expect("you can only call set_text on editors for singleton buffers")
15677 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15678 });
15679 }
15680
15681 pub fn display_text(&self, cx: &mut App) -> String {
15682 self.display_map
15683 .update(cx, |map, cx| map.snapshot(cx))
15684 .text()
15685 }
15686
15687 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15688 let mut wrap_guides = smallvec::smallvec![];
15689
15690 if self.show_wrap_guides == Some(false) {
15691 return wrap_guides;
15692 }
15693
15694 let settings = self.buffer.read(cx).language_settings(cx);
15695 if settings.show_wrap_guides {
15696 match self.soft_wrap_mode(cx) {
15697 SoftWrap::Column(soft_wrap) => {
15698 wrap_guides.push((soft_wrap as usize, true));
15699 }
15700 SoftWrap::Bounded(soft_wrap) => {
15701 wrap_guides.push((soft_wrap as usize, true));
15702 }
15703 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15704 }
15705 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15706 }
15707
15708 wrap_guides
15709 }
15710
15711 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15712 let settings = self.buffer.read(cx).language_settings(cx);
15713 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15714 match mode {
15715 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15716 SoftWrap::None
15717 }
15718 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15719 language_settings::SoftWrap::PreferredLineLength => {
15720 SoftWrap::Column(settings.preferred_line_length)
15721 }
15722 language_settings::SoftWrap::Bounded => {
15723 SoftWrap::Bounded(settings.preferred_line_length)
15724 }
15725 }
15726 }
15727
15728 pub fn set_soft_wrap_mode(
15729 &mut self,
15730 mode: language_settings::SoftWrap,
15731
15732 cx: &mut Context<Self>,
15733 ) {
15734 self.soft_wrap_mode_override = Some(mode);
15735 cx.notify();
15736 }
15737
15738 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15739 self.hard_wrap = hard_wrap;
15740 cx.notify();
15741 }
15742
15743 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15744 self.text_style_refinement = Some(style);
15745 }
15746
15747 /// called by the Element so we know what style we were most recently rendered with.
15748 pub(crate) fn set_style(
15749 &mut self,
15750 style: EditorStyle,
15751 window: &mut Window,
15752 cx: &mut Context<Self>,
15753 ) {
15754 let rem_size = window.rem_size();
15755 self.display_map.update(cx, |map, cx| {
15756 map.set_font(
15757 style.text.font(),
15758 style.text.font_size.to_pixels(rem_size),
15759 cx,
15760 )
15761 });
15762 self.style = Some(style);
15763 }
15764
15765 pub fn style(&self) -> Option<&EditorStyle> {
15766 self.style.as_ref()
15767 }
15768
15769 // Called by the element. This method is not designed to be called outside of the editor
15770 // element's layout code because it does not notify when rewrapping is computed synchronously.
15771 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15772 self.display_map
15773 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15774 }
15775
15776 pub fn set_soft_wrap(&mut self) {
15777 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15778 }
15779
15780 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15781 if self.soft_wrap_mode_override.is_some() {
15782 self.soft_wrap_mode_override.take();
15783 } else {
15784 let soft_wrap = match self.soft_wrap_mode(cx) {
15785 SoftWrap::GitDiff => return,
15786 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15787 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15788 language_settings::SoftWrap::None
15789 }
15790 };
15791 self.soft_wrap_mode_override = Some(soft_wrap);
15792 }
15793 cx.notify();
15794 }
15795
15796 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15797 let Some(workspace) = self.workspace() else {
15798 return;
15799 };
15800 let fs = workspace.read(cx).app_state().fs.clone();
15801 let current_show = TabBarSettings::get_global(cx).show;
15802 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15803 setting.show = Some(!current_show);
15804 });
15805 }
15806
15807 pub fn toggle_indent_guides(
15808 &mut self,
15809 _: &ToggleIndentGuides,
15810 _: &mut Window,
15811 cx: &mut Context<Self>,
15812 ) {
15813 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15814 self.buffer
15815 .read(cx)
15816 .language_settings(cx)
15817 .indent_guides
15818 .enabled
15819 });
15820 self.show_indent_guides = Some(!currently_enabled);
15821 cx.notify();
15822 }
15823
15824 fn should_show_indent_guides(&self) -> Option<bool> {
15825 self.show_indent_guides
15826 }
15827
15828 pub fn toggle_line_numbers(
15829 &mut self,
15830 _: &ToggleLineNumbers,
15831 _: &mut Window,
15832 cx: &mut Context<Self>,
15833 ) {
15834 let mut editor_settings = EditorSettings::get_global(cx).clone();
15835 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15836 EditorSettings::override_global(editor_settings, cx);
15837 }
15838
15839 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15840 if let Some(show_line_numbers) = self.show_line_numbers {
15841 return show_line_numbers;
15842 }
15843 EditorSettings::get_global(cx).gutter.line_numbers
15844 }
15845
15846 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15847 self.use_relative_line_numbers
15848 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15849 }
15850
15851 pub fn toggle_relative_line_numbers(
15852 &mut self,
15853 _: &ToggleRelativeLineNumbers,
15854 _: &mut Window,
15855 cx: &mut Context<Self>,
15856 ) {
15857 let is_relative = self.should_use_relative_line_numbers(cx);
15858 self.set_relative_line_number(Some(!is_relative), cx)
15859 }
15860
15861 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15862 self.use_relative_line_numbers = is_relative;
15863 cx.notify();
15864 }
15865
15866 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15867 self.show_gutter = show_gutter;
15868 cx.notify();
15869 }
15870
15871 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15872 self.show_scrollbars = show_scrollbars;
15873 cx.notify();
15874 }
15875
15876 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15877 self.show_line_numbers = Some(show_line_numbers);
15878 cx.notify();
15879 }
15880
15881 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15882 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15883 cx.notify();
15884 }
15885
15886 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15887 self.show_code_actions = Some(show_code_actions);
15888 cx.notify();
15889 }
15890
15891 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15892 self.show_runnables = Some(show_runnables);
15893 cx.notify();
15894 }
15895
15896 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15897 self.show_breakpoints = Some(show_breakpoints);
15898 cx.notify();
15899 }
15900
15901 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15902 if self.display_map.read(cx).masked != masked {
15903 self.display_map.update(cx, |map, _| map.masked = masked);
15904 }
15905 cx.notify()
15906 }
15907
15908 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15909 self.show_wrap_guides = Some(show_wrap_guides);
15910 cx.notify();
15911 }
15912
15913 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15914 self.show_indent_guides = Some(show_indent_guides);
15915 cx.notify();
15916 }
15917
15918 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15919 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15920 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15921 if let Some(dir) = file.abs_path(cx).parent() {
15922 return Some(dir.to_owned());
15923 }
15924 }
15925
15926 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15927 return Some(project_path.path.to_path_buf());
15928 }
15929 }
15930
15931 None
15932 }
15933
15934 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15935 self.active_excerpt(cx)?
15936 .1
15937 .read(cx)
15938 .file()
15939 .and_then(|f| f.as_local())
15940 }
15941
15942 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15943 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15944 let buffer = buffer.read(cx);
15945 if let Some(project_path) = buffer.project_path(cx) {
15946 let project = self.project.as_ref()?.read(cx);
15947 project.absolute_path(&project_path, cx)
15948 } else {
15949 buffer
15950 .file()
15951 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15952 }
15953 })
15954 }
15955
15956 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15957 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15958 let project_path = buffer.read(cx).project_path(cx)?;
15959 let project = self.project.as_ref()?.read(cx);
15960 let entry = project.entry_for_path(&project_path, cx)?;
15961 let path = entry.path.to_path_buf();
15962 Some(path)
15963 })
15964 }
15965
15966 pub fn reveal_in_finder(
15967 &mut self,
15968 _: &RevealInFileManager,
15969 _window: &mut Window,
15970 cx: &mut Context<Self>,
15971 ) {
15972 if let Some(target) = self.target_file(cx) {
15973 cx.reveal_path(&target.abs_path(cx));
15974 }
15975 }
15976
15977 pub fn copy_path(
15978 &mut self,
15979 _: &zed_actions::workspace::CopyPath,
15980 _window: &mut Window,
15981 cx: &mut Context<Self>,
15982 ) {
15983 if let Some(path) = self.target_file_abs_path(cx) {
15984 if let Some(path) = path.to_str() {
15985 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15986 }
15987 }
15988 }
15989
15990 pub fn copy_relative_path(
15991 &mut self,
15992 _: &zed_actions::workspace::CopyRelativePath,
15993 _window: &mut Window,
15994 cx: &mut Context<Self>,
15995 ) {
15996 if let Some(path) = self.target_file_path(cx) {
15997 if let Some(path) = path.to_str() {
15998 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15999 }
16000 }
16001 }
16002
16003 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16004 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16005 buffer.read(cx).project_path(cx)
16006 } else {
16007 None
16008 }
16009 }
16010
16011 // Returns true if the editor handled a go-to-line request
16012 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16013 maybe!({
16014 let breakpoint_store = self.breakpoint_store.as_ref()?;
16015
16016 let Some((_, _, active_position)) =
16017 breakpoint_store.read(cx).active_position().cloned()
16018 else {
16019 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16020 return None;
16021 };
16022
16023 let snapshot = self
16024 .project
16025 .as_ref()?
16026 .read(cx)
16027 .buffer_for_id(active_position.buffer_id?, cx)?
16028 .read(cx)
16029 .snapshot();
16030
16031 let mut handled = false;
16032 for (id, ExcerptRange { context, .. }) in self
16033 .buffer
16034 .read(cx)
16035 .excerpts_for_buffer(active_position.buffer_id?, cx)
16036 {
16037 if context.start.cmp(&active_position, &snapshot).is_ge()
16038 || context.end.cmp(&active_position, &snapshot).is_lt()
16039 {
16040 continue;
16041 }
16042 let snapshot = self.buffer.read(cx).snapshot(cx);
16043 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
16044
16045 handled = true;
16046 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16047 self.go_to_line::<DebugCurrentRowHighlight>(
16048 multibuffer_anchor,
16049 Some(cx.theme().colors().editor_debugger_active_line_background),
16050 window,
16051 cx,
16052 );
16053
16054 cx.notify();
16055 }
16056 handled.then_some(())
16057 })
16058 .is_some()
16059 }
16060
16061 pub fn copy_file_name_without_extension(
16062 &mut self,
16063 _: &CopyFileNameWithoutExtension,
16064 _: &mut Window,
16065 cx: &mut Context<Self>,
16066 ) {
16067 if let Some(file) = self.target_file(cx) {
16068 if let Some(file_stem) = file.path().file_stem() {
16069 if let Some(name) = file_stem.to_str() {
16070 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16071 }
16072 }
16073 }
16074 }
16075
16076 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16077 if let Some(file) = self.target_file(cx) {
16078 if let Some(file_name) = file.path().file_name() {
16079 if let Some(name) = file_name.to_str() {
16080 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16081 }
16082 }
16083 }
16084 }
16085
16086 pub fn toggle_git_blame(
16087 &mut self,
16088 _: &::git::Blame,
16089 window: &mut Window,
16090 cx: &mut Context<Self>,
16091 ) {
16092 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16093
16094 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16095 self.start_git_blame(true, window, cx);
16096 }
16097
16098 cx.notify();
16099 }
16100
16101 pub fn toggle_git_blame_inline(
16102 &mut self,
16103 _: &ToggleGitBlameInline,
16104 window: &mut Window,
16105 cx: &mut Context<Self>,
16106 ) {
16107 self.toggle_git_blame_inline_internal(true, window, cx);
16108 cx.notify();
16109 }
16110
16111 pub fn open_git_blame_commit(
16112 &mut self,
16113 _: &OpenGitBlameCommit,
16114 window: &mut Window,
16115 cx: &mut Context<Self>,
16116 ) {
16117 self.open_git_blame_commit_internal(window, cx);
16118 }
16119
16120 fn open_git_blame_commit_internal(
16121 &mut self,
16122 window: &mut Window,
16123 cx: &mut Context<Self>,
16124 ) -> Option<()> {
16125 let blame = self.blame.as_ref()?;
16126 let snapshot = self.snapshot(window, cx);
16127 let cursor = self.selections.newest::<Point>(cx).head();
16128 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16129 let blame_entry = blame
16130 .update(cx, |blame, cx| {
16131 blame
16132 .blame_for_rows(
16133 &[RowInfo {
16134 buffer_id: Some(buffer.remote_id()),
16135 buffer_row: Some(point.row),
16136 ..Default::default()
16137 }],
16138 cx,
16139 )
16140 .next()
16141 })
16142 .flatten()?;
16143 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16144 let repo = blame.read(cx).repository(cx)?;
16145 let workspace = self.workspace()?.downgrade();
16146 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16147 None
16148 }
16149
16150 pub fn git_blame_inline_enabled(&self) -> bool {
16151 self.git_blame_inline_enabled
16152 }
16153
16154 pub fn toggle_selection_menu(
16155 &mut self,
16156 _: &ToggleSelectionMenu,
16157 _: &mut Window,
16158 cx: &mut Context<Self>,
16159 ) {
16160 self.show_selection_menu = self
16161 .show_selection_menu
16162 .map(|show_selections_menu| !show_selections_menu)
16163 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16164
16165 cx.notify();
16166 }
16167
16168 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16169 self.show_selection_menu
16170 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16171 }
16172
16173 fn start_git_blame(
16174 &mut self,
16175 user_triggered: bool,
16176 window: &mut Window,
16177 cx: &mut Context<Self>,
16178 ) {
16179 if let Some(project) = self.project.as_ref() {
16180 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16181 return;
16182 };
16183
16184 if buffer.read(cx).file().is_none() {
16185 return;
16186 }
16187
16188 let focused = self.focus_handle(cx).contains_focused(window, cx);
16189
16190 let project = project.clone();
16191 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16192 self.blame_subscription =
16193 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16194 self.blame = Some(blame);
16195 }
16196 }
16197
16198 fn toggle_git_blame_inline_internal(
16199 &mut self,
16200 user_triggered: bool,
16201 window: &mut Window,
16202 cx: &mut Context<Self>,
16203 ) {
16204 if self.git_blame_inline_enabled {
16205 self.git_blame_inline_enabled = false;
16206 self.show_git_blame_inline = false;
16207 self.show_git_blame_inline_delay_task.take();
16208 } else {
16209 self.git_blame_inline_enabled = true;
16210 self.start_git_blame_inline(user_triggered, window, cx);
16211 }
16212
16213 cx.notify();
16214 }
16215
16216 fn start_git_blame_inline(
16217 &mut self,
16218 user_triggered: bool,
16219 window: &mut Window,
16220 cx: &mut Context<Self>,
16221 ) {
16222 self.start_git_blame(user_triggered, window, cx);
16223
16224 if ProjectSettings::get_global(cx)
16225 .git
16226 .inline_blame_delay()
16227 .is_some()
16228 {
16229 self.start_inline_blame_timer(window, cx);
16230 } else {
16231 self.show_git_blame_inline = true
16232 }
16233 }
16234
16235 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16236 self.blame.as_ref()
16237 }
16238
16239 pub fn show_git_blame_gutter(&self) -> bool {
16240 self.show_git_blame_gutter
16241 }
16242
16243 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16244 self.show_git_blame_gutter && self.has_blame_entries(cx)
16245 }
16246
16247 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16248 self.show_git_blame_inline
16249 && (self.focus_handle.is_focused(window)
16250 || self
16251 .git_blame_inline_tooltip
16252 .as_ref()
16253 .and_then(|t| t.upgrade())
16254 .is_some())
16255 && !self.newest_selection_head_on_empty_line(cx)
16256 && self.has_blame_entries(cx)
16257 }
16258
16259 fn has_blame_entries(&self, cx: &App) -> bool {
16260 self.blame()
16261 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16262 }
16263
16264 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16265 let cursor_anchor = self.selections.newest_anchor().head();
16266
16267 let snapshot = self.buffer.read(cx).snapshot(cx);
16268 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16269
16270 snapshot.line_len(buffer_row) == 0
16271 }
16272
16273 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16274 let buffer_and_selection = maybe!({
16275 let selection = self.selections.newest::<Point>(cx);
16276 let selection_range = selection.range();
16277
16278 let multi_buffer = self.buffer().read(cx);
16279 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16280 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16281
16282 let (buffer, range, _) = if selection.reversed {
16283 buffer_ranges.first()
16284 } else {
16285 buffer_ranges.last()
16286 }?;
16287
16288 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16289 ..text::ToPoint::to_point(&range.end, &buffer).row;
16290 Some((
16291 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16292 selection,
16293 ))
16294 });
16295
16296 let Some((buffer, selection)) = buffer_and_selection else {
16297 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16298 };
16299
16300 let Some(project) = self.project.as_ref() else {
16301 return Task::ready(Err(anyhow!("editor does not have project")));
16302 };
16303
16304 project.update(cx, |project, cx| {
16305 project.get_permalink_to_line(&buffer, selection, cx)
16306 })
16307 }
16308
16309 pub fn copy_permalink_to_line(
16310 &mut self,
16311 _: &CopyPermalinkToLine,
16312 window: &mut Window,
16313 cx: &mut Context<Self>,
16314 ) {
16315 let permalink_task = self.get_permalink_to_line(cx);
16316 let workspace = self.workspace();
16317
16318 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16319 Ok(permalink) => {
16320 cx.update(|_, cx| {
16321 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16322 })
16323 .ok();
16324 }
16325 Err(err) => {
16326 let message = format!("Failed to copy permalink: {err}");
16327
16328 Err::<(), anyhow::Error>(err).log_err();
16329
16330 if let Some(workspace) = workspace {
16331 workspace
16332 .update_in(cx, |workspace, _, cx| {
16333 struct CopyPermalinkToLine;
16334
16335 workspace.show_toast(
16336 Toast::new(
16337 NotificationId::unique::<CopyPermalinkToLine>(),
16338 message,
16339 ),
16340 cx,
16341 )
16342 })
16343 .ok();
16344 }
16345 }
16346 })
16347 .detach();
16348 }
16349
16350 pub fn copy_file_location(
16351 &mut self,
16352 _: &CopyFileLocation,
16353 _: &mut Window,
16354 cx: &mut Context<Self>,
16355 ) {
16356 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16357 if let Some(file) = self.target_file(cx) {
16358 if let Some(path) = file.path().to_str() {
16359 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16360 }
16361 }
16362 }
16363
16364 pub fn open_permalink_to_line(
16365 &mut self,
16366 _: &OpenPermalinkToLine,
16367 window: &mut Window,
16368 cx: &mut Context<Self>,
16369 ) {
16370 let permalink_task = self.get_permalink_to_line(cx);
16371 let workspace = self.workspace();
16372
16373 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16374 Ok(permalink) => {
16375 cx.update(|_, cx| {
16376 cx.open_url(permalink.as_ref());
16377 })
16378 .ok();
16379 }
16380 Err(err) => {
16381 let message = format!("Failed to open permalink: {err}");
16382
16383 Err::<(), anyhow::Error>(err).log_err();
16384
16385 if let Some(workspace) = workspace {
16386 workspace
16387 .update(cx, |workspace, cx| {
16388 struct OpenPermalinkToLine;
16389
16390 workspace.show_toast(
16391 Toast::new(
16392 NotificationId::unique::<OpenPermalinkToLine>(),
16393 message,
16394 ),
16395 cx,
16396 )
16397 })
16398 .ok();
16399 }
16400 }
16401 })
16402 .detach();
16403 }
16404
16405 pub fn insert_uuid_v4(
16406 &mut self,
16407 _: &InsertUuidV4,
16408 window: &mut Window,
16409 cx: &mut Context<Self>,
16410 ) {
16411 self.insert_uuid(UuidVersion::V4, window, cx);
16412 }
16413
16414 pub fn insert_uuid_v7(
16415 &mut self,
16416 _: &InsertUuidV7,
16417 window: &mut Window,
16418 cx: &mut Context<Self>,
16419 ) {
16420 self.insert_uuid(UuidVersion::V7, window, cx);
16421 }
16422
16423 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16424 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16425 self.transact(window, cx, |this, window, cx| {
16426 let edits = this
16427 .selections
16428 .all::<Point>(cx)
16429 .into_iter()
16430 .map(|selection| {
16431 let uuid = match version {
16432 UuidVersion::V4 => uuid::Uuid::new_v4(),
16433 UuidVersion::V7 => uuid::Uuid::now_v7(),
16434 };
16435
16436 (selection.range(), uuid.to_string())
16437 });
16438 this.edit(edits, cx);
16439 this.refresh_inline_completion(true, false, window, cx);
16440 });
16441 }
16442
16443 pub fn open_selections_in_multibuffer(
16444 &mut self,
16445 _: &OpenSelectionsInMultibuffer,
16446 window: &mut Window,
16447 cx: &mut Context<Self>,
16448 ) {
16449 let multibuffer = self.buffer.read(cx);
16450
16451 let Some(buffer) = multibuffer.as_singleton() else {
16452 return;
16453 };
16454
16455 let Some(workspace) = self.workspace() else {
16456 return;
16457 };
16458
16459 let locations = self
16460 .selections
16461 .disjoint_anchors()
16462 .iter()
16463 .map(|range| Location {
16464 buffer: buffer.clone(),
16465 range: range.start.text_anchor..range.end.text_anchor,
16466 })
16467 .collect::<Vec<_>>();
16468
16469 let title = multibuffer.title(cx).to_string();
16470
16471 cx.spawn_in(window, async move |_, cx| {
16472 workspace.update_in(cx, |workspace, window, cx| {
16473 Self::open_locations_in_multibuffer(
16474 workspace,
16475 locations,
16476 format!("Selections for '{title}'"),
16477 false,
16478 MultibufferSelectionMode::All,
16479 window,
16480 cx,
16481 );
16482 })
16483 })
16484 .detach();
16485 }
16486
16487 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16488 /// last highlight added will be used.
16489 ///
16490 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16491 pub fn highlight_rows<T: 'static>(
16492 &mut self,
16493 range: Range<Anchor>,
16494 color: Hsla,
16495 should_autoscroll: bool,
16496 cx: &mut Context<Self>,
16497 ) {
16498 let snapshot = self.buffer().read(cx).snapshot(cx);
16499 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16500 let ix = row_highlights.binary_search_by(|highlight| {
16501 Ordering::Equal
16502 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16503 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16504 });
16505
16506 if let Err(mut ix) = ix {
16507 let index = post_inc(&mut self.highlight_order);
16508
16509 // If this range intersects with the preceding highlight, then merge it with
16510 // the preceding highlight. Otherwise insert a new highlight.
16511 let mut merged = false;
16512 if ix > 0 {
16513 let prev_highlight = &mut row_highlights[ix - 1];
16514 if prev_highlight
16515 .range
16516 .end
16517 .cmp(&range.start, &snapshot)
16518 .is_ge()
16519 {
16520 ix -= 1;
16521 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16522 prev_highlight.range.end = range.end;
16523 }
16524 merged = true;
16525 prev_highlight.index = index;
16526 prev_highlight.color = color;
16527 prev_highlight.should_autoscroll = should_autoscroll;
16528 }
16529 }
16530
16531 if !merged {
16532 row_highlights.insert(
16533 ix,
16534 RowHighlight {
16535 range: range.clone(),
16536 index,
16537 color,
16538 should_autoscroll,
16539 },
16540 );
16541 }
16542
16543 // If any of the following highlights intersect with this one, merge them.
16544 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16545 let highlight = &row_highlights[ix];
16546 if next_highlight
16547 .range
16548 .start
16549 .cmp(&highlight.range.end, &snapshot)
16550 .is_le()
16551 {
16552 if next_highlight
16553 .range
16554 .end
16555 .cmp(&highlight.range.end, &snapshot)
16556 .is_gt()
16557 {
16558 row_highlights[ix].range.end = next_highlight.range.end;
16559 }
16560 row_highlights.remove(ix + 1);
16561 } else {
16562 break;
16563 }
16564 }
16565 }
16566 }
16567
16568 /// Remove any highlighted row ranges of the given type that intersect the
16569 /// given ranges.
16570 pub fn remove_highlighted_rows<T: 'static>(
16571 &mut self,
16572 ranges_to_remove: Vec<Range<Anchor>>,
16573 cx: &mut Context<Self>,
16574 ) {
16575 let snapshot = self.buffer().read(cx).snapshot(cx);
16576 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16577 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16578 row_highlights.retain(|highlight| {
16579 while let Some(range_to_remove) = ranges_to_remove.peek() {
16580 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16581 Ordering::Less | Ordering::Equal => {
16582 ranges_to_remove.next();
16583 }
16584 Ordering::Greater => {
16585 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16586 Ordering::Less | Ordering::Equal => {
16587 return false;
16588 }
16589 Ordering::Greater => break,
16590 }
16591 }
16592 }
16593 }
16594
16595 true
16596 })
16597 }
16598
16599 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16600 pub fn clear_row_highlights<T: 'static>(&mut self) {
16601 self.highlighted_rows.remove(&TypeId::of::<T>());
16602 }
16603
16604 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16605 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16606 self.highlighted_rows
16607 .get(&TypeId::of::<T>())
16608 .map_or(&[] as &[_], |vec| vec.as_slice())
16609 .iter()
16610 .map(|highlight| (highlight.range.clone(), highlight.color))
16611 }
16612
16613 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16614 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16615 /// Allows to ignore certain kinds of highlights.
16616 pub fn highlighted_display_rows(
16617 &self,
16618 window: &mut Window,
16619 cx: &mut App,
16620 ) -> BTreeMap<DisplayRow, LineHighlight> {
16621 let snapshot = self.snapshot(window, cx);
16622 let mut used_highlight_orders = HashMap::default();
16623 self.highlighted_rows
16624 .iter()
16625 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16626 .fold(
16627 BTreeMap::<DisplayRow, LineHighlight>::new(),
16628 |mut unique_rows, highlight| {
16629 let start = highlight.range.start.to_display_point(&snapshot);
16630 let end = highlight.range.end.to_display_point(&snapshot);
16631 let start_row = start.row().0;
16632 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16633 && end.column() == 0
16634 {
16635 end.row().0.saturating_sub(1)
16636 } else {
16637 end.row().0
16638 };
16639 for row in start_row..=end_row {
16640 let used_index =
16641 used_highlight_orders.entry(row).or_insert(highlight.index);
16642 if highlight.index >= *used_index {
16643 *used_index = highlight.index;
16644 unique_rows.insert(DisplayRow(row), highlight.color.into());
16645 }
16646 }
16647 unique_rows
16648 },
16649 )
16650 }
16651
16652 pub fn highlighted_display_row_for_autoscroll(
16653 &self,
16654 snapshot: &DisplaySnapshot,
16655 ) -> Option<DisplayRow> {
16656 self.highlighted_rows
16657 .values()
16658 .flat_map(|highlighted_rows| highlighted_rows.iter())
16659 .filter_map(|highlight| {
16660 if highlight.should_autoscroll {
16661 Some(highlight.range.start.to_display_point(snapshot).row())
16662 } else {
16663 None
16664 }
16665 })
16666 .min()
16667 }
16668
16669 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16670 self.highlight_background::<SearchWithinRange>(
16671 ranges,
16672 |colors| colors.editor_document_highlight_read_background,
16673 cx,
16674 )
16675 }
16676
16677 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16678 self.breadcrumb_header = Some(new_header);
16679 }
16680
16681 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16682 self.clear_background_highlights::<SearchWithinRange>(cx);
16683 }
16684
16685 pub fn highlight_background<T: 'static>(
16686 &mut self,
16687 ranges: &[Range<Anchor>],
16688 color_fetcher: fn(&ThemeColors) -> Hsla,
16689 cx: &mut Context<Self>,
16690 ) {
16691 self.background_highlights
16692 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16693 self.scrollbar_marker_state.dirty = true;
16694 cx.notify();
16695 }
16696
16697 pub fn clear_background_highlights<T: 'static>(
16698 &mut self,
16699 cx: &mut Context<Self>,
16700 ) -> Option<BackgroundHighlight> {
16701 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16702 if !text_highlights.1.is_empty() {
16703 self.scrollbar_marker_state.dirty = true;
16704 cx.notify();
16705 }
16706 Some(text_highlights)
16707 }
16708
16709 pub fn highlight_gutter<T: 'static>(
16710 &mut self,
16711 ranges: &[Range<Anchor>],
16712 color_fetcher: fn(&App) -> Hsla,
16713 cx: &mut Context<Self>,
16714 ) {
16715 self.gutter_highlights
16716 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16717 cx.notify();
16718 }
16719
16720 pub fn clear_gutter_highlights<T: 'static>(
16721 &mut self,
16722 cx: &mut Context<Self>,
16723 ) -> Option<GutterHighlight> {
16724 cx.notify();
16725 self.gutter_highlights.remove(&TypeId::of::<T>())
16726 }
16727
16728 #[cfg(feature = "test-support")]
16729 pub fn all_text_background_highlights(
16730 &self,
16731 window: &mut Window,
16732 cx: &mut Context<Self>,
16733 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16734 let snapshot = self.snapshot(window, cx);
16735 let buffer = &snapshot.buffer_snapshot;
16736 let start = buffer.anchor_before(0);
16737 let end = buffer.anchor_after(buffer.len());
16738 let theme = cx.theme().colors();
16739 self.background_highlights_in_range(start..end, &snapshot, theme)
16740 }
16741
16742 #[cfg(feature = "test-support")]
16743 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16744 let snapshot = self.buffer().read(cx).snapshot(cx);
16745
16746 let highlights = self
16747 .background_highlights
16748 .get(&TypeId::of::<items::BufferSearchHighlights>());
16749
16750 if let Some((_color, ranges)) = highlights {
16751 ranges
16752 .iter()
16753 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16754 .collect_vec()
16755 } else {
16756 vec![]
16757 }
16758 }
16759
16760 fn document_highlights_for_position<'a>(
16761 &'a self,
16762 position: Anchor,
16763 buffer: &'a MultiBufferSnapshot,
16764 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16765 let read_highlights = self
16766 .background_highlights
16767 .get(&TypeId::of::<DocumentHighlightRead>())
16768 .map(|h| &h.1);
16769 let write_highlights = self
16770 .background_highlights
16771 .get(&TypeId::of::<DocumentHighlightWrite>())
16772 .map(|h| &h.1);
16773 let left_position = position.bias_left(buffer);
16774 let right_position = position.bias_right(buffer);
16775 read_highlights
16776 .into_iter()
16777 .chain(write_highlights)
16778 .flat_map(move |ranges| {
16779 let start_ix = match ranges.binary_search_by(|probe| {
16780 let cmp = probe.end.cmp(&left_position, buffer);
16781 if cmp.is_ge() {
16782 Ordering::Greater
16783 } else {
16784 Ordering::Less
16785 }
16786 }) {
16787 Ok(i) | Err(i) => i,
16788 };
16789
16790 ranges[start_ix..]
16791 .iter()
16792 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16793 })
16794 }
16795
16796 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16797 self.background_highlights
16798 .get(&TypeId::of::<T>())
16799 .map_or(false, |(_, highlights)| !highlights.is_empty())
16800 }
16801
16802 pub fn background_highlights_in_range(
16803 &self,
16804 search_range: Range<Anchor>,
16805 display_snapshot: &DisplaySnapshot,
16806 theme: &ThemeColors,
16807 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16808 let mut results = Vec::new();
16809 for (color_fetcher, ranges) in self.background_highlights.values() {
16810 let color = color_fetcher(theme);
16811 let start_ix = match ranges.binary_search_by(|probe| {
16812 let cmp = probe
16813 .end
16814 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16815 if cmp.is_gt() {
16816 Ordering::Greater
16817 } else {
16818 Ordering::Less
16819 }
16820 }) {
16821 Ok(i) | Err(i) => i,
16822 };
16823 for range in &ranges[start_ix..] {
16824 if range
16825 .start
16826 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16827 .is_ge()
16828 {
16829 break;
16830 }
16831
16832 let start = range.start.to_display_point(display_snapshot);
16833 let end = range.end.to_display_point(display_snapshot);
16834 results.push((start..end, color))
16835 }
16836 }
16837 results
16838 }
16839
16840 pub fn background_highlight_row_ranges<T: 'static>(
16841 &self,
16842 search_range: Range<Anchor>,
16843 display_snapshot: &DisplaySnapshot,
16844 count: usize,
16845 ) -> Vec<RangeInclusive<DisplayPoint>> {
16846 let mut results = Vec::new();
16847 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16848 return vec![];
16849 };
16850
16851 let start_ix = match ranges.binary_search_by(|probe| {
16852 let cmp = probe
16853 .end
16854 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16855 if cmp.is_gt() {
16856 Ordering::Greater
16857 } else {
16858 Ordering::Less
16859 }
16860 }) {
16861 Ok(i) | Err(i) => i,
16862 };
16863 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16864 if let (Some(start_display), Some(end_display)) = (start, end) {
16865 results.push(
16866 start_display.to_display_point(display_snapshot)
16867 ..=end_display.to_display_point(display_snapshot),
16868 );
16869 }
16870 };
16871 let mut start_row: Option<Point> = None;
16872 let mut end_row: Option<Point> = None;
16873 if ranges.len() > count {
16874 return Vec::new();
16875 }
16876 for range in &ranges[start_ix..] {
16877 if range
16878 .start
16879 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16880 .is_ge()
16881 {
16882 break;
16883 }
16884 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16885 if let Some(current_row) = &end_row {
16886 if end.row == current_row.row {
16887 continue;
16888 }
16889 }
16890 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16891 if start_row.is_none() {
16892 assert_eq!(end_row, None);
16893 start_row = Some(start);
16894 end_row = Some(end);
16895 continue;
16896 }
16897 if let Some(current_end) = end_row.as_mut() {
16898 if start.row > current_end.row + 1 {
16899 push_region(start_row, end_row);
16900 start_row = Some(start);
16901 end_row = Some(end);
16902 } else {
16903 // Merge two hunks.
16904 *current_end = end;
16905 }
16906 } else {
16907 unreachable!();
16908 }
16909 }
16910 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16911 push_region(start_row, end_row);
16912 results
16913 }
16914
16915 pub fn gutter_highlights_in_range(
16916 &self,
16917 search_range: Range<Anchor>,
16918 display_snapshot: &DisplaySnapshot,
16919 cx: &App,
16920 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16921 let mut results = Vec::new();
16922 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16923 let color = color_fetcher(cx);
16924 let start_ix = match ranges.binary_search_by(|probe| {
16925 let cmp = probe
16926 .end
16927 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16928 if cmp.is_gt() {
16929 Ordering::Greater
16930 } else {
16931 Ordering::Less
16932 }
16933 }) {
16934 Ok(i) | Err(i) => i,
16935 };
16936 for range in &ranges[start_ix..] {
16937 if range
16938 .start
16939 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16940 .is_ge()
16941 {
16942 break;
16943 }
16944
16945 let start = range.start.to_display_point(display_snapshot);
16946 let end = range.end.to_display_point(display_snapshot);
16947 results.push((start..end, color))
16948 }
16949 }
16950 results
16951 }
16952
16953 /// Get the text ranges corresponding to the redaction query
16954 pub fn redacted_ranges(
16955 &self,
16956 search_range: Range<Anchor>,
16957 display_snapshot: &DisplaySnapshot,
16958 cx: &App,
16959 ) -> Vec<Range<DisplayPoint>> {
16960 display_snapshot
16961 .buffer_snapshot
16962 .redacted_ranges(search_range, |file| {
16963 if let Some(file) = file {
16964 file.is_private()
16965 && EditorSettings::get(
16966 Some(SettingsLocation {
16967 worktree_id: file.worktree_id(cx),
16968 path: file.path().as_ref(),
16969 }),
16970 cx,
16971 )
16972 .redact_private_values
16973 } else {
16974 false
16975 }
16976 })
16977 .map(|range| {
16978 range.start.to_display_point(display_snapshot)
16979 ..range.end.to_display_point(display_snapshot)
16980 })
16981 .collect()
16982 }
16983
16984 pub fn highlight_text<T: 'static>(
16985 &mut self,
16986 ranges: Vec<Range<Anchor>>,
16987 style: HighlightStyle,
16988 cx: &mut Context<Self>,
16989 ) {
16990 self.display_map.update(cx, |map, _| {
16991 map.highlight_text(TypeId::of::<T>(), ranges, style)
16992 });
16993 cx.notify();
16994 }
16995
16996 pub(crate) fn highlight_inlays<T: 'static>(
16997 &mut self,
16998 highlights: Vec<InlayHighlight>,
16999 style: HighlightStyle,
17000 cx: &mut Context<Self>,
17001 ) {
17002 self.display_map.update(cx, |map, _| {
17003 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17004 });
17005 cx.notify();
17006 }
17007
17008 pub fn text_highlights<'a, T: 'static>(
17009 &'a self,
17010 cx: &'a App,
17011 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17012 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17013 }
17014
17015 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17016 let cleared = self
17017 .display_map
17018 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17019 if cleared {
17020 cx.notify();
17021 }
17022 }
17023
17024 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17025 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17026 && self.focus_handle.is_focused(window)
17027 }
17028
17029 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17030 self.show_cursor_when_unfocused = is_enabled;
17031 cx.notify();
17032 }
17033
17034 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17035 cx.notify();
17036 }
17037
17038 fn on_buffer_event(
17039 &mut self,
17040 multibuffer: &Entity<MultiBuffer>,
17041 event: &multi_buffer::Event,
17042 window: &mut Window,
17043 cx: &mut Context<Self>,
17044 ) {
17045 match event {
17046 multi_buffer::Event::Edited {
17047 singleton_buffer_edited,
17048 edited_buffer: buffer_edited,
17049 } => {
17050 self.scrollbar_marker_state.dirty = true;
17051 self.active_indent_guides_state.dirty = true;
17052 self.refresh_active_diagnostics(cx);
17053 self.refresh_code_actions(window, cx);
17054 if self.has_active_inline_completion() {
17055 self.update_visible_inline_completion(window, cx);
17056 }
17057 if let Some(buffer) = buffer_edited {
17058 let buffer_id = buffer.read(cx).remote_id();
17059 if !self.registered_buffers.contains_key(&buffer_id) {
17060 if let Some(project) = self.project.as_ref() {
17061 project.update(cx, |project, cx| {
17062 self.registered_buffers.insert(
17063 buffer_id,
17064 project.register_buffer_with_language_servers(&buffer, cx),
17065 );
17066 })
17067 }
17068 }
17069 }
17070 cx.emit(EditorEvent::BufferEdited);
17071 cx.emit(SearchEvent::MatchesInvalidated);
17072 if *singleton_buffer_edited {
17073 if let Some(project) = &self.project {
17074 #[allow(clippy::mutable_key_type)]
17075 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17076 multibuffer
17077 .all_buffers()
17078 .into_iter()
17079 .filter_map(|buffer| {
17080 buffer.update(cx, |buffer, cx| {
17081 let language = buffer.language()?;
17082 let should_discard = project.update(cx, |project, cx| {
17083 project.is_local()
17084 && !project.has_language_servers_for(buffer, cx)
17085 });
17086 should_discard.not().then_some(language.clone())
17087 })
17088 })
17089 .collect::<HashSet<_>>()
17090 });
17091 if !languages_affected.is_empty() {
17092 self.refresh_inlay_hints(
17093 InlayHintRefreshReason::BufferEdited(languages_affected),
17094 cx,
17095 );
17096 }
17097 }
17098 }
17099
17100 let Some(project) = &self.project else { return };
17101 let (telemetry, is_via_ssh) = {
17102 let project = project.read(cx);
17103 let telemetry = project.client().telemetry().clone();
17104 let is_via_ssh = project.is_via_ssh();
17105 (telemetry, is_via_ssh)
17106 };
17107 refresh_linked_ranges(self, window, cx);
17108 telemetry.log_edit_event("editor", is_via_ssh);
17109 }
17110 multi_buffer::Event::ExcerptsAdded {
17111 buffer,
17112 predecessor,
17113 excerpts,
17114 } => {
17115 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17116 let buffer_id = buffer.read(cx).remote_id();
17117 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17118 if let Some(project) = &self.project {
17119 get_uncommitted_diff_for_buffer(
17120 project,
17121 [buffer.clone()],
17122 self.buffer.clone(),
17123 cx,
17124 )
17125 .detach();
17126 }
17127 }
17128 cx.emit(EditorEvent::ExcerptsAdded {
17129 buffer: buffer.clone(),
17130 predecessor: *predecessor,
17131 excerpts: excerpts.clone(),
17132 });
17133 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17134 }
17135 multi_buffer::Event::ExcerptsRemoved { ids } => {
17136 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17137 let buffer = self.buffer.read(cx);
17138 self.registered_buffers
17139 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17140 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17141 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
17142 }
17143 multi_buffer::Event::ExcerptsEdited {
17144 excerpt_ids,
17145 buffer_ids,
17146 } => {
17147 self.display_map.update(cx, |map, cx| {
17148 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17149 });
17150 cx.emit(EditorEvent::ExcerptsEdited {
17151 ids: excerpt_ids.clone(),
17152 })
17153 }
17154 multi_buffer::Event::ExcerptsExpanded { ids } => {
17155 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17156 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17157 }
17158 multi_buffer::Event::Reparsed(buffer_id) => {
17159 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17160 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17161
17162 cx.emit(EditorEvent::Reparsed(*buffer_id));
17163 }
17164 multi_buffer::Event::DiffHunksToggled => {
17165 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17166 }
17167 multi_buffer::Event::LanguageChanged(buffer_id) => {
17168 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17169 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17170 cx.emit(EditorEvent::Reparsed(*buffer_id));
17171 cx.notify();
17172 }
17173 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17174 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17175 multi_buffer::Event::FileHandleChanged
17176 | multi_buffer::Event::Reloaded
17177 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17178 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17179 multi_buffer::Event::DiagnosticsUpdated => {
17180 self.refresh_active_diagnostics(cx);
17181 self.refresh_inline_diagnostics(true, window, cx);
17182 self.scrollbar_marker_state.dirty = true;
17183 cx.notify();
17184 }
17185 _ => {}
17186 };
17187 }
17188
17189 fn on_display_map_changed(
17190 &mut self,
17191 _: Entity<DisplayMap>,
17192 _: &mut Window,
17193 cx: &mut Context<Self>,
17194 ) {
17195 cx.notify();
17196 }
17197
17198 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17199 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17200 self.update_edit_prediction_settings(cx);
17201 self.refresh_inline_completion(true, false, window, cx);
17202 self.refresh_inlay_hints(
17203 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17204 self.selections.newest_anchor().head(),
17205 &self.buffer.read(cx).snapshot(cx),
17206 cx,
17207 )),
17208 cx,
17209 );
17210
17211 let old_cursor_shape = self.cursor_shape;
17212
17213 {
17214 let editor_settings = EditorSettings::get_global(cx);
17215 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17216 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17217 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17218 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17219 }
17220
17221 if old_cursor_shape != self.cursor_shape {
17222 cx.emit(EditorEvent::CursorShapeChanged);
17223 }
17224
17225 let project_settings = ProjectSettings::get_global(cx);
17226 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17227
17228 if self.mode == EditorMode::Full {
17229 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17230 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17231 if self.show_inline_diagnostics != show_inline_diagnostics {
17232 self.show_inline_diagnostics = show_inline_diagnostics;
17233 self.refresh_inline_diagnostics(false, window, cx);
17234 }
17235
17236 if self.git_blame_inline_enabled != inline_blame_enabled {
17237 self.toggle_git_blame_inline_internal(false, window, cx);
17238 }
17239 }
17240
17241 cx.notify();
17242 }
17243
17244 pub fn set_searchable(&mut self, searchable: bool) {
17245 self.searchable = searchable;
17246 }
17247
17248 pub fn searchable(&self) -> bool {
17249 self.searchable
17250 }
17251
17252 fn open_proposed_changes_editor(
17253 &mut self,
17254 _: &OpenProposedChangesEditor,
17255 window: &mut Window,
17256 cx: &mut Context<Self>,
17257 ) {
17258 let Some(workspace) = self.workspace() else {
17259 cx.propagate();
17260 return;
17261 };
17262
17263 let selections = self.selections.all::<usize>(cx);
17264 let multi_buffer = self.buffer.read(cx);
17265 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17266 let mut new_selections_by_buffer = HashMap::default();
17267 for selection in selections {
17268 for (buffer, range, _) in
17269 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17270 {
17271 let mut range = range.to_point(buffer);
17272 range.start.column = 0;
17273 range.end.column = buffer.line_len(range.end.row);
17274 new_selections_by_buffer
17275 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17276 .or_insert(Vec::new())
17277 .push(range)
17278 }
17279 }
17280
17281 let proposed_changes_buffers = new_selections_by_buffer
17282 .into_iter()
17283 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17284 .collect::<Vec<_>>();
17285 let proposed_changes_editor = cx.new(|cx| {
17286 ProposedChangesEditor::new(
17287 "Proposed changes",
17288 proposed_changes_buffers,
17289 self.project.clone(),
17290 window,
17291 cx,
17292 )
17293 });
17294
17295 window.defer(cx, move |window, cx| {
17296 workspace.update(cx, |workspace, cx| {
17297 workspace.active_pane().update(cx, |pane, cx| {
17298 pane.add_item(
17299 Box::new(proposed_changes_editor),
17300 true,
17301 true,
17302 None,
17303 window,
17304 cx,
17305 );
17306 });
17307 });
17308 });
17309 }
17310
17311 pub fn open_excerpts_in_split(
17312 &mut self,
17313 _: &OpenExcerptsSplit,
17314 window: &mut Window,
17315 cx: &mut Context<Self>,
17316 ) {
17317 self.open_excerpts_common(None, true, window, cx)
17318 }
17319
17320 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17321 self.open_excerpts_common(None, false, window, cx)
17322 }
17323
17324 fn open_excerpts_common(
17325 &mut self,
17326 jump_data: Option<JumpData>,
17327 split: bool,
17328 window: &mut Window,
17329 cx: &mut Context<Self>,
17330 ) {
17331 let Some(workspace) = self.workspace() else {
17332 cx.propagate();
17333 return;
17334 };
17335
17336 if self.buffer.read(cx).is_singleton() {
17337 cx.propagate();
17338 return;
17339 }
17340
17341 let mut new_selections_by_buffer = HashMap::default();
17342 match &jump_data {
17343 Some(JumpData::MultiBufferPoint {
17344 excerpt_id,
17345 position,
17346 anchor,
17347 line_offset_from_top,
17348 }) => {
17349 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17350 if let Some(buffer) = multi_buffer_snapshot
17351 .buffer_id_for_excerpt(*excerpt_id)
17352 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17353 {
17354 let buffer_snapshot = buffer.read(cx).snapshot();
17355 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17356 language::ToPoint::to_point(anchor, &buffer_snapshot)
17357 } else {
17358 buffer_snapshot.clip_point(*position, Bias::Left)
17359 };
17360 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17361 new_selections_by_buffer.insert(
17362 buffer,
17363 (
17364 vec![jump_to_offset..jump_to_offset],
17365 Some(*line_offset_from_top),
17366 ),
17367 );
17368 }
17369 }
17370 Some(JumpData::MultiBufferRow {
17371 row,
17372 line_offset_from_top,
17373 }) => {
17374 let point = MultiBufferPoint::new(row.0, 0);
17375 if let Some((buffer, buffer_point, _)) =
17376 self.buffer.read(cx).point_to_buffer_point(point, cx)
17377 {
17378 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17379 new_selections_by_buffer
17380 .entry(buffer)
17381 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17382 .0
17383 .push(buffer_offset..buffer_offset)
17384 }
17385 }
17386 None => {
17387 let selections = self.selections.all::<usize>(cx);
17388 let multi_buffer = self.buffer.read(cx);
17389 for selection in selections {
17390 for (snapshot, range, _, anchor) in multi_buffer
17391 .snapshot(cx)
17392 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17393 {
17394 if let Some(anchor) = anchor {
17395 // selection is in a deleted hunk
17396 let Some(buffer_id) = anchor.buffer_id else {
17397 continue;
17398 };
17399 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17400 continue;
17401 };
17402 let offset = text::ToOffset::to_offset(
17403 &anchor.text_anchor,
17404 &buffer_handle.read(cx).snapshot(),
17405 );
17406 let range = offset..offset;
17407 new_selections_by_buffer
17408 .entry(buffer_handle)
17409 .or_insert((Vec::new(), None))
17410 .0
17411 .push(range)
17412 } else {
17413 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17414 else {
17415 continue;
17416 };
17417 new_selections_by_buffer
17418 .entry(buffer_handle)
17419 .or_insert((Vec::new(), None))
17420 .0
17421 .push(range)
17422 }
17423 }
17424 }
17425 }
17426 }
17427
17428 new_selections_by_buffer
17429 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
17430
17431 if new_selections_by_buffer.is_empty() {
17432 return;
17433 }
17434
17435 // We defer the pane interaction because we ourselves are a workspace item
17436 // and activating a new item causes the pane to call a method on us reentrantly,
17437 // which panics if we're on the stack.
17438 window.defer(cx, move |window, cx| {
17439 workspace.update(cx, |workspace, cx| {
17440 let pane = if split {
17441 workspace.adjacent_pane(window, cx)
17442 } else {
17443 workspace.active_pane().clone()
17444 };
17445
17446 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17447 let editor = buffer
17448 .read(cx)
17449 .file()
17450 .is_none()
17451 .then(|| {
17452 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17453 // so `workspace.open_project_item` will never find them, always opening a new editor.
17454 // Instead, we try to activate the existing editor in the pane first.
17455 let (editor, pane_item_index) =
17456 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17457 let editor = item.downcast::<Editor>()?;
17458 let singleton_buffer =
17459 editor.read(cx).buffer().read(cx).as_singleton()?;
17460 if singleton_buffer == buffer {
17461 Some((editor, i))
17462 } else {
17463 None
17464 }
17465 })?;
17466 pane.update(cx, |pane, cx| {
17467 pane.activate_item(pane_item_index, true, true, window, cx)
17468 });
17469 Some(editor)
17470 })
17471 .flatten()
17472 .unwrap_or_else(|| {
17473 workspace.open_project_item::<Self>(
17474 pane.clone(),
17475 buffer,
17476 true,
17477 true,
17478 window,
17479 cx,
17480 )
17481 });
17482
17483 editor.update(cx, |editor, cx| {
17484 let autoscroll = match scroll_offset {
17485 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17486 None => Autoscroll::newest(),
17487 };
17488 let nav_history = editor.nav_history.take();
17489 editor.change_selections(Some(autoscroll), window, cx, |s| {
17490 s.select_ranges(ranges);
17491 });
17492 editor.nav_history = nav_history;
17493 });
17494 }
17495 })
17496 });
17497 }
17498
17499 // For now, don't allow opening excerpts in buffers that aren't backed by
17500 // regular project files.
17501 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
17502 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
17503 }
17504
17505 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17506 let snapshot = self.buffer.read(cx).read(cx);
17507 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17508 Some(
17509 ranges
17510 .iter()
17511 .map(move |range| {
17512 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17513 })
17514 .collect(),
17515 )
17516 }
17517
17518 fn selection_replacement_ranges(
17519 &self,
17520 range: Range<OffsetUtf16>,
17521 cx: &mut App,
17522 ) -> Vec<Range<OffsetUtf16>> {
17523 let selections = self.selections.all::<OffsetUtf16>(cx);
17524 let newest_selection = selections
17525 .iter()
17526 .max_by_key(|selection| selection.id)
17527 .unwrap();
17528 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17529 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17530 let snapshot = self.buffer.read(cx).read(cx);
17531 selections
17532 .into_iter()
17533 .map(|mut selection| {
17534 selection.start.0 =
17535 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17536 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17537 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17538 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17539 })
17540 .collect()
17541 }
17542
17543 fn report_editor_event(
17544 &self,
17545 event_type: &'static str,
17546 file_extension: Option<String>,
17547 cx: &App,
17548 ) {
17549 if cfg!(any(test, feature = "test-support")) {
17550 return;
17551 }
17552
17553 let Some(project) = &self.project else { return };
17554
17555 // If None, we are in a file without an extension
17556 let file = self
17557 .buffer
17558 .read(cx)
17559 .as_singleton()
17560 .and_then(|b| b.read(cx).file());
17561 let file_extension = file_extension.or(file
17562 .as_ref()
17563 .and_then(|file| Path::new(file.file_name(cx)).extension())
17564 .and_then(|e| e.to_str())
17565 .map(|a| a.to_string()));
17566
17567 let vim_mode = cx
17568 .global::<SettingsStore>()
17569 .raw_user_settings()
17570 .get("vim_mode")
17571 == Some(&serde_json::Value::Bool(true));
17572
17573 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17574 let copilot_enabled = edit_predictions_provider
17575 == language::language_settings::EditPredictionProvider::Copilot;
17576 let copilot_enabled_for_language = self
17577 .buffer
17578 .read(cx)
17579 .language_settings(cx)
17580 .show_edit_predictions;
17581
17582 let project = project.read(cx);
17583 telemetry::event!(
17584 event_type,
17585 file_extension,
17586 vim_mode,
17587 copilot_enabled,
17588 copilot_enabled_for_language,
17589 edit_predictions_provider,
17590 is_via_ssh = project.is_via_ssh(),
17591 );
17592 }
17593
17594 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17595 /// with each line being an array of {text, highlight} objects.
17596 fn copy_highlight_json(
17597 &mut self,
17598 _: &CopyHighlightJson,
17599 window: &mut Window,
17600 cx: &mut Context<Self>,
17601 ) {
17602 #[derive(Serialize)]
17603 struct Chunk<'a> {
17604 text: String,
17605 highlight: Option<&'a str>,
17606 }
17607
17608 let snapshot = self.buffer.read(cx).snapshot(cx);
17609 let range = self
17610 .selected_text_range(false, window, cx)
17611 .and_then(|selection| {
17612 if selection.range.is_empty() {
17613 None
17614 } else {
17615 Some(selection.range)
17616 }
17617 })
17618 .unwrap_or_else(|| 0..snapshot.len());
17619
17620 let chunks = snapshot.chunks(range, true);
17621 let mut lines = Vec::new();
17622 let mut line: VecDeque<Chunk> = VecDeque::new();
17623
17624 let Some(style) = self.style.as_ref() else {
17625 return;
17626 };
17627
17628 for chunk in chunks {
17629 let highlight = chunk
17630 .syntax_highlight_id
17631 .and_then(|id| id.name(&style.syntax));
17632 let mut chunk_lines = chunk.text.split('\n').peekable();
17633 while let Some(text) = chunk_lines.next() {
17634 let mut merged_with_last_token = false;
17635 if let Some(last_token) = line.back_mut() {
17636 if last_token.highlight == highlight {
17637 last_token.text.push_str(text);
17638 merged_with_last_token = true;
17639 }
17640 }
17641
17642 if !merged_with_last_token {
17643 line.push_back(Chunk {
17644 text: text.into(),
17645 highlight,
17646 });
17647 }
17648
17649 if chunk_lines.peek().is_some() {
17650 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17651 line.pop_front();
17652 }
17653 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17654 line.pop_back();
17655 }
17656
17657 lines.push(mem::take(&mut line));
17658 }
17659 }
17660 }
17661
17662 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17663 return;
17664 };
17665 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17666 }
17667
17668 pub fn open_context_menu(
17669 &mut self,
17670 _: &OpenContextMenu,
17671 window: &mut Window,
17672 cx: &mut Context<Self>,
17673 ) {
17674 self.request_autoscroll(Autoscroll::newest(), cx);
17675 let position = self.selections.newest_display(cx).start;
17676 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17677 }
17678
17679 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17680 &self.inlay_hint_cache
17681 }
17682
17683 pub fn replay_insert_event(
17684 &mut self,
17685 text: &str,
17686 relative_utf16_range: Option<Range<isize>>,
17687 window: &mut Window,
17688 cx: &mut Context<Self>,
17689 ) {
17690 if !self.input_enabled {
17691 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17692 return;
17693 }
17694 if let Some(relative_utf16_range) = relative_utf16_range {
17695 let selections = self.selections.all::<OffsetUtf16>(cx);
17696 self.change_selections(None, window, cx, |s| {
17697 let new_ranges = selections.into_iter().map(|range| {
17698 let start = OffsetUtf16(
17699 range
17700 .head()
17701 .0
17702 .saturating_add_signed(relative_utf16_range.start),
17703 );
17704 let end = OffsetUtf16(
17705 range
17706 .head()
17707 .0
17708 .saturating_add_signed(relative_utf16_range.end),
17709 );
17710 start..end
17711 });
17712 s.select_ranges(new_ranges);
17713 });
17714 }
17715
17716 self.handle_input(text, window, cx);
17717 }
17718
17719 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17720 let Some(provider) = self.semantics_provider.as_ref() else {
17721 return false;
17722 };
17723
17724 let mut supports = false;
17725 self.buffer().update(cx, |this, cx| {
17726 this.for_each_buffer(|buffer| {
17727 supports |= provider.supports_inlay_hints(buffer, cx);
17728 });
17729 });
17730
17731 supports
17732 }
17733
17734 pub fn is_focused(&self, window: &Window) -> bool {
17735 self.focus_handle.is_focused(window)
17736 }
17737
17738 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17739 cx.emit(EditorEvent::Focused);
17740
17741 if let Some(descendant) = self
17742 .last_focused_descendant
17743 .take()
17744 .and_then(|descendant| descendant.upgrade())
17745 {
17746 window.focus(&descendant);
17747 } else {
17748 if let Some(blame) = self.blame.as_ref() {
17749 blame.update(cx, GitBlame::focus)
17750 }
17751
17752 self.blink_manager.update(cx, BlinkManager::enable);
17753 self.show_cursor_names(window, cx);
17754 self.buffer.update(cx, |buffer, cx| {
17755 buffer.finalize_last_transaction(cx);
17756 if self.leader_peer_id.is_none() {
17757 buffer.set_active_selections(
17758 &self.selections.disjoint_anchors(),
17759 self.selections.line_mode,
17760 self.cursor_shape,
17761 cx,
17762 );
17763 }
17764 });
17765 }
17766 }
17767
17768 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17769 cx.emit(EditorEvent::FocusedIn)
17770 }
17771
17772 fn handle_focus_out(
17773 &mut self,
17774 event: FocusOutEvent,
17775 _window: &mut Window,
17776 cx: &mut Context<Self>,
17777 ) {
17778 if event.blurred != self.focus_handle {
17779 self.last_focused_descendant = Some(event.blurred);
17780 }
17781 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17782 }
17783
17784 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17785 self.blink_manager.update(cx, BlinkManager::disable);
17786 self.buffer
17787 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17788
17789 if let Some(blame) = self.blame.as_ref() {
17790 blame.update(cx, GitBlame::blur)
17791 }
17792 if !self.hover_state.focused(window, cx) {
17793 hide_hover(self, cx);
17794 }
17795 if !self
17796 .context_menu
17797 .borrow()
17798 .as_ref()
17799 .is_some_and(|context_menu| context_menu.focused(window, cx))
17800 {
17801 self.hide_context_menu(window, cx);
17802 }
17803 self.discard_inline_completion(false, cx);
17804 cx.emit(EditorEvent::Blurred);
17805 cx.notify();
17806 }
17807
17808 pub fn register_action<A: Action>(
17809 &mut self,
17810 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17811 ) -> Subscription {
17812 let id = self.next_editor_action_id.post_inc();
17813 let listener = Arc::new(listener);
17814 self.editor_actions.borrow_mut().insert(
17815 id,
17816 Box::new(move |window, _| {
17817 let listener = listener.clone();
17818 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17819 let action = action.downcast_ref().unwrap();
17820 if phase == DispatchPhase::Bubble {
17821 listener(action, window, cx)
17822 }
17823 })
17824 }),
17825 );
17826
17827 let editor_actions = self.editor_actions.clone();
17828 Subscription::new(move || {
17829 editor_actions.borrow_mut().remove(&id);
17830 })
17831 }
17832
17833 pub fn file_header_size(&self) -> u32 {
17834 FILE_HEADER_HEIGHT
17835 }
17836
17837 pub fn restore(
17838 &mut self,
17839 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17840 window: &mut Window,
17841 cx: &mut Context<Self>,
17842 ) {
17843 let workspace = self.workspace();
17844 let project = self.project.as_ref();
17845 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17846 let mut tasks = Vec::new();
17847 for (buffer_id, changes) in revert_changes {
17848 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17849 buffer.update(cx, |buffer, cx| {
17850 buffer.edit(
17851 changes
17852 .into_iter()
17853 .map(|(range, text)| (range, text.to_string())),
17854 None,
17855 cx,
17856 );
17857 });
17858
17859 if let Some(project) =
17860 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17861 {
17862 project.update(cx, |project, cx| {
17863 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17864 })
17865 }
17866 }
17867 }
17868 tasks
17869 });
17870 cx.spawn_in(window, async move |_, cx| {
17871 for (buffer, task) in save_tasks {
17872 let result = task.await;
17873 if result.is_err() {
17874 let Some(path) = buffer
17875 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17876 .ok()
17877 else {
17878 continue;
17879 };
17880 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17881 let Some(task) = cx
17882 .update_window_entity(&workspace, |workspace, window, cx| {
17883 workspace
17884 .open_path_preview(path, None, false, false, false, window, cx)
17885 })
17886 .ok()
17887 else {
17888 continue;
17889 };
17890 task.await.log_err();
17891 }
17892 }
17893 }
17894 })
17895 .detach();
17896 self.change_selections(None, window, cx, |selections| selections.refresh());
17897 }
17898
17899 pub fn to_pixel_point(
17900 &self,
17901 source: multi_buffer::Anchor,
17902 editor_snapshot: &EditorSnapshot,
17903 window: &mut Window,
17904 ) -> Option<gpui::Point<Pixels>> {
17905 let source_point = source.to_display_point(editor_snapshot);
17906 self.display_to_pixel_point(source_point, editor_snapshot, window)
17907 }
17908
17909 pub fn display_to_pixel_point(
17910 &self,
17911 source: DisplayPoint,
17912 editor_snapshot: &EditorSnapshot,
17913 window: &mut Window,
17914 ) -> Option<gpui::Point<Pixels>> {
17915 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17916 let text_layout_details = self.text_layout_details(window);
17917 let scroll_top = text_layout_details
17918 .scroll_anchor
17919 .scroll_position(editor_snapshot)
17920 .y;
17921
17922 if source.row().as_f32() < scroll_top.floor() {
17923 return None;
17924 }
17925 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17926 let source_y = line_height * (source.row().as_f32() - scroll_top);
17927 Some(gpui::Point::new(source_x, source_y))
17928 }
17929
17930 pub fn has_visible_completions_menu(&self) -> bool {
17931 !self.edit_prediction_preview_is_active()
17932 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17933 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17934 })
17935 }
17936
17937 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17938 self.addons
17939 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17940 }
17941
17942 pub fn unregister_addon<T: Addon>(&mut self) {
17943 self.addons.remove(&std::any::TypeId::of::<T>());
17944 }
17945
17946 pub fn addon<T: Addon>(&self) -> Option<&T> {
17947 let type_id = std::any::TypeId::of::<T>();
17948 self.addons
17949 .get(&type_id)
17950 .and_then(|item| item.to_any().downcast_ref::<T>())
17951 }
17952
17953 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17954 let text_layout_details = self.text_layout_details(window);
17955 let style = &text_layout_details.editor_style;
17956 let font_id = window.text_system().resolve_font(&style.text.font());
17957 let font_size = style.text.font_size.to_pixels(window.rem_size());
17958 let line_height = style.text.line_height_in_pixels(window.rem_size());
17959 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17960
17961 gpui::Size::new(em_width, line_height)
17962 }
17963
17964 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17965 self.load_diff_task.clone()
17966 }
17967
17968 fn read_metadata_from_db(
17969 &mut self,
17970 item_id: u64,
17971 workspace_id: WorkspaceId,
17972 window: &mut Window,
17973 cx: &mut Context<Editor>,
17974 ) {
17975 if self.is_singleton(cx)
17976 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
17977 {
17978 let buffer_snapshot = OnceCell::new();
17979
17980 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
17981 if !folds.is_empty() {
17982 let snapshot =
17983 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17984 self.fold_ranges(
17985 folds
17986 .into_iter()
17987 .map(|(start, end)| {
17988 snapshot.clip_offset(start, Bias::Left)
17989 ..snapshot.clip_offset(end, Bias::Right)
17990 })
17991 .collect(),
17992 false,
17993 window,
17994 cx,
17995 );
17996 }
17997 }
17998
17999 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18000 if !selections.is_empty() {
18001 let snapshot =
18002 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18003 self.change_selections(None, window, cx, |s| {
18004 s.select_ranges(selections.into_iter().map(|(start, end)| {
18005 snapshot.clip_offset(start, Bias::Left)
18006 ..snapshot.clip_offset(end, Bias::Right)
18007 }));
18008 });
18009 }
18010 };
18011 }
18012
18013 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18014 }
18015}
18016
18017// Consider user intent and default settings
18018fn choose_completion_range(
18019 completion: &Completion,
18020 intent: CompletionIntent,
18021 buffer: &Entity<Buffer>,
18022 cx: &mut Context<Editor>,
18023) -> Range<usize> {
18024 fn should_replace(
18025 completion: &Completion,
18026 insert_range: &Range<text::Anchor>,
18027 intent: CompletionIntent,
18028 completion_mode_setting: LspInsertMode,
18029 buffer: &Buffer,
18030 ) -> bool {
18031 // specific actions take precedence over settings
18032 match intent {
18033 CompletionIntent::CompleteWithInsert => return false,
18034 CompletionIntent::CompleteWithReplace => return true,
18035 CompletionIntent::Complete | CompletionIntent::Compose => {}
18036 }
18037
18038 match completion_mode_setting {
18039 LspInsertMode::Insert => false,
18040 LspInsertMode::Replace => true,
18041 LspInsertMode::ReplaceSubsequence => {
18042 let mut text_to_replace = buffer.chars_for_range(
18043 buffer.anchor_before(completion.replace_range.start)
18044 ..buffer.anchor_after(completion.replace_range.end),
18045 );
18046 let mut completion_text = completion.new_text.chars();
18047
18048 // is `text_to_replace` a subsequence of `completion_text`
18049 text_to_replace
18050 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18051 }
18052 LspInsertMode::ReplaceSuffix => {
18053 let range_after_cursor = insert_range.end..completion.replace_range.end;
18054
18055 let text_after_cursor = buffer
18056 .text_for_range(
18057 buffer.anchor_before(range_after_cursor.start)
18058 ..buffer.anchor_after(range_after_cursor.end),
18059 )
18060 .collect::<String>();
18061 completion.new_text.ends_with(&text_after_cursor)
18062 }
18063 }
18064 }
18065
18066 let buffer = buffer.read(cx);
18067
18068 if let CompletionSource::Lsp {
18069 insert_range: Some(insert_range),
18070 ..
18071 } = &completion.source
18072 {
18073 let completion_mode_setting =
18074 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18075 .completions
18076 .lsp_insert_mode;
18077
18078 if !should_replace(
18079 completion,
18080 &insert_range,
18081 intent,
18082 completion_mode_setting,
18083 buffer,
18084 ) {
18085 return insert_range.to_offset(buffer);
18086 }
18087 }
18088
18089 completion.replace_range.to_offset(buffer)
18090}
18091
18092fn insert_extra_newline_brackets(
18093 buffer: &MultiBufferSnapshot,
18094 range: Range<usize>,
18095 language: &language::LanguageScope,
18096) -> bool {
18097 let leading_whitespace_len = buffer
18098 .reversed_chars_at(range.start)
18099 .take_while(|c| c.is_whitespace() && *c != '\n')
18100 .map(|c| c.len_utf8())
18101 .sum::<usize>();
18102 let trailing_whitespace_len = buffer
18103 .chars_at(range.end)
18104 .take_while(|c| c.is_whitespace() && *c != '\n')
18105 .map(|c| c.len_utf8())
18106 .sum::<usize>();
18107 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18108
18109 language.brackets().any(|(pair, enabled)| {
18110 let pair_start = pair.start.trim_end();
18111 let pair_end = pair.end.trim_start();
18112
18113 enabled
18114 && pair.newline
18115 && buffer.contains_str_at(range.end, pair_end)
18116 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18117 })
18118}
18119
18120fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18121 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18122 [(buffer, range, _)] => (*buffer, range.clone()),
18123 _ => return false,
18124 };
18125 let pair = {
18126 let mut result: Option<BracketMatch> = None;
18127
18128 for pair in buffer
18129 .all_bracket_ranges(range.clone())
18130 .filter(move |pair| {
18131 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18132 })
18133 {
18134 let len = pair.close_range.end - pair.open_range.start;
18135
18136 if let Some(existing) = &result {
18137 let existing_len = existing.close_range.end - existing.open_range.start;
18138 if len > existing_len {
18139 continue;
18140 }
18141 }
18142
18143 result = Some(pair);
18144 }
18145
18146 result
18147 };
18148 let Some(pair) = pair else {
18149 return false;
18150 };
18151 pair.newline_only
18152 && buffer
18153 .chars_for_range(pair.open_range.end..range.start)
18154 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18155 .all(|c| c.is_whitespace() && c != '\n')
18156}
18157
18158fn get_uncommitted_diff_for_buffer(
18159 project: &Entity<Project>,
18160 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18161 buffer: Entity<MultiBuffer>,
18162 cx: &mut App,
18163) -> Task<()> {
18164 let mut tasks = Vec::new();
18165 project.update(cx, |project, cx| {
18166 for buffer in buffers {
18167 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18168 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18169 }
18170 }
18171 });
18172 cx.spawn(async move |cx| {
18173 let diffs = future::join_all(tasks).await;
18174 buffer
18175 .update(cx, |buffer, cx| {
18176 for diff in diffs.into_iter().flatten() {
18177 buffer.add_diff(diff, cx);
18178 }
18179 })
18180 .ok();
18181 })
18182}
18183
18184fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18185 let tab_size = tab_size.get() as usize;
18186 let mut width = offset;
18187
18188 for ch in text.chars() {
18189 width += if ch == '\t' {
18190 tab_size - (width % tab_size)
18191 } else {
18192 1
18193 };
18194 }
18195
18196 width - offset
18197}
18198
18199#[cfg(test)]
18200mod tests {
18201 use super::*;
18202
18203 #[test]
18204 fn test_string_size_with_expanded_tabs() {
18205 let nz = |val| NonZeroU32::new(val).unwrap();
18206 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18207 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18208 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18209 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18210 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18211 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18212 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18213 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18214 }
18215}
18216
18217/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18218struct WordBreakingTokenizer<'a> {
18219 input: &'a str,
18220}
18221
18222impl<'a> WordBreakingTokenizer<'a> {
18223 fn new(input: &'a str) -> Self {
18224 Self { input }
18225 }
18226}
18227
18228fn is_char_ideographic(ch: char) -> bool {
18229 use unicode_script::Script::*;
18230 use unicode_script::UnicodeScript;
18231 matches!(ch.script(), Han | Tangut | Yi)
18232}
18233
18234fn is_grapheme_ideographic(text: &str) -> bool {
18235 text.chars().any(is_char_ideographic)
18236}
18237
18238fn is_grapheme_whitespace(text: &str) -> bool {
18239 text.chars().any(|x| x.is_whitespace())
18240}
18241
18242fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18243 text.chars().next().map_or(false, |ch| {
18244 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18245 })
18246}
18247
18248#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18249enum WordBreakToken<'a> {
18250 Word { token: &'a str, grapheme_len: usize },
18251 InlineWhitespace { token: &'a str, grapheme_len: usize },
18252 Newline,
18253}
18254
18255impl<'a> Iterator for WordBreakingTokenizer<'a> {
18256 /// Yields a span, the count of graphemes in the token, and whether it was
18257 /// whitespace. Note that it also breaks at word boundaries.
18258 type Item = WordBreakToken<'a>;
18259
18260 fn next(&mut self) -> Option<Self::Item> {
18261 use unicode_segmentation::UnicodeSegmentation;
18262 if self.input.is_empty() {
18263 return None;
18264 }
18265
18266 let mut iter = self.input.graphemes(true).peekable();
18267 let mut offset = 0;
18268 let mut grapheme_len = 0;
18269 if let Some(first_grapheme) = iter.next() {
18270 let is_newline = first_grapheme == "\n";
18271 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18272 offset += first_grapheme.len();
18273 grapheme_len += 1;
18274 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18275 if let Some(grapheme) = iter.peek().copied() {
18276 if should_stay_with_preceding_ideograph(grapheme) {
18277 offset += grapheme.len();
18278 grapheme_len += 1;
18279 }
18280 }
18281 } else {
18282 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18283 let mut next_word_bound = words.peek().copied();
18284 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18285 next_word_bound = words.next();
18286 }
18287 while let Some(grapheme) = iter.peek().copied() {
18288 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18289 break;
18290 };
18291 if is_grapheme_whitespace(grapheme) != is_whitespace
18292 || (grapheme == "\n") != is_newline
18293 {
18294 break;
18295 };
18296 offset += grapheme.len();
18297 grapheme_len += 1;
18298 iter.next();
18299 }
18300 }
18301 let token = &self.input[..offset];
18302 self.input = &self.input[offset..];
18303 if token == "\n" {
18304 Some(WordBreakToken::Newline)
18305 } else if is_whitespace {
18306 Some(WordBreakToken::InlineWhitespace {
18307 token,
18308 grapheme_len,
18309 })
18310 } else {
18311 Some(WordBreakToken::Word {
18312 token,
18313 grapheme_len,
18314 })
18315 }
18316 } else {
18317 None
18318 }
18319 }
18320}
18321
18322#[test]
18323fn test_word_breaking_tokenizer() {
18324 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18325 ("", &[]),
18326 (" ", &[whitespace(" ", 2)]),
18327 ("Ʒ", &[word("Ʒ", 1)]),
18328 ("Ǽ", &[word("Ǽ", 1)]),
18329 ("⋑", &[word("⋑", 1)]),
18330 ("⋑⋑", &[word("⋑⋑", 2)]),
18331 (
18332 "原理,进而",
18333 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18334 ),
18335 (
18336 "hello world",
18337 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18338 ),
18339 (
18340 "hello, world",
18341 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18342 ),
18343 (
18344 " hello world",
18345 &[
18346 whitespace(" ", 2),
18347 word("hello", 5),
18348 whitespace(" ", 1),
18349 word("world", 5),
18350 ],
18351 ),
18352 (
18353 "这是什么 \n 钢笔",
18354 &[
18355 word("这", 1),
18356 word("是", 1),
18357 word("什", 1),
18358 word("么", 1),
18359 whitespace(" ", 1),
18360 newline(),
18361 whitespace(" ", 1),
18362 word("钢", 1),
18363 word("笔", 1),
18364 ],
18365 ),
18366 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
18367 ];
18368
18369 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18370 WordBreakToken::Word {
18371 token,
18372 grapheme_len,
18373 }
18374 }
18375
18376 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18377 WordBreakToken::InlineWhitespace {
18378 token,
18379 grapheme_len,
18380 }
18381 }
18382
18383 fn newline() -> WordBreakToken<'static> {
18384 WordBreakToken::Newline
18385 }
18386
18387 for (input, result) in tests {
18388 assert_eq!(
18389 WordBreakingTokenizer::new(input)
18390 .collect::<Vec<_>>()
18391 .as_slice(),
18392 *result,
18393 );
18394 }
18395}
18396
18397fn wrap_with_prefix(
18398 line_prefix: String,
18399 unwrapped_text: String,
18400 wrap_column: usize,
18401 tab_size: NonZeroU32,
18402 preserve_existing_whitespace: bool,
18403) -> String {
18404 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18405 let mut wrapped_text = String::new();
18406 let mut current_line = line_prefix.clone();
18407
18408 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18409 let mut current_line_len = line_prefix_len;
18410 let mut in_whitespace = false;
18411 for token in tokenizer {
18412 let have_preceding_whitespace = in_whitespace;
18413 match token {
18414 WordBreakToken::Word {
18415 token,
18416 grapheme_len,
18417 } => {
18418 in_whitespace = false;
18419 if current_line_len + grapheme_len > wrap_column
18420 && current_line_len != line_prefix_len
18421 {
18422 wrapped_text.push_str(current_line.trim_end());
18423 wrapped_text.push('\n');
18424 current_line.truncate(line_prefix.len());
18425 current_line_len = line_prefix_len;
18426 }
18427 current_line.push_str(token);
18428 current_line_len += grapheme_len;
18429 }
18430 WordBreakToken::InlineWhitespace {
18431 mut token,
18432 mut grapheme_len,
18433 } => {
18434 in_whitespace = true;
18435 if have_preceding_whitespace && !preserve_existing_whitespace {
18436 continue;
18437 }
18438 if !preserve_existing_whitespace {
18439 token = " ";
18440 grapheme_len = 1;
18441 }
18442 if current_line_len + grapheme_len > wrap_column {
18443 wrapped_text.push_str(current_line.trim_end());
18444 wrapped_text.push('\n');
18445 current_line.truncate(line_prefix.len());
18446 current_line_len = line_prefix_len;
18447 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18448 current_line.push_str(token);
18449 current_line_len += grapheme_len;
18450 }
18451 }
18452 WordBreakToken::Newline => {
18453 in_whitespace = true;
18454 if preserve_existing_whitespace {
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 } else if have_preceding_whitespace {
18460 continue;
18461 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18462 {
18463 wrapped_text.push_str(current_line.trim_end());
18464 wrapped_text.push('\n');
18465 current_line.truncate(line_prefix.len());
18466 current_line_len = line_prefix_len;
18467 } else if current_line_len != line_prefix_len {
18468 current_line.push(' ');
18469 current_line_len += 1;
18470 }
18471 }
18472 }
18473 }
18474
18475 if !current_line.is_empty() {
18476 wrapped_text.push_str(¤t_line);
18477 }
18478 wrapped_text
18479}
18480
18481#[test]
18482fn test_wrap_with_prefix() {
18483 assert_eq!(
18484 wrap_with_prefix(
18485 "# ".to_string(),
18486 "abcdefg".to_string(),
18487 4,
18488 NonZeroU32::new(4).unwrap(),
18489 false,
18490 ),
18491 "# abcdefg"
18492 );
18493 assert_eq!(
18494 wrap_with_prefix(
18495 "".to_string(),
18496 "\thello world".to_string(),
18497 8,
18498 NonZeroU32::new(4).unwrap(),
18499 false,
18500 ),
18501 "hello\nworld"
18502 );
18503 assert_eq!(
18504 wrap_with_prefix(
18505 "// ".to_string(),
18506 "xx \nyy zz aa bb cc".to_string(),
18507 12,
18508 NonZeroU32::new(4).unwrap(),
18509 false,
18510 ),
18511 "// xx yy zz\n// aa bb cc"
18512 );
18513 assert_eq!(
18514 wrap_with_prefix(
18515 String::new(),
18516 "这是什么 \n 钢笔".to_string(),
18517 3,
18518 NonZeroU32::new(4).unwrap(),
18519 false,
18520 ),
18521 "这是什\n么 钢\n笔"
18522 );
18523}
18524
18525pub trait CollaborationHub {
18526 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18527 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18528 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18529}
18530
18531impl CollaborationHub for Entity<Project> {
18532 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18533 self.read(cx).collaborators()
18534 }
18535
18536 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18537 self.read(cx).user_store().read(cx).participant_indices()
18538 }
18539
18540 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18541 let this = self.read(cx);
18542 let user_ids = this.collaborators().values().map(|c| c.user_id);
18543 this.user_store().read_with(cx, |user_store, cx| {
18544 user_store.participant_names(user_ids, cx)
18545 })
18546 }
18547}
18548
18549pub trait SemanticsProvider {
18550 fn hover(
18551 &self,
18552 buffer: &Entity<Buffer>,
18553 position: text::Anchor,
18554 cx: &mut App,
18555 ) -> Option<Task<Vec<project::Hover>>>;
18556
18557 fn inlay_hints(
18558 &self,
18559 buffer_handle: Entity<Buffer>,
18560 range: Range<text::Anchor>,
18561 cx: &mut App,
18562 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18563
18564 fn resolve_inlay_hint(
18565 &self,
18566 hint: InlayHint,
18567 buffer_handle: Entity<Buffer>,
18568 server_id: LanguageServerId,
18569 cx: &mut App,
18570 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18571
18572 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18573
18574 fn document_highlights(
18575 &self,
18576 buffer: &Entity<Buffer>,
18577 position: text::Anchor,
18578 cx: &mut App,
18579 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18580
18581 fn definitions(
18582 &self,
18583 buffer: &Entity<Buffer>,
18584 position: text::Anchor,
18585 kind: GotoDefinitionKind,
18586 cx: &mut App,
18587 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18588
18589 fn range_for_rename(
18590 &self,
18591 buffer: &Entity<Buffer>,
18592 position: text::Anchor,
18593 cx: &mut App,
18594 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18595
18596 fn perform_rename(
18597 &self,
18598 buffer: &Entity<Buffer>,
18599 position: text::Anchor,
18600 new_name: String,
18601 cx: &mut App,
18602 ) -> Option<Task<Result<ProjectTransaction>>>;
18603}
18604
18605pub trait CompletionProvider {
18606 fn completions(
18607 &self,
18608 excerpt_id: ExcerptId,
18609 buffer: &Entity<Buffer>,
18610 buffer_position: text::Anchor,
18611 trigger: CompletionContext,
18612 window: &mut Window,
18613 cx: &mut Context<Editor>,
18614 ) -> Task<Result<Option<Vec<Completion>>>>;
18615
18616 fn resolve_completions(
18617 &self,
18618 buffer: Entity<Buffer>,
18619 completion_indices: Vec<usize>,
18620 completions: Rc<RefCell<Box<[Completion]>>>,
18621 cx: &mut Context<Editor>,
18622 ) -> Task<Result<bool>>;
18623
18624 fn apply_additional_edits_for_completion(
18625 &self,
18626 _buffer: Entity<Buffer>,
18627 _completions: Rc<RefCell<Box<[Completion]>>>,
18628 _completion_index: usize,
18629 _push_to_history: bool,
18630 _cx: &mut Context<Editor>,
18631 ) -> Task<Result<Option<language::Transaction>>> {
18632 Task::ready(Ok(None))
18633 }
18634
18635 fn is_completion_trigger(
18636 &self,
18637 buffer: &Entity<Buffer>,
18638 position: language::Anchor,
18639 text: &str,
18640 trigger_in_words: bool,
18641 cx: &mut Context<Editor>,
18642 ) -> bool;
18643
18644 fn sort_completions(&self) -> bool {
18645 true
18646 }
18647
18648 fn filter_completions(&self) -> bool {
18649 true
18650 }
18651}
18652
18653pub trait CodeActionProvider {
18654 fn id(&self) -> Arc<str>;
18655
18656 fn code_actions(
18657 &self,
18658 buffer: &Entity<Buffer>,
18659 range: Range<text::Anchor>,
18660 window: &mut Window,
18661 cx: &mut App,
18662 ) -> Task<Result<Vec<CodeAction>>>;
18663
18664 fn apply_code_action(
18665 &self,
18666 buffer_handle: Entity<Buffer>,
18667 action: CodeAction,
18668 excerpt_id: ExcerptId,
18669 push_to_history: bool,
18670 window: &mut Window,
18671 cx: &mut App,
18672 ) -> Task<Result<ProjectTransaction>>;
18673}
18674
18675impl CodeActionProvider for Entity<Project> {
18676 fn id(&self) -> Arc<str> {
18677 "project".into()
18678 }
18679
18680 fn code_actions(
18681 &self,
18682 buffer: &Entity<Buffer>,
18683 range: Range<text::Anchor>,
18684 _window: &mut Window,
18685 cx: &mut App,
18686 ) -> Task<Result<Vec<CodeAction>>> {
18687 self.update(cx, |project, cx| {
18688 let code_lens = project.code_lens(buffer, range.clone(), cx);
18689 let code_actions = project.code_actions(buffer, range, None, cx);
18690 cx.background_spawn(async move {
18691 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18692 Ok(code_lens
18693 .context("code lens fetch")?
18694 .into_iter()
18695 .chain(code_actions.context("code action fetch")?)
18696 .collect())
18697 })
18698 })
18699 }
18700
18701 fn apply_code_action(
18702 &self,
18703 buffer_handle: Entity<Buffer>,
18704 action: CodeAction,
18705 _excerpt_id: ExcerptId,
18706 push_to_history: bool,
18707 _window: &mut Window,
18708 cx: &mut App,
18709 ) -> Task<Result<ProjectTransaction>> {
18710 self.update(cx, |project, cx| {
18711 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18712 })
18713 }
18714}
18715
18716fn snippet_completions(
18717 project: &Project,
18718 buffer: &Entity<Buffer>,
18719 buffer_position: text::Anchor,
18720 cx: &mut App,
18721) -> Task<Result<Vec<Completion>>> {
18722 let language = buffer.read(cx).language_at(buffer_position);
18723 let language_name = language.as_ref().map(|language| language.lsp_id());
18724 let snippet_store = project.snippets().read(cx);
18725 let snippets = snippet_store.snippets_for(language_name, cx);
18726
18727 if snippets.is_empty() {
18728 return Task::ready(Ok(vec![]));
18729 }
18730 let snapshot = buffer.read(cx).text_snapshot();
18731 let chars: String = snapshot
18732 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18733 .collect();
18734
18735 let scope = language.map(|language| language.default_scope());
18736 let executor = cx.background_executor().clone();
18737
18738 cx.background_spawn(async move {
18739 let classifier = CharClassifier::new(scope).for_completion(true);
18740 let mut last_word = chars
18741 .chars()
18742 .take_while(|c| classifier.is_word(*c))
18743 .collect::<String>();
18744 last_word = last_word.chars().rev().collect();
18745
18746 if last_word.is_empty() {
18747 return Ok(vec![]);
18748 }
18749
18750 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18751 let to_lsp = |point: &text::Anchor| {
18752 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18753 point_to_lsp(end)
18754 };
18755 let lsp_end = to_lsp(&buffer_position);
18756
18757 let candidates = snippets
18758 .iter()
18759 .enumerate()
18760 .flat_map(|(ix, snippet)| {
18761 snippet
18762 .prefix
18763 .iter()
18764 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18765 })
18766 .collect::<Vec<StringMatchCandidate>>();
18767
18768 let mut matches = fuzzy::match_strings(
18769 &candidates,
18770 &last_word,
18771 last_word.chars().any(|c| c.is_uppercase()),
18772 100,
18773 &Default::default(),
18774 executor,
18775 )
18776 .await;
18777
18778 // Remove all candidates where the query's start does not match the start of any word in the candidate
18779 if let Some(query_start) = last_word.chars().next() {
18780 matches.retain(|string_match| {
18781 split_words(&string_match.string).any(|word| {
18782 // Check that the first codepoint of the word as lowercase matches the first
18783 // codepoint of the query as lowercase
18784 word.chars()
18785 .flat_map(|codepoint| codepoint.to_lowercase())
18786 .zip(query_start.to_lowercase())
18787 .all(|(word_cp, query_cp)| word_cp == query_cp)
18788 })
18789 });
18790 }
18791
18792 let matched_strings = matches
18793 .into_iter()
18794 .map(|m| m.string)
18795 .collect::<HashSet<_>>();
18796
18797 let result: Vec<Completion> = snippets
18798 .into_iter()
18799 .filter_map(|snippet| {
18800 let matching_prefix = snippet
18801 .prefix
18802 .iter()
18803 .find(|prefix| matched_strings.contains(*prefix))?;
18804 let start = as_offset - last_word.len();
18805 let start = snapshot.anchor_before(start);
18806 let range = start..buffer_position;
18807 let lsp_start = to_lsp(&start);
18808 let lsp_range = lsp::Range {
18809 start: lsp_start,
18810 end: lsp_end,
18811 };
18812 Some(Completion {
18813 replace_range: range,
18814 new_text: snippet.body.clone(),
18815 source: CompletionSource::Lsp {
18816 insert_range: None,
18817 server_id: LanguageServerId(usize::MAX),
18818 resolved: true,
18819 lsp_completion: Box::new(lsp::CompletionItem {
18820 label: snippet.prefix.first().unwrap().clone(),
18821 kind: Some(CompletionItemKind::SNIPPET),
18822 label_details: snippet.description.as_ref().map(|description| {
18823 lsp::CompletionItemLabelDetails {
18824 detail: Some(description.clone()),
18825 description: None,
18826 }
18827 }),
18828 insert_text_format: Some(InsertTextFormat::SNIPPET),
18829 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18830 lsp::InsertReplaceEdit {
18831 new_text: snippet.body.clone(),
18832 insert: lsp_range,
18833 replace: lsp_range,
18834 },
18835 )),
18836 filter_text: Some(snippet.body.clone()),
18837 sort_text: Some(char::MAX.to_string()),
18838 ..lsp::CompletionItem::default()
18839 }),
18840 lsp_defaults: None,
18841 },
18842 label: CodeLabel {
18843 text: matching_prefix.clone(),
18844 runs: Vec::new(),
18845 filter_range: 0..matching_prefix.len(),
18846 },
18847 icon_path: None,
18848 documentation: snippet
18849 .description
18850 .clone()
18851 .map(|description| CompletionDocumentation::SingleLine(description.into())),
18852 insert_text_mode: None,
18853 confirm: None,
18854 })
18855 })
18856 .collect();
18857
18858 Ok(result)
18859 })
18860}
18861
18862impl CompletionProvider for Entity<Project> {
18863 fn completions(
18864 &self,
18865 _excerpt_id: ExcerptId,
18866 buffer: &Entity<Buffer>,
18867 buffer_position: text::Anchor,
18868 options: CompletionContext,
18869 _window: &mut Window,
18870 cx: &mut Context<Editor>,
18871 ) -> Task<Result<Option<Vec<Completion>>>> {
18872 self.update(cx, |project, cx| {
18873 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18874 let project_completions = project.completions(buffer, buffer_position, options, cx);
18875 cx.background_spawn(async move {
18876 let snippets_completions = snippets.await?;
18877 match project_completions.await? {
18878 Some(mut completions) => {
18879 completions.extend(snippets_completions);
18880 Ok(Some(completions))
18881 }
18882 None => {
18883 if snippets_completions.is_empty() {
18884 Ok(None)
18885 } else {
18886 Ok(Some(snippets_completions))
18887 }
18888 }
18889 }
18890 })
18891 })
18892 }
18893
18894 fn resolve_completions(
18895 &self,
18896 buffer: Entity<Buffer>,
18897 completion_indices: Vec<usize>,
18898 completions: Rc<RefCell<Box<[Completion]>>>,
18899 cx: &mut Context<Editor>,
18900 ) -> Task<Result<bool>> {
18901 self.update(cx, |project, cx| {
18902 project.lsp_store().update(cx, |lsp_store, cx| {
18903 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18904 })
18905 })
18906 }
18907
18908 fn apply_additional_edits_for_completion(
18909 &self,
18910 buffer: Entity<Buffer>,
18911 completions: Rc<RefCell<Box<[Completion]>>>,
18912 completion_index: usize,
18913 push_to_history: bool,
18914 cx: &mut Context<Editor>,
18915 ) -> Task<Result<Option<language::Transaction>>> {
18916 self.update(cx, |project, cx| {
18917 project.lsp_store().update(cx, |lsp_store, cx| {
18918 lsp_store.apply_additional_edits_for_completion(
18919 buffer,
18920 completions,
18921 completion_index,
18922 push_to_history,
18923 cx,
18924 )
18925 })
18926 })
18927 }
18928
18929 fn is_completion_trigger(
18930 &self,
18931 buffer: &Entity<Buffer>,
18932 position: language::Anchor,
18933 text: &str,
18934 trigger_in_words: bool,
18935 cx: &mut Context<Editor>,
18936 ) -> bool {
18937 let mut chars = text.chars();
18938 let char = if let Some(char) = chars.next() {
18939 char
18940 } else {
18941 return false;
18942 };
18943 if chars.next().is_some() {
18944 return false;
18945 }
18946
18947 let buffer = buffer.read(cx);
18948 let snapshot = buffer.snapshot();
18949 if !snapshot.settings_at(position, cx).show_completions_on_input {
18950 return false;
18951 }
18952 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18953 if trigger_in_words && classifier.is_word(char) {
18954 return true;
18955 }
18956
18957 buffer.completion_triggers().contains(text)
18958 }
18959}
18960
18961impl SemanticsProvider for Entity<Project> {
18962 fn hover(
18963 &self,
18964 buffer: &Entity<Buffer>,
18965 position: text::Anchor,
18966 cx: &mut App,
18967 ) -> Option<Task<Vec<project::Hover>>> {
18968 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18969 }
18970
18971 fn document_highlights(
18972 &self,
18973 buffer: &Entity<Buffer>,
18974 position: text::Anchor,
18975 cx: &mut App,
18976 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18977 Some(self.update(cx, |project, cx| {
18978 project.document_highlights(buffer, position, cx)
18979 }))
18980 }
18981
18982 fn definitions(
18983 &self,
18984 buffer: &Entity<Buffer>,
18985 position: text::Anchor,
18986 kind: GotoDefinitionKind,
18987 cx: &mut App,
18988 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18989 Some(self.update(cx, |project, cx| match kind {
18990 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18991 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18992 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18993 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18994 }))
18995 }
18996
18997 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18998 // TODO: make this work for remote projects
18999 self.update(cx, |this, cx| {
19000 buffer.update(cx, |buffer, cx| {
19001 this.any_language_server_supports_inlay_hints(buffer, cx)
19002 })
19003 })
19004 }
19005
19006 fn inlay_hints(
19007 &self,
19008 buffer_handle: Entity<Buffer>,
19009 range: Range<text::Anchor>,
19010 cx: &mut App,
19011 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19012 Some(self.update(cx, |project, cx| {
19013 project.inlay_hints(buffer_handle, range, cx)
19014 }))
19015 }
19016
19017 fn resolve_inlay_hint(
19018 &self,
19019 hint: InlayHint,
19020 buffer_handle: Entity<Buffer>,
19021 server_id: LanguageServerId,
19022 cx: &mut App,
19023 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19024 Some(self.update(cx, |project, cx| {
19025 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19026 }))
19027 }
19028
19029 fn range_for_rename(
19030 &self,
19031 buffer: &Entity<Buffer>,
19032 position: text::Anchor,
19033 cx: &mut App,
19034 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19035 Some(self.update(cx, |project, cx| {
19036 let buffer = buffer.clone();
19037 let task = project.prepare_rename(buffer.clone(), position, cx);
19038 cx.spawn(async move |_, cx| {
19039 Ok(match task.await? {
19040 PrepareRenameResponse::Success(range) => Some(range),
19041 PrepareRenameResponse::InvalidPosition => None,
19042 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19043 // Fallback on using TreeSitter info to determine identifier range
19044 buffer.update(cx, |buffer, _| {
19045 let snapshot = buffer.snapshot();
19046 let (range, kind) = snapshot.surrounding_word(position);
19047 if kind != Some(CharKind::Word) {
19048 return None;
19049 }
19050 Some(
19051 snapshot.anchor_before(range.start)
19052 ..snapshot.anchor_after(range.end),
19053 )
19054 })?
19055 }
19056 })
19057 })
19058 }))
19059 }
19060
19061 fn perform_rename(
19062 &self,
19063 buffer: &Entity<Buffer>,
19064 position: text::Anchor,
19065 new_name: String,
19066 cx: &mut App,
19067 ) -> Option<Task<Result<ProjectTransaction>>> {
19068 Some(self.update(cx, |project, cx| {
19069 project.perform_rename(buffer.clone(), position, new_name, cx)
19070 }))
19071 }
19072}
19073
19074fn inlay_hint_settings(
19075 location: Anchor,
19076 snapshot: &MultiBufferSnapshot,
19077 cx: &mut Context<Editor>,
19078) -> InlayHintSettings {
19079 let file = snapshot.file_at(location);
19080 let language = snapshot.language_at(location).map(|l| l.name());
19081 language_settings(language, file, cx).inlay_hints
19082}
19083
19084fn consume_contiguous_rows(
19085 contiguous_row_selections: &mut Vec<Selection<Point>>,
19086 selection: &Selection<Point>,
19087 display_map: &DisplaySnapshot,
19088 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19089) -> (MultiBufferRow, MultiBufferRow) {
19090 contiguous_row_selections.push(selection.clone());
19091 let start_row = MultiBufferRow(selection.start.row);
19092 let mut end_row = ending_row(selection, display_map);
19093
19094 while let Some(next_selection) = selections.peek() {
19095 if next_selection.start.row <= end_row.0 {
19096 end_row = ending_row(next_selection, display_map);
19097 contiguous_row_selections.push(selections.next().unwrap().clone());
19098 } else {
19099 break;
19100 }
19101 }
19102 (start_row, end_row)
19103}
19104
19105fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19106 if next_selection.end.column > 0 || next_selection.is_empty() {
19107 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19108 } else {
19109 MultiBufferRow(next_selection.end.row)
19110 }
19111}
19112
19113impl EditorSnapshot {
19114 pub fn remote_selections_in_range<'a>(
19115 &'a self,
19116 range: &'a Range<Anchor>,
19117 collaboration_hub: &dyn CollaborationHub,
19118 cx: &'a App,
19119 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19120 let participant_names = collaboration_hub.user_names(cx);
19121 let participant_indices = collaboration_hub.user_participant_indices(cx);
19122 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19123 let collaborators_by_replica_id = collaborators_by_peer_id
19124 .iter()
19125 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19126 .collect::<HashMap<_, _>>();
19127 self.buffer_snapshot
19128 .selections_in_range(range, false)
19129 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19130 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19131 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19132 let user_name = participant_names.get(&collaborator.user_id).cloned();
19133 Some(RemoteSelection {
19134 replica_id,
19135 selection,
19136 cursor_shape,
19137 line_mode,
19138 participant_index,
19139 peer_id: collaborator.peer_id,
19140 user_name,
19141 })
19142 })
19143 }
19144
19145 pub fn hunks_for_ranges(
19146 &self,
19147 ranges: impl IntoIterator<Item = Range<Point>>,
19148 ) -> Vec<MultiBufferDiffHunk> {
19149 let mut hunks = Vec::new();
19150 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19151 HashMap::default();
19152 for query_range in ranges {
19153 let query_rows =
19154 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19155 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19156 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19157 ) {
19158 // Include deleted hunks that are adjacent to the query range, because
19159 // otherwise they would be missed.
19160 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19161 if hunk.status().is_deleted() {
19162 intersects_range |= hunk.row_range.start == query_rows.end;
19163 intersects_range |= hunk.row_range.end == query_rows.start;
19164 }
19165 if intersects_range {
19166 if !processed_buffer_rows
19167 .entry(hunk.buffer_id)
19168 .or_default()
19169 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19170 {
19171 continue;
19172 }
19173 hunks.push(hunk);
19174 }
19175 }
19176 }
19177
19178 hunks
19179 }
19180
19181 fn display_diff_hunks_for_rows<'a>(
19182 &'a self,
19183 display_rows: Range<DisplayRow>,
19184 folded_buffers: &'a HashSet<BufferId>,
19185 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19186 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19187 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19188
19189 self.buffer_snapshot
19190 .diff_hunks_in_range(buffer_start..buffer_end)
19191 .filter_map(|hunk| {
19192 if folded_buffers.contains(&hunk.buffer_id) {
19193 return None;
19194 }
19195
19196 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19197 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19198
19199 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19200 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19201
19202 let display_hunk = if hunk_display_start.column() != 0 {
19203 DisplayDiffHunk::Folded {
19204 display_row: hunk_display_start.row(),
19205 }
19206 } else {
19207 let mut end_row = hunk_display_end.row();
19208 if hunk_display_end.column() > 0 {
19209 end_row.0 += 1;
19210 }
19211 let is_created_file = hunk.is_created_file();
19212 DisplayDiffHunk::Unfolded {
19213 status: hunk.status(),
19214 diff_base_byte_range: hunk.diff_base_byte_range,
19215 display_row_range: hunk_display_start.row()..end_row,
19216 multi_buffer_range: Anchor::range_in_buffer(
19217 hunk.excerpt_id,
19218 hunk.buffer_id,
19219 hunk.buffer_range,
19220 ),
19221 is_created_file,
19222 }
19223 };
19224
19225 Some(display_hunk)
19226 })
19227 }
19228
19229 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19230 self.display_snapshot.buffer_snapshot.language_at(position)
19231 }
19232
19233 pub fn is_focused(&self) -> bool {
19234 self.is_focused
19235 }
19236
19237 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19238 self.placeholder_text.as_ref()
19239 }
19240
19241 pub fn scroll_position(&self) -> gpui::Point<f32> {
19242 self.scroll_anchor.scroll_position(&self.display_snapshot)
19243 }
19244
19245 fn gutter_dimensions(
19246 &self,
19247 font_id: FontId,
19248 font_size: Pixels,
19249 max_line_number_width: Pixels,
19250 cx: &App,
19251 ) -> Option<GutterDimensions> {
19252 if !self.show_gutter {
19253 return None;
19254 }
19255
19256 let descent = cx.text_system().descent(font_id, font_size);
19257 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19258 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19259
19260 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19261 matches!(
19262 ProjectSettings::get_global(cx).git.git_gutter,
19263 Some(GitGutterSetting::TrackedFiles)
19264 )
19265 });
19266 let gutter_settings = EditorSettings::get_global(cx).gutter;
19267 let show_line_numbers = self
19268 .show_line_numbers
19269 .unwrap_or(gutter_settings.line_numbers);
19270 let line_gutter_width = if show_line_numbers {
19271 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19272 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19273 max_line_number_width.max(min_width_for_number_on_gutter)
19274 } else {
19275 0.0.into()
19276 };
19277
19278 let show_code_actions = self
19279 .show_code_actions
19280 .unwrap_or(gutter_settings.code_actions);
19281
19282 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19283 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19284
19285 let git_blame_entries_width =
19286 self.git_blame_gutter_max_author_length
19287 .map(|max_author_length| {
19288 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19289 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19290
19291 /// The number of characters to dedicate to gaps and margins.
19292 const SPACING_WIDTH: usize = 4;
19293
19294 let max_char_count = max_author_length.min(renderer.max_author_length())
19295 + ::git::SHORT_SHA_LENGTH
19296 + MAX_RELATIVE_TIMESTAMP.len()
19297 + SPACING_WIDTH;
19298
19299 em_advance * max_char_count
19300 });
19301
19302 let is_singleton = self.buffer_snapshot.is_singleton();
19303
19304 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19305 left_padding += if !is_singleton {
19306 em_width * 4.0
19307 } else if show_code_actions || show_runnables || show_breakpoints {
19308 em_width * 3.0
19309 } else if show_git_gutter && show_line_numbers {
19310 em_width * 2.0
19311 } else if show_git_gutter || show_line_numbers {
19312 em_width
19313 } else {
19314 px(0.)
19315 };
19316
19317 let shows_folds = is_singleton && gutter_settings.folds;
19318
19319 let right_padding = if shows_folds && show_line_numbers {
19320 em_width * 4.0
19321 } else if shows_folds || (!is_singleton && show_line_numbers) {
19322 em_width * 3.0
19323 } else if show_line_numbers {
19324 em_width
19325 } else {
19326 px(0.)
19327 };
19328
19329 Some(GutterDimensions {
19330 left_padding,
19331 right_padding,
19332 width: line_gutter_width + left_padding + right_padding,
19333 margin: -descent,
19334 git_blame_entries_width,
19335 })
19336 }
19337
19338 pub fn render_crease_toggle(
19339 &self,
19340 buffer_row: MultiBufferRow,
19341 row_contains_cursor: bool,
19342 editor: Entity<Editor>,
19343 window: &mut Window,
19344 cx: &mut App,
19345 ) -> Option<AnyElement> {
19346 let folded = self.is_line_folded(buffer_row);
19347 let mut is_foldable = false;
19348
19349 if let Some(crease) = self
19350 .crease_snapshot
19351 .query_row(buffer_row, &self.buffer_snapshot)
19352 {
19353 is_foldable = true;
19354 match crease {
19355 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
19356 if let Some(render_toggle) = render_toggle {
19357 let toggle_callback =
19358 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
19359 if folded {
19360 editor.update(cx, |editor, cx| {
19361 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
19362 });
19363 } else {
19364 editor.update(cx, |editor, cx| {
19365 editor.unfold_at(
19366 &crate::UnfoldAt { buffer_row },
19367 window,
19368 cx,
19369 )
19370 });
19371 }
19372 });
19373 return Some((render_toggle)(
19374 buffer_row,
19375 folded,
19376 toggle_callback,
19377 window,
19378 cx,
19379 ));
19380 }
19381 }
19382 }
19383 }
19384
19385 is_foldable |= self.starts_indent(buffer_row);
19386
19387 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19388 Some(
19389 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19390 .toggle_state(folded)
19391 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19392 if folded {
19393 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
19394 } else {
19395 this.fold_at(&FoldAt { buffer_row }, window, cx);
19396 }
19397 }))
19398 .into_any_element(),
19399 )
19400 } else {
19401 None
19402 }
19403 }
19404
19405 pub fn render_crease_trailer(
19406 &self,
19407 buffer_row: MultiBufferRow,
19408 window: &mut Window,
19409 cx: &mut App,
19410 ) -> Option<AnyElement> {
19411 let folded = self.is_line_folded(buffer_row);
19412 if let Crease::Inline { render_trailer, .. } = self
19413 .crease_snapshot
19414 .query_row(buffer_row, &self.buffer_snapshot)?
19415 {
19416 let render_trailer = render_trailer.as_ref()?;
19417 Some(render_trailer(buffer_row, folded, window, cx))
19418 } else {
19419 None
19420 }
19421 }
19422}
19423
19424impl Deref for EditorSnapshot {
19425 type Target = DisplaySnapshot;
19426
19427 fn deref(&self) -> &Self::Target {
19428 &self.display_snapshot
19429 }
19430}
19431
19432#[derive(Clone, Debug, PartialEq, Eq)]
19433pub enum EditorEvent {
19434 InputIgnored {
19435 text: Arc<str>,
19436 },
19437 InputHandled {
19438 utf16_range_to_replace: Option<Range<isize>>,
19439 text: Arc<str>,
19440 },
19441 ExcerptsAdded {
19442 buffer: Entity<Buffer>,
19443 predecessor: ExcerptId,
19444 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19445 },
19446 ExcerptsRemoved {
19447 ids: Vec<ExcerptId>,
19448 },
19449 BufferFoldToggled {
19450 ids: Vec<ExcerptId>,
19451 folded: bool,
19452 },
19453 ExcerptsEdited {
19454 ids: Vec<ExcerptId>,
19455 },
19456 ExcerptsExpanded {
19457 ids: Vec<ExcerptId>,
19458 },
19459 BufferEdited,
19460 Edited {
19461 transaction_id: clock::Lamport,
19462 },
19463 Reparsed(BufferId),
19464 Focused,
19465 FocusedIn,
19466 Blurred,
19467 DirtyChanged,
19468 Saved,
19469 TitleChanged,
19470 DiffBaseChanged,
19471 SelectionsChanged {
19472 local: bool,
19473 },
19474 ScrollPositionChanged {
19475 local: bool,
19476 autoscroll: bool,
19477 },
19478 Closed,
19479 TransactionUndone {
19480 transaction_id: clock::Lamport,
19481 },
19482 TransactionBegun {
19483 transaction_id: clock::Lamport,
19484 },
19485 Reloaded,
19486 CursorShapeChanged,
19487 PushedToNavHistory {
19488 anchor: Anchor,
19489 is_deactivate: bool,
19490 },
19491}
19492
19493impl EventEmitter<EditorEvent> for Editor {}
19494
19495impl Focusable for Editor {
19496 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19497 self.focus_handle.clone()
19498 }
19499}
19500
19501impl Render for Editor {
19502 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19503 let settings = ThemeSettings::get_global(cx);
19504
19505 let mut text_style = match self.mode {
19506 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19507 color: cx.theme().colors().editor_foreground,
19508 font_family: settings.ui_font.family.clone(),
19509 font_features: settings.ui_font.features.clone(),
19510 font_fallbacks: settings.ui_font.fallbacks.clone(),
19511 font_size: rems(0.875).into(),
19512 font_weight: settings.ui_font.weight,
19513 line_height: relative(settings.buffer_line_height.value()),
19514 ..Default::default()
19515 },
19516 EditorMode::Full => TextStyle {
19517 color: cx.theme().colors().editor_foreground,
19518 font_family: settings.buffer_font.family.clone(),
19519 font_features: settings.buffer_font.features.clone(),
19520 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19521 font_size: settings.buffer_font_size(cx).into(),
19522 font_weight: settings.buffer_font.weight,
19523 line_height: relative(settings.buffer_line_height.value()),
19524 ..Default::default()
19525 },
19526 };
19527 if let Some(text_style_refinement) = &self.text_style_refinement {
19528 text_style.refine(text_style_refinement)
19529 }
19530
19531 let background = match self.mode {
19532 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19533 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19534 EditorMode::Full => cx.theme().colors().editor_background,
19535 };
19536
19537 EditorElement::new(
19538 &cx.entity(),
19539 EditorStyle {
19540 background,
19541 local_player: cx.theme().players().local(),
19542 text: text_style,
19543 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19544 syntax: cx.theme().syntax().clone(),
19545 status: cx.theme().status().clone(),
19546 inlay_hints_style: make_inlay_hints_style(cx),
19547 inline_completion_styles: make_suggestion_styles(cx),
19548 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19549 },
19550 )
19551 }
19552}
19553
19554impl EntityInputHandler for Editor {
19555 fn text_for_range(
19556 &mut self,
19557 range_utf16: Range<usize>,
19558 adjusted_range: &mut Option<Range<usize>>,
19559 _: &mut Window,
19560 cx: &mut Context<Self>,
19561 ) -> Option<String> {
19562 let snapshot = self.buffer.read(cx).read(cx);
19563 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19564 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19565 if (start.0..end.0) != range_utf16 {
19566 adjusted_range.replace(start.0..end.0);
19567 }
19568 Some(snapshot.text_for_range(start..end).collect())
19569 }
19570
19571 fn selected_text_range(
19572 &mut self,
19573 ignore_disabled_input: bool,
19574 _: &mut Window,
19575 cx: &mut Context<Self>,
19576 ) -> Option<UTF16Selection> {
19577 // Prevent the IME menu from appearing when holding down an alphabetic key
19578 // while input is disabled.
19579 if !ignore_disabled_input && !self.input_enabled {
19580 return None;
19581 }
19582
19583 let selection = self.selections.newest::<OffsetUtf16>(cx);
19584 let range = selection.range();
19585
19586 Some(UTF16Selection {
19587 range: range.start.0..range.end.0,
19588 reversed: selection.reversed,
19589 })
19590 }
19591
19592 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19593 let snapshot = self.buffer.read(cx).read(cx);
19594 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19595 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19596 }
19597
19598 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19599 self.clear_highlights::<InputComposition>(cx);
19600 self.ime_transaction.take();
19601 }
19602
19603 fn replace_text_in_range(
19604 &mut self,
19605 range_utf16: Option<Range<usize>>,
19606 text: &str,
19607 window: &mut Window,
19608 cx: &mut Context<Self>,
19609 ) {
19610 if !self.input_enabled {
19611 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19612 return;
19613 }
19614
19615 self.transact(window, cx, |this, window, cx| {
19616 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19617 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19618 Some(this.selection_replacement_ranges(range_utf16, cx))
19619 } else {
19620 this.marked_text_ranges(cx)
19621 };
19622
19623 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19624 let newest_selection_id = this.selections.newest_anchor().id;
19625 this.selections
19626 .all::<OffsetUtf16>(cx)
19627 .iter()
19628 .zip(ranges_to_replace.iter())
19629 .find_map(|(selection, range)| {
19630 if selection.id == newest_selection_id {
19631 Some(
19632 (range.start.0 as isize - selection.head().0 as isize)
19633 ..(range.end.0 as isize - selection.head().0 as isize),
19634 )
19635 } else {
19636 None
19637 }
19638 })
19639 });
19640
19641 cx.emit(EditorEvent::InputHandled {
19642 utf16_range_to_replace: range_to_replace,
19643 text: text.into(),
19644 });
19645
19646 if let Some(new_selected_ranges) = new_selected_ranges {
19647 this.change_selections(None, window, cx, |selections| {
19648 selections.select_ranges(new_selected_ranges)
19649 });
19650 this.backspace(&Default::default(), window, cx);
19651 }
19652
19653 this.handle_input(text, window, cx);
19654 });
19655
19656 if let Some(transaction) = self.ime_transaction {
19657 self.buffer.update(cx, |buffer, cx| {
19658 buffer.group_until_transaction(transaction, cx);
19659 });
19660 }
19661
19662 self.unmark_text(window, cx);
19663 }
19664
19665 fn replace_and_mark_text_in_range(
19666 &mut self,
19667 range_utf16: Option<Range<usize>>,
19668 text: &str,
19669 new_selected_range_utf16: Option<Range<usize>>,
19670 window: &mut Window,
19671 cx: &mut Context<Self>,
19672 ) {
19673 if !self.input_enabled {
19674 return;
19675 }
19676
19677 let transaction = self.transact(window, cx, |this, window, cx| {
19678 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19679 let snapshot = this.buffer.read(cx).read(cx);
19680 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19681 for marked_range in &mut marked_ranges {
19682 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19683 marked_range.start.0 += relative_range_utf16.start;
19684 marked_range.start =
19685 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19686 marked_range.end =
19687 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19688 }
19689 }
19690 Some(marked_ranges)
19691 } else if let Some(range_utf16) = range_utf16 {
19692 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19693 Some(this.selection_replacement_ranges(range_utf16, cx))
19694 } else {
19695 None
19696 };
19697
19698 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19699 let newest_selection_id = this.selections.newest_anchor().id;
19700 this.selections
19701 .all::<OffsetUtf16>(cx)
19702 .iter()
19703 .zip(ranges_to_replace.iter())
19704 .find_map(|(selection, range)| {
19705 if selection.id == newest_selection_id {
19706 Some(
19707 (range.start.0 as isize - selection.head().0 as isize)
19708 ..(range.end.0 as isize - selection.head().0 as isize),
19709 )
19710 } else {
19711 None
19712 }
19713 })
19714 });
19715
19716 cx.emit(EditorEvent::InputHandled {
19717 utf16_range_to_replace: range_to_replace,
19718 text: text.into(),
19719 });
19720
19721 if let Some(ranges) = ranges_to_replace {
19722 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19723 }
19724
19725 let marked_ranges = {
19726 let snapshot = this.buffer.read(cx).read(cx);
19727 this.selections
19728 .disjoint_anchors()
19729 .iter()
19730 .map(|selection| {
19731 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19732 })
19733 .collect::<Vec<_>>()
19734 };
19735
19736 if text.is_empty() {
19737 this.unmark_text(window, cx);
19738 } else {
19739 this.highlight_text::<InputComposition>(
19740 marked_ranges.clone(),
19741 HighlightStyle {
19742 underline: Some(UnderlineStyle {
19743 thickness: px(1.),
19744 color: None,
19745 wavy: false,
19746 }),
19747 ..Default::default()
19748 },
19749 cx,
19750 );
19751 }
19752
19753 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19754 let use_autoclose = this.use_autoclose;
19755 let use_auto_surround = this.use_auto_surround;
19756 this.set_use_autoclose(false);
19757 this.set_use_auto_surround(false);
19758 this.handle_input(text, window, cx);
19759 this.set_use_autoclose(use_autoclose);
19760 this.set_use_auto_surround(use_auto_surround);
19761
19762 if let Some(new_selected_range) = new_selected_range_utf16 {
19763 let snapshot = this.buffer.read(cx).read(cx);
19764 let new_selected_ranges = marked_ranges
19765 .into_iter()
19766 .map(|marked_range| {
19767 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19768 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19769 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19770 snapshot.clip_offset_utf16(new_start, Bias::Left)
19771 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19772 })
19773 .collect::<Vec<_>>();
19774
19775 drop(snapshot);
19776 this.change_selections(None, window, cx, |selections| {
19777 selections.select_ranges(new_selected_ranges)
19778 });
19779 }
19780 });
19781
19782 self.ime_transaction = self.ime_transaction.or(transaction);
19783 if let Some(transaction) = self.ime_transaction {
19784 self.buffer.update(cx, |buffer, cx| {
19785 buffer.group_until_transaction(transaction, cx);
19786 });
19787 }
19788
19789 if self.text_highlights::<InputComposition>(cx).is_none() {
19790 self.ime_transaction.take();
19791 }
19792 }
19793
19794 fn bounds_for_range(
19795 &mut self,
19796 range_utf16: Range<usize>,
19797 element_bounds: gpui::Bounds<Pixels>,
19798 window: &mut Window,
19799 cx: &mut Context<Self>,
19800 ) -> Option<gpui::Bounds<Pixels>> {
19801 let text_layout_details = self.text_layout_details(window);
19802 let gpui::Size {
19803 width: em_width,
19804 height: line_height,
19805 } = self.character_size(window);
19806
19807 let snapshot = self.snapshot(window, cx);
19808 let scroll_position = snapshot.scroll_position();
19809 let scroll_left = scroll_position.x * em_width;
19810
19811 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19812 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19813 + self.gutter_dimensions.width
19814 + self.gutter_dimensions.margin;
19815 let y = line_height * (start.row().as_f32() - scroll_position.y);
19816
19817 Some(Bounds {
19818 origin: element_bounds.origin + point(x, y),
19819 size: size(em_width, line_height),
19820 })
19821 }
19822
19823 fn character_index_for_point(
19824 &mut self,
19825 point: gpui::Point<Pixels>,
19826 _window: &mut Window,
19827 _cx: &mut Context<Self>,
19828 ) -> Option<usize> {
19829 let position_map = self.last_position_map.as_ref()?;
19830 if !position_map.text_hitbox.contains(&point) {
19831 return None;
19832 }
19833 let display_point = position_map.point_for_position(point).previous_valid;
19834 let anchor = position_map
19835 .snapshot
19836 .display_point_to_anchor(display_point, Bias::Left);
19837 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19838 Some(utf16_offset.0)
19839 }
19840}
19841
19842trait SelectionExt {
19843 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19844 fn spanned_rows(
19845 &self,
19846 include_end_if_at_line_start: bool,
19847 map: &DisplaySnapshot,
19848 ) -> Range<MultiBufferRow>;
19849}
19850
19851impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19852 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19853 let start = self
19854 .start
19855 .to_point(&map.buffer_snapshot)
19856 .to_display_point(map);
19857 let end = self
19858 .end
19859 .to_point(&map.buffer_snapshot)
19860 .to_display_point(map);
19861 if self.reversed {
19862 end..start
19863 } else {
19864 start..end
19865 }
19866 }
19867
19868 fn spanned_rows(
19869 &self,
19870 include_end_if_at_line_start: bool,
19871 map: &DisplaySnapshot,
19872 ) -> Range<MultiBufferRow> {
19873 let start = self.start.to_point(&map.buffer_snapshot);
19874 let mut end = self.end.to_point(&map.buffer_snapshot);
19875 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19876 end.row -= 1;
19877 }
19878
19879 let buffer_start = map.prev_line_boundary(start).0;
19880 let buffer_end = map.next_line_boundary(end).0;
19881 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19882 }
19883}
19884
19885impl<T: InvalidationRegion> InvalidationStack<T> {
19886 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19887 where
19888 S: Clone + ToOffset,
19889 {
19890 while let Some(region) = self.last() {
19891 let all_selections_inside_invalidation_ranges =
19892 if selections.len() == region.ranges().len() {
19893 selections
19894 .iter()
19895 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19896 .all(|(selection, invalidation_range)| {
19897 let head = selection.head().to_offset(buffer);
19898 invalidation_range.start <= head && invalidation_range.end >= head
19899 })
19900 } else {
19901 false
19902 };
19903
19904 if all_selections_inside_invalidation_ranges {
19905 break;
19906 } else {
19907 self.pop();
19908 }
19909 }
19910 }
19911}
19912
19913impl<T> Default for InvalidationStack<T> {
19914 fn default() -> Self {
19915 Self(Default::default())
19916 }
19917}
19918
19919impl<T> Deref for InvalidationStack<T> {
19920 type Target = Vec<T>;
19921
19922 fn deref(&self) -> &Self::Target {
19923 &self.0
19924 }
19925}
19926
19927impl<T> DerefMut for InvalidationStack<T> {
19928 fn deref_mut(&mut self) -> &mut Self::Target {
19929 &mut self.0
19930 }
19931}
19932
19933impl InvalidationRegion for SnippetState {
19934 fn ranges(&self) -> &[Range<Anchor>] {
19935 &self.ranges[self.active_index]
19936 }
19937}
19938
19939pub fn diagnostic_block_renderer(
19940 diagnostic: Diagnostic,
19941 max_message_rows: Option<u8>,
19942 allow_closing: bool,
19943) -> RenderBlock {
19944 let (text_without_backticks, code_ranges) =
19945 highlight_diagnostic_message(&diagnostic, max_message_rows);
19946
19947 Arc::new(move |cx: &mut BlockContext| {
19948 let group_id: SharedString = cx.block_id.to_string().into();
19949
19950 let mut text_style = cx.window.text_style().clone();
19951 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19952 let theme_settings = ThemeSettings::get_global(cx);
19953 text_style.font_family = theme_settings.buffer_font.family.clone();
19954 text_style.font_style = theme_settings.buffer_font.style;
19955 text_style.font_features = theme_settings.buffer_font.features.clone();
19956 text_style.font_weight = theme_settings.buffer_font.weight;
19957
19958 let multi_line_diagnostic = diagnostic.message.contains('\n');
19959
19960 let buttons = |diagnostic: &Diagnostic| {
19961 if multi_line_diagnostic {
19962 v_flex()
19963 } else {
19964 h_flex()
19965 }
19966 .when(allow_closing, |div| {
19967 div.children(diagnostic.is_primary.then(|| {
19968 IconButton::new("close-block", IconName::XCircle)
19969 .icon_color(Color::Muted)
19970 .size(ButtonSize::Compact)
19971 .style(ButtonStyle::Transparent)
19972 .visible_on_hover(group_id.clone())
19973 .on_click(move |_click, window, cx| {
19974 window.dispatch_action(Box::new(Cancel), cx)
19975 })
19976 .tooltip(|window, cx| {
19977 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19978 })
19979 }))
19980 })
19981 .child(
19982 IconButton::new("copy-block", IconName::Copy)
19983 .icon_color(Color::Muted)
19984 .size(ButtonSize::Compact)
19985 .style(ButtonStyle::Transparent)
19986 .visible_on_hover(group_id.clone())
19987 .on_click({
19988 let message = diagnostic.message.clone();
19989 move |_click, _, cx| {
19990 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19991 }
19992 })
19993 .tooltip(Tooltip::text("Copy diagnostic message")),
19994 )
19995 };
19996
19997 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19998 AvailableSpace::min_size(),
19999 cx.window,
20000 cx.app,
20001 );
20002
20003 h_flex()
20004 .id(cx.block_id)
20005 .group(group_id.clone())
20006 .relative()
20007 .size_full()
20008 .block_mouse_down()
20009 .pl(cx.gutter_dimensions.width)
20010 .w(cx.max_width - cx.gutter_dimensions.full_width())
20011 .child(
20012 div()
20013 .flex()
20014 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
20015 .flex_shrink(),
20016 )
20017 .child(buttons(&diagnostic))
20018 .child(div().flex().flex_shrink_0().child(
20019 StyledText::new(text_without_backticks.clone()).with_default_highlights(
20020 &text_style,
20021 code_ranges.iter().map(|range| {
20022 (
20023 range.clone(),
20024 HighlightStyle {
20025 font_weight: Some(FontWeight::BOLD),
20026 ..Default::default()
20027 },
20028 )
20029 }),
20030 ),
20031 ))
20032 .into_any_element()
20033 })
20034}
20035
20036fn inline_completion_edit_text(
20037 current_snapshot: &BufferSnapshot,
20038 edits: &[(Range<Anchor>, String)],
20039 edit_preview: &EditPreview,
20040 include_deletions: bool,
20041 cx: &App,
20042) -> HighlightedText {
20043 let edits = edits
20044 .iter()
20045 .map(|(anchor, text)| {
20046 (
20047 anchor.start.text_anchor..anchor.end.text_anchor,
20048 text.clone(),
20049 )
20050 })
20051 .collect::<Vec<_>>();
20052
20053 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20054}
20055
20056pub fn highlight_diagnostic_message(
20057 diagnostic: &Diagnostic,
20058 mut max_message_rows: Option<u8>,
20059) -> (SharedString, Vec<Range<usize>>) {
20060 let mut text_without_backticks = String::new();
20061 let mut code_ranges = Vec::new();
20062
20063 if let Some(source) = &diagnostic.source {
20064 text_without_backticks.push_str(source);
20065 code_ranges.push(0..source.len());
20066 text_without_backticks.push_str(": ");
20067 }
20068
20069 let mut prev_offset = 0;
20070 let mut in_code_block = false;
20071 let has_row_limit = max_message_rows.is_some();
20072 let mut newline_indices = diagnostic
20073 .message
20074 .match_indices('\n')
20075 .filter(|_| has_row_limit)
20076 .map(|(ix, _)| ix)
20077 .fuse()
20078 .peekable();
20079
20080 for (quote_ix, _) in diagnostic
20081 .message
20082 .match_indices('`')
20083 .chain([(diagnostic.message.len(), "")])
20084 {
20085 let mut first_newline_ix = None;
20086 let mut last_newline_ix = None;
20087 while let Some(newline_ix) = newline_indices.peek() {
20088 if *newline_ix < quote_ix {
20089 if first_newline_ix.is_none() {
20090 first_newline_ix = Some(*newline_ix);
20091 }
20092 last_newline_ix = Some(*newline_ix);
20093
20094 if let Some(rows_left) = &mut max_message_rows {
20095 if *rows_left == 0 {
20096 break;
20097 } else {
20098 *rows_left -= 1;
20099 }
20100 }
20101 let _ = newline_indices.next();
20102 } else {
20103 break;
20104 }
20105 }
20106 let prev_len = text_without_backticks.len();
20107 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
20108 text_without_backticks.push_str(new_text);
20109 if in_code_block {
20110 code_ranges.push(prev_len..text_without_backticks.len());
20111 }
20112 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
20113 in_code_block = !in_code_block;
20114 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
20115 text_without_backticks.push_str("...");
20116 break;
20117 }
20118 }
20119
20120 (text_without_backticks.into(), code_ranges)
20121}
20122
20123fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20124 match severity {
20125 DiagnosticSeverity::ERROR => colors.error,
20126 DiagnosticSeverity::WARNING => colors.warning,
20127 DiagnosticSeverity::INFORMATION => colors.info,
20128 DiagnosticSeverity::HINT => colors.info,
20129 _ => colors.ignored,
20130 }
20131}
20132
20133pub fn styled_runs_for_code_label<'a>(
20134 label: &'a CodeLabel,
20135 syntax_theme: &'a theme::SyntaxTheme,
20136) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20137 let fade_out = HighlightStyle {
20138 fade_out: Some(0.35),
20139 ..Default::default()
20140 };
20141
20142 let mut prev_end = label.filter_range.end;
20143 label
20144 .runs
20145 .iter()
20146 .enumerate()
20147 .flat_map(move |(ix, (range, highlight_id))| {
20148 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20149 style
20150 } else {
20151 return Default::default();
20152 };
20153 let mut muted_style = style;
20154 muted_style.highlight(fade_out);
20155
20156 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20157 if range.start >= label.filter_range.end {
20158 if range.start > prev_end {
20159 runs.push((prev_end..range.start, fade_out));
20160 }
20161 runs.push((range.clone(), muted_style));
20162 } else if range.end <= label.filter_range.end {
20163 runs.push((range.clone(), style));
20164 } else {
20165 runs.push((range.start..label.filter_range.end, style));
20166 runs.push((label.filter_range.end..range.end, muted_style));
20167 }
20168 prev_end = cmp::max(prev_end, range.end);
20169
20170 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20171 runs.push((prev_end..label.text.len(), fade_out));
20172 }
20173
20174 runs
20175 })
20176}
20177
20178pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20179 let mut prev_index = 0;
20180 let mut prev_codepoint: Option<char> = None;
20181 text.char_indices()
20182 .chain([(text.len(), '\0')])
20183 .filter_map(move |(index, codepoint)| {
20184 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20185 let is_boundary = index == text.len()
20186 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20187 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20188 if is_boundary {
20189 let chunk = &text[prev_index..index];
20190 prev_index = index;
20191 Some(chunk)
20192 } else {
20193 None
20194 }
20195 })
20196}
20197
20198pub trait RangeToAnchorExt: Sized {
20199 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20200
20201 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20202 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20203 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20204 }
20205}
20206
20207impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20208 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20209 let start_offset = self.start.to_offset(snapshot);
20210 let end_offset = self.end.to_offset(snapshot);
20211 if start_offset == end_offset {
20212 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20213 } else {
20214 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20215 }
20216 }
20217}
20218
20219pub trait RowExt {
20220 fn as_f32(&self) -> f32;
20221
20222 fn next_row(&self) -> Self;
20223
20224 fn previous_row(&self) -> Self;
20225
20226 fn minus(&self, other: Self) -> u32;
20227}
20228
20229impl RowExt for DisplayRow {
20230 fn as_f32(&self) -> f32 {
20231 self.0 as f32
20232 }
20233
20234 fn next_row(&self) -> Self {
20235 Self(self.0 + 1)
20236 }
20237
20238 fn previous_row(&self) -> Self {
20239 Self(self.0.saturating_sub(1))
20240 }
20241
20242 fn minus(&self, other: Self) -> u32 {
20243 self.0 - other.0
20244 }
20245}
20246
20247impl RowExt for MultiBufferRow {
20248 fn as_f32(&self) -> f32 {
20249 self.0 as f32
20250 }
20251
20252 fn next_row(&self) -> Self {
20253 Self(self.0 + 1)
20254 }
20255
20256 fn previous_row(&self) -> Self {
20257 Self(self.0.saturating_sub(1))
20258 }
20259
20260 fn minus(&self, other: Self) -> u32 {
20261 self.0 - other.0
20262 }
20263}
20264
20265trait RowRangeExt {
20266 type Row;
20267
20268 fn len(&self) -> usize;
20269
20270 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20271}
20272
20273impl RowRangeExt for Range<MultiBufferRow> {
20274 type Row = MultiBufferRow;
20275
20276 fn len(&self) -> usize {
20277 (self.end.0 - self.start.0) as usize
20278 }
20279
20280 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20281 (self.start.0..self.end.0).map(MultiBufferRow)
20282 }
20283}
20284
20285impl RowRangeExt for Range<DisplayRow> {
20286 type Row = DisplayRow;
20287
20288 fn len(&self) -> usize {
20289 (self.end.0 - self.start.0) as usize
20290 }
20291
20292 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20293 (self.start.0..self.end.0).map(DisplayRow)
20294 }
20295}
20296
20297/// If select range has more than one line, we
20298/// just point the cursor to range.start.
20299fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20300 if range.start.row == range.end.row {
20301 range
20302 } else {
20303 range.start..range.start
20304 }
20305}
20306pub struct KillRing(ClipboardItem);
20307impl Global for KillRing {}
20308
20309const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20310
20311enum BreakpointPromptEditAction {
20312 Log,
20313 Condition,
20314 HitCondition,
20315}
20316
20317struct BreakpointPromptEditor {
20318 pub(crate) prompt: Entity<Editor>,
20319 editor: WeakEntity<Editor>,
20320 breakpoint_anchor: Anchor,
20321 breakpoint: Breakpoint,
20322 edit_action: BreakpointPromptEditAction,
20323 block_ids: HashSet<CustomBlockId>,
20324 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20325 _subscriptions: Vec<Subscription>,
20326}
20327
20328impl BreakpointPromptEditor {
20329 const MAX_LINES: u8 = 4;
20330
20331 fn new(
20332 editor: WeakEntity<Editor>,
20333 breakpoint_anchor: Anchor,
20334 breakpoint: Breakpoint,
20335 edit_action: BreakpointPromptEditAction,
20336 window: &mut Window,
20337 cx: &mut Context<Self>,
20338 ) -> Self {
20339 let base_text = match edit_action {
20340 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20341 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20342 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20343 }
20344 .map(|msg| msg.to_string())
20345 .unwrap_or_default();
20346
20347 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20348 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20349
20350 let prompt = cx.new(|cx| {
20351 let mut prompt = Editor::new(
20352 EditorMode::AutoHeight {
20353 max_lines: Self::MAX_LINES as usize,
20354 },
20355 buffer,
20356 None,
20357 window,
20358 cx,
20359 );
20360 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20361 prompt.set_show_cursor_when_unfocused(false, cx);
20362 prompt.set_placeholder_text(
20363 match edit_action {
20364 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20365 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20366 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20367 },
20368 cx,
20369 );
20370
20371 prompt
20372 });
20373
20374 Self {
20375 prompt,
20376 editor,
20377 breakpoint_anchor,
20378 breakpoint,
20379 edit_action,
20380 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20381 block_ids: Default::default(),
20382 _subscriptions: vec![],
20383 }
20384 }
20385
20386 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20387 self.block_ids.extend(block_ids)
20388 }
20389
20390 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20391 if let Some(editor) = self.editor.upgrade() {
20392 let message = self
20393 .prompt
20394 .read(cx)
20395 .buffer
20396 .read(cx)
20397 .as_singleton()
20398 .expect("A multi buffer in breakpoint prompt isn't possible")
20399 .read(cx)
20400 .as_rope()
20401 .to_string();
20402
20403 editor.update(cx, |editor, cx| {
20404 editor.edit_breakpoint_at_anchor(
20405 self.breakpoint_anchor,
20406 self.breakpoint.clone(),
20407 match self.edit_action {
20408 BreakpointPromptEditAction::Log => {
20409 BreakpointEditAction::EditLogMessage(message.into())
20410 }
20411 BreakpointPromptEditAction::Condition => {
20412 BreakpointEditAction::EditCondition(message.into())
20413 }
20414 BreakpointPromptEditAction::HitCondition => {
20415 BreakpointEditAction::EditHitCondition(message.into())
20416 }
20417 },
20418 cx,
20419 );
20420
20421 editor.remove_blocks(self.block_ids.clone(), None, cx);
20422 cx.focus_self(window);
20423 });
20424 }
20425 }
20426
20427 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20428 self.editor
20429 .update(cx, |editor, cx| {
20430 editor.remove_blocks(self.block_ids.clone(), None, cx);
20431 window.focus(&editor.focus_handle);
20432 })
20433 .log_err();
20434 }
20435
20436 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20437 let settings = ThemeSettings::get_global(cx);
20438 let text_style = TextStyle {
20439 color: if self.prompt.read(cx).read_only(cx) {
20440 cx.theme().colors().text_disabled
20441 } else {
20442 cx.theme().colors().text
20443 },
20444 font_family: settings.buffer_font.family.clone(),
20445 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20446 font_size: settings.buffer_font_size(cx).into(),
20447 font_weight: settings.buffer_font.weight,
20448 line_height: relative(settings.buffer_line_height.value()),
20449 ..Default::default()
20450 };
20451 EditorElement::new(
20452 &self.prompt,
20453 EditorStyle {
20454 background: cx.theme().colors().editor_background,
20455 local_player: cx.theme().players().local(),
20456 text: text_style,
20457 ..Default::default()
20458 },
20459 )
20460 }
20461}
20462
20463impl Render for BreakpointPromptEditor {
20464 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20465 let gutter_dimensions = *self.gutter_dimensions.lock();
20466 h_flex()
20467 .key_context("Editor")
20468 .bg(cx.theme().colors().editor_background)
20469 .border_y_1()
20470 .border_color(cx.theme().status().info_border)
20471 .size_full()
20472 .py(window.line_height() / 2.5)
20473 .on_action(cx.listener(Self::confirm))
20474 .on_action(cx.listener(Self::cancel))
20475 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20476 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20477 }
20478}
20479
20480impl Focusable for BreakpointPromptEditor {
20481 fn focus_handle(&self, cx: &App) -> FocusHandle {
20482 self.prompt.focus_handle(cx)
20483 }
20484}
20485
20486fn all_edits_insertions_or_deletions(
20487 edits: &Vec<(Range<Anchor>, String)>,
20488 snapshot: &MultiBufferSnapshot,
20489) -> bool {
20490 let mut all_insertions = true;
20491 let mut all_deletions = true;
20492
20493 for (range, new_text) in edits.iter() {
20494 let range_is_empty = range.to_offset(&snapshot).is_empty();
20495 let text_is_empty = new_text.is_empty();
20496
20497 if range_is_empty != text_is_empty {
20498 if range_is_empty {
20499 all_deletions = false;
20500 } else {
20501 all_insertions = false;
20502 }
20503 } else {
20504 return false;
20505 }
20506
20507 if !all_insertions && !all_deletions {
20508 return false;
20509 }
20510 }
20511 all_insertions || all_deletions
20512}
20513
20514struct MissingEditPredictionKeybindingTooltip;
20515
20516impl Render for MissingEditPredictionKeybindingTooltip {
20517 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20518 ui::tooltip_container(window, cx, |container, _, cx| {
20519 container
20520 .flex_shrink_0()
20521 .max_w_80()
20522 .min_h(rems_from_px(124.))
20523 .justify_between()
20524 .child(
20525 v_flex()
20526 .flex_1()
20527 .text_ui_sm(cx)
20528 .child(Label::new("Conflict with Accept Keybinding"))
20529 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20530 )
20531 .child(
20532 h_flex()
20533 .pb_1()
20534 .gap_1()
20535 .items_end()
20536 .w_full()
20537 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20538 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20539 }))
20540 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20541 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20542 })),
20543 )
20544 })
20545 }
20546}
20547
20548#[derive(Debug, Clone, Copy, PartialEq)]
20549pub struct LineHighlight {
20550 pub background: Background,
20551 pub border: Option<gpui::Hsla>,
20552}
20553
20554impl From<Hsla> for LineHighlight {
20555 fn from(hsla: Hsla) -> Self {
20556 Self {
20557 background: hsla.into(),
20558 border: None,
20559 }
20560 }
20561}
20562
20563impl From<Background> for LineHighlight {
20564 fn from(background: Background) -> Self {
20565 Self {
20566 background,
20567 border: None,
20568 }
20569 }
20570}
20571
20572fn render_diff_hunk_controls(
20573 row: u32,
20574 status: &DiffHunkStatus,
20575 hunk_range: Range<Anchor>,
20576 is_created_file: bool,
20577 line_height: Pixels,
20578 editor: &Entity<Editor>,
20579 _window: &mut Window,
20580 cx: &mut App,
20581) -> AnyElement {
20582 h_flex()
20583 .h(line_height)
20584 .mr_1()
20585 .gap_1()
20586 .px_0p5()
20587 .pb_1()
20588 .border_x_1()
20589 .border_b_1()
20590 .border_color(cx.theme().colors().border_variant)
20591 .rounded_b_lg()
20592 .bg(cx.theme().colors().editor_background)
20593 .gap_1()
20594 .occlude()
20595 .shadow_md()
20596 .child(if status.has_secondary_hunk() {
20597 Button::new(("stage", row as u64), "Stage")
20598 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20599 .tooltip({
20600 let focus_handle = editor.focus_handle(cx);
20601 move |window, cx| {
20602 Tooltip::for_action_in(
20603 "Stage Hunk",
20604 &::git::ToggleStaged,
20605 &focus_handle,
20606 window,
20607 cx,
20608 )
20609 }
20610 })
20611 .on_click({
20612 let editor = editor.clone();
20613 move |_event, _window, cx| {
20614 editor.update(cx, |editor, cx| {
20615 editor.stage_or_unstage_diff_hunks(
20616 true,
20617 vec![hunk_range.start..hunk_range.start],
20618 cx,
20619 );
20620 });
20621 }
20622 })
20623 } else {
20624 Button::new(("unstage", row as u64), "Unstage")
20625 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20626 .tooltip({
20627 let focus_handle = editor.focus_handle(cx);
20628 move |window, cx| {
20629 Tooltip::for_action_in(
20630 "Unstage Hunk",
20631 &::git::ToggleStaged,
20632 &focus_handle,
20633 window,
20634 cx,
20635 )
20636 }
20637 })
20638 .on_click({
20639 let editor = editor.clone();
20640 move |_event, _window, cx| {
20641 editor.update(cx, |editor, cx| {
20642 editor.stage_or_unstage_diff_hunks(
20643 false,
20644 vec![hunk_range.start..hunk_range.start],
20645 cx,
20646 );
20647 });
20648 }
20649 })
20650 })
20651 .child(
20652 Button::new(("restore", row as u64), "Restore")
20653 .tooltip({
20654 let focus_handle = editor.focus_handle(cx);
20655 move |window, cx| {
20656 Tooltip::for_action_in(
20657 "Restore Hunk",
20658 &::git::Restore,
20659 &focus_handle,
20660 window,
20661 cx,
20662 )
20663 }
20664 })
20665 .on_click({
20666 let editor = editor.clone();
20667 move |_event, window, cx| {
20668 editor.update(cx, |editor, cx| {
20669 let snapshot = editor.snapshot(window, cx);
20670 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20671 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20672 });
20673 }
20674 })
20675 .disabled(is_created_file),
20676 )
20677 .when(
20678 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20679 |el| {
20680 el.child(
20681 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20682 .shape(IconButtonShape::Square)
20683 .icon_size(IconSize::Small)
20684 // .disabled(!has_multiple_hunks)
20685 .tooltip({
20686 let focus_handle = editor.focus_handle(cx);
20687 move |window, cx| {
20688 Tooltip::for_action_in(
20689 "Next Hunk",
20690 &GoToHunk,
20691 &focus_handle,
20692 window,
20693 cx,
20694 )
20695 }
20696 })
20697 .on_click({
20698 let editor = editor.clone();
20699 move |_event, window, cx| {
20700 editor.update(cx, |editor, cx| {
20701 let snapshot = editor.snapshot(window, cx);
20702 let position =
20703 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20704 editor.go_to_hunk_before_or_after_position(
20705 &snapshot,
20706 position,
20707 Direction::Next,
20708 window,
20709 cx,
20710 );
20711 editor.expand_selected_diff_hunks(cx);
20712 });
20713 }
20714 }),
20715 )
20716 .child(
20717 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20718 .shape(IconButtonShape::Square)
20719 .icon_size(IconSize::Small)
20720 // .disabled(!has_multiple_hunks)
20721 .tooltip({
20722 let focus_handle = editor.focus_handle(cx);
20723 move |window, cx| {
20724 Tooltip::for_action_in(
20725 "Previous Hunk",
20726 &GoToPreviousHunk,
20727 &focus_handle,
20728 window,
20729 cx,
20730 )
20731 }
20732 })
20733 .on_click({
20734 let editor = editor.clone();
20735 move |_event, window, cx| {
20736 editor.update(cx, |editor, cx| {
20737 let snapshot = editor.snapshot(window, cx);
20738 let point =
20739 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20740 editor.go_to_hunk_before_or_after_position(
20741 &snapshot,
20742 point,
20743 Direction::Prev,
20744 window,
20745 cx,
20746 );
20747 editor.expand_selected_diff_hunks(cx);
20748 });
20749 }
20750 }),
20751 )
20752 },
20753 )
20754 .into_any_element()
20755}