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 run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6419 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6420
6421 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6422 BreakpointState::Enabled => Some("Disable"),
6423 BreakpointState::Disabled => Some("Enable"),
6424 });
6425
6426 let (anchor, breakpoint) =
6427 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6428
6429 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6430 menu.on_blur_subscription(Subscription::new(|| {}))
6431 .context(focus_handle)
6432 .when(run_to_cursor, |this| {
6433 let weak_editor = weak_editor.clone();
6434 this.entry("Run to cursor", None, move |window, cx| {
6435 weak_editor
6436 .update(cx, |editor, cx| {
6437 editor.change_selections(None, window, cx, |s| {
6438 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6439 });
6440 })
6441 .ok();
6442
6443 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6444 })
6445 .separator()
6446 })
6447 .when_some(toggle_state_msg, |this, msg| {
6448 this.entry(msg, None, {
6449 let weak_editor = weak_editor.clone();
6450 let breakpoint = breakpoint.clone();
6451 move |_window, cx| {
6452 weak_editor
6453 .update(cx, |this, cx| {
6454 this.edit_breakpoint_at_anchor(
6455 anchor,
6456 breakpoint.as_ref().clone(),
6457 BreakpointEditAction::InvertState,
6458 cx,
6459 );
6460 })
6461 .log_err();
6462 }
6463 })
6464 })
6465 .entry(set_breakpoint_msg, None, {
6466 let weak_editor = weak_editor.clone();
6467 let breakpoint = breakpoint.clone();
6468 move |_window, cx| {
6469 weak_editor
6470 .update(cx, |this, cx| {
6471 this.edit_breakpoint_at_anchor(
6472 anchor,
6473 breakpoint.as_ref().clone(),
6474 BreakpointEditAction::Toggle,
6475 cx,
6476 );
6477 })
6478 .log_err();
6479 }
6480 })
6481 .entry(log_breakpoint_msg, None, {
6482 let breakpoint = breakpoint.clone();
6483 let weak_editor = weak_editor.clone();
6484 move |window, cx| {
6485 weak_editor
6486 .update(cx, |this, cx| {
6487 this.add_edit_breakpoint_block(
6488 anchor,
6489 breakpoint.as_ref(),
6490 BreakpointPromptEditAction::Log,
6491 window,
6492 cx,
6493 );
6494 })
6495 .log_err();
6496 }
6497 })
6498 .entry(condition_breakpoint_msg, None, {
6499 let breakpoint = breakpoint.clone();
6500 let weak_editor = weak_editor.clone();
6501 move |window, cx| {
6502 weak_editor
6503 .update(cx, |this, cx| {
6504 this.add_edit_breakpoint_block(
6505 anchor,
6506 breakpoint.as_ref(),
6507 BreakpointPromptEditAction::Condition,
6508 window,
6509 cx,
6510 );
6511 })
6512 .log_err();
6513 }
6514 })
6515 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6516 weak_editor
6517 .update(cx, |this, cx| {
6518 this.add_edit_breakpoint_block(
6519 anchor,
6520 breakpoint.as_ref(),
6521 BreakpointPromptEditAction::HitCondition,
6522 window,
6523 cx,
6524 );
6525 })
6526 .log_err();
6527 })
6528 })
6529 }
6530
6531 fn render_breakpoint(
6532 &self,
6533 position: Anchor,
6534 row: DisplayRow,
6535 breakpoint: &Breakpoint,
6536 cx: &mut Context<Self>,
6537 ) -> IconButton {
6538 let (color, icon) = {
6539 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6540 (false, false) => ui::IconName::DebugBreakpoint,
6541 (true, false) => ui::IconName::DebugLogBreakpoint,
6542 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6543 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6544 };
6545
6546 let color = if self
6547 .gutter_breakpoint_indicator
6548 .0
6549 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6550 {
6551 Color::Hint
6552 } else {
6553 Color::Debugger
6554 };
6555
6556 (color, icon)
6557 };
6558
6559 let breakpoint = Arc::from(breakpoint.clone());
6560
6561 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6562 .icon_size(IconSize::XSmall)
6563 .size(ui::ButtonSize::None)
6564 .icon_color(color)
6565 .style(ButtonStyle::Transparent)
6566 .on_click(cx.listener({
6567 let breakpoint = breakpoint.clone();
6568
6569 move |editor, event: &ClickEvent, window, cx| {
6570 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6571 BreakpointEditAction::InvertState
6572 } else {
6573 BreakpointEditAction::Toggle
6574 };
6575
6576 window.focus(&editor.focus_handle(cx));
6577 editor.edit_breakpoint_at_anchor(
6578 position,
6579 breakpoint.as_ref().clone(),
6580 edit_action,
6581 cx,
6582 );
6583 }
6584 }))
6585 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6586 editor.set_breakpoint_context_menu(
6587 row,
6588 Some(position),
6589 event.down.position,
6590 window,
6591 cx,
6592 );
6593 }))
6594 }
6595
6596 fn build_tasks_context(
6597 project: &Entity<Project>,
6598 buffer: &Entity<Buffer>,
6599 buffer_row: u32,
6600 tasks: &Arc<RunnableTasks>,
6601 cx: &mut Context<Self>,
6602 ) -> Task<Option<task::TaskContext>> {
6603 let position = Point::new(buffer_row, tasks.column);
6604 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6605 let location = Location {
6606 buffer: buffer.clone(),
6607 range: range_start..range_start,
6608 };
6609 // Fill in the environmental variables from the tree-sitter captures
6610 let mut captured_task_variables = TaskVariables::default();
6611 for (capture_name, value) in tasks.extra_variables.clone() {
6612 captured_task_variables.insert(
6613 task::VariableName::Custom(capture_name.into()),
6614 value.clone(),
6615 );
6616 }
6617 project.update(cx, |project, cx| {
6618 project.task_store().update(cx, |task_store, cx| {
6619 task_store.task_context_for_location(captured_task_variables, location, cx)
6620 })
6621 })
6622 }
6623
6624 pub fn spawn_nearest_task(
6625 &mut self,
6626 action: &SpawnNearestTask,
6627 window: &mut Window,
6628 cx: &mut Context<Self>,
6629 ) {
6630 let Some((workspace, _)) = self.workspace.clone() else {
6631 return;
6632 };
6633 let Some(project) = self.project.clone() else {
6634 return;
6635 };
6636
6637 // Try to find a closest, enclosing node using tree-sitter that has a
6638 // task
6639 let Some((buffer, buffer_row, tasks)) = self
6640 .find_enclosing_node_task(cx)
6641 // Or find the task that's closest in row-distance.
6642 .or_else(|| self.find_closest_task(cx))
6643 else {
6644 return;
6645 };
6646
6647 let reveal_strategy = action.reveal;
6648 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6649 cx.spawn_in(window, async move |_, cx| {
6650 let context = task_context.await?;
6651 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6652
6653 let resolved = resolved_task.resolved.as_mut()?;
6654 resolved.reveal = reveal_strategy;
6655
6656 workspace
6657 .update(cx, |workspace, cx| {
6658 workspace::tasks::schedule_resolved_task(
6659 workspace,
6660 task_source_kind,
6661 resolved_task,
6662 false,
6663 cx,
6664 );
6665 })
6666 .ok()
6667 })
6668 .detach();
6669 }
6670
6671 fn find_closest_task(
6672 &mut self,
6673 cx: &mut Context<Self>,
6674 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6675 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6676
6677 let ((buffer_id, row), tasks) = self
6678 .tasks
6679 .iter()
6680 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6681
6682 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6683 let tasks = Arc::new(tasks.to_owned());
6684 Some((buffer, *row, tasks))
6685 }
6686
6687 fn find_enclosing_node_task(
6688 &mut self,
6689 cx: &mut Context<Self>,
6690 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6691 let snapshot = self.buffer.read(cx).snapshot(cx);
6692 let offset = self.selections.newest::<usize>(cx).head();
6693 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6694 let buffer_id = excerpt.buffer().remote_id();
6695
6696 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6697 let mut cursor = layer.node().walk();
6698
6699 while cursor.goto_first_child_for_byte(offset).is_some() {
6700 if cursor.node().end_byte() == offset {
6701 cursor.goto_next_sibling();
6702 }
6703 }
6704
6705 // Ascend to the smallest ancestor that contains the range and has a task.
6706 loop {
6707 let node = cursor.node();
6708 let node_range = node.byte_range();
6709 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6710
6711 // Check if this node contains our offset
6712 if node_range.start <= offset && node_range.end >= offset {
6713 // If it contains offset, check for task
6714 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6715 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6716 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6717 }
6718 }
6719
6720 if !cursor.goto_parent() {
6721 break;
6722 }
6723 }
6724 None
6725 }
6726
6727 fn render_run_indicator(
6728 &self,
6729 _style: &EditorStyle,
6730 is_active: bool,
6731 row: DisplayRow,
6732 breakpoint: Option<(Anchor, Breakpoint)>,
6733 cx: &mut Context<Self>,
6734 ) -> IconButton {
6735 let color = Color::Muted;
6736 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6737
6738 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6739 .shape(ui::IconButtonShape::Square)
6740 .icon_size(IconSize::XSmall)
6741 .icon_color(color)
6742 .toggle_state(is_active)
6743 .on_click(cx.listener(move |editor, _e, window, cx| {
6744 window.focus(&editor.focus_handle(cx));
6745 editor.toggle_code_actions(
6746 &ToggleCodeActions {
6747 deployed_from_indicator: Some(row),
6748 },
6749 window,
6750 cx,
6751 );
6752 }))
6753 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6754 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6755 }))
6756 }
6757
6758 pub fn context_menu_visible(&self) -> bool {
6759 !self.edit_prediction_preview_is_active()
6760 && self
6761 .context_menu
6762 .borrow()
6763 .as_ref()
6764 .map_or(false, |menu| menu.visible())
6765 }
6766
6767 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6768 self.context_menu
6769 .borrow()
6770 .as_ref()
6771 .map(|menu| menu.origin())
6772 }
6773
6774 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6775 self.context_menu_options = Some(options);
6776 }
6777
6778 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6779 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6780
6781 fn render_edit_prediction_popover(
6782 &mut self,
6783 text_bounds: &Bounds<Pixels>,
6784 content_origin: gpui::Point<Pixels>,
6785 editor_snapshot: &EditorSnapshot,
6786 visible_row_range: Range<DisplayRow>,
6787 scroll_top: f32,
6788 scroll_bottom: f32,
6789 line_layouts: &[LineWithInvisibles],
6790 line_height: Pixels,
6791 scroll_pixel_position: gpui::Point<Pixels>,
6792 newest_selection_head: Option<DisplayPoint>,
6793 editor_width: Pixels,
6794 style: &EditorStyle,
6795 window: &mut Window,
6796 cx: &mut App,
6797 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6798 let active_inline_completion = self.active_inline_completion.as_ref()?;
6799
6800 if self.edit_prediction_visible_in_cursor_popover(true) {
6801 return None;
6802 }
6803
6804 match &active_inline_completion.completion {
6805 InlineCompletion::Move { target, .. } => {
6806 let target_display_point = target.to_display_point(editor_snapshot);
6807
6808 if self.edit_prediction_requires_modifier() {
6809 if !self.edit_prediction_preview_is_active() {
6810 return None;
6811 }
6812
6813 self.render_edit_prediction_modifier_jump_popover(
6814 text_bounds,
6815 content_origin,
6816 visible_row_range,
6817 line_layouts,
6818 line_height,
6819 scroll_pixel_position,
6820 newest_selection_head,
6821 target_display_point,
6822 window,
6823 cx,
6824 )
6825 } else {
6826 self.render_edit_prediction_eager_jump_popover(
6827 text_bounds,
6828 content_origin,
6829 editor_snapshot,
6830 visible_row_range,
6831 scroll_top,
6832 scroll_bottom,
6833 line_height,
6834 scroll_pixel_position,
6835 target_display_point,
6836 editor_width,
6837 window,
6838 cx,
6839 )
6840 }
6841 }
6842 InlineCompletion::Edit {
6843 display_mode: EditDisplayMode::Inline,
6844 ..
6845 } => None,
6846 InlineCompletion::Edit {
6847 display_mode: EditDisplayMode::TabAccept,
6848 edits,
6849 ..
6850 } => {
6851 let range = &edits.first()?.0;
6852 let target_display_point = range.end.to_display_point(editor_snapshot);
6853
6854 self.render_edit_prediction_end_of_line_popover(
6855 "Accept",
6856 editor_snapshot,
6857 visible_row_range,
6858 target_display_point,
6859 line_height,
6860 scroll_pixel_position,
6861 content_origin,
6862 editor_width,
6863 window,
6864 cx,
6865 )
6866 }
6867 InlineCompletion::Edit {
6868 edits,
6869 edit_preview,
6870 display_mode: EditDisplayMode::DiffPopover,
6871 snapshot,
6872 } => self.render_edit_prediction_diff_popover(
6873 text_bounds,
6874 content_origin,
6875 editor_snapshot,
6876 visible_row_range,
6877 line_layouts,
6878 line_height,
6879 scroll_pixel_position,
6880 newest_selection_head,
6881 editor_width,
6882 style,
6883 edits,
6884 edit_preview,
6885 snapshot,
6886 window,
6887 cx,
6888 ),
6889 }
6890 }
6891
6892 fn render_edit_prediction_modifier_jump_popover(
6893 &mut self,
6894 text_bounds: &Bounds<Pixels>,
6895 content_origin: gpui::Point<Pixels>,
6896 visible_row_range: Range<DisplayRow>,
6897 line_layouts: &[LineWithInvisibles],
6898 line_height: Pixels,
6899 scroll_pixel_position: gpui::Point<Pixels>,
6900 newest_selection_head: Option<DisplayPoint>,
6901 target_display_point: DisplayPoint,
6902 window: &mut Window,
6903 cx: &mut App,
6904 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6905 let scrolled_content_origin =
6906 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6907
6908 const SCROLL_PADDING_Y: Pixels = px(12.);
6909
6910 if target_display_point.row() < visible_row_range.start {
6911 return self.render_edit_prediction_scroll_popover(
6912 |_| SCROLL_PADDING_Y,
6913 IconName::ArrowUp,
6914 visible_row_range,
6915 line_layouts,
6916 newest_selection_head,
6917 scrolled_content_origin,
6918 window,
6919 cx,
6920 );
6921 } else if target_display_point.row() >= visible_row_range.end {
6922 return self.render_edit_prediction_scroll_popover(
6923 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6924 IconName::ArrowDown,
6925 visible_row_range,
6926 line_layouts,
6927 newest_selection_head,
6928 scrolled_content_origin,
6929 window,
6930 cx,
6931 );
6932 }
6933
6934 const POLE_WIDTH: Pixels = px(2.);
6935
6936 let line_layout =
6937 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6938 let target_column = target_display_point.column() as usize;
6939
6940 let target_x = line_layout.x_for_index(target_column);
6941 let target_y =
6942 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6943
6944 let flag_on_right = target_x < text_bounds.size.width / 2.;
6945
6946 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6947 border_color.l += 0.001;
6948
6949 let mut element = v_flex()
6950 .items_end()
6951 .when(flag_on_right, |el| el.items_start())
6952 .child(if flag_on_right {
6953 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6954 .rounded_bl(px(0.))
6955 .rounded_tl(px(0.))
6956 .border_l_2()
6957 .border_color(border_color)
6958 } else {
6959 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6960 .rounded_br(px(0.))
6961 .rounded_tr(px(0.))
6962 .border_r_2()
6963 .border_color(border_color)
6964 })
6965 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6966 .into_any();
6967
6968 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6969
6970 let mut origin = scrolled_content_origin + point(target_x, target_y)
6971 - point(
6972 if flag_on_right {
6973 POLE_WIDTH
6974 } else {
6975 size.width - POLE_WIDTH
6976 },
6977 size.height - line_height,
6978 );
6979
6980 origin.x = origin.x.max(content_origin.x);
6981
6982 element.prepaint_at(origin, window, cx);
6983
6984 Some((element, origin))
6985 }
6986
6987 fn render_edit_prediction_scroll_popover(
6988 &mut self,
6989 to_y: impl Fn(Size<Pixels>) -> Pixels,
6990 scroll_icon: IconName,
6991 visible_row_range: Range<DisplayRow>,
6992 line_layouts: &[LineWithInvisibles],
6993 newest_selection_head: Option<DisplayPoint>,
6994 scrolled_content_origin: gpui::Point<Pixels>,
6995 window: &mut Window,
6996 cx: &mut App,
6997 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6998 let mut element = self
6999 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7000 .into_any();
7001
7002 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7003
7004 let cursor = newest_selection_head?;
7005 let cursor_row_layout =
7006 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7007 let cursor_column = cursor.column() as usize;
7008
7009 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7010
7011 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7012
7013 element.prepaint_at(origin, window, cx);
7014 Some((element, origin))
7015 }
7016
7017 fn render_edit_prediction_eager_jump_popover(
7018 &mut self,
7019 text_bounds: &Bounds<Pixels>,
7020 content_origin: gpui::Point<Pixels>,
7021 editor_snapshot: &EditorSnapshot,
7022 visible_row_range: Range<DisplayRow>,
7023 scroll_top: f32,
7024 scroll_bottom: f32,
7025 line_height: Pixels,
7026 scroll_pixel_position: gpui::Point<Pixels>,
7027 target_display_point: DisplayPoint,
7028 editor_width: Pixels,
7029 window: &mut Window,
7030 cx: &mut App,
7031 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7032 if target_display_point.row().as_f32() < scroll_top {
7033 let mut element = self
7034 .render_edit_prediction_line_popover(
7035 "Jump to Edit",
7036 Some(IconName::ArrowUp),
7037 window,
7038 cx,
7039 )?
7040 .into_any();
7041
7042 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7043 let offset = point(
7044 (text_bounds.size.width - size.width) / 2.,
7045 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7046 );
7047
7048 let origin = text_bounds.origin + offset;
7049 element.prepaint_at(origin, window, cx);
7050 Some((element, origin))
7051 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7052 let mut element = self
7053 .render_edit_prediction_line_popover(
7054 "Jump to Edit",
7055 Some(IconName::ArrowDown),
7056 window,
7057 cx,
7058 )?
7059 .into_any();
7060
7061 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7062 let offset = point(
7063 (text_bounds.size.width - size.width) / 2.,
7064 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7065 );
7066
7067 let origin = text_bounds.origin + offset;
7068 element.prepaint_at(origin, window, cx);
7069 Some((element, origin))
7070 } else {
7071 self.render_edit_prediction_end_of_line_popover(
7072 "Jump to Edit",
7073 editor_snapshot,
7074 visible_row_range,
7075 target_display_point,
7076 line_height,
7077 scroll_pixel_position,
7078 content_origin,
7079 editor_width,
7080 window,
7081 cx,
7082 )
7083 }
7084 }
7085
7086 fn render_edit_prediction_end_of_line_popover(
7087 self: &mut Editor,
7088 label: &'static str,
7089 editor_snapshot: &EditorSnapshot,
7090 visible_row_range: Range<DisplayRow>,
7091 target_display_point: DisplayPoint,
7092 line_height: Pixels,
7093 scroll_pixel_position: gpui::Point<Pixels>,
7094 content_origin: gpui::Point<Pixels>,
7095 editor_width: Pixels,
7096 window: &mut Window,
7097 cx: &mut App,
7098 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7099 let target_line_end = DisplayPoint::new(
7100 target_display_point.row(),
7101 editor_snapshot.line_len(target_display_point.row()),
7102 );
7103
7104 let mut element = self
7105 .render_edit_prediction_line_popover(label, None, window, cx)?
7106 .into_any();
7107
7108 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7109
7110 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7111
7112 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7113 let mut origin = start_point
7114 + line_origin
7115 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7116 origin.x = origin.x.max(content_origin.x);
7117
7118 let max_x = content_origin.x + editor_width - size.width;
7119
7120 if origin.x > max_x {
7121 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7122
7123 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7124 origin.y += offset;
7125 IconName::ArrowUp
7126 } else {
7127 origin.y -= offset;
7128 IconName::ArrowDown
7129 };
7130
7131 element = self
7132 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7133 .into_any();
7134
7135 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7136
7137 origin.x = content_origin.x + editor_width - size.width - px(2.);
7138 }
7139
7140 element.prepaint_at(origin, window, cx);
7141 Some((element, origin))
7142 }
7143
7144 fn render_edit_prediction_diff_popover(
7145 self: &Editor,
7146 text_bounds: &Bounds<Pixels>,
7147 content_origin: gpui::Point<Pixels>,
7148 editor_snapshot: &EditorSnapshot,
7149 visible_row_range: Range<DisplayRow>,
7150 line_layouts: &[LineWithInvisibles],
7151 line_height: Pixels,
7152 scroll_pixel_position: gpui::Point<Pixels>,
7153 newest_selection_head: Option<DisplayPoint>,
7154 editor_width: Pixels,
7155 style: &EditorStyle,
7156 edits: &Vec<(Range<Anchor>, String)>,
7157 edit_preview: &Option<language::EditPreview>,
7158 snapshot: &language::BufferSnapshot,
7159 window: &mut Window,
7160 cx: &mut App,
7161 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7162 let edit_start = edits
7163 .first()
7164 .unwrap()
7165 .0
7166 .start
7167 .to_display_point(editor_snapshot);
7168 let edit_end = edits
7169 .last()
7170 .unwrap()
7171 .0
7172 .end
7173 .to_display_point(editor_snapshot);
7174
7175 let is_visible = visible_row_range.contains(&edit_start.row())
7176 || visible_row_range.contains(&edit_end.row());
7177 if !is_visible {
7178 return None;
7179 }
7180
7181 let highlighted_edits =
7182 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7183
7184 let styled_text = highlighted_edits.to_styled_text(&style.text);
7185 let line_count = highlighted_edits.text.lines().count();
7186
7187 const BORDER_WIDTH: Pixels = px(1.);
7188
7189 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7190 let has_keybind = keybind.is_some();
7191
7192 let mut element = h_flex()
7193 .items_start()
7194 .child(
7195 h_flex()
7196 .bg(cx.theme().colors().editor_background)
7197 .border(BORDER_WIDTH)
7198 .shadow_sm()
7199 .border_color(cx.theme().colors().border)
7200 .rounded_l_lg()
7201 .when(line_count > 1, |el| el.rounded_br_lg())
7202 .pr_1()
7203 .child(styled_text),
7204 )
7205 .child(
7206 h_flex()
7207 .h(line_height + BORDER_WIDTH * 2.)
7208 .px_1p5()
7209 .gap_1()
7210 // Workaround: For some reason, there's a gap if we don't do this
7211 .ml(-BORDER_WIDTH)
7212 .shadow(smallvec![gpui::BoxShadow {
7213 color: gpui::black().opacity(0.05),
7214 offset: point(px(1.), px(1.)),
7215 blur_radius: px(2.),
7216 spread_radius: px(0.),
7217 }])
7218 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7219 .border(BORDER_WIDTH)
7220 .border_color(cx.theme().colors().border)
7221 .rounded_r_lg()
7222 .id("edit_prediction_diff_popover_keybind")
7223 .when(!has_keybind, |el| {
7224 let status_colors = cx.theme().status();
7225
7226 el.bg(status_colors.error_background)
7227 .border_color(status_colors.error.opacity(0.6))
7228 .child(Icon::new(IconName::Info).color(Color::Error))
7229 .cursor_default()
7230 .hoverable_tooltip(move |_window, cx| {
7231 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7232 })
7233 })
7234 .children(keybind),
7235 )
7236 .into_any();
7237
7238 let longest_row =
7239 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7240 let longest_line_width = if visible_row_range.contains(&longest_row) {
7241 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7242 } else {
7243 layout_line(
7244 longest_row,
7245 editor_snapshot,
7246 style,
7247 editor_width,
7248 |_| false,
7249 window,
7250 cx,
7251 )
7252 .width
7253 };
7254
7255 let viewport_bounds =
7256 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7257 right: -EditorElement::SCROLLBAR_WIDTH,
7258 ..Default::default()
7259 });
7260
7261 let x_after_longest =
7262 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7263 - scroll_pixel_position.x;
7264
7265 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7266
7267 // Fully visible if it can be displayed within the window (allow overlapping other
7268 // panes). However, this is only allowed if the popover starts within text_bounds.
7269 let can_position_to_the_right = x_after_longest < text_bounds.right()
7270 && x_after_longest + element_bounds.width < viewport_bounds.right();
7271
7272 let mut origin = if can_position_to_the_right {
7273 point(
7274 x_after_longest,
7275 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7276 - scroll_pixel_position.y,
7277 )
7278 } else {
7279 let cursor_row = newest_selection_head.map(|head| head.row());
7280 let above_edit = edit_start
7281 .row()
7282 .0
7283 .checked_sub(line_count as u32)
7284 .map(DisplayRow);
7285 let below_edit = Some(edit_end.row() + 1);
7286 let above_cursor =
7287 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7288 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7289
7290 // Place the edit popover adjacent to the edit if there is a location
7291 // available that is onscreen and does not obscure the cursor. Otherwise,
7292 // place it adjacent to the cursor.
7293 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7294 .into_iter()
7295 .flatten()
7296 .find(|&start_row| {
7297 let end_row = start_row + line_count as u32;
7298 visible_row_range.contains(&start_row)
7299 && visible_row_range.contains(&end_row)
7300 && cursor_row.map_or(true, |cursor_row| {
7301 !((start_row..end_row).contains(&cursor_row))
7302 })
7303 })?;
7304
7305 content_origin
7306 + point(
7307 -scroll_pixel_position.x,
7308 row_target.as_f32() * line_height - scroll_pixel_position.y,
7309 )
7310 };
7311
7312 origin.x -= BORDER_WIDTH;
7313
7314 window.defer_draw(element, origin, 1);
7315
7316 // Do not return an element, since it will already be drawn due to defer_draw.
7317 None
7318 }
7319
7320 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7321 px(30.)
7322 }
7323
7324 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7325 if self.read_only(cx) {
7326 cx.theme().players().read_only()
7327 } else {
7328 self.style.as_ref().unwrap().local_player
7329 }
7330 }
7331
7332 fn render_edit_prediction_accept_keybind(
7333 &self,
7334 window: &mut Window,
7335 cx: &App,
7336 ) -> Option<AnyElement> {
7337 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7338 let accept_keystroke = accept_binding.keystroke()?;
7339
7340 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7341
7342 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7343 Color::Accent
7344 } else {
7345 Color::Muted
7346 };
7347
7348 h_flex()
7349 .px_0p5()
7350 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7351 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7352 .text_size(TextSize::XSmall.rems(cx))
7353 .child(h_flex().children(ui::render_modifiers(
7354 &accept_keystroke.modifiers,
7355 PlatformStyle::platform(),
7356 Some(modifiers_color),
7357 Some(IconSize::XSmall.rems().into()),
7358 true,
7359 )))
7360 .when(is_platform_style_mac, |parent| {
7361 parent.child(accept_keystroke.key.clone())
7362 })
7363 .when(!is_platform_style_mac, |parent| {
7364 parent.child(
7365 Key::new(
7366 util::capitalize(&accept_keystroke.key),
7367 Some(Color::Default),
7368 )
7369 .size(Some(IconSize::XSmall.rems().into())),
7370 )
7371 })
7372 .into_any()
7373 .into()
7374 }
7375
7376 fn render_edit_prediction_line_popover(
7377 &self,
7378 label: impl Into<SharedString>,
7379 icon: Option<IconName>,
7380 window: &mut Window,
7381 cx: &App,
7382 ) -> Option<Stateful<Div>> {
7383 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7384
7385 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7386 let has_keybind = keybind.is_some();
7387
7388 let result = h_flex()
7389 .id("ep-line-popover")
7390 .py_0p5()
7391 .pl_1()
7392 .pr(padding_right)
7393 .gap_1()
7394 .rounded_md()
7395 .border_1()
7396 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7397 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7398 .shadow_sm()
7399 .when(!has_keybind, |el| {
7400 let status_colors = cx.theme().status();
7401
7402 el.bg(status_colors.error_background)
7403 .border_color(status_colors.error.opacity(0.6))
7404 .pl_2()
7405 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7406 .cursor_default()
7407 .hoverable_tooltip(move |_window, cx| {
7408 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7409 })
7410 })
7411 .children(keybind)
7412 .child(
7413 Label::new(label)
7414 .size(LabelSize::Small)
7415 .when(!has_keybind, |el| {
7416 el.color(cx.theme().status().error.into()).strikethrough()
7417 }),
7418 )
7419 .when(!has_keybind, |el| {
7420 el.child(
7421 h_flex().ml_1().child(
7422 Icon::new(IconName::Info)
7423 .size(IconSize::Small)
7424 .color(cx.theme().status().error.into()),
7425 ),
7426 )
7427 })
7428 .when_some(icon, |element, icon| {
7429 element.child(
7430 div()
7431 .mt(px(1.5))
7432 .child(Icon::new(icon).size(IconSize::Small)),
7433 )
7434 });
7435
7436 Some(result)
7437 }
7438
7439 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7440 let accent_color = cx.theme().colors().text_accent;
7441 let editor_bg_color = cx.theme().colors().editor_background;
7442 editor_bg_color.blend(accent_color.opacity(0.1))
7443 }
7444
7445 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7446 let accent_color = cx.theme().colors().text_accent;
7447 let editor_bg_color = cx.theme().colors().editor_background;
7448 editor_bg_color.blend(accent_color.opacity(0.6))
7449 }
7450
7451 fn render_edit_prediction_cursor_popover(
7452 &self,
7453 min_width: Pixels,
7454 max_width: Pixels,
7455 cursor_point: Point,
7456 style: &EditorStyle,
7457 accept_keystroke: Option<&gpui::Keystroke>,
7458 _window: &Window,
7459 cx: &mut Context<Editor>,
7460 ) -> Option<AnyElement> {
7461 let provider = self.edit_prediction_provider.as_ref()?;
7462
7463 if provider.provider.needs_terms_acceptance(cx) {
7464 return Some(
7465 h_flex()
7466 .min_w(min_width)
7467 .flex_1()
7468 .px_2()
7469 .py_1()
7470 .gap_3()
7471 .elevation_2(cx)
7472 .hover(|style| style.bg(cx.theme().colors().element_hover))
7473 .id("accept-terms")
7474 .cursor_pointer()
7475 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7476 .on_click(cx.listener(|this, _event, window, cx| {
7477 cx.stop_propagation();
7478 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7479 window.dispatch_action(
7480 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7481 cx,
7482 );
7483 }))
7484 .child(
7485 h_flex()
7486 .flex_1()
7487 .gap_2()
7488 .child(Icon::new(IconName::ZedPredict))
7489 .child(Label::new("Accept Terms of Service"))
7490 .child(div().w_full())
7491 .child(
7492 Icon::new(IconName::ArrowUpRight)
7493 .color(Color::Muted)
7494 .size(IconSize::Small),
7495 )
7496 .into_any_element(),
7497 )
7498 .into_any(),
7499 );
7500 }
7501
7502 let is_refreshing = provider.provider.is_refreshing(cx);
7503
7504 fn pending_completion_container() -> Div {
7505 h_flex()
7506 .h_full()
7507 .flex_1()
7508 .gap_2()
7509 .child(Icon::new(IconName::ZedPredict))
7510 }
7511
7512 let completion = match &self.active_inline_completion {
7513 Some(prediction) => {
7514 if !self.has_visible_completions_menu() {
7515 const RADIUS: Pixels = px(6.);
7516 const BORDER_WIDTH: Pixels = px(1.);
7517
7518 return Some(
7519 h_flex()
7520 .elevation_2(cx)
7521 .border(BORDER_WIDTH)
7522 .border_color(cx.theme().colors().border)
7523 .when(accept_keystroke.is_none(), |el| {
7524 el.border_color(cx.theme().status().error)
7525 })
7526 .rounded(RADIUS)
7527 .rounded_tl(px(0.))
7528 .overflow_hidden()
7529 .child(div().px_1p5().child(match &prediction.completion {
7530 InlineCompletion::Move { target, snapshot } => {
7531 use text::ToPoint as _;
7532 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7533 {
7534 Icon::new(IconName::ZedPredictDown)
7535 } else {
7536 Icon::new(IconName::ZedPredictUp)
7537 }
7538 }
7539 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7540 }))
7541 .child(
7542 h_flex()
7543 .gap_1()
7544 .py_1()
7545 .px_2()
7546 .rounded_r(RADIUS - BORDER_WIDTH)
7547 .border_l_1()
7548 .border_color(cx.theme().colors().border)
7549 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7550 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7551 el.child(
7552 Label::new("Hold")
7553 .size(LabelSize::Small)
7554 .when(accept_keystroke.is_none(), |el| {
7555 el.strikethrough()
7556 })
7557 .line_height_style(LineHeightStyle::UiLabel),
7558 )
7559 })
7560 .id("edit_prediction_cursor_popover_keybind")
7561 .when(accept_keystroke.is_none(), |el| {
7562 let status_colors = cx.theme().status();
7563
7564 el.bg(status_colors.error_background)
7565 .border_color(status_colors.error.opacity(0.6))
7566 .child(Icon::new(IconName::Info).color(Color::Error))
7567 .cursor_default()
7568 .hoverable_tooltip(move |_window, cx| {
7569 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7570 .into()
7571 })
7572 })
7573 .when_some(
7574 accept_keystroke.as_ref(),
7575 |el, accept_keystroke| {
7576 el.child(h_flex().children(ui::render_modifiers(
7577 &accept_keystroke.modifiers,
7578 PlatformStyle::platform(),
7579 Some(Color::Default),
7580 Some(IconSize::XSmall.rems().into()),
7581 false,
7582 )))
7583 },
7584 ),
7585 )
7586 .into_any(),
7587 );
7588 }
7589
7590 self.render_edit_prediction_cursor_popover_preview(
7591 prediction,
7592 cursor_point,
7593 style,
7594 cx,
7595 )?
7596 }
7597
7598 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7599 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7600 stale_completion,
7601 cursor_point,
7602 style,
7603 cx,
7604 )?,
7605
7606 None => {
7607 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7608 }
7609 },
7610
7611 None => pending_completion_container().child(Label::new("No Prediction")),
7612 };
7613
7614 let completion = if is_refreshing {
7615 completion
7616 .with_animation(
7617 "loading-completion",
7618 Animation::new(Duration::from_secs(2))
7619 .repeat()
7620 .with_easing(pulsating_between(0.4, 0.8)),
7621 |label, delta| label.opacity(delta),
7622 )
7623 .into_any_element()
7624 } else {
7625 completion.into_any_element()
7626 };
7627
7628 let has_completion = self.active_inline_completion.is_some();
7629
7630 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7631 Some(
7632 h_flex()
7633 .min_w(min_width)
7634 .max_w(max_width)
7635 .flex_1()
7636 .elevation_2(cx)
7637 .border_color(cx.theme().colors().border)
7638 .child(
7639 div()
7640 .flex_1()
7641 .py_1()
7642 .px_2()
7643 .overflow_hidden()
7644 .child(completion),
7645 )
7646 .when_some(accept_keystroke, |el, accept_keystroke| {
7647 if !accept_keystroke.modifiers.modified() {
7648 return el;
7649 }
7650
7651 el.child(
7652 h_flex()
7653 .h_full()
7654 .border_l_1()
7655 .rounded_r_lg()
7656 .border_color(cx.theme().colors().border)
7657 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7658 .gap_1()
7659 .py_1()
7660 .px_2()
7661 .child(
7662 h_flex()
7663 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7664 .when(is_platform_style_mac, |parent| parent.gap_1())
7665 .child(h_flex().children(ui::render_modifiers(
7666 &accept_keystroke.modifiers,
7667 PlatformStyle::platform(),
7668 Some(if !has_completion {
7669 Color::Muted
7670 } else {
7671 Color::Default
7672 }),
7673 None,
7674 false,
7675 ))),
7676 )
7677 .child(Label::new("Preview").into_any_element())
7678 .opacity(if has_completion { 1.0 } else { 0.4 }),
7679 )
7680 })
7681 .into_any(),
7682 )
7683 }
7684
7685 fn render_edit_prediction_cursor_popover_preview(
7686 &self,
7687 completion: &InlineCompletionState,
7688 cursor_point: Point,
7689 style: &EditorStyle,
7690 cx: &mut Context<Editor>,
7691 ) -> Option<Div> {
7692 use text::ToPoint as _;
7693
7694 fn render_relative_row_jump(
7695 prefix: impl Into<String>,
7696 current_row: u32,
7697 target_row: u32,
7698 ) -> Div {
7699 let (row_diff, arrow) = if target_row < current_row {
7700 (current_row - target_row, IconName::ArrowUp)
7701 } else {
7702 (target_row - current_row, IconName::ArrowDown)
7703 };
7704
7705 h_flex()
7706 .child(
7707 Label::new(format!("{}{}", prefix.into(), row_diff))
7708 .color(Color::Muted)
7709 .size(LabelSize::Small),
7710 )
7711 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7712 }
7713
7714 match &completion.completion {
7715 InlineCompletion::Move {
7716 target, snapshot, ..
7717 } => Some(
7718 h_flex()
7719 .px_2()
7720 .gap_2()
7721 .flex_1()
7722 .child(
7723 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7724 Icon::new(IconName::ZedPredictDown)
7725 } else {
7726 Icon::new(IconName::ZedPredictUp)
7727 },
7728 )
7729 .child(Label::new("Jump to Edit")),
7730 ),
7731
7732 InlineCompletion::Edit {
7733 edits,
7734 edit_preview,
7735 snapshot,
7736 display_mode: _,
7737 } => {
7738 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7739
7740 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7741 &snapshot,
7742 &edits,
7743 edit_preview.as_ref()?,
7744 true,
7745 cx,
7746 )
7747 .first_line_preview();
7748
7749 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7750 .with_default_highlights(&style.text, highlighted_edits.highlights);
7751
7752 let preview = h_flex()
7753 .gap_1()
7754 .min_w_16()
7755 .child(styled_text)
7756 .when(has_more_lines, |parent| parent.child("…"));
7757
7758 let left = if first_edit_row != cursor_point.row {
7759 render_relative_row_jump("", cursor_point.row, first_edit_row)
7760 .into_any_element()
7761 } else {
7762 Icon::new(IconName::ZedPredict).into_any_element()
7763 };
7764
7765 Some(
7766 h_flex()
7767 .h_full()
7768 .flex_1()
7769 .gap_2()
7770 .pr_1()
7771 .overflow_x_hidden()
7772 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7773 .child(left)
7774 .child(preview),
7775 )
7776 }
7777 }
7778 }
7779
7780 fn render_context_menu(
7781 &self,
7782 style: &EditorStyle,
7783 max_height_in_lines: u32,
7784 window: &mut Window,
7785 cx: &mut Context<Editor>,
7786 ) -> Option<AnyElement> {
7787 let menu = self.context_menu.borrow();
7788 let menu = menu.as_ref()?;
7789 if !menu.visible() {
7790 return None;
7791 };
7792 Some(menu.render(style, max_height_in_lines, window, cx))
7793 }
7794
7795 fn render_context_menu_aside(
7796 &mut self,
7797 max_size: Size<Pixels>,
7798 window: &mut Window,
7799 cx: &mut Context<Editor>,
7800 ) -> Option<AnyElement> {
7801 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7802 if menu.visible() {
7803 menu.render_aside(self, max_size, window, cx)
7804 } else {
7805 None
7806 }
7807 })
7808 }
7809
7810 fn hide_context_menu(
7811 &mut self,
7812 window: &mut Window,
7813 cx: &mut Context<Self>,
7814 ) -> Option<CodeContextMenu> {
7815 cx.notify();
7816 self.completion_tasks.clear();
7817 let context_menu = self.context_menu.borrow_mut().take();
7818 self.stale_inline_completion_in_menu.take();
7819 self.update_visible_inline_completion(window, cx);
7820 context_menu
7821 }
7822
7823 fn show_snippet_choices(
7824 &mut self,
7825 choices: &Vec<String>,
7826 selection: Range<Anchor>,
7827 cx: &mut Context<Self>,
7828 ) {
7829 if selection.start.buffer_id.is_none() {
7830 return;
7831 }
7832 let buffer_id = selection.start.buffer_id.unwrap();
7833 let buffer = self.buffer().read(cx).buffer(buffer_id);
7834 let id = post_inc(&mut self.next_completion_id);
7835
7836 if let Some(buffer) = buffer {
7837 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7838 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7839 ));
7840 }
7841 }
7842
7843 pub fn insert_snippet(
7844 &mut self,
7845 insertion_ranges: &[Range<usize>],
7846 snippet: Snippet,
7847 window: &mut Window,
7848 cx: &mut Context<Self>,
7849 ) -> Result<()> {
7850 struct Tabstop<T> {
7851 is_end_tabstop: bool,
7852 ranges: Vec<Range<T>>,
7853 choices: Option<Vec<String>>,
7854 }
7855
7856 let tabstops = self.buffer.update(cx, |buffer, cx| {
7857 let snippet_text: Arc<str> = snippet.text.clone().into();
7858 let edits = insertion_ranges
7859 .iter()
7860 .cloned()
7861 .map(|range| (range, snippet_text.clone()));
7862 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
7863
7864 let snapshot = &*buffer.read(cx);
7865 let snippet = &snippet;
7866 snippet
7867 .tabstops
7868 .iter()
7869 .map(|tabstop| {
7870 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7871 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7872 });
7873 let mut tabstop_ranges = tabstop
7874 .ranges
7875 .iter()
7876 .flat_map(|tabstop_range| {
7877 let mut delta = 0_isize;
7878 insertion_ranges.iter().map(move |insertion_range| {
7879 let insertion_start = insertion_range.start as isize + delta;
7880 delta +=
7881 snippet.text.len() as isize - insertion_range.len() as isize;
7882
7883 let start = ((insertion_start + tabstop_range.start) as usize)
7884 .min(snapshot.len());
7885 let end = ((insertion_start + tabstop_range.end) as usize)
7886 .min(snapshot.len());
7887 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7888 })
7889 })
7890 .collect::<Vec<_>>();
7891 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7892
7893 Tabstop {
7894 is_end_tabstop,
7895 ranges: tabstop_ranges,
7896 choices: tabstop.choices.clone(),
7897 }
7898 })
7899 .collect::<Vec<_>>()
7900 });
7901 if let Some(tabstop) = tabstops.first() {
7902 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7903 s.select_ranges(tabstop.ranges.iter().cloned());
7904 });
7905
7906 if let Some(choices) = &tabstop.choices {
7907 if let Some(selection) = tabstop.ranges.first() {
7908 self.show_snippet_choices(choices, selection.clone(), cx)
7909 }
7910 }
7911
7912 // If we're already at the last tabstop and it's at the end of the snippet,
7913 // we're done, we don't need to keep the state around.
7914 if !tabstop.is_end_tabstop {
7915 let choices = tabstops
7916 .iter()
7917 .map(|tabstop| tabstop.choices.clone())
7918 .collect();
7919
7920 let ranges = tabstops
7921 .into_iter()
7922 .map(|tabstop| tabstop.ranges)
7923 .collect::<Vec<_>>();
7924
7925 self.snippet_stack.push(SnippetState {
7926 active_index: 0,
7927 ranges,
7928 choices,
7929 });
7930 }
7931
7932 // Check whether the just-entered snippet ends with an auto-closable bracket.
7933 if self.autoclose_regions.is_empty() {
7934 let snapshot = self.buffer.read(cx).snapshot(cx);
7935 for selection in &mut self.selections.all::<Point>(cx) {
7936 let selection_head = selection.head();
7937 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7938 continue;
7939 };
7940
7941 let mut bracket_pair = None;
7942 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7943 let prev_chars = snapshot
7944 .reversed_chars_at(selection_head)
7945 .collect::<String>();
7946 for (pair, enabled) in scope.brackets() {
7947 if enabled
7948 && pair.close
7949 && prev_chars.starts_with(pair.start.as_str())
7950 && next_chars.starts_with(pair.end.as_str())
7951 {
7952 bracket_pair = Some(pair.clone());
7953 break;
7954 }
7955 }
7956 if let Some(pair) = bracket_pair {
7957 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
7958 let autoclose_enabled =
7959 self.use_autoclose && snapshot_settings.use_autoclose;
7960 if autoclose_enabled {
7961 let start = snapshot.anchor_after(selection_head);
7962 let end = snapshot.anchor_after(selection_head);
7963 self.autoclose_regions.push(AutocloseRegion {
7964 selection_id: selection.id,
7965 range: start..end,
7966 pair,
7967 });
7968 }
7969 }
7970 }
7971 }
7972 }
7973 Ok(())
7974 }
7975
7976 pub fn move_to_next_snippet_tabstop(
7977 &mut self,
7978 window: &mut Window,
7979 cx: &mut Context<Self>,
7980 ) -> bool {
7981 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7982 }
7983
7984 pub fn move_to_prev_snippet_tabstop(
7985 &mut self,
7986 window: &mut Window,
7987 cx: &mut Context<Self>,
7988 ) -> bool {
7989 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7990 }
7991
7992 pub fn move_to_snippet_tabstop(
7993 &mut self,
7994 bias: Bias,
7995 window: &mut Window,
7996 cx: &mut Context<Self>,
7997 ) -> bool {
7998 if let Some(mut snippet) = self.snippet_stack.pop() {
7999 match bias {
8000 Bias::Left => {
8001 if snippet.active_index > 0 {
8002 snippet.active_index -= 1;
8003 } else {
8004 self.snippet_stack.push(snippet);
8005 return false;
8006 }
8007 }
8008 Bias::Right => {
8009 if snippet.active_index + 1 < snippet.ranges.len() {
8010 snippet.active_index += 1;
8011 } else {
8012 self.snippet_stack.push(snippet);
8013 return false;
8014 }
8015 }
8016 }
8017 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8018 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8019 s.select_anchor_ranges(current_ranges.iter().cloned())
8020 });
8021
8022 if let Some(choices) = &snippet.choices[snippet.active_index] {
8023 if let Some(selection) = current_ranges.first() {
8024 self.show_snippet_choices(&choices, selection.clone(), cx);
8025 }
8026 }
8027
8028 // If snippet state is not at the last tabstop, push it back on the stack
8029 if snippet.active_index + 1 < snippet.ranges.len() {
8030 self.snippet_stack.push(snippet);
8031 }
8032 return true;
8033 }
8034 }
8035
8036 false
8037 }
8038
8039 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8040 self.transact(window, cx, |this, window, cx| {
8041 this.select_all(&SelectAll, window, cx);
8042 this.insert("", window, cx);
8043 });
8044 }
8045
8046 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8047 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8048 self.transact(window, cx, |this, window, cx| {
8049 this.select_autoclose_pair(window, cx);
8050 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8051 if !this.linked_edit_ranges.is_empty() {
8052 let selections = this.selections.all::<MultiBufferPoint>(cx);
8053 let snapshot = this.buffer.read(cx).snapshot(cx);
8054
8055 for selection in selections.iter() {
8056 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8057 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8058 if selection_start.buffer_id != selection_end.buffer_id {
8059 continue;
8060 }
8061 if let Some(ranges) =
8062 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8063 {
8064 for (buffer, entries) in ranges {
8065 linked_ranges.entry(buffer).or_default().extend(entries);
8066 }
8067 }
8068 }
8069 }
8070
8071 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8072 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8073 for selection in &mut selections {
8074 if selection.is_empty() {
8075 let old_head = selection.head();
8076 let mut new_head =
8077 movement::left(&display_map, old_head.to_display_point(&display_map))
8078 .to_point(&display_map);
8079 if let Some((buffer, line_buffer_range)) = display_map
8080 .buffer_snapshot
8081 .buffer_line_for_row(MultiBufferRow(old_head.row))
8082 {
8083 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8084 let indent_len = match indent_size.kind {
8085 IndentKind::Space => {
8086 buffer.settings_at(line_buffer_range.start, cx).tab_size
8087 }
8088 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8089 };
8090 if old_head.column <= indent_size.len && old_head.column > 0 {
8091 let indent_len = indent_len.get();
8092 new_head = cmp::min(
8093 new_head,
8094 MultiBufferPoint::new(
8095 old_head.row,
8096 ((old_head.column - 1) / indent_len) * indent_len,
8097 ),
8098 );
8099 }
8100 }
8101
8102 selection.set_head(new_head, SelectionGoal::None);
8103 }
8104 }
8105
8106 this.signature_help_state.set_backspace_pressed(true);
8107 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8108 s.select(selections)
8109 });
8110 this.insert("", window, cx);
8111 let empty_str: Arc<str> = Arc::from("");
8112 for (buffer, edits) in linked_ranges {
8113 let snapshot = buffer.read(cx).snapshot();
8114 use text::ToPoint as TP;
8115
8116 let edits = edits
8117 .into_iter()
8118 .map(|range| {
8119 let end_point = TP::to_point(&range.end, &snapshot);
8120 let mut start_point = TP::to_point(&range.start, &snapshot);
8121
8122 if end_point == start_point {
8123 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8124 .saturating_sub(1);
8125 start_point =
8126 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8127 };
8128
8129 (start_point..end_point, empty_str.clone())
8130 })
8131 .sorted_by_key(|(range, _)| range.start)
8132 .collect::<Vec<_>>();
8133 buffer.update(cx, |this, cx| {
8134 this.edit(edits, None, cx);
8135 })
8136 }
8137 this.refresh_inline_completion(true, false, window, cx);
8138 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8139 });
8140 }
8141
8142 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8143 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8144 self.transact(window, cx, |this, window, cx| {
8145 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8146 s.move_with(|map, selection| {
8147 if selection.is_empty() {
8148 let cursor = movement::right(map, selection.head());
8149 selection.end = cursor;
8150 selection.reversed = true;
8151 selection.goal = SelectionGoal::None;
8152 }
8153 })
8154 });
8155 this.insert("", window, cx);
8156 this.refresh_inline_completion(true, false, window, cx);
8157 });
8158 }
8159
8160 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8161 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8162 if self.move_to_prev_snippet_tabstop(window, cx) {
8163 return;
8164 }
8165 self.outdent(&Outdent, window, cx);
8166 }
8167
8168 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8169 if self.move_to_next_snippet_tabstop(window, cx) {
8170 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8171 return;
8172 }
8173 if self.read_only(cx) {
8174 return;
8175 }
8176 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8177 let mut selections = self.selections.all_adjusted(cx);
8178 let buffer = self.buffer.read(cx);
8179 let snapshot = buffer.snapshot(cx);
8180 let rows_iter = selections.iter().map(|s| s.head().row);
8181 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8182
8183 let mut edits = Vec::new();
8184 let mut prev_edited_row = 0;
8185 let mut row_delta = 0;
8186 for selection in &mut selections {
8187 if selection.start.row != prev_edited_row {
8188 row_delta = 0;
8189 }
8190 prev_edited_row = selection.end.row;
8191
8192 // If the selection is non-empty, then increase the indentation of the selected lines.
8193 if !selection.is_empty() {
8194 row_delta =
8195 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8196 continue;
8197 }
8198
8199 // If the selection is empty and the cursor is in the leading whitespace before the
8200 // suggested indentation, then auto-indent the line.
8201 let cursor = selection.head();
8202 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8203 if let Some(suggested_indent) =
8204 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8205 {
8206 if cursor.column < suggested_indent.len
8207 && cursor.column <= current_indent.len
8208 && current_indent.len <= suggested_indent.len
8209 {
8210 selection.start = Point::new(cursor.row, suggested_indent.len);
8211 selection.end = selection.start;
8212 if row_delta == 0 {
8213 edits.extend(Buffer::edit_for_indent_size_adjustment(
8214 cursor.row,
8215 current_indent,
8216 suggested_indent,
8217 ));
8218 row_delta = suggested_indent.len - current_indent.len;
8219 }
8220 continue;
8221 }
8222 }
8223
8224 // Otherwise, insert a hard or soft tab.
8225 let settings = buffer.language_settings_at(cursor, cx);
8226 let tab_size = if settings.hard_tabs {
8227 IndentSize::tab()
8228 } else {
8229 let tab_size = settings.tab_size.get();
8230 let indent_remainder = snapshot
8231 .text_for_range(Point::new(cursor.row, 0)..cursor)
8232 .flat_map(str::chars)
8233 .fold(row_delta % tab_size, |counter: u32, c| {
8234 if c == '\t' {
8235 0
8236 } else {
8237 (counter + 1) % tab_size
8238 }
8239 });
8240
8241 let chars_to_next_tab_stop = tab_size - indent_remainder;
8242 IndentSize::spaces(chars_to_next_tab_stop)
8243 };
8244 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8245 selection.end = selection.start;
8246 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8247 row_delta += tab_size.len;
8248 }
8249
8250 self.transact(window, cx, |this, window, cx| {
8251 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8252 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8253 s.select(selections)
8254 });
8255 this.refresh_inline_completion(true, false, window, cx);
8256 });
8257 }
8258
8259 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8260 if self.read_only(cx) {
8261 return;
8262 }
8263 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8264 let mut selections = self.selections.all::<Point>(cx);
8265 let mut prev_edited_row = 0;
8266 let mut row_delta = 0;
8267 let mut edits = Vec::new();
8268 let buffer = self.buffer.read(cx);
8269 let snapshot = buffer.snapshot(cx);
8270 for selection in &mut selections {
8271 if selection.start.row != prev_edited_row {
8272 row_delta = 0;
8273 }
8274 prev_edited_row = selection.end.row;
8275
8276 row_delta =
8277 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8278 }
8279
8280 self.transact(window, cx, |this, window, cx| {
8281 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8282 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8283 s.select(selections)
8284 });
8285 });
8286 }
8287
8288 fn indent_selection(
8289 buffer: &MultiBuffer,
8290 snapshot: &MultiBufferSnapshot,
8291 selection: &mut Selection<Point>,
8292 edits: &mut Vec<(Range<Point>, String)>,
8293 delta_for_start_row: u32,
8294 cx: &App,
8295 ) -> u32 {
8296 let settings = buffer.language_settings_at(selection.start, cx);
8297 let tab_size = settings.tab_size.get();
8298 let indent_kind = if settings.hard_tabs {
8299 IndentKind::Tab
8300 } else {
8301 IndentKind::Space
8302 };
8303 let mut start_row = selection.start.row;
8304 let mut end_row = selection.end.row + 1;
8305
8306 // If a selection ends at the beginning of a line, don't indent
8307 // that last line.
8308 if selection.end.column == 0 && selection.end.row > selection.start.row {
8309 end_row -= 1;
8310 }
8311
8312 // Avoid re-indenting a row that has already been indented by a
8313 // previous selection, but still update this selection's column
8314 // to reflect that indentation.
8315 if delta_for_start_row > 0 {
8316 start_row += 1;
8317 selection.start.column += delta_for_start_row;
8318 if selection.end.row == selection.start.row {
8319 selection.end.column += delta_for_start_row;
8320 }
8321 }
8322
8323 let mut delta_for_end_row = 0;
8324 let has_multiple_rows = start_row + 1 != end_row;
8325 for row in start_row..end_row {
8326 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8327 let indent_delta = match (current_indent.kind, indent_kind) {
8328 (IndentKind::Space, IndentKind::Space) => {
8329 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8330 IndentSize::spaces(columns_to_next_tab_stop)
8331 }
8332 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8333 (_, IndentKind::Tab) => IndentSize::tab(),
8334 };
8335
8336 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8337 0
8338 } else {
8339 selection.start.column
8340 };
8341 let row_start = Point::new(row, start);
8342 edits.push((
8343 row_start..row_start,
8344 indent_delta.chars().collect::<String>(),
8345 ));
8346
8347 // Update this selection's endpoints to reflect the indentation.
8348 if row == selection.start.row {
8349 selection.start.column += indent_delta.len;
8350 }
8351 if row == selection.end.row {
8352 selection.end.column += indent_delta.len;
8353 delta_for_end_row = indent_delta.len;
8354 }
8355 }
8356
8357 if selection.start.row == selection.end.row {
8358 delta_for_start_row + delta_for_end_row
8359 } else {
8360 delta_for_end_row
8361 }
8362 }
8363
8364 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8365 if self.read_only(cx) {
8366 return;
8367 }
8368 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8369 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8370 let selections = self.selections.all::<Point>(cx);
8371 let mut deletion_ranges = Vec::new();
8372 let mut last_outdent = None;
8373 {
8374 let buffer = self.buffer.read(cx);
8375 let snapshot = buffer.snapshot(cx);
8376 for selection in &selections {
8377 let settings = buffer.language_settings_at(selection.start, cx);
8378 let tab_size = settings.tab_size.get();
8379 let mut rows = selection.spanned_rows(false, &display_map);
8380
8381 // Avoid re-outdenting a row that has already been outdented by a
8382 // previous selection.
8383 if let Some(last_row) = last_outdent {
8384 if last_row == rows.start {
8385 rows.start = rows.start.next_row();
8386 }
8387 }
8388 let has_multiple_rows = rows.len() > 1;
8389 for row in rows.iter_rows() {
8390 let indent_size = snapshot.indent_size_for_line(row);
8391 if indent_size.len > 0 {
8392 let deletion_len = match indent_size.kind {
8393 IndentKind::Space => {
8394 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8395 if columns_to_prev_tab_stop == 0 {
8396 tab_size
8397 } else {
8398 columns_to_prev_tab_stop
8399 }
8400 }
8401 IndentKind::Tab => 1,
8402 };
8403 let start = if has_multiple_rows
8404 || deletion_len > selection.start.column
8405 || indent_size.len < selection.start.column
8406 {
8407 0
8408 } else {
8409 selection.start.column - deletion_len
8410 };
8411 deletion_ranges.push(
8412 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8413 );
8414 last_outdent = Some(row);
8415 }
8416 }
8417 }
8418 }
8419
8420 self.transact(window, cx, |this, window, cx| {
8421 this.buffer.update(cx, |buffer, cx| {
8422 let empty_str: Arc<str> = Arc::default();
8423 buffer.edit(
8424 deletion_ranges
8425 .into_iter()
8426 .map(|range| (range, empty_str.clone())),
8427 None,
8428 cx,
8429 );
8430 });
8431 let selections = this.selections.all::<usize>(cx);
8432 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8433 s.select(selections)
8434 });
8435 });
8436 }
8437
8438 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8439 if self.read_only(cx) {
8440 return;
8441 }
8442 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8443 let selections = self
8444 .selections
8445 .all::<usize>(cx)
8446 .into_iter()
8447 .map(|s| s.range());
8448
8449 self.transact(window, cx, |this, window, cx| {
8450 this.buffer.update(cx, |buffer, cx| {
8451 buffer.autoindent_ranges(selections, cx);
8452 });
8453 let selections = this.selections.all::<usize>(cx);
8454 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8455 s.select(selections)
8456 });
8457 });
8458 }
8459
8460 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8461 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8462 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8463 let selections = self.selections.all::<Point>(cx);
8464
8465 let mut new_cursors = Vec::new();
8466 let mut edit_ranges = Vec::new();
8467 let mut selections = selections.iter().peekable();
8468 while let Some(selection) = selections.next() {
8469 let mut rows = selection.spanned_rows(false, &display_map);
8470 let goal_display_column = selection.head().to_display_point(&display_map).column();
8471
8472 // Accumulate contiguous regions of rows that we want to delete.
8473 while let Some(next_selection) = selections.peek() {
8474 let next_rows = next_selection.spanned_rows(false, &display_map);
8475 if next_rows.start <= rows.end {
8476 rows.end = next_rows.end;
8477 selections.next().unwrap();
8478 } else {
8479 break;
8480 }
8481 }
8482
8483 let buffer = &display_map.buffer_snapshot;
8484 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8485 let edit_end;
8486 let cursor_buffer_row;
8487 if buffer.max_point().row >= rows.end.0 {
8488 // If there's a line after the range, delete the \n from the end of the row range
8489 // and position the cursor on the next line.
8490 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8491 cursor_buffer_row = rows.end;
8492 } else {
8493 // If there isn't a line after the range, delete the \n from the line before the
8494 // start of the row range and position the cursor there.
8495 edit_start = edit_start.saturating_sub(1);
8496 edit_end = buffer.len();
8497 cursor_buffer_row = rows.start.previous_row();
8498 }
8499
8500 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8501 *cursor.column_mut() =
8502 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8503
8504 new_cursors.push((
8505 selection.id,
8506 buffer.anchor_after(cursor.to_point(&display_map)),
8507 ));
8508 edit_ranges.push(edit_start..edit_end);
8509 }
8510
8511 self.transact(window, cx, |this, window, cx| {
8512 let buffer = this.buffer.update(cx, |buffer, cx| {
8513 let empty_str: Arc<str> = Arc::default();
8514 buffer.edit(
8515 edit_ranges
8516 .into_iter()
8517 .map(|range| (range, empty_str.clone())),
8518 None,
8519 cx,
8520 );
8521 buffer.snapshot(cx)
8522 });
8523 let new_selections = new_cursors
8524 .into_iter()
8525 .map(|(id, cursor)| {
8526 let cursor = cursor.to_point(&buffer);
8527 Selection {
8528 id,
8529 start: cursor,
8530 end: cursor,
8531 reversed: false,
8532 goal: SelectionGoal::None,
8533 }
8534 })
8535 .collect();
8536
8537 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8538 s.select(new_selections);
8539 });
8540 });
8541 }
8542
8543 pub fn join_lines_impl(
8544 &mut self,
8545 insert_whitespace: bool,
8546 window: &mut Window,
8547 cx: &mut Context<Self>,
8548 ) {
8549 if self.read_only(cx) {
8550 return;
8551 }
8552 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8553 for selection in self.selections.all::<Point>(cx) {
8554 let start = MultiBufferRow(selection.start.row);
8555 // Treat single line selections as if they include the next line. Otherwise this action
8556 // would do nothing for single line selections individual cursors.
8557 let end = if selection.start.row == selection.end.row {
8558 MultiBufferRow(selection.start.row + 1)
8559 } else {
8560 MultiBufferRow(selection.end.row)
8561 };
8562
8563 if let Some(last_row_range) = row_ranges.last_mut() {
8564 if start <= last_row_range.end {
8565 last_row_range.end = end;
8566 continue;
8567 }
8568 }
8569 row_ranges.push(start..end);
8570 }
8571
8572 let snapshot = self.buffer.read(cx).snapshot(cx);
8573 let mut cursor_positions = Vec::new();
8574 for row_range in &row_ranges {
8575 let anchor = snapshot.anchor_before(Point::new(
8576 row_range.end.previous_row().0,
8577 snapshot.line_len(row_range.end.previous_row()),
8578 ));
8579 cursor_positions.push(anchor..anchor);
8580 }
8581
8582 self.transact(window, cx, |this, window, cx| {
8583 for row_range in row_ranges.into_iter().rev() {
8584 for row in row_range.iter_rows().rev() {
8585 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8586 let next_line_row = row.next_row();
8587 let indent = snapshot.indent_size_for_line(next_line_row);
8588 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8589
8590 let replace =
8591 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8592 " "
8593 } else {
8594 ""
8595 };
8596
8597 this.buffer.update(cx, |buffer, cx| {
8598 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8599 });
8600 }
8601 }
8602
8603 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8604 s.select_anchor_ranges(cursor_positions)
8605 });
8606 });
8607 }
8608
8609 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8610 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8611 self.join_lines_impl(true, window, cx);
8612 }
8613
8614 pub fn sort_lines_case_sensitive(
8615 &mut self,
8616 _: &SortLinesCaseSensitive,
8617 window: &mut Window,
8618 cx: &mut Context<Self>,
8619 ) {
8620 self.manipulate_lines(window, cx, |lines| lines.sort())
8621 }
8622
8623 pub fn sort_lines_case_insensitive(
8624 &mut self,
8625 _: &SortLinesCaseInsensitive,
8626 window: &mut Window,
8627 cx: &mut Context<Self>,
8628 ) {
8629 self.manipulate_lines(window, cx, |lines| {
8630 lines.sort_by_key(|line| line.to_lowercase())
8631 })
8632 }
8633
8634 pub fn unique_lines_case_insensitive(
8635 &mut self,
8636 _: &UniqueLinesCaseInsensitive,
8637 window: &mut Window,
8638 cx: &mut Context<Self>,
8639 ) {
8640 self.manipulate_lines(window, cx, |lines| {
8641 let mut seen = HashSet::default();
8642 lines.retain(|line| seen.insert(line.to_lowercase()));
8643 })
8644 }
8645
8646 pub fn unique_lines_case_sensitive(
8647 &mut self,
8648 _: &UniqueLinesCaseSensitive,
8649 window: &mut Window,
8650 cx: &mut Context<Self>,
8651 ) {
8652 self.manipulate_lines(window, cx, |lines| {
8653 let mut seen = HashSet::default();
8654 lines.retain(|line| seen.insert(*line));
8655 })
8656 }
8657
8658 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8659 let Some(project) = self.project.clone() else {
8660 return;
8661 };
8662 self.reload(project, window, cx)
8663 .detach_and_notify_err(window, cx);
8664 }
8665
8666 pub fn restore_file(
8667 &mut self,
8668 _: &::git::RestoreFile,
8669 window: &mut Window,
8670 cx: &mut Context<Self>,
8671 ) {
8672 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8673 let mut buffer_ids = HashSet::default();
8674 let snapshot = self.buffer().read(cx).snapshot(cx);
8675 for selection in self.selections.all::<usize>(cx) {
8676 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8677 }
8678
8679 let buffer = self.buffer().read(cx);
8680 let ranges = buffer_ids
8681 .into_iter()
8682 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8683 .collect::<Vec<_>>();
8684
8685 self.restore_hunks_in_ranges(ranges, window, cx);
8686 }
8687
8688 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8689 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8690 let selections = self
8691 .selections
8692 .all(cx)
8693 .into_iter()
8694 .map(|s| s.range())
8695 .collect();
8696 self.restore_hunks_in_ranges(selections, window, cx);
8697 }
8698
8699 pub fn restore_hunks_in_ranges(
8700 &mut self,
8701 ranges: Vec<Range<Point>>,
8702 window: &mut Window,
8703 cx: &mut Context<Editor>,
8704 ) {
8705 let mut revert_changes = HashMap::default();
8706 let chunk_by = self
8707 .snapshot(window, cx)
8708 .hunks_for_ranges(ranges)
8709 .into_iter()
8710 .chunk_by(|hunk| hunk.buffer_id);
8711 for (buffer_id, hunks) in &chunk_by {
8712 let hunks = hunks.collect::<Vec<_>>();
8713 for hunk in &hunks {
8714 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8715 }
8716 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8717 }
8718 drop(chunk_by);
8719 if !revert_changes.is_empty() {
8720 self.transact(window, cx, |editor, window, cx| {
8721 editor.restore(revert_changes, window, cx);
8722 });
8723 }
8724 }
8725
8726 pub fn open_active_item_in_terminal(
8727 &mut self,
8728 _: &OpenInTerminal,
8729 window: &mut Window,
8730 cx: &mut Context<Self>,
8731 ) {
8732 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8733 let project_path = buffer.read(cx).project_path(cx)?;
8734 let project = self.project.as_ref()?.read(cx);
8735 let entry = project.entry_for_path(&project_path, cx)?;
8736 let parent = match &entry.canonical_path {
8737 Some(canonical_path) => canonical_path.to_path_buf(),
8738 None => project.absolute_path(&project_path, cx)?,
8739 }
8740 .parent()?
8741 .to_path_buf();
8742 Some(parent)
8743 }) {
8744 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8745 }
8746 }
8747
8748 fn set_breakpoint_context_menu(
8749 &mut self,
8750 display_row: DisplayRow,
8751 position: Option<Anchor>,
8752 clicked_point: gpui::Point<Pixels>,
8753 window: &mut Window,
8754 cx: &mut Context<Self>,
8755 ) {
8756 if !cx.has_flag::<Debugger>() {
8757 return;
8758 }
8759 let source = self
8760 .buffer
8761 .read(cx)
8762 .snapshot(cx)
8763 .anchor_before(Point::new(display_row.0, 0u32));
8764
8765 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8766
8767 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8768 self,
8769 source,
8770 clicked_point,
8771 context_menu,
8772 window,
8773 cx,
8774 );
8775 }
8776
8777 fn add_edit_breakpoint_block(
8778 &mut self,
8779 anchor: Anchor,
8780 breakpoint: &Breakpoint,
8781 edit_action: BreakpointPromptEditAction,
8782 window: &mut Window,
8783 cx: &mut Context<Self>,
8784 ) {
8785 let weak_editor = cx.weak_entity();
8786 let bp_prompt = cx.new(|cx| {
8787 BreakpointPromptEditor::new(
8788 weak_editor,
8789 anchor,
8790 breakpoint.clone(),
8791 edit_action,
8792 window,
8793 cx,
8794 )
8795 });
8796
8797 let height = bp_prompt.update(cx, |this, cx| {
8798 this.prompt
8799 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8800 });
8801 let cloned_prompt = bp_prompt.clone();
8802 let blocks = vec![BlockProperties {
8803 style: BlockStyle::Sticky,
8804 placement: BlockPlacement::Above(anchor),
8805 height: Some(height),
8806 render: Arc::new(move |cx| {
8807 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8808 cloned_prompt.clone().into_any_element()
8809 }),
8810 priority: 0,
8811 }];
8812
8813 let focus_handle = bp_prompt.focus_handle(cx);
8814 window.focus(&focus_handle);
8815
8816 let block_ids = self.insert_blocks(blocks, None, cx);
8817 bp_prompt.update(cx, |prompt, _| {
8818 prompt.add_block_ids(block_ids);
8819 });
8820 }
8821
8822 fn breakpoint_at_cursor_head(
8823 &self,
8824 window: &mut Window,
8825 cx: &mut Context<Self>,
8826 ) -> Option<(Anchor, Breakpoint)> {
8827 let cursor_position: Point = self.selections.newest(cx).head();
8828 self.breakpoint_at_row(cursor_position.row, window, cx)
8829 }
8830
8831 pub(crate) fn breakpoint_at_row(
8832 &self,
8833 row: u32,
8834 window: &mut Window,
8835 cx: &mut Context<Self>,
8836 ) -> Option<(Anchor, Breakpoint)> {
8837 let snapshot = self.snapshot(window, cx);
8838 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
8839
8840 let project = self.project.clone()?;
8841
8842 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
8843 snapshot
8844 .buffer_snapshot
8845 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
8846 })?;
8847
8848 let enclosing_excerpt = breakpoint_position.excerpt_id;
8849 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8850 let buffer_snapshot = buffer.read(cx).snapshot();
8851
8852 let row = buffer_snapshot
8853 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
8854 .row;
8855
8856 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
8857 let anchor_end = snapshot
8858 .buffer_snapshot
8859 .anchor_after(Point::new(row, line_len));
8860
8861 let bp = self
8862 .breakpoint_store
8863 .as_ref()?
8864 .read_with(cx, |breakpoint_store, cx| {
8865 breakpoint_store
8866 .breakpoints(
8867 &buffer,
8868 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
8869 &buffer_snapshot,
8870 cx,
8871 )
8872 .next()
8873 .and_then(|(anchor, bp)| {
8874 let breakpoint_row = buffer_snapshot
8875 .summary_for_anchor::<text::PointUtf16>(anchor)
8876 .row;
8877
8878 if breakpoint_row == row {
8879 snapshot
8880 .buffer_snapshot
8881 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8882 .map(|anchor| (anchor, bp.clone()))
8883 } else {
8884 None
8885 }
8886 })
8887 });
8888 bp
8889 }
8890
8891 pub fn edit_log_breakpoint(
8892 &mut self,
8893 _: &EditLogBreakpoint,
8894 window: &mut Window,
8895 cx: &mut Context<Self>,
8896 ) {
8897 let (anchor, bp) = self
8898 .breakpoint_at_cursor_head(window, cx)
8899 .unwrap_or_else(|| {
8900 let cursor_position: Point = self.selections.newest(cx).head();
8901
8902 let breakpoint_position = self
8903 .snapshot(window, cx)
8904 .display_snapshot
8905 .buffer_snapshot
8906 .anchor_after(Point::new(cursor_position.row, 0));
8907
8908 (
8909 breakpoint_position,
8910 Breakpoint {
8911 message: None,
8912 state: BreakpointState::Enabled,
8913 condition: None,
8914 hit_condition: None,
8915 },
8916 )
8917 });
8918
8919 self.add_edit_breakpoint_block(anchor, &bp, BreakpointPromptEditAction::Log, window, cx);
8920 }
8921
8922 pub fn enable_breakpoint(
8923 &mut self,
8924 _: &crate::actions::EnableBreakpoint,
8925 window: &mut Window,
8926 cx: &mut Context<Self>,
8927 ) {
8928 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8929 if breakpoint.is_disabled() {
8930 self.edit_breakpoint_at_anchor(
8931 anchor,
8932 breakpoint,
8933 BreakpointEditAction::InvertState,
8934 cx,
8935 );
8936 }
8937 }
8938 }
8939
8940 pub fn disable_breakpoint(
8941 &mut self,
8942 _: &crate::actions::DisableBreakpoint,
8943 window: &mut Window,
8944 cx: &mut Context<Self>,
8945 ) {
8946 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8947 if breakpoint.is_enabled() {
8948 self.edit_breakpoint_at_anchor(
8949 anchor,
8950 breakpoint,
8951 BreakpointEditAction::InvertState,
8952 cx,
8953 );
8954 }
8955 }
8956 }
8957
8958 pub fn toggle_breakpoint(
8959 &mut self,
8960 _: &crate::actions::ToggleBreakpoint,
8961 window: &mut Window,
8962 cx: &mut Context<Self>,
8963 ) {
8964 let edit_action = BreakpointEditAction::Toggle;
8965
8966 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8967 self.edit_breakpoint_at_anchor(anchor, breakpoint, edit_action, cx);
8968 } else {
8969 let cursor_position: Point = self.selections.newest(cx).head();
8970
8971 let breakpoint_position = self
8972 .snapshot(window, cx)
8973 .display_snapshot
8974 .buffer_snapshot
8975 .anchor_after(Point::new(cursor_position.row, 0));
8976
8977 self.edit_breakpoint_at_anchor(
8978 breakpoint_position,
8979 Breakpoint::new_standard(),
8980 edit_action,
8981 cx,
8982 );
8983 }
8984 }
8985
8986 pub fn edit_breakpoint_at_anchor(
8987 &mut self,
8988 breakpoint_position: Anchor,
8989 breakpoint: Breakpoint,
8990 edit_action: BreakpointEditAction,
8991 cx: &mut Context<Self>,
8992 ) {
8993 let Some(breakpoint_store) = &self.breakpoint_store else {
8994 return;
8995 };
8996
8997 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8998 if breakpoint_position == Anchor::min() {
8999 self.buffer()
9000 .read(cx)
9001 .excerpt_buffer_ids()
9002 .into_iter()
9003 .next()
9004 } else {
9005 None
9006 }
9007 }) else {
9008 return;
9009 };
9010
9011 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9012 return;
9013 };
9014
9015 breakpoint_store.update(cx, |breakpoint_store, cx| {
9016 breakpoint_store.toggle_breakpoint(
9017 buffer,
9018 (breakpoint_position.text_anchor, breakpoint),
9019 edit_action,
9020 cx,
9021 );
9022 });
9023
9024 cx.notify();
9025 }
9026
9027 #[cfg(any(test, feature = "test-support"))]
9028 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9029 self.breakpoint_store.clone()
9030 }
9031
9032 pub fn prepare_restore_change(
9033 &self,
9034 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9035 hunk: &MultiBufferDiffHunk,
9036 cx: &mut App,
9037 ) -> Option<()> {
9038 if hunk.is_created_file() {
9039 return None;
9040 }
9041 let buffer = self.buffer.read(cx);
9042 let diff = buffer.diff_for(hunk.buffer_id)?;
9043 let buffer = buffer.buffer(hunk.buffer_id)?;
9044 let buffer = buffer.read(cx);
9045 let original_text = diff
9046 .read(cx)
9047 .base_text()
9048 .as_rope()
9049 .slice(hunk.diff_base_byte_range.clone());
9050 let buffer_snapshot = buffer.snapshot();
9051 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9052 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9053 probe
9054 .0
9055 .start
9056 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9057 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9058 }) {
9059 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9060 Some(())
9061 } else {
9062 None
9063 }
9064 }
9065
9066 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9067 self.manipulate_lines(window, cx, |lines| lines.reverse())
9068 }
9069
9070 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9071 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9072 }
9073
9074 fn manipulate_lines<Fn>(
9075 &mut self,
9076 window: &mut Window,
9077 cx: &mut Context<Self>,
9078 mut callback: Fn,
9079 ) where
9080 Fn: FnMut(&mut Vec<&str>),
9081 {
9082 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9083
9084 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9085 let buffer = self.buffer.read(cx).snapshot(cx);
9086
9087 let mut edits = Vec::new();
9088
9089 let selections = self.selections.all::<Point>(cx);
9090 let mut selections = selections.iter().peekable();
9091 let mut contiguous_row_selections = Vec::new();
9092 let mut new_selections = Vec::new();
9093 let mut added_lines = 0;
9094 let mut removed_lines = 0;
9095
9096 while let Some(selection) = selections.next() {
9097 let (start_row, end_row) = consume_contiguous_rows(
9098 &mut contiguous_row_selections,
9099 selection,
9100 &display_map,
9101 &mut selections,
9102 );
9103
9104 let start_point = Point::new(start_row.0, 0);
9105 let end_point = Point::new(
9106 end_row.previous_row().0,
9107 buffer.line_len(end_row.previous_row()),
9108 );
9109 let text = buffer
9110 .text_for_range(start_point..end_point)
9111 .collect::<String>();
9112
9113 let mut lines = text.split('\n').collect_vec();
9114
9115 let lines_before = lines.len();
9116 callback(&mut lines);
9117 let lines_after = lines.len();
9118
9119 edits.push((start_point..end_point, lines.join("\n")));
9120
9121 // Selections must change based on added and removed line count
9122 let start_row =
9123 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9124 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9125 new_selections.push(Selection {
9126 id: selection.id,
9127 start: start_row,
9128 end: end_row,
9129 goal: SelectionGoal::None,
9130 reversed: selection.reversed,
9131 });
9132
9133 if lines_after > lines_before {
9134 added_lines += lines_after - lines_before;
9135 } else if lines_before > lines_after {
9136 removed_lines += lines_before - lines_after;
9137 }
9138 }
9139
9140 self.transact(window, cx, |this, window, cx| {
9141 let buffer = this.buffer.update(cx, |buffer, cx| {
9142 buffer.edit(edits, None, cx);
9143 buffer.snapshot(cx)
9144 });
9145
9146 // Recalculate offsets on newly edited buffer
9147 let new_selections = new_selections
9148 .iter()
9149 .map(|s| {
9150 let start_point = Point::new(s.start.0, 0);
9151 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9152 Selection {
9153 id: s.id,
9154 start: buffer.point_to_offset(start_point),
9155 end: buffer.point_to_offset(end_point),
9156 goal: s.goal,
9157 reversed: s.reversed,
9158 }
9159 })
9160 .collect();
9161
9162 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9163 s.select(new_selections);
9164 });
9165
9166 this.request_autoscroll(Autoscroll::fit(), cx);
9167 });
9168 }
9169
9170 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9171 self.manipulate_text(window, cx, |text| {
9172 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9173 if has_upper_case_characters {
9174 text.to_lowercase()
9175 } else {
9176 text.to_uppercase()
9177 }
9178 })
9179 }
9180
9181 pub fn convert_to_upper_case(
9182 &mut self,
9183 _: &ConvertToUpperCase,
9184 window: &mut Window,
9185 cx: &mut Context<Self>,
9186 ) {
9187 self.manipulate_text(window, cx, |text| text.to_uppercase())
9188 }
9189
9190 pub fn convert_to_lower_case(
9191 &mut self,
9192 _: &ConvertToLowerCase,
9193 window: &mut Window,
9194 cx: &mut Context<Self>,
9195 ) {
9196 self.manipulate_text(window, cx, |text| text.to_lowercase())
9197 }
9198
9199 pub fn convert_to_title_case(
9200 &mut self,
9201 _: &ConvertToTitleCase,
9202 window: &mut Window,
9203 cx: &mut Context<Self>,
9204 ) {
9205 self.manipulate_text(window, cx, |text| {
9206 text.split('\n')
9207 .map(|line| line.to_case(Case::Title))
9208 .join("\n")
9209 })
9210 }
9211
9212 pub fn convert_to_snake_case(
9213 &mut self,
9214 _: &ConvertToSnakeCase,
9215 window: &mut Window,
9216 cx: &mut Context<Self>,
9217 ) {
9218 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9219 }
9220
9221 pub fn convert_to_kebab_case(
9222 &mut self,
9223 _: &ConvertToKebabCase,
9224 window: &mut Window,
9225 cx: &mut Context<Self>,
9226 ) {
9227 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9228 }
9229
9230 pub fn convert_to_upper_camel_case(
9231 &mut self,
9232 _: &ConvertToUpperCamelCase,
9233 window: &mut Window,
9234 cx: &mut Context<Self>,
9235 ) {
9236 self.manipulate_text(window, cx, |text| {
9237 text.split('\n')
9238 .map(|line| line.to_case(Case::UpperCamel))
9239 .join("\n")
9240 })
9241 }
9242
9243 pub fn convert_to_lower_camel_case(
9244 &mut self,
9245 _: &ConvertToLowerCamelCase,
9246 window: &mut Window,
9247 cx: &mut Context<Self>,
9248 ) {
9249 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9250 }
9251
9252 pub fn convert_to_opposite_case(
9253 &mut self,
9254 _: &ConvertToOppositeCase,
9255 window: &mut Window,
9256 cx: &mut Context<Self>,
9257 ) {
9258 self.manipulate_text(window, cx, |text| {
9259 text.chars()
9260 .fold(String::with_capacity(text.len()), |mut t, c| {
9261 if c.is_uppercase() {
9262 t.extend(c.to_lowercase());
9263 } else {
9264 t.extend(c.to_uppercase());
9265 }
9266 t
9267 })
9268 })
9269 }
9270
9271 pub fn convert_to_rot13(
9272 &mut self,
9273 _: &ConvertToRot13,
9274 window: &mut Window,
9275 cx: &mut Context<Self>,
9276 ) {
9277 self.manipulate_text(window, cx, |text| {
9278 text.chars()
9279 .map(|c| match c {
9280 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9281 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9282 _ => c,
9283 })
9284 .collect()
9285 })
9286 }
9287
9288 pub fn convert_to_rot47(
9289 &mut self,
9290 _: &ConvertToRot47,
9291 window: &mut Window,
9292 cx: &mut Context<Self>,
9293 ) {
9294 self.manipulate_text(window, cx, |text| {
9295 text.chars()
9296 .map(|c| {
9297 let code_point = c as u32;
9298 if code_point >= 33 && code_point <= 126 {
9299 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9300 }
9301 c
9302 })
9303 .collect()
9304 })
9305 }
9306
9307 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9308 where
9309 Fn: FnMut(&str) -> String,
9310 {
9311 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9312 let buffer = self.buffer.read(cx).snapshot(cx);
9313
9314 let mut new_selections = Vec::new();
9315 let mut edits = Vec::new();
9316 let mut selection_adjustment = 0i32;
9317
9318 for selection in self.selections.all::<usize>(cx) {
9319 let selection_is_empty = selection.is_empty();
9320
9321 let (start, end) = if selection_is_empty {
9322 let word_range = movement::surrounding_word(
9323 &display_map,
9324 selection.start.to_display_point(&display_map),
9325 );
9326 let start = word_range.start.to_offset(&display_map, Bias::Left);
9327 let end = word_range.end.to_offset(&display_map, Bias::Left);
9328 (start, end)
9329 } else {
9330 (selection.start, selection.end)
9331 };
9332
9333 let text = buffer.text_for_range(start..end).collect::<String>();
9334 let old_length = text.len() as i32;
9335 let text = callback(&text);
9336
9337 new_selections.push(Selection {
9338 start: (start as i32 - selection_adjustment) as usize,
9339 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9340 goal: SelectionGoal::None,
9341 ..selection
9342 });
9343
9344 selection_adjustment += old_length - text.len() as i32;
9345
9346 edits.push((start..end, text));
9347 }
9348
9349 self.transact(window, cx, |this, window, cx| {
9350 this.buffer.update(cx, |buffer, cx| {
9351 buffer.edit(edits, None, cx);
9352 });
9353
9354 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9355 s.select(new_selections);
9356 });
9357
9358 this.request_autoscroll(Autoscroll::fit(), cx);
9359 });
9360 }
9361
9362 pub fn duplicate(
9363 &mut self,
9364 upwards: bool,
9365 whole_lines: bool,
9366 window: &mut Window,
9367 cx: &mut Context<Self>,
9368 ) {
9369 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9370
9371 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9372 let buffer = &display_map.buffer_snapshot;
9373 let selections = self.selections.all::<Point>(cx);
9374
9375 let mut edits = Vec::new();
9376 let mut selections_iter = selections.iter().peekable();
9377 while let Some(selection) = selections_iter.next() {
9378 let mut rows = selection.spanned_rows(false, &display_map);
9379 // duplicate line-wise
9380 if whole_lines || selection.start == selection.end {
9381 // Avoid duplicating the same lines twice.
9382 while let Some(next_selection) = selections_iter.peek() {
9383 let next_rows = next_selection.spanned_rows(false, &display_map);
9384 if next_rows.start < rows.end {
9385 rows.end = next_rows.end;
9386 selections_iter.next().unwrap();
9387 } else {
9388 break;
9389 }
9390 }
9391
9392 // Copy the text from the selected row region and splice it either at the start
9393 // or end of the region.
9394 let start = Point::new(rows.start.0, 0);
9395 let end = Point::new(
9396 rows.end.previous_row().0,
9397 buffer.line_len(rows.end.previous_row()),
9398 );
9399 let text = buffer
9400 .text_for_range(start..end)
9401 .chain(Some("\n"))
9402 .collect::<String>();
9403 let insert_location = if upwards {
9404 Point::new(rows.end.0, 0)
9405 } else {
9406 start
9407 };
9408 edits.push((insert_location..insert_location, text));
9409 } else {
9410 // duplicate character-wise
9411 let start = selection.start;
9412 let end = selection.end;
9413 let text = buffer.text_for_range(start..end).collect::<String>();
9414 edits.push((selection.end..selection.end, text));
9415 }
9416 }
9417
9418 self.transact(window, cx, |this, _, cx| {
9419 this.buffer.update(cx, |buffer, cx| {
9420 buffer.edit(edits, None, cx);
9421 });
9422
9423 this.request_autoscroll(Autoscroll::fit(), cx);
9424 });
9425 }
9426
9427 pub fn duplicate_line_up(
9428 &mut self,
9429 _: &DuplicateLineUp,
9430 window: &mut Window,
9431 cx: &mut Context<Self>,
9432 ) {
9433 self.duplicate(true, true, window, cx);
9434 }
9435
9436 pub fn duplicate_line_down(
9437 &mut self,
9438 _: &DuplicateLineDown,
9439 window: &mut Window,
9440 cx: &mut Context<Self>,
9441 ) {
9442 self.duplicate(false, true, window, cx);
9443 }
9444
9445 pub fn duplicate_selection(
9446 &mut self,
9447 _: &DuplicateSelection,
9448 window: &mut Window,
9449 cx: &mut Context<Self>,
9450 ) {
9451 self.duplicate(false, false, window, cx);
9452 }
9453
9454 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9455 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9456
9457 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9458 let buffer = self.buffer.read(cx).snapshot(cx);
9459
9460 let mut edits = Vec::new();
9461 let mut unfold_ranges = Vec::new();
9462 let mut refold_creases = Vec::new();
9463
9464 let selections = self.selections.all::<Point>(cx);
9465 let mut selections = selections.iter().peekable();
9466 let mut contiguous_row_selections = Vec::new();
9467 let mut new_selections = Vec::new();
9468
9469 while let Some(selection) = selections.next() {
9470 // Find all the selections that span a contiguous row range
9471 let (start_row, end_row) = consume_contiguous_rows(
9472 &mut contiguous_row_selections,
9473 selection,
9474 &display_map,
9475 &mut selections,
9476 );
9477
9478 // Move the text spanned by the row range to be before the line preceding the row range
9479 if start_row.0 > 0 {
9480 let range_to_move = Point::new(
9481 start_row.previous_row().0,
9482 buffer.line_len(start_row.previous_row()),
9483 )
9484 ..Point::new(
9485 end_row.previous_row().0,
9486 buffer.line_len(end_row.previous_row()),
9487 );
9488 let insertion_point = display_map
9489 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9490 .0;
9491
9492 // Don't move lines across excerpts
9493 if buffer
9494 .excerpt_containing(insertion_point..range_to_move.end)
9495 .is_some()
9496 {
9497 let text = buffer
9498 .text_for_range(range_to_move.clone())
9499 .flat_map(|s| s.chars())
9500 .skip(1)
9501 .chain(['\n'])
9502 .collect::<String>();
9503
9504 edits.push((
9505 buffer.anchor_after(range_to_move.start)
9506 ..buffer.anchor_before(range_to_move.end),
9507 String::new(),
9508 ));
9509 let insertion_anchor = buffer.anchor_after(insertion_point);
9510 edits.push((insertion_anchor..insertion_anchor, text));
9511
9512 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9513
9514 // Move selections up
9515 new_selections.extend(contiguous_row_selections.drain(..).map(
9516 |mut selection| {
9517 selection.start.row -= row_delta;
9518 selection.end.row -= row_delta;
9519 selection
9520 },
9521 ));
9522
9523 // Move folds up
9524 unfold_ranges.push(range_to_move.clone());
9525 for fold in display_map.folds_in_range(
9526 buffer.anchor_before(range_to_move.start)
9527 ..buffer.anchor_after(range_to_move.end),
9528 ) {
9529 let mut start = fold.range.start.to_point(&buffer);
9530 let mut end = fold.range.end.to_point(&buffer);
9531 start.row -= row_delta;
9532 end.row -= row_delta;
9533 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9534 }
9535 }
9536 }
9537
9538 // If we didn't move line(s), preserve the existing selections
9539 new_selections.append(&mut contiguous_row_selections);
9540 }
9541
9542 self.transact(window, cx, |this, window, cx| {
9543 this.unfold_ranges(&unfold_ranges, true, true, cx);
9544 this.buffer.update(cx, |buffer, cx| {
9545 for (range, text) in edits {
9546 buffer.edit([(range, text)], None, cx);
9547 }
9548 });
9549 this.fold_creases(refold_creases, true, window, cx);
9550 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9551 s.select(new_selections);
9552 })
9553 });
9554 }
9555
9556 pub fn move_line_down(
9557 &mut self,
9558 _: &MoveLineDown,
9559 window: &mut Window,
9560 cx: &mut Context<Self>,
9561 ) {
9562 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9563
9564 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9565 let buffer = self.buffer.read(cx).snapshot(cx);
9566
9567 let mut edits = Vec::new();
9568 let mut unfold_ranges = Vec::new();
9569 let mut refold_creases = Vec::new();
9570
9571 let selections = self.selections.all::<Point>(cx);
9572 let mut selections = selections.iter().peekable();
9573 let mut contiguous_row_selections = Vec::new();
9574 let mut new_selections = Vec::new();
9575
9576 while let Some(selection) = selections.next() {
9577 // Find all the selections that span a contiguous row range
9578 let (start_row, end_row) = consume_contiguous_rows(
9579 &mut contiguous_row_selections,
9580 selection,
9581 &display_map,
9582 &mut selections,
9583 );
9584
9585 // Move the text spanned by the row range to be after the last line of the row range
9586 if end_row.0 <= buffer.max_point().row {
9587 let range_to_move =
9588 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9589 let insertion_point = display_map
9590 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9591 .0;
9592
9593 // Don't move lines across excerpt boundaries
9594 if buffer
9595 .excerpt_containing(range_to_move.start..insertion_point)
9596 .is_some()
9597 {
9598 let mut text = String::from("\n");
9599 text.extend(buffer.text_for_range(range_to_move.clone()));
9600 text.pop(); // Drop trailing newline
9601 edits.push((
9602 buffer.anchor_after(range_to_move.start)
9603 ..buffer.anchor_before(range_to_move.end),
9604 String::new(),
9605 ));
9606 let insertion_anchor = buffer.anchor_after(insertion_point);
9607 edits.push((insertion_anchor..insertion_anchor, text));
9608
9609 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9610
9611 // Move selections down
9612 new_selections.extend(contiguous_row_selections.drain(..).map(
9613 |mut selection| {
9614 selection.start.row += row_delta;
9615 selection.end.row += row_delta;
9616 selection
9617 },
9618 ));
9619
9620 // Move folds down
9621 unfold_ranges.push(range_to_move.clone());
9622 for fold in display_map.folds_in_range(
9623 buffer.anchor_before(range_to_move.start)
9624 ..buffer.anchor_after(range_to_move.end),
9625 ) {
9626 let mut start = fold.range.start.to_point(&buffer);
9627 let mut end = fold.range.end.to_point(&buffer);
9628 start.row += row_delta;
9629 end.row += row_delta;
9630 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9631 }
9632 }
9633 }
9634
9635 // If we didn't move line(s), preserve the existing selections
9636 new_selections.append(&mut contiguous_row_selections);
9637 }
9638
9639 self.transact(window, cx, |this, window, cx| {
9640 this.unfold_ranges(&unfold_ranges, true, true, cx);
9641 this.buffer.update(cx, |buffer, cx| {
9642 for (range, text) in edits {
9643 buffer.edit([(range, text)], None, cx);
9644 }
9645 });
9646 this.fold_creases(refold_creases, true, window, cx);
9647 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9648 s.select(new_selections)
9649 });
9650 });
9651 }
9652
9653 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9654 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9655 let text_layout_details = &self.text_layout_details(window);
9656 self.transact(window, cx, |this, window, cx| {
9657 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9658 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9659 s.move_with(|display_map, selection| {
9660 if !selection.is_empty() {
9661 return;
9662 }
9663
9664 let mut head = selection.head();
9665 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9666 if head.column() == display_map.line_len(head.row()) {
9667 transpose_offset = display_map
9668 .buffer_snapshot
9669 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9670 }
9671
9672 if transpose_offset == 0 {
9673 return;
9674 }
9675
9676 *head.column_mut() += 1;
9677 head = display_map.clip_point(head, Bias::Right);
9678 let goal = SelectionGoal::HorizontalPosition(
9679 display_map
9680 .x_for_display_point(head, text_layout_details)
9681 .into(),
9682 );
9683 selection.collapse_to(head, goal);
9684
9685 let transpose_start = display_map
9686 .buffer_snapshot
9687 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9688 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9689 let transpose_end = display_map
9690 .buffer_snapshot
9691 .clip_offset(transpose_offset + 1, Bias::Right);
9692 if let Some(ch) =
9693 display_map.buffer_snapshot.chars_at(transpose_start).next()
9694 {
9695 edits.push((transpose_start..transpose_offset, String::new()));
9696 edits.push((transpose_end..transpose_end, ch.to_string()));
9697 }
9698 }
9699 });
9700 edits
9701 });
9702 this.buffer
9703 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9704 let selections = this.selections.all::<usize>(cx);
9705 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9706 s.select(selections);
9707 });
9708 });
9709 }
9710
9711 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9712 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9713 self.rewrap_impl(RewrapOptions::default(), cx)
9714 }
9715
9716 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9717 let buffer = self.buffer.read(cx).snapshot(cx);
9718 let selections = self.selections.all::<Point>(cx);
9719 let mut selections = selections.iter().peekable();
9720
9721 let mut edits = Vec::new();
9722 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9723
9724 while let Some(selection) = selections.next() {
9725 let mut start_row = selection.start.row;
9726 let mut end_row = selection.end.row;
9727
9728 // Skip selections that overlap with a range that has already been rewrapped.
9729 let selection_range = start_row..end_row;
9730 if rewrapped_row_ranges
9731 .iter()
9732 .any(|range| range.overlaps(&selection_range))
9733 {
9734 continue;
9735 }
9736
9737 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9738
9739 // Since not all lines in the selection may be at the same indent
9740 // level, choose the indent size that is the most common between all
9741 // of the lines.
9742 //
9743 // If there is a tie, we use the deepest indent.
9744 let (indent_size, indent_end) = {
9745 let mut indent_size_occurrences = HashMap::default();
9746 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9747
9748 for row in start_row..=end_row {
9749 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9750 rows_by_indent_size.entry(indent).or_default().push(row);
9751 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9752 }
9753
9754 let indent_size = indent_size_occurrences
9755 .into_iter()
9756 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9757 .map(|(indent, _)| indent)
9758 .unwrap_or_default();
9759 let row = rows_by_indent_size[&indent_size][0];
9760 let indent_end = Point::new(row, indent_size.len);
9761
9762 (indent_size, indent_end)
9763 };
9764
9765 let mut line_prefix = indent_size.chars().collect::<String>();
9766
9767 let mut inside_comment = false;
9768 if let Some(comment_prefix) =
9769 buffer
9770 .language_scope_at(selection.head())
9771 .and_then(|language| {
9772 language
9773 .line_comment_prefixes()
9774 .iter()
9775 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9776 .cloned()
9777 })
9778 {
9779 line_prefix.push_str(&comment_prefix);
9780 inside_comment = true;
9781 }
9782
9783 let language_settings = buffer.language_settings_at(selection.head(), cx);
9784 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9785 RewrapBehavior::InComments => inside_comment,
9786 RewrapBehavior::InSelections => !selection.is_empty(),
9787 RewrapBehavior::Anywhere => true,
9788 };
9789
9790 let should_rewrap = options.override_language_settings
9791 || allow_rewrap_based_on_language
9792 || self.hard_wrap.is_some();
9793 if !should_rewrap {
9794 continue;
9795 }
9796
9797 if selection.is_empty() {
9798 'expand_upwards: while start_row > 0 {
9799 let prev_row = start_row - 1;
9800 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9801 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9802 {
9803 start_row = prev_row;
9804 } else {
9805 break 'expand_upwards;
9806 }
9807 }
9808
9809 'expand_downwards: while end_row < buffer.max_point().row {
9810 let next_row = end_row + 1;
9811 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9812 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9813 {
9814 end_row = next_row;
9815 } else {
9816 break 'expand_downwards;
9817 }
9818 }
9819 }
9820
9821 let start = Point::new(start_row, 0);
9822 let start_offset = start.to_offset(&buffer);
9823 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9824 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9825 let Some(lines_without_prefixes) = selection_text
9826 .lines()
9827 .map(|line| {
9828 line.strip_prefix(&line_prefix)
9829 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9830 .ok_or_else(|| {
9831 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9832 })
9833 })
9834 .collect::<Result<Vec<_>, _>>()
9835 .log_err()
9836 else {
9837 continue;
9838 };
9839
9840 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9841 buffer
9842 .language_settings_at(Point::new(start_row, 0), cx)
9843 .preferred_line_length as usize
9844 });
9845 let wrapped_text = wrap_with_prefix(
9846 line_prefix,
9847 lines_without_prefixes.join("\n"),
9848 wrap_column,
9849 tab_size,
9850 options.preserve_existing_whitespace,
9851 );
9852
9853 // TODO: should always use char-based diff while still supporting cursor behavior that
9854 // matches vim.
9855 let mut diff_options = DiffOptions::default();
9856 if options.override_language_settings {
9857 diff_options.max_word_diff_len = 0;
9858 diff_options.max_word_diff_line_count = 0;
9859 } else {
9860 diff_options.max_word_diff_len = usize::MAX;
9861 diff_options.max_word_diff_line_count = usize::MAX;
9862 }
9863
9864 for (old_range, new_text) in
9865 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9866 {
9867 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9868 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9869 edits.push((edit_start..edit_end, new_text));
9870 }
9871
9872 rewrapped_row_ranges.push(start_row..=end_row);
9873 }
9874
9875 self.buffer
9876 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9877 }
9878
9879 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9880 let mut text = String::new();
9881 let buffer = self.buffer.read(cx).snapshot(cx);
9882 let mut selections = self.selections.all::<Point>(cx);
9883 let mut clipboard_selections = Vec::with_capacity(selections.len());
9884 {
9885 let max_point = buffer.max_point();
9886 let mut is_first = true;
9887 for selection in &mut selections {
9888 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9889 if is_entire_line {
9890 selection.start = Point::new(selection.start.row, 0);
9891 if !selection.is_empty() && selection.end.column == 0 {
9892 selection.end = cmp::min(max_point, selection.end);
9893 } else {
9894 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9895 }
9896 selection.goal = SelectionGoal::None;
9897 }
9898 if is_first {
9899 is_first = false;
9900 } else {
9901 text += "\n";
9902 }
9903 let mut len = 0;
9904 for chunk in buffer.text_for_range(selection.start..selection.end) {
9905 text.push_str(chunk);
9906 len += chunk.len();
9907 }
9908 clipboard_selections.push(ClipboardSelection {
9909 len,
9910 is_entire_line,
9911 first_line_indent: buffer
9912 .indent_size_for_line(MultiBufferRow(selection.start.row))
9913 .len,
9914 });
9915 }
9916 }
9917
9918 self.transact(window, cx, |this, window, cx| {
9919 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9920 s.select(selections);
9921 });
9922 this.insert("", window, cx);
9923 });
9924 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9925 }
9926
9927 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9928 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9929 let item = self.cut_common(window, cx);
9930 cx.write_to_clipboard(item);
9931 }
9932
9933 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9934 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9935 self.change_selections(None, window, cx, |s| {
9936 s.move_with(|snapshot, sel| {
9937 if sel.is_empty() {
9938 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9939 }
9940 });
9941 });
9942 let item = self.cut_common(window, cx);
9943 cx.set_global(KillRing(item))
9944 }
9945
9946 pub fn kill_ring_yank(
9947 &mut self,
9948 _: &KillRingYank,
9949 window: &mut Window,
9950 cx: &mut Context<Self>,
9951 ) {
9952 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9953 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9954 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9955 (kill_ring.text().to_string(), kill_ring.metadata_json())
9956 } else {
9957 return;
9958 }
9959 } else {
9960 return;
9961 };
9962 self.do_paste(&text, metadata, false, window, cx);
9963 }
9964
9965 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9966 self.do_copy(true, cx);
9967 }
9968
9969 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9970 self.do_copy(false, cx);
9971 }
9972
9973 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9974 let selections = self.selections.all::<Point>(cx);
9975 let buffer = self.buffer.read(cx).read(cx);
9976 let mut text = String::new();
9977
9978 let mut clipboard_selections = Vec::with_capacity(selections.len());
9979 {
9980 let max_point = buffer.max_point();
9981 let mut is_first = true;
9982 for selection in &selections {
9983 let mut start = selection.start;
9984 let mut end = selection.end;
9985 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9986 if is_entire_line {
9987 start = Point::new(start.row, 0);
9988 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9989 }
9990
9991 let mut trimmed_selections = Vec::new();
9992 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
9993 let row = MultiBufferRow(start.row);
9994 let first_indent = buffer.indent_size_for_line(row);
9995 if first_indent.len == 0 || start.column > first_indent.len {
9996 trimmed_selections.push(start..end);
9997 } else {
9998 trimmed_selections.push(
9999 Point::new(row.0, first_indent.len)
10000 ..Point::new(row.0, buffer.line_len(row)),
10001 );
10002 for row in start.row + 1..=end.row {
10003 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10004 if row_indent_size.len >= first_indent.len {
10005 trimmed_selections.push(
10006 Point::new(row, first_indent.len)
10007 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
10008 );
10009 } else {
10010 trimmed_selections.clear();
10011 trimmed_selections.push(start..end);
10012 break;
10013 }
10014 }
10015 }
10016 } else {
10017 trimmed_selections.push(start..end);
10018 }
10019
10020 for trimmed_range in trimmed_selections {
10021 if is_first {
10022 is_first = false;
10023 } else {
10024 text += "\n";
10025 }
10026 let mut len = 0;
10027 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10028 text.push_str(chunk);
10029 len += chunk.len();
10030 }
10031 clipboard_selections.push(ClipboardSelection {
10032 len,
10033 is_entire_line,
10034 first_line_indent: buffer
10035 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10036 .len,
10037 });
10038 }
10039 }
10040 }
10041
10042 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10043 text,
10044 clipboard_selections,
10045 ));
10046 }
10047
10048 pub fn do_paste(
10049 &mut self,
10050 text: &String,
10051 clipboard_selections: Option<Vec<ClipboardSelection>>,
10052 handle_entire_lines: bool,
10053 window: &mut Window,
10054 cx: &mut Context<Self>,
10055 ) {
10056 if self.read_only(cx) {
10057 return;
10058 }
10059
10060 let clipboard_text = Cow::Borrowed(text);
10061
10062 self.transact(window, cx, |this, window, cx| {
10063 if let Some(mut clipboard_selections) = clipboard_selections {
10064 let old_selections = this.selections.all::<usize>(cx);
10065 let all_selections_were_entire_line =
10066 clipboard_selections.iter().all(|s| s.is_entire_line);
10067 let first_selection_indent_column =
10068 clipboard_selections.first().map(|s| s.first_line_indent);
10069 if clipboard_selections.len() != old_selections.len() {
10070 clipboard_selections.drain(..);
10071 }
10072 let cursor_offset = this.selections.last::<usize>(cx).head();
10073 let mut auto_indent_on_paste = true;
10074
10075 this.buffer.update(cx, |buffer, cx| {
10076 let snapshot = buffer.read(cx);
10077 auto_indent_on_paste = snapshot
10078 .language_settings_at(cursor_offset, cx)
10079 .auto_indent_on_paste;
10080
10081 let mut start_offset = 0;
10082 let mut edits = Vec::new();
10083 let mut original_indent_columns = Vec::new();
10084 for (ix, selection) in old_selections.iter().enumerate() {
10085 let to_insert;
10086 let entire_line;
10087 let original_indent_column;
10088 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10089 let end_offset = start_offset + clipboard_selection.len;
10090 to_insert = &clipboard_text[start_offset..end_offset];
10091 entire_line = clipboard_selection.is_entire_line;
10092 start_offset = end_offset + 1;
10093 original_indent_column = Some(clipboard_selection.first_line_indent);
10094 } else {
10095 to_insert = clipboard_text.as_str();
10096 entire_line = all_selections_were_entire_line;
10097 original_indent_column = first_selection_indent_column
10098 }
10099
10100 // If the corresponding selection was empty when this slice of the
10101 // clipboard text was written, then the entire line containing the
10102 // selection was copied. If this selection is also currently empty,
10103 // then paste the line before the current line of the buffer.
10104 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10105 let column = selection.start.to_point(&snapshot).column as usize;
10106 let line_start = selection.start - column;
10107 line_start..line_start
10108 } else {
10109 selection.range()
10110 };
10111
10112 edits.push((range, to_insert));
10113 original_indent_columns.push(original_indent_column);
10114 }
10115 drop(snapshot);
10116
10117 buffer.edit(
10118 edits,
10119 if auto_indent_on_paste {
10120 Some(AutoindentMode::Block {
10121 original_indent_columns,
10122 })
10123 } else {
10124 None
10125 },
10126 cx,
10127 );
10128 });
10129
10130 let selections = this.selections.all::<usize>(cx);
10131 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10132 s.select(selections)
10133 });
10134 } else {
10135 this.insert(&clipboard_text, window, cx);
10136 }
10137 });
10138 }
10139
10140 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10141 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10142 if let Some(item) = cx.read_from_clipboard() {
10143 let entries = item.entries();
10144
10145 match entries.first() {
10146 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10147 // of all the pasted entries.
10148 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10149 .do_paste(
10150 clipboard_string.text(),
10151 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10152 true,
10153 window,
10154 cx,
10155 ),
10156 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10157 }
10158 }
10159 }
10160
10161 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10162 if self.read_only(cx) {
10163 return;
10164 }
10165
10166 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10167
10168 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10169 if let Some((selections, _)) =
10170 self.selection_history.transaction(transaction_id).cloned()
10171 {
10172 self.change_selections(None, window, cx, |s| {
10173 s.select_anchors(selections.to_vec());
10174 });
10175 } else {
10176 log::error!(
10177 "No entry in selection_history found for undo. \
10178 This may correspond to a bug where undo does not update the selection. \
10179 If this is occurring, please add details to \
10180 https://github.com/zed-industries/zed/issues/22692"
10181 );
10182 }
10183 self.request_autoscroll(Autoscroll::fit(), cx);
10184 self.unmark_text(window, cx);
10185 self.refresh_inline_completion(true, false, window, cx);
10186 cx.emit(EditorEvent::Edited { transaction_id });
10187 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10188 }
10189 }
10190
10191 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10192 if self.read_only(cx) {
10193 return;
10194 }
10195
10196 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10197
10198 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10199 if let Some((_, Some(selections))) =
10200 self.selection_history.transaction(transaction_id).cloned()
10201 {
10202 self.change_selections(None, window, cx, |s| {
10203 s.select_anchors(selections.to_vec());
10204 });
10205 } else {
10206 log::error!(
10207 "No entry in selection_history found for redo. \
10208 This may correspond to a bug where undo does not update the selection. \
10209 If this is occurring, please add details to \
10210 https://github.com/zed-industries/zed/issues/22692"
10211 );
10212 }
10213 self.request_autoscroll(Autoscroll::fit(), cx);
10214 self.unmark_text(window, cx);
10215 self.refresh_inline_completion(true, false, window, cx);
10216 cx.emit(EditorEvent::Edited { transaction_id });
10217 }
10218 }
10219
10220 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10221 self.buffer
10222 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10223 }
10224
10225 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10226 self.buffer
10227 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10228 }
10229
10230 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10231 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10232 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10233 s.move_with(|map, selection| {
10234 let cursor = if selection.is_empty() {
10235 movement::left(map, selection.start)
10236 } else {
10237 selection.start
10238 };
10239 selection.collapse_to(cursor, SelectionGoal::None);
10240 });
10241 })
10242 }
10243
10244 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10245 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10246 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10247 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10248 })
10249 }
10250
10251 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10252 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10253 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10254 s.move_with(|map, selection| {
10255 let cursor = if selection.is_empty() {
10256 movement::right(map, selection.end)
10257 } else {
10258 selection.end
10259 };
10260 selection.collapse_to(cursor, SelectionGoal::None)
10261 });
10262 })
10263 }
10264
10265 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10266 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10267 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10268 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10269 })
10270 }
10271
10272 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10273 if self.take_rename(true, window, cx).is_some() {
10274 return;
10275 }
10276
10277 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10278 cx.propagate();
10279 return;
10280 }
10281
10282 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10283
10284 let text_layout_details = &self.text_layout_details(window);
10285 let selection_count = self.selections.count();
10286 let first_selection = self.selections.first_anchor();
10287
10288 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10289 s.move_with(|map, selection| {
10290 if !selection.is_empty() {
10291 selection.goal = SelectionGoal::None;
10292 }
10293 let (cursor, goal) = movement::up(
10294 map,
10295 selection.start,
10296 selection.goal,
10297 false,
10298 text_layout_details,
10299 );
10300 selection.collapse_to(cursor, goal);
10301 });
10302 });
10303
10304 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10305 {
10306 cx.propagate();
10307 }
10308 }
10309
10310 pub fn move_up_by_lines(
10311 &mut self,
10312 action: &MoveUpByLines,
10313 window: &mut Window,
10314 cx: &mut Context<Self>,
10315 ) {
10316 if self.take_rename(true, window, cx).is_some() {
10317 return;
10318 }
10319
10320 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10321 cx.propagate();
10322 return;
10323 }
10324
10325 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10326
10327 let text_layout_details = &self.text_layout_details(window);
10328
10329 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10330 s.move_with(|map, selection| {
10331 if !selection.is_empty() {
10332 selection.goal = SelectionGoal::None;
10333 }
10334 let (cursor, goal) = movement::up_by_rows(
10335 map,
10336 selection.start,
10337 action.lines,
10338 selection.goal,
10339 false,
10340 text_layout_details,
10341 );
10342 selection.collapse_to(cursor, goal);
10343 });
10344 })
10345 }
10346
10347 pub fn move_down_by_lines(
10348 &mut self,
10349 action: &MoveDownByLines,
10350 window: &mut Window,
10351 cx: &mut Context<Self>,
10352 ) {
10353 if self.take_rename(true, window, cx).is_some() {
10354 return;
10355 }
10356
10357 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10358 cx.propagate();
10359 return;
10360 }
10361
10362 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10363
10364 let text_layout_details = &self.text_layout_details(window);
10365
10366 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10367 s.move_with(|map, selection| {
10368 if !selection.is_empty() {
10369 selection.goal = SelectionGoal::None;
10370 }
10371 let (cursor, goal) = movement::down_by_rows(
10372 map,
10373 selection.start,
10374 action.lines,
10375 selection.goal,
10376 false,
10377 text_layout_details,
10378 );
10379 selection.collapse_to(cursor, goal);
10380 });
10381 })
10382 }
10383
10384 pub fn select_down_by_lines(
10385 &mut self,
10386 action: &SelectDownByLines,
10387 window: &mut Window,
10388 cx: &mut Context<Self>,
10389 ) {
10390 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10391 let text_layout_details = &self.text_layout_details(window);
10392 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10393 s.move_heads_with(|map, head, goal| {
10394 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10395 })
10396 })
10397 }
10398
10399 pub fn select_up_by_lines(
10400 &mut self,
10401 action: &SelectUpByLines,
10402 window: &mut Window,
10403 cx: &mut Context<Self>,
10404 ) {
10405 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10406 let text_layout_details = &self.text_layout_details(window);
10407 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10408 s.move_heads_with(|map, head, goal| {
10409 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10410 })
10411 })
10412 }
10413
10414 pub fn select_page_up(
10415 &mut self,
10416 _: &SelectPageUp,
10417 window: &mut Window,
10418 cx: &mut Context<Self>,
10419 ) {
10420 let Some(row_count) = self.visible_row_count() else {
10421 return;
10422 };
10423
10424 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10425
10426 let text_layout_details = &self.text_layout_details(window);
10427
10428 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10429 s.move_heads_with(|map, head, goal| {
10430 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10431 })
10432 })
10433 }
10434
10435 pub fn move_page_up(
10436 &mut self,
10437 action: &MovePageUp,
10438 window: &mut Window,
10439 cx: &mut Context<Self>,
10440 ) {
10441 if self.take_rename(true, window, cx).is_some() {
10442 return;
10443 }
10444
10445 if self
10446 .context_menu
10447 .borrow_mut()
10448 .as_mut()
10449 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10450 .unwrap_or(false)
10451 {
10452 return;
10453 }
10454
10455 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10456 cx.propagate();
10457 return;
10458 }
10459
10460 let Some(row_count) = self.visible_row_count() else {
10461 return;
10462 };
10463
10464 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10465
10466 let autoscroll = if action.center_cursor {
10467 Autoscroll::center()
10468 } else {
10469 Autoscroll::fit()
10470 };
10471
10472 let text_layout_details = &self.text_layout_details(window);
10473
10474 self.change_selections(Some(autoscroll), window, cx, |s| {
10475 s.move_with(|map, selection| {
10476 if !selection.is_empty() {
10477 selection.goal = SelectionGoal::None;
10478 }
10479 let (cursor, goal) = movement::up_by_rows(
10480 map,
10481 selection.end,
10482 row_count,
10483 selection.goal,
10484 false,
10485 text_layout_details,
10486 );
10487 selection.collapse_to(cursor, goal);
10488 });
10489 });
10490 }
10491
10492 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10493 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10494 let text_layout_details = &self.text_layout_details(window);
10495 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10496 s.move_heads_with(|map, head, goal| {
10497 movement::up(map, head, goal, false, text_layout_details)
10498 })
10499 })
10500 }
10501
10502 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10503 self.take_rename(true, window, cx);
10504
10505 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10506 cx.propagate();
10507 return;
10508 }
10509
10510 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10511
10512 let text_layout_details = &self.text_layout_details(window);
10513 let selection_count = self.selections.count();
10514 let first_selection = self.selections.first_anchor();
10515
10516 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10517 s.move_with(|map, selection| {
10518 if !selection.is_empty() {
10519 selection.goal = SelectionGoal::None;
10520 }
10521 let (cursor, goal) = movement::down(
10522 map,
10523 selection.end,
10524 selection.goal,
10525 false,
10526 text_layout_details,
10527 );
10528 selection.collapse_to(cursor, goal);
10529 });
10530 });
10531
10532 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10533 {
10534 cx.propagate();
10535 }
10536 }
10537
10538 pub fn select_page_down(
10539 &mut self,
10540 _: &SelectPageDown,
10541 window: &mut Window,
10542 cx: &mut Context<Self>,
10543 ) {
10544 let Some(row_count) = self.visible_row_count() else {
10545 return;
10546 };
10547
10548 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10549
10550 let text_layout_details = &self.text_layout_details(window);
10551
10552 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10553 s.move_heads_with(|map, head, goal| {
10554 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10555 })
10556 })
10557 }
10558
10559 pub fn move_page_down(
10560 &mut self,
10561 action: &MovePageDown,
10562 window: &mut Window,
10563 cx: &mut Context<Self>,
10564 ) {
10565 if self.take_rename(true, window, cx).is_some() {
10566 return;
10567 }
10568
10569 if self
10570 .context_menu
10571 .borrow_mut()
10572 .as_mut()
10573 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10574 .unwrap_or(false)
10575 {
10576 return;
10577 }
10578
10579 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10580 cx.propagate();
10581 return;
10582 }
10583
10584 let Some(row_count) = self.visible_row_count() else {
10585 return;
10586 };
10587
10588 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10589
10590 let autoscroll = if action.center_cursor {
10591 Autoscroll::center()
10592 } else {
10593 Autoscroll::fit()
10594 };
10595
10596 let text_layout_details = &self.text_layout_details(window);
10597 self.change_selections(Some(autoscroll), window, cx, |s| {
10598 s.move_with(|map, selection| {
10599 if !selection.is_empty() {
10600 selection.goal = SelectionGoal::None;
10601 }
10602 let (cursor, goal) = movement::down_by_rows(
10603 map,
10604 selection.end,
10605 row_count,
10606 selection.goal,
10607 false,
10608 text_layout_details,
10609 );
10610 selection.collapse_to(cursor, goal);
10611 });
10612 });
10613 }
10614
10615 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10616 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10617 let text_layout_details = &self.text_layout_details(window);
10618 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10619 s.move_heads_with(|map, head, goal| {
10620 movement::down(map, head, goal, false, text_layout_details)
10621 })
10622 });
10623 }
10624
10625 pub fn context_menu_first(
10626 &mut self,
10627 _: &ContextMenuFirst,
10628 _window: &mut Window,
10629 cx: &mut Context<Self>,
10630 ) {
10631 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10632 context_menu.select_first(self.completion_provider.as_deref(), cx);
10633 }
10634 }
10635
10636 pub fn context_menu_prev(
10637 &mut self,
10638 _: &ContextMenuPrevious,
10639 _window: &mut Window,
10640 cx: &mut Context<Self>,
10641 ) {
10642 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10643 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10644 }
10645 }
10646
10647 pub fn context_menu_next(
10648 &mut self,
10649 _: &ContextMenuNext,
10650 _window: &mut Window,
10651 cx: &mut Context<Self>,
10652 ) {
10653 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10654 context_menu.select_next(self.completion_provider.as_deref(), cx);
10655 }
10656 }
10657
10658 pub fn context_menu_last(
10659 &mut self,
10660 _: &ContextMenuLast,
10661 _window: &mut Window,
10662 cx: &mut Context<Self>,
10663 ) {
10664 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10665 context_menu.select_last(self.completion_provider.as_deref(), cx);
10666 }
10667 }
10668
10669 pub fn move_to_previous_word_start(
10670 &mut self,
10671 _: &MoveToPreviousWordStart,
10672 window: &mut Window,
10673 cx: &mut Context<Self>,
10674 ) {
10675 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10676 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10677 s.move_cursors_with(|map, head, _| {
10678 (
10679 movement::previous_word_start(map, head),
10680 SelectionGoal::None,
10681 )
10682 });
10683 })
10684 }
10685
10686 pub fn move_to_previous_subword_start(
10687 &mut self,
10688 _: &MoveToPreviousSubwordStart,
10689 window: &mut Window,
10690 cx: &mut Context<Self>,
10691 ) {
10692 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10693 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10694 s.move_cursors_with(|map, head, _| {
10695 (
10696 movement::previous_subword_start(map, head),
10697 SelectionGoal::None,
10698 )
10699 });
10700 })
10701 }
10702
10703 pub fn select_to_previous_word_start(
10704 &mut self,
10705 _: &SelectToPreviousWordStart,
10706 window: &mut Window,
10707 cx: &mut Context<Self>,
10708 ) {
10709 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10710 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10711 s.move_heads_with(|map, head, _| {
10712 (
10713 movement::previous_word_start(map, head),
10714 SelectionGoal::None,
10715 )
10716 });
10717 })
10718 }
10719
10720 pub fn select_to_previous_subword_start(
10721 &mut self,
10722 _: &SelectToPreviousSubwordStart,
10723 window: &mut Window,
10724 cx: &mut Context<Self>,
10725 ) {
10726 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10727 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10728 s.move_heads_with(|map, head, _| {
10729 (
10730 movement::previous_subword_start(map, head),
10731 SelectionGoal::None,
10732 )
10733 });
10734 })
10735 }
10736
10737 pub fn delete_to_previous_word_start(
10738 &mut self,
10739 action: &DeleteToPreviousWordStart,
10740 window: &mut Window,
10741 cx: &mut Context<Self>,
10742 ) {
10743 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10744 self.transact(window, cx, |this, window, cx| {
10745 this.select_autoclose_pair(window, cx);
10746 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10747 s.move_with(|map, selection| {
10748 if selection.is_empty() {
10749 let cursor = if action.ignore_newlines {
10750 movement::previous_word_start(map, selection.head())
10751 } else {
10752 movement::previous_word_start_or_newline(map, selection.head())
10753 };
10754 selection.set_head(cursor, SelectionGoal::None);
10755 }
10756 });
10757 });
10758 this.insert("", window, cx);
10759 });
10760 }
10761
10762 pub fn delete_to_previous_subword_start(
10763 &mut self,
10764 _: &DeleteToPreviousSubwordStart,
10765 window: &mut Window,
10766 cx: &mut Context<Self>,
10767 ) {
10768 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10769 self.transact(window, cx, |this, window, cx| {
10770 this.select_autoclose_pair(window, cx);
10771 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10772 s.move_with(|map, selection| {
10773 if selection.is_empty() {
10774 let cursor = movement::previous_subword_start(map, selection.head());
10775 selection.set_head(cursor, SelectionGoal::None);
10776 }
10777 });
10778 });
10779 this.insert("", window, cx);
10780 });
10781 }
10782
10783 pub fn move_to_next_word_end(
10784 &mut self,
10785 _: &MoveToNextWordEnd,
10786 window: &mut Window,
10787 cx: &mut Context<Self>,
10788 ) {
10789 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10790 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10791 s.move_cursors_with(|map, head, _| {
10792 (movement::next_word_end(map, head), SelectionGoal::None)
10793 });
10794 })
10795 }
10796
10797 pub fn move_to_next_subword_end(
10798 &mut self,
10799 _: &MoveToNextSubwordEnd,
10800 window: &mut Window,
10801 cx: &mut Context<Self>,
10802 ) {
10803 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10804 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10805 s.move_cursors_with(|map, head, _| {
10806 (movement::next_subword_end(map, head), SelectionGoal::None)
10807 });
10808 })
10809 }
10810
10811 pub fn select_to_next_word_end(
10812 &mut self,
10813 _: &SelectToNextWordEnd,
10814 window: &mut Window,
10815 cx: &mut Context<Self>,
10816 ) {
10817 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10818 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10819 s.move_heads_with(|map, head, _| {
10820 (movement::next_word_end(map, head), SelectionGoal::None)
10821 });
10822 })
10823 }
10824
10825 pub fn select_to_next_subword_end(
10826 &mut self,
10827 _: &SelectToNextSubwordEnd,
10828 window: &mut Window,
10829 cx: &mut Context<Self>,
10830 ) {
10831 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10832 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10833 s.move_heads_with(|map, head, _| {
10834 (movement::next_subword_end(map, head), SelectionGoal::None)
10835 });
10836 })
10837 }
10838
10839 pub fn delete_to_next_word_end(
10840 &mut self,
10841 action: &DeleteToNextWordEnd,
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 = if action.ignore_newlines {
10851 movement::next_word_end(map, selection.head())
10852 } else {
10853 movement::next_word_end_or_newline(map, selection.head())
10854 };
10855 selection.set_head(cursor, SelectionGoal::None);
10856 }
10857 });
10858 });
10859 this.insert("", window, cx);
10860 });
10861 }
10862
10863 pub fn delete_to_next_subword_end(
10864 &mut self,
10865 _: &DeleteToNextSubwordEnd,
10866 window: &mut Window,
10867 cx: &mut Context<Self>,
10868 ) {
10869 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10870 self.transact(window, cx, |this, window, cx| {
10871 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10872 s.move_with(|map, selection| {
10873 if selection.is_empty() {
10874 let cursor = movement::next_subword_end(map, selection.head());
10875 selection.set_head(cursor, SelectionGoal::None);
10876 }
10877 });
10878 });
10879 this.insert("", window, cx);
10880 });
10881 }
10882
10883 pub fn move_to_beginning_of_line(
10884 &mut self,
10885 action: &MoveToBeginningOfLine,
10886 window: &mut Window,
10887 cx: &mut Context<Self>,
10888 ) {
10889 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10890 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10891 s.move_cursors_with(|map, head, _| {
10892 (
10893 movement::indented_line_beginning(
10894 map,
10895 head,
10896 action.stop_at_soft_wraps,
10897 action.stop_at_indent,
10898 ),
10899 SelectionGoal::None,
10900 )
10901 });
10902 })
10903 }
10904
10905 pub fn select_to_beginning_of_line(
10906 &mut self,
10907 action: &SelectToBeginningOfLine,
10908 window: &mut Window,
10909 cx: &mut Context<Self>,
10910 ) {
10911 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10912 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10913 s.move_heads_with(|map, head, _| {
10914 (
10915 movement::indented_line_beginning(
10916 map,
10917 head,
10918 action.stop_at_soft_wraps,
10919 action.stop_at_indent,
10920 ),
10921 SelectionGoal::None,
10922 )
10923 });
10924 });
10925 }
10926
10927 pub fn delete_to_beginning_of_line(
10928 &mut self,
10929 action: &DeleteToBeginningOfLine,
10930 window: &mut Window,
10931 cx: &mut Context<Self>,
10932 ) {
10933 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10934 self.transact(window, cx, |this, window, cx| {
10935 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10936 s.move_with(|_, selection| {
10937 selection.reversed = true;
10938 });
10939 });
10940
10941 this.select_to_beginning_of_line(
10942 &SelectToBeginningOfLine {
10943 stop_at_soft_wraps: false,
10944 stop_at_indent: action.stop_at_indent,
10945 },
10946 window,
10947 cx,
10948 );
10949 this.backspace(&Backspace, window, cx);
10950 });
10951 }
10952
10953 pub fn move_to_end_of_line(
10954 &mut self,
10955 action: &MoveToEndOfLine,
10956 window: &mut Window,
10957 cx: &mut Context<Self>,
10958 ) {
10959 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10960 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10961 s.move_cursors_with(|map, head, _| {
10962 (
10963 movement::line_end(map, head, action.stop_at_soft_wraps),
10964 SelectionGoal::None,
10965 )
10966 });
10967 })
10968 }
10969
10970 pub fn select_to_end_of_line(
10971 &mut self,
10972 action: &SelectToEndOfLine,
10973 window: &mut Window,
10974 cx: &mut Context<Self>,
10975 ) {
10976 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10977 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10978 s.move_heads_with(|map, head, _| {
10979 (
10980 movement::line_end(map, head, action.stop_at_soft_wraps),
10981 SelectionGoal::None,
10982 )
10983 });
10984 })
10985 }
10986
10987 pub fn delete_to_end_of_line(
10988 &mut self,
10989 _: &DeleteToEndOfLine,
10990 window: &mut Window,
10991 cx: &mut Context<Self>,
10992 ) {
10993 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10994 self.transact(window, cx, |this, window, cx| {
10995 this.select_to_end_of_line(
10996 &SelectToEndOfLine {
10997 stop_at_soft_wraps: false,
10998 },
10999 window,
11000 cx,
11001 );
11002 this.delete(&Delete, window, cx);
11003 });
11004 }
11005
11006 pub fn cut_to_end_of_line(
11007 &mut self,
11008 _: &CutToEndOfLine,
11009 window: &mut Window,
11010 cx: &mut Context<Self>,
11011 ) {
11012 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11013 self.transact(window, cx, |this, window, cx| {
11014 this.select_to_end_of_line(
11015 &SelectToEndOfLine {
11016 stop_at_soft_wraps: false,
11017 },
11018 window,
11019 cx,
11020 );
11021 this.cut(&Cut, window, cx);
11022 });
11023 }
11024
11025 pub fn move_to_start_of_paragraph(
11026 &mut self,
11027 _: &MoveToStartOfParagraph,
11028 window: &mut Window,
11029 cx: &mut Context<Self>,
11030 ) {
11031 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11032 cx.propagate();
11033 return;
11034 }
11035 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11036 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11037 s.move_with(|map, selection| {
11038 selection.collapse_to(
11039 movement::start_of_paragraph(map, selection.head(), 1),
11040 SelectionGoal::None,
11041 )
11042 });
11043 })
11044 }
11045
11046 pub fn move_to_end_of_paragraph(
11047 &mut self,
11048 _: &MoveToEndOfParagraph,
11049 window: &mut Window,
11050 cx: &mut Context<Self>,
11051 ) {
11052 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11053 cx.propagate();
11054 return;
11055 }
11056 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11057 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11058 s.move_with(|map, selection| {
11059 selection.collapse_to(
11060 movement::end_of_paragraph(map, selection.head(), 1),
11061 SelectionGoal::None,
11062 )
11063 });
11064 })
11065 }
11066
11067 pub fn select_to_start_of_paragraph(
11068 &mut self,
11069 _: &SelectToStartOfParagraph,
11070 window: &mut Window,
11071 cx: &mut Context<Self>,
11072 ) {
11073 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11074 cx.propagate();
11075 return;
11076 }
11077 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11078 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11079 s.move_heads_with(|map, head, _| {
11080 (
11081 movement::start_of_paragraph(map, head, 1),
11082 SelectionGoal::None,
11083 )
11084 });
11085 })
11086 }
11087
11088 pub fn select_to_end_of_paragraph(
11089 &mut self,
11090 _: &SelectToEndOfParagraph,
11091 window: &mut Window,
11092 cx: &mut Context<Self>,
11093 ) {
11094 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11095 cx.propagate();
11096 return;
11097 }
11098 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11099 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11100 s.move_heads_with(|map, head, _| {
11101 (
11102 movement::end_of_paragraph(map, head, 1),
11103 SelectionGoal::None,
11104 )
11105 });
11106 })
11107 }
11108
11109 pub fn move_to_start_of_excerpt(
11110 &mut self,
11111 _: &MoveToStartOfExcerpt,
11112 window: &mut Window,
11113 cx: &mut Context<Self>,
11114 ) {
11115 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11116 cx.propagate();
11117 return;
11118 }
11119 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11120 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11121 s.move_with(|map, selection| {
11122 selection.collapse_to(
11123 movement::start_of_excerpt(
11124 map,
11125 selection.head(),
11126 workspace::searchable::Direction::Prev,
11127 ),
11128 SelectionGoal::None,
11129 )
11130 });
11131 })
11132 }
11133
11134 pub fn move_to_start_of_next_excerpt(
11135 &mut self,
11136 _: &MoveToStartOfNextExcerpt,
11137 window: &mut Window,
11138 cx: &mut Context<Self>,
11139 ) {
11140 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11141 cx.propagate();
11142 return;
11143 }
11144
11145 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11146 s.move_with(|map, selection| {
11147 selection.collapse_to(
11148 movement::start_of_excerpt(
11149 map,
11150 selection.head(),
11151 workspace::searchable::Direction::Next,
11152 ),
11153 SelectionGoal::None,
11154 )
11155 });
11156 })
11157 }
11158
11159 pub fn move_to_end_of_excerpt(
11160 &mut self,
11161 _: &MoveToEndOfExcerpt,
11162 window: &mut Window,
11163 cx: &mut Context<Self>,
11164 ) {
11165 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11166 cx.propagate();
11167 return;
11168 }
11169 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11170 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11171 s.move_with(|map, selection| {
11172 selection.collapse_to(
11173 movement::end_of_excerpt(
11174 map,
11175 selection.head(),
11176 workspace::searchable::Direction::Next,
11177 ),
11178 SelectionGoal::None,
11179 )
11180 });
11181 })
11182 }
11183
11184 pub fn move_to_end_of_previous_excerpt(
11185 &mut self,
11186 _: &MoveToEndOfPreviousExcerpt,
11187 window: &mut Window,
11188 cx: &mut Context<Self>,
11189 ) {
11190 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11191 cx.propagate();
11192 return;
11193 }
11194 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11195 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11196 s.move_with(|map, selection| {
11197 selection.collapse_to(
11198 movement::end_of_excerpt(
11199 map,
11200 selection.head(),
11201 workspace::searchable::Direction::Prev,
11202 ),
11203 SelectionGoal::None,
11204 )
11205 });
11206 })
11207 }
11208
11209 pub fn select_to_start_of_excerpt(
11210 &mut self,
11211 _: &SelectToStartOfExcerpt,
11212 window: &mut Window,
11213 cx: &mut Context<Self>,
11214 ) {
11215 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11216 cx.propagate();
11217 return;
11218 }
11219 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11220 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11221 s.move_heads_with(|map, head, _| {
11222 (
11223 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11224 SelectionGoal::None,
11225 )
11226 });
11227 })
11228 }
11229
11230 pub fn select_to_start_of_next_excerpt(
11231 &mut self,
11232 _: &SelectToStartOfNextExcerpt,
11233 window: &mut Window,
11234 cx: &mut Context<Self>,
11235 ) {
11236 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11237 cx.propagate();
11238 return;
11239 }
11240 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11241 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11242 s.move_heads_with(|map, head, _| {
11243 (
11244 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11245 SelectionGoal::None,
11246 )
11247 });
11248 })
11249 }
11250
11251 pub fn select_to_end_of_excerpt(
11252 &mut self,
11253 _: &SelectToEndOfExcerpt,
11254 window: &mut Window,
11255 cx: &mut Context<Self>,
11256 ) {
11257 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11258 cx.propagate();
11259 return;
11260 }
11261 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11262 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11263 s.move_heads_with(|map, head, _| {
11264 (
11265 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11266 SelectionGoal::None,
11267 )
11268 });
11269 })
11270 }
11271
11272 pub fn select_to_end_of_previous_excerpt(
11273 &mut self,
11274 _: &SelectToEndOfPreviousExcerpt,
11275 window: &mut Window,
11276 cx: &mut Context<Self>,
11277 ) {
11278 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11279 cx.propagate();
11280 return;
11281 }
11282 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11283 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11284 s.move_heads_with(|map, head, _| {
11285 (
11286 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11287 SelectionGoal::None,
11288 )
11289 });
11290 })
11291 }
11292
11293 pub fn move_to_beginning(
11294 &mut self,
11295 _: &MoveToBeginning,
11296 window: &mut Window,
11297 cx: &mut Context<Self>,
11298 ) {
11299 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11300 cx.propagate();
11301 return;
11302 }
11303 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11304 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11305 s.select_ranges(vec![0..0]);
11306 });
11307 }
11308
11309 pub fn select_to_beginning(
11310 &mut self,
11311 _: &SelectToBeginning,
11312 window: &mut Window,
11313 cx: &mut Context<Self>,
11314 ) {
11315 let mut selection = self.selections.last::<Point>(cx);
11316 selection.set_head(Point::zero(), SelectionGoal::None);
11317 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11318 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11319 s.select(vec![selection]);
11320 });
11321 }
11322
11323 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11324 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11325 cx.propagate();
11326 return;
11327 }
11328 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11329 let cursor = self.buffer.read(cx).read(cx).len();
11330 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11331 s.select_ranges(vec![cursor..cursor])
11332 });
11333 }
11334
11335 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11336 self.nav_history = nav_history;
11337 }
11338
11339 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11340 self.nav_history.as_ref()
11341 }
11342
11343 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11344 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11345 }
11346
11347 fn push_to_nav_history(
11348 &mut self,
11349 cursor_anchor: Anchor,
11350 new_position: Option<Point>,
11351 is_deactivate: bool,
11352 cx: &mut Context<Self>,
11353 ) {
11354 if let Some(nav_history) = self.nav_history.as_mut() {
11355 let buffer = self.buffer.read(cx).read(cx);
11356 let cursor_position = cursor_anchor.to_point(&buffer);
11357 let scroll_state = self.scroll_manager.anchor();
11358 let scroll_top_row = scroll_state.top_row(&buffer);
11359 drop(buffer);
11360
11361 if let Some(new_position) = new_position {
11362 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11363 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11364 return;
11365 }
11366 }
11367
11368 nav_history.push(
11369 Some(NavigationData {
11370 cursor_anchor,
11371 cursor_position,
11372 scroll_anchor: scroll_state,
11373 scroll_top_row,
11374 }),
11375 cx,
11376 );
11377 cx.emit(EditorEvent::PushedToNavHistory {
11378 anchor: cursor_anchor,
11379 is_deactivate,
11380 })
11381 }
11382 }
11383
11384 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11385 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11386 let buffer = self.buffer.read(cx).snapshot(cx);
11387 let mut selection = self.selections.first::<usize>(cx);
11388 selection.set_head(buffer.len(), SelectionGoal::None);
11389 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11390 s.select(vec![selection]);
11391 });
11392 }
11393
11394 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11395 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11396 let end = self.buffer.read(cx).read(cx).len();
11397 self.change_selections(None, window, cx, |s| {
11398 s.select_ranges(vec![0..end]);
11399 });
11400 }
11401
11402 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11403 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11404 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11405 let mut selections = self.selections.all::<Point>(cx);
11406 let max_point = display_map.buffer_snapshot.max_point();
11407 for selection in &mut selections {
11408 let rows = selection.spanned_rows(true, &display_map);
11409 selection.start = Point::new(rows.start.0, 0);
11410 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11411 selection.reversed = false;
11412 }
11413 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11414 s.select(selections);
11415 });
11416 }
11417
11418 pub fn split_selection_into_lines(
11419 &mut self,
11420 _: &SplitSelectionIntoLines,
11421 window: &mut Window,
11422 cx: &mut Context<Self>,
11423 ) {
11424 let selections = self
11425 .selections
11426 .all::<Point>(cx)
11427 .into_iter()
11428 .map(|selection| selection.start..selection.end)
11429 .collect::<Vec<_>>();
11430 self.unfold_ranges(&selections, true, true, cx);
11431
11432 let mut new_selection_ranges = Vec::new();
11433 {
11434 let buffer = self.buffer.read(cx).read(cx);
11435 for selection in selections {
11436 for row in selection.start.row..selection.end.row {
11437 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11438 new_selection_ranges.push(cursor..cursor);
11439 }
11440
11441 let is_multiline_selection = selection.start.row != selection.end.row;
11442 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11443 // so this action feels more ergonomic when paired with other selection operations
11444 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11445 if !should_skip_last {
11446 new_selection_ranges.push(selection.end..selection.end);
11447 }
11448 }
11449 }
11450 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11451 s.select_ranges(new_selection_ranges);
11452 });
11453 }
11454
11455 pub fn add_selection_above(
11456 &mut self,
11457 _: &AddSelectionAbove,
11458 window: &mut Window,
11459 cx: &mut Context<Self>,
11460 ) {
11461 self.add_selection(true, window, cx);
11462 }
11463
11464 pub fn add_selection_below(
11465 &mut self,
11466 _: &AddSelectionBelow,
11467 window: &mut Window,
11468 cx: &mut Context<Self>,
11469 ) {
11470 self.add_selection(false, window, cx);
11471 }
11472
11473 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11474 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11475
11476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11477 let mut selections = self.selections.all::<Point>(cx);
11478 let text_layout_details = self.text_layout_details(window);
11479 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11480 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11481 let range = oldest_selection.display_range(&display_map).sorted();
11482
11483 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11484 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11485 let positions = start_x.min(end_x)..start_x.max(end_x);
11486
11487 selections.clear();
11488 let mut stack = Vec::new();
11489 for row in range.start.row().0..=range.end.row().0 {
11490 if let Some(selection) = self.selections.build_columnar_selection(
11491 &display_map,
11492 DisplayRow(row),
11493 &positions,
11494 oldest_selection.reversed,
11495 &text_layout_details,
11496 ) {
11497 stack.push(selection.id);
11498 selections.push(selection);
11499 }
11500 }
11501
11502 if above {
11503 stack.reverse();
11504 }
11505
11506 AddSelectionsState { above, stack }
11507 });
11508
11509 let last_added_selection = *state.stack.last().unwrap();
11510 let mut new_selections = Vec::new();
11511 if above == state.above {
11512 let end_row = if above {
11513 DisplayRow(0)
11514 } else {
11515 display_map.max_point().row()
11516 };
11517
11518 'outer: for selection in selections {
11519 if selection.id == last_added_selection {
11520 let range = selection.display_range(&display_map).sorted();
11521 debug_assert_eq!(range.start.row(), range.end.row());
11522 let mut row = range.start.row();
11523 let positions =
11524 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11525 px(start)..px(end)
11526 } else {
11527 let start_x =
11528 display_map.x_for_display_point(range.start, &text_layout_details);
11529 let end_x =
11530 display_map.x_for_display_point(range.end, &text_layout_details);
11531 start_x.min(end_x)..start_x.max(end_x)
11532 };
11533
11534 while row != end_row {
11535 if above {
11536 row.0 -= 1;
11537 } else {
11538 row.0 += 1;
11539 }
11540
11541 if let Some(new_selection) = self.selections.build_columnar_selection(
11542 &display_map,
11543 row,
11544 &positions,
11545 selection.reversed,
11546 &text_layout_details,
11547 ) {
11548 state.stack.push(new_selection.id);
11549 if above {
11550 new_selections.push(new_selection);
11551 new_selections.push(selection);
11552 } else {
11553 new_selections.push(selection);
11554 new_selections.push(new_selection);
11555 }
11556
11557 continue 'outer;
11558 }
11559 }
11560 }
11561
11562 new_selections.push(selection);
11563 }
11564 } else {
11565 new_selections = selections;
11566 new_selections.retain(|s| s.id != last_added_selection);
11567 state.stack.pop();
11568 }
11569
11570 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11571 s.select(new_selections);
11572 });
11573 if state.stack.len() > 1 {
11574 self.add_selections_state = Some(state);
11575 }
11576 }
11577
11578 pub fn select_next_match_internal(
11579 &mut self,
11580 display_map: &DisplaySnapshot,
11581 replace_newest: bool,
11582 autoscroll: Option<Autoscroll>,
11583 window: &mut Window,
11584 cx: &mut Context<Self>,
11585 ) -> Result<()> {
11586 fn select_next_match_ranges(
11587 this: &mut Editor,
11588 range: Range<usize>,
11589 replace_newest: bool,
11590 auto_scroll: Option<Autoscroll>,
11591 window: &mut Window,
11592 cx: &mut Context<Editor>,
11593 ) {
11594 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
11595 this.change_selections(auto_scroll, window, cx, |s| {
11596 if replace_newest {
11597 s.delete(s.newest_anchor().id);
11598 }
11599 s.insert_range(range.clone());
11600 });
11601 }
11602
11603 let buffer = &display_map.buffer_snapshot;
11604 let mut selections = self.selections.all::<usize>(cx);
11605 if let Some(mut select_next_state) = self.select_next_state.take() {
11606 let query = &select_next_state.query;
11607 if !select_next_state.done {
11608 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11609 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11610 let mut next_selected_range = None;
11611
11612 let bytes_after_last_selection =
11613 buffer.bytes_in_range(last_selection.end..buffer.len());
11614 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11615 let query_matches = query
11616 .stream_find_iter(bytes_after_last_selection)
11617 .map(|result| (last_selection.end, result))
11618 .chain(
11619 query
11620 .stream_find_iter(bytes_before_first_selection)
11621 .map(|result| (0, result)),
11622 );
11623
11624 for (start_offset, query_match) in query_matches {
11625 let query_match = query_match.unwrap(); // can only fail due to I/O
11626 let offset_range =
11627 start_offset + query_match.start()..start_offset + query_match.end();
11628 let display_range = offset_range.start.to_display_point(display_map)
11629 ..offset_range.end.to_display_point(display_map);
11630
11631 if !select_next_state.wordwise
11632 || (!movement::is_inside_word(display_map, display_range.start)
11633 && !movement::is_inside_word(display_map, display_range.end))
11634 {
11635 // TODO: This is n^2, because we might check all the selections
11636 if !selections
11637 .iter()
11638 .any(|selection| selection.range().overlaps(&offset_range))
11639 {
11640 next_selected_range = Some(offset_range);
11641 break;
11642 }
11643 }
11644 }
11645
11646 if let Some(next_selected_range) = next_selected_range {
11647 select_next_match_ranges(
11648 self,
11649 next_selected_range,
11650 replace_newest,
11651 autoscroll,
11652 window,
11653 cx,
11654 );
11655 } else {
11656 select_next_state.done = true;
11657 }
11658 }
11659
11660 self.select_next_state = Some(select_next_state);
11661 } else {
11662 let mut only_carets = true;
11663 let mut same_text_selected = true;
11664 let mut selected_text = None;
11665
11666 let mut selections_iter = selections.iter().peekable();
11667 while let Some(selection) = selections_iter.next() {
11668 if selection.start != selection.end {
11669 only_carets = false;
11670 }
11671
11672 if same_text_selected {
11673 if selected_text.is_none() {
11674 selected_text =
11675 Some(buffer.text_for_range(selection.range()).collect::<String>());
11676 }
11677
11678 if let Some(next_selection) = selections_iter.peek() {
11679 if next_selection.range().len() == selection.range().len() {
11680 let next_selected_text = buffer
11681 .text_for_range(next_selection.range())
11682 .collect::<String>();
11683 if Some(next_selected_text) != selected_text {
11684 same_text_selected = false;
11685 selected_text = None;
11686 }
11687 } else {
11688 same_text_selected = false;
11689 selected_text = None;
11690 }
11691 }
11692 }
11693 }
11694
11695 if only_carets {
11696 for selection in &mut selections {
11697 let word_range = movement::surrounding_word(
11698 display_map,
11699 selection.start.to_display_point(display_map),
11700 );
11701 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11702 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11703 selection.goal = SelectionGoal::None;
11704 selection.reversed = false;
11705 select_next_match_ranges(
11706 self,
11707 selection.start..selection.end,
11708 replace_newest,
11709 autoscroll,
11710 window,
11711 cx,
11712 );
11713 }
11714
11715 if selections.len() == 1 {
11716 let selection = selections
11717 .last()
11718 .expect("ensured that there's only one selection");
11719 let query = buffer
11720 .text_for_range(selection.start..selection.end)
11721 .collect::<String>();
11722 let is_empty = query.is_empty();
11723 let select_state = SelectNextState {
11724 query: AhoCorasick::new(&[query])?,
11725 wordwise: true,
11726 done: is_empty,
11727 };
11728 self.select_next_state = Some(select_state);
11729 } else {
11730 self.select_next_state = None;
11731 }
11732 } else if let Some(selected_text) = selected_text {
11733 self.select_next_state = Some(SelectNextState {
11734 query: AhoCorasick::new(&[selected_text])?,
11735 wordwise: false,
11736 done: false,
11737 });
11738 self.select_next_match_internal(
11739 display_map,
11740 replace_newest,
11741 autoscroll,
11742 window,
11743 cx,
11744 )?;
11745 }
11746 }
11747 Ok(())
11748 }
11749
11750 pub fn select_all_matches(
11751 &mut self,
11752 _action: &SelectAllMatches,
11753 window: &mut Window,
11754 cx: &mut Context<Self>,
11755 ) -> Result<()> {
11756 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11757
11758 self.push_to_selection_history();
11759 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11760
11761 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11762 let Some(select_next_state) = self.select_next_state.as_mut() else {
11763 return Ok(());
11764 };
11765 if select_next_state.done {
11766 return Ok(());
11767 }
11768
11769 let mut new_selections = Vec::new();
11770
11771 let reversed = self.selections.oldest::<usize>(cx).reversed;
11772 let buffer = &display_map.buffer_snapshot;
11773 let query_matches = select_next_state
11774 .query
11775 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11776
11777 for query_match in query_matches.into_iter() {
11778 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
11779 let offset_range = if reversed {
11780 query_match.end()..query_match.start()
11781 } else {
11782 query_match.start()..query_match.end()
11783 };
11784 let display_range = offset_range.start.to_display_point(&display_map)
11785 ..offset_range.end.to_display_point(&display_map);
11786
11787 if !select_next_state.wordwise
11788 || (!movement::is_inside_word(&display_map, display_range.start)
11789 && !movement::is_inside_word(&display_map, display_range.end))
11790 {
11791 new_selections.push(offset_range.start..offset_range.end);
11792 }
11793 }
11794
11795 select_next_state.done = true;
11796 self.unfold_ranges(&new_selections.clone(), false, false, cx);
11797 self.change_selections(None, window, cx, |selections| {
11798 selections.select_ranges(new_selections)
11799 });
11800
11801 Ok(())
11802 }
11803
11804 pub fn select_next(
11805 &mut self,
11806 action: &SelectNext,
11807 window: &mut Window,
11808 cx: &mut Context<Self>,
11809 ) -> Result<()> {
11810 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11811 self.push_to_selection_history();
11812 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11813 self.select_next_match_internal(
11814 &display_map,
11815 action.replace_newest,
11816 Some(Autoscroll::newest()),
11817 window,
11818 cx,
11819 )?;
11820 Ok(())
11821 }
11822
11823 pub fn select_previous(
11824 &mut self,
11825 action: &SelectPrevious,
11826 window: &mut Window,
11827 cx: &mut Context<Self>,
11828 ) -> Result<()> {
11829 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11830 self.push_to_selection_history();
11831 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11832 let buffer = &display_map.buffer_snapshot;
11833 let mut selections = self.selections.all::<usize>(cx);
11834 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11835 let query = &select_prev_state.query;
11836 if !select_prev_state.done {
11837 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11838 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11839 let mut next_selected_range = None;
11840 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11841 let bytes_before_last_selection =
11842 buffer.reversed_bytes_in_range(0..last_selection.start);
11843 let bytes_after_first_selection =
11844 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11845 let query_matches = query
11846 .stream_find_iter(bytes_before_last_selection)
11847 .map(|result| (last_selection.start, result))
11848 .chain(
11849 query
11850 .stream_find_iter(bytes_after_first_selection)
11851 .map(|result| (buffer.len(), result)),
11852 );
11853 for (end_offset, query_match) in query_matches {
11854 let query_match = query_match.unwrap(); // can only fail due to I/O
11855 let offset_range =
11856 end_offset - query_match.end()..end_offset - query_match.start();
11857 let display_range = offset_range.start.to_display_point(&display_map)
11858 ..offset_range.end.to_display_point(&display_map);
11859
11860 if !select_prev_state.wordwise
11861 || (!movement::is_inside_word(&display_map, display_range.start)
11862 && !movement::is_inside_word(&display_map, display_range.end))
11863 {
11864 next_selected_range = Some(offset_range);
11865 break;
11866 }
11867 }
11868
11869 if let Some(next_selected_range) = next_selected_range {
11870 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11871 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11872 if action.replace_newest {
11873 s.delete(s.newest_anchor().id);
11874 }
11875 s.insert_range(next_selected_range);
11876 });
11877 } else {
11878 select_prev_state.done = true;
11879 }
11880 }
11881
11882 self.select_prev_state = Some(select_prev_state);
11883 } else {
11884 let mut only_carets = true;
11885 let mut same_text_selected = true;
11886 let mut selected_text = None;
11887
11888 let mut selections_iter = selections.iter().peekable();
11889 while let Some(selection) = selections_iter.next() {
11890 if selection.start != selection.end {
11891 only_carets = false;
11892 }
11893
11894 if same_text_selected {
11895 if selected_text.is_none() {
11896 selected_text =
11897 Some(buffer.text_for_range(selection.range()).collect::<String>());
11898 }
11899
11900 if let Some(next_selection) = selections_iter.peek() {
11901 if next_selection.range().len() == selection.range().len() {
11902 let next_selected_text = buffer
11903 .text_for_range(next_selection.range())
11904 .collect::<String>();
11905 if Some(next_selected_text) != selected_text {
11906 same_text_selected = false;
11907 selected_text = None;
11908 }
11909 } else {
11910 same_text_selected = false;
11911 selected_text = None;
11912 }
11913 }
11914 }
11915 }
11916
11917 if only_carets {
11918 for selection in &mut selections {
11919 let word_range = movement::surrounding_word(
11920 &display_map,
11921 selection.start.to_display_point(&display_map),
11922 );
11923 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11924 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11925 selection.goal = SelectionGoal::None;
11926 selection.reversed = false;
11927 }
11928 if selections.len() == 1 {
11929 let selection = selections
11930 .last()
11931 .expect("ensured that there's only one selection");
11932 let query = buffer
11933 .text_for_range(selection.start..selection.end)
11934 .collect::<String>();
11935 let is_empty = query.is_empty();
11936 let select_state = SelectNextState {
11937 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11938 wordwise: true,
11939 done: is_empty,
11940 };
11941 self.select_prev_state = Some(select_state);
11942 } else {
11943 self.select_prev_state = None;
11944 }
11945
11946 self.unfold_ranges(
11947 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11948 false,
11949 true,
11950 cx,
11951 );
11952 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11953 s.select(selections);
11954 });
11955 } else if let Some(selected_text) = selected_text {
11956 self.select_prev_state = Some(SelectNextState {
11957 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11958 wordwise: false,
11959 done: false,
11960 });
11961 self.select_previous(action, window, cx)?;
11962 }
11963 }
11964 Ok(())
11965 }
11966
11967 pub fn toggle_comments(
11968 &mut self,
11969 action: &ToggleComments,
11970 window: &mut Window,
11971 cx: &mut Context<Self>,
11972 ) {
11973 if self.read_only(cx) {
11974 return;
11975 }
11976 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11977 let text_layout_details = &self.text_layout_details(window);
11978 self.transact(window, cx, |this, window, cx| {
11979 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11980 let mut edits = Vec::new();
11981 let mut selection_edit_ranges = Vec::new();
11982 let mut last_toggled_row = None;
11983 let snapshot = this.buffer.read(cx).read(cx);
11984 let empty_str: Arc<str> = Arc::default();
11985 let mut suffixes_inserted = Vec::new();
11986 let ignore_indent = action.ignore_indent;
11987
11988 fn comment_prefix_range(
11989 snapshot: &MultiBufferSnapshot,
11990 row: MultiBufferRow,
11991 comment_prefix: &str,
11992 comment_prefix_whitespace: &str,
11993 ignore_indent: bool,
11994 ) -> Range<Point> {
11995 let indent_size = if ignore_indent {
11996 0
11997 } else {
11998 snapshot.indent_size_for_line(row).len
11999 };
12000
12001 let start = Point::new(row.0, indent_size);
12002
12003 let mut line_bytes = snapshot
12004 .bytes_in_range(start..snapshot.max_point())
12005 .flatten()
12006 .copied();
12007
12008 // If this line currently begins with the line comment prefix, then record
12009 // the range containing the prefix.
12010 if line_bytes
12011 .by_ref()
12012 .take(comment_prefix.len())
12013 .eq(comment_prefix.bytes())
12014 {
12015 // Include any whitespace that matches the comment prefix.
12016 let matching_whitespace_len = line_bytes
12017 .zip(comment_prefix_whitespace.bytes())
12018 .take_while(|(a, b)| a == b)
12019 .count() as u32;
12020 let end = Point::new(
12021 start.row,
12022 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12023 );
12024 start..end
12025 } else {
12026 start..start
12027 }
12028 }
12029
12030 fn comment_suffix_range(
12031 snapshot: &MultiBufferSnapshot,
12032 row: MultiBufferRow,
12033 comment_suffix: &str,
12034 comment_suffix_has_leading_space: bool,
12035 ) -> Range<Point> {
12036 let end = Point::new(row.0, snapshot.line_len(row));
12037 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12038
12039 let mut line_end_bytes = snapshot
12040 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12041 .flatten()
12042 .copied();
12043
12044 let leading_space_len = if suffix_start_column > 0
12045 && line_end_bytes.next() == Some(b' ')
12046 && comment_suffix_has_leading_space
12047 {
12048 1
12049 } else {
12050 0
12051 };
12052
12053 // If this line currently begins with the line comment prefix, then record
12054 // the range containing the prefix.
12055 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12056 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12057 start..end
12058 } else {
12059 end..end
12060 }
12061 }
12062
12063 // TODO: Handle selections that cross excerpts
12064 for selection in &mut selections {
12065 let start_column = snapshot
12066 .indent_size_for_line(MultiBufferRow(selection.start.row))
12067 .len;
12068 let language = if let Some(language) =
12069 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12070 {
12071 language
12072 } else {
12073 continue;
12074 };
12075
12076 selection_edit_ranges.clear();
12077
12078 // If multiple selections contain a given row, avoid processing that
12079 // row more than once.
12080 let mut start_row = MultiBufferRow(selection.start.row);
12081 if last_toggled_row == Some(start_row) {
12082 start_row = start_row.next_row();
12083 }
12084 let end_row =
12085 if selection.end.row > selection.start.row && selection.end.column == 0 {
12086 MultiBufferRow(selection.end.row - 1)
12087 } else {
12088 MultiBufferRow(selection.end.row)
12089 };
12090 last_toggled_row = Some(end_row);
12091
12092 if start_row > end_row {
12093 continue;
12094 }
12095
12096 // If the language has line comments, toggle those.
12097 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12098
12099 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12100 if ignore_indent {
12101 full_comment_prefixes = full_comment_prefixes
12102 .into_iter()
12103 .map(|s| Arc::from(s.trim_end()))
12104 .collect();
12105 }
12106
12107 if !full_comment_prefixes.is_empty() {
12108 let first_prefix = full_comment_prefixes
12109 .first()
12110 .expect("prefixes is non-empty");
12111 let prefix_trimmed_lengths = full_comment_prefixes
12112 .iter()
12113 .map(|p| p.trim_end_matches(' ').len())
12114 .collect::<SmallVec<[usize; 4]>>();
12115
12116 let mut all_selection_lines_are_comments = true;
12117
12118 for row in start_row.0..=end_row.0 {
12119 let row = MultiBufferRow(row);
12120 if start_row < end_row && snapshot.is_line_blank(row) {
12121 continue;
12122 }
12123
12124 let prefix_range = full_comment_prefixes
12125 .iter()
12126 .zip(prefix_trimmed_lengths.iter().copied())
12127 .map(|(prefix, trimmed_prefix_len)| {
12128 comment_prefix_range(
12129 snapshot.deref(),
12130 row,
12131 &prefix[..trimmed_prefix_len],
12132 &prefix[trimmed_prefix_len..],
12133 ignore_indent,
12134 )
12135 })
12136 .max_by_key(|range| range.end.column - range.start.column)
12137 .expect("prefixes is non-empty");
12138
12139 if prefix_range.is_empty() {
12140 all_selection_lines_are_comments = false;
12141 }
12142
12143 selection_edit_ranges.push(prefix_range);
12144 }
12145
12146 if all_selection_lines_are_comments {
12147 edits.extend(
12148 selection_edit_ranges
12149 .iter()
12150 .cloned()
12151 .map(|range| (range, empty_str.clone())),
12152 );
12153 } else {
12154 let min_column = selection_edit_ranges
12155 .iter()
12156 .map(|range| range.start.column)
12157 .min()
12158 .unwrap_or(0);
12159 edits.extend(selection_edit_ranges.iter().map(|range| {
12160 let position = Point::new(range.start.row, min_column);
12161 (position..position, first_prefix.clone())
12162 }));
12163 }
12164 } else if let Some((full_comment_prefix, comment_suffix)) =
12165 language.block_comment_delimiters()
12166 {
12167 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12168 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12169 let prefix_range = comment_prefix_range(
12170 snapshot.deref(),
12171 start_row,
12172 comment_prefix,
12173 comment_prefix_whitespace,
12174 ignore_indent,
12175 );
12176 let suffix_range = comment_suffix_range(
12177 snapshot.deref(),
12178 end_row,
12179 comment_suffix.trim_start_matches(' '),
12180 comment_suffix.starts_with(' '),
12181 );
12182
12183 if prefix_range.is_empty() || suffix_range.is_empty() {
12184 edits.push((
12185 prefix_range.start..prefix_range.start,
12186 full_comment_prefix.clone(),
12187 ));
12188 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12189 suffixes_inserted.push((end_row, comment_suffix.len()));
12190 } else {
12191 edits.push((prefix_range, empty_str.clone()));
12192 edits.push((suffix_range, empty_str.clone()));
12193 }
12194 } else {
12195 continue;
12196 }
12197 }
12198
12199 drop(snapshot);
12200 this.buffer.update(cx, |buffer, cx| {
12201 buffer.edit(edits, None, cx);
12202 });
12203
12204 // Adjust selections so that they end before any comment suffixes that
12205 // were inserted.
12206 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12207 let mut selections = this.selections.all::<Point>(cx);
12208 let snapshot = this.buffer.read(cx).read(cx);
12209 for selection in &mut selections {
12210 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12211 match row.cmp(&MultiBufferRow(selection.end.row)) {
12212 Ordering::Less => {
12213 suffixes_inserted.next();
12214 continue;
12215 }
12216 Ordering::Greater => break,
12217 Ordering::Equal => {
12218 if selection.end.column == snapshot.line_len(row) {
12219 if selection.is_empty() {
12220 selection.start.column -= suffix_len as u32;
12221 }
12222 selection.end.column -= suffix_len as u32;
12223 }
12224 break;
12225 }
12226 }
12227 }
12228 }
12229
12230 drop(snapshot);
12231 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12232 s.select(selections)
12233 });
12234
12235 let selections = this.selections.all::<Point>(cx);
12236 let selections_on_single_row = selections.windows(2).all(|selections| {
12237 selections[0].start.row == selections[1].start.row
12238 && selections[0].end.row == selections[1].end.row
12239 && selections[0].start.row == selections[0].end.row
12240 });
12241 let selections_selecting = selections
12242 .iter()
12243 .any(|selection| selection.start != selection.end);
12244 let advance_downwards = action.advance_downwards
12245 && selections_on_single_row
12246 && !selections_selecting
12247 && !matches!(this.mode, EditorMode::SingleLine { .. });
12248
12249 if advance_downwards {
12250 let snapshot = this.buffer.read(cx).snapshot(cx);
12251
12252 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12253 s.move_cursors_with(|display_snapshot, display_point, _| {
12254 let mut point = display_point.to_point(display_snapshot);
12255 point.row += 1;
12256 point = snapshot.clip_point(point, Bias::Left);
12257 let display_point = point.to_display_point(display_snapshot);
12258 let goal = SelectionGoal::HorizontalPosition(
12259 display_snapshot
12260 .x_for_display_point(display_point, text_layout_details)
12261 .into(),
12262 );
12263 (display_point, goal)
12264 })
12265 });
12266 }
12267 });
12268 }
12269
12270 pub fn select_enclosing_symbol(
12271 &mut self,
12272 _: &SelectEnclosingSymbol,
12273 window: &mut Window,
12274 cx: &mut Context<Self>,
12275 ) {
12276 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12277
12278 let buffer = self.buffer.read(cx).snapshot(cx);
12279 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12280
12281 fn update_selection(
12282 selection: &Selection<usize>,
12283 buffer_snap: &MultiBufferSnapshot,
12284 ) -> Option<Selection<usize>> {
12285 let cursor = selection.head();
12286 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12287 for symbol in symbols.iter().rev() {
12288 let start = symbol.range.start.to_offset(buffer_snap);
12289 let end = symbol.range.end.to_offset(buffer_snap);
12290 let new_range = start..end;
12291 if start < selection.start || end > selection.end {
12292 return Some(Selection {
12293 id: selection.id,
12294 start: new_range.start,
12295 end: new_range.end,
12296 goal: SelectionGoal::None,
12297 reversed: selection.reversed,
12298 });
12299 }
12300 }
12301 None
12302 }
12303
12304 let mut selected_larger_symbol = false;
12305 let new_selections = old_selections
12306 .iter()
12307 .map(|selection| match update_selection(selection, &buffer) {
12308 Some(new_selection) => {
12309 if new_selection.range() != selection.range() {
12310 selected_larger_symbol = true;
12311 }
12312 new_selection
12313 }
12314 None => selection.clone(),
12315 })
12316 .collect::<Vec<_>>();
12317
12318 if selected_larger_symbol {
12319 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12320 s.select(new_selections);
12321 });
12322 }
12323 }
12324
12325 pub fn select_larger_syntax_node(
12326 &mut self,
12327 _: &SelectLargerSyntaxNode,
12328 window: &mut Window,
12329 cx: &mut Context<Self>,
12330 ) {
12331 let Some(visible_row_count) = self.visible_row_count() else {
12332 return;
12333 };
12334 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12335 if old_selections.is_empty() {
12336 return;
12337 }
12338
12339 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12340
12341 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12342 let buffer = self.buffer.read(cx).snapshot(cx);
12343
12344 let mut selected_larger_node = false;
12345 let mut new_selections = old_selections
12346 .iter()
12347 .map(|selection| {
12348 let old_range = selection.start..selection.end;
12349 let mut new_range = old_range.clone();
12350 let mut new_node = None;
12351 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12352 {
12353 new_node = Some(node);
12354 new_range = match containing_range {
12355 MultiOrSingleBufferOffsetRange::Single(_) => break,
12356 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12357 };
12358 if !display_map.intersects_fold(new_range.start)
12359 && !display_map.intersects_fold(new_range.end)
12360 {
12361 break;
12362 }
12363 }
12364
12365 if let Some(node) = new_node {
12366 // Log the ancestor, to support using this action as a way to explore TreeSitter
12367 // nodes. Parent and grandparent are also logged because this operation will not
12368 // visit nodes that have the same range as their parent.
12369 log::info!("Node: {node:?}");
12370 let parent = node.parent();
12371 log::info!("Parent: {parent:?}");
12372 let grandparent = parent.and_then(|x| x.parent());
12373 log::info!("Grandparent: {grandparent:?}");
12374 }
12375
12376 selected_larger_node |= new_range != old_range;
12377 Selection {
12378 id: selection.id,
12379 start: new_range.start,
12380 end: new_range.end,
12381 goal: SelectionGoal::None,
12382 reversed: selection.reversed,
12383 }
12384 })
12385 .collect::<Vec<_>>();
12386
12387 if !selected_larger_node {
12388 return; // don't put this call in the history
12389 }
12390
12391 // scroll based on transformation done to the last selection created by the user
12392 let (last_old, last_new) = old_selections
12393 .last()
12394 .zip(new_selections.last().cloned())
12395 .expect("old_selections isn't empty");
12396
12397 // revert selection
12398 let is_selection_reversed = {
12399 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12400 new_selections.last_mut().expect("checked above").reversed =
12401 should_newest_selection_be_reversed;
12402 should_newest_selection_be_reversed
12403 };
12404
12405 if selected_larger_node {
12406 self.select_syntax_node_history.disable_clearing = true;
12407 self.change_selections(None, window, cx, |s| {
12408 s.select(new_selections.clone());
12409 });
12410 self.select_syntax_node_history.disable_clearing = false;
12411 }
12412
12413 let start_row = last_new.start.to_display_point(&display_map).row().0;
12414 let end_row = last_new.end.to_display_point(&display_map).row().0;
12415 let selection_height = end_row - start_row + 1;
12416 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12417
12418 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
12419 let scroll_behavior = if fits_on_the_screen {
12420 self.request_autoscroll(Autoscroll::fit(), cx);
12421 SelectSyntaxNodeScrollBehavior::FitSelection
12422 } else if is_selection_reversed {
12423 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12424 SelectSyntaxNodeScrollBehavior::CursorTop
12425 } else {
12426 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12427 SelectSyntaxNodeScrollBehavior::CursorBottom
12428 };
12429
12430 self.select_syntax_node_history.push((
12431 old_selections,
12432 scroll_behavior,
12433 is_selection_reversed,
12434 ));
12435 }
12436
12437 pub fn select_smaller_syntax_node(
12438 &mut self,
12439 _: &SelectSmallerSyntaxNode,
12440 window: &mut Window,
12441 cx: &mut Context<Self>,
12442 ) {
12443 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12444
12445 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12446 self.select_syntax_node_history.pop()
12447 {
12448 if let Some(selection) = selections.last_mut() {
12449 selection.reversed = is_selection_reversed;
12450 }
12451
12452 self.select_syntax_node_history.disable_clearing = true;
12453 self.change_selections(None, window, cx, |s| {
12454 s.select(selections.to_vec());
12455 });
12456 self.select_syntax_node_history.disable_clearing = false;
12457
12458 match scroll_behavior {
12459 SelectSyntaxNodeScrollBehavior::CursorTop => {
12460 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12461 }
12462 SelectSyntaxNodeScrollBehavior::FitSelection => {
12463 self.request_autoscroll(Autoscroll::fit(), cx);
12464 }
12465 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12466 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12467 }
12468 }
12469 }
12470 }
12471
12472 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12473 if !EditorSettings::get_global(cx).gutter.runnables {
12474 self.clear_tasks();
12475 return Task::ready(());
12476 }
12477 let project = self.project.as_ref().map(Entity::downgrade);
12478 let task_sources = self.lsp_task_sources(cx);
12479 cx.spawn_in(window, async move |editor, cx| {
12480 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12481 let Some(project) = project.and_then(|p| p.upgrade()) else {
12482 return;
12483 };
12484 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
12485 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12486 }) else {
12487 return;
12488 };
12489
12490 let hide_runnables = project
12491 .update(cx, |project, cx| {
12492 // Do not display any test indicators in non-dev server remote projects.
12493 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12494 })
12495 .unwrap_or(true);
12496 if hide_runnables {
12497 return;
12498 }
12499 let new_rows =
12500 cx.background_spawn({
12501 let snapshot = display_snapshot.clone();
12502 async move {
12503 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12504 }
12505 })
12506 .await;
12507 let Ok(lsp_tasks) =
12508 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
12509 else {
12510 return;
12511 };
12512 let lsp_tasks = lsp_tasks.await;
12513
12514 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
12515 lsp_tasks
12516 .into_iter()
12517 .flat_map(|(kind, tasks)| {
12518 tasks.into_iter().filter_map(move |(location, task)| {
12519 Some((kind.clone(), location?, task))
12520 })
12521 })
12522 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
12523 let buffer = location.target.buffer;
12524 let buffer_snapshot = buffer.read(cx).snapshot();
12525 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
12526 |(excerpt_id, snapshot, _)| {
12527 if snapshot.remote_id() == buffer_snapshot.remote_id() {
12528 display_snapshot
12529 .buffer_snapshot
12530 .anchor_in_excerpt(excerpt_id, location.target.range.start)
12531 } else {
12532 None
12533 }
12534 },
12535 );
12536 if let Some(offset) = offset {
12537 let task_buffer_range =
12538 location.target.range.to_point(&buffer_snapshot);
12539 let context_buffer_range =
12540 task_buffer_range.to_offset(&buffer_snapshot);
12541 let context_range = BufferOffset(context_buffer_range.start)
12542 ..BufferOffset(context_buffer_range.end);
12543
12544 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
12545 .or_insert_with(|| RunnableTasks {
12546 templates: Vec::new(),
12547 offset,
12548 column: task_buffer_range.start.column,
12549 extra_variables: HashMap::default(),
12550 context_range,
12551 })
12552 .templates
12553 .push((kind, task.original_task().clone()));
12554 }
12555
12556 acc
12557 })
12558 }) else {
12559 return;
12560 };
12561
12562 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12563 editor
12564 .update(cx, |editor, _| {
12565 editor.clear_tasks();
12566 for (key, mut value) in rows {
12567 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
12568 value.templates.extend(lsp_tasks.templates);
12569 }
12570
12571 editor.insert_tasks(key, value);
12572 }
12573 for (key, value) in lsp_tasks_by_rows {
12574 editor.insert_tasks(key, value);
12575 }
12576 })
12577 .ok();
12578 })
12579 }
12580 fn fetch_runnable_ranges(
12581 snapshot: &DisplaySnapshot,
12582 range: Range<Anchor>,
12583 ) -> Vec<language::RunnableRange> {
12584 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12585 }
12586
12587 fn runnable_rows(
12588 project: Entity<Project>,
12589 snapshot: DisplaySnapshot,
12590 runnable_ranges: Vec<RunnableRange>,
12591 mut cx: AsyncWindowContext,
12592 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
12593 runnable_ranges
12594 .into_iter()
12595 .filter_map(|mut runnable| {
12596 let tasks = cx
12597 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12598 .ok()?;
12599 if tasks.is_empty() {
12600 return None;
12601 }
12602
12603 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12604
12605 let row = snapshot
12606 .buffer_snapshot
12607 .buffer_line_for_row(MultiBufferRow(point.row))?
12608 .1
12609 .start
12610 .row;
12611
12612 let context_range =
12613 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12614 Some((
12615 (runnable.buffer_id, row),
12616 RunnableTasks {
12617 templates: tasks,
12618 offset: snapshot
12619 .buffer_snapshot
12620 .anchor_before(runnable.run_range.start),
12621 context_range,
12622 column: point.column,
12623 extra_variables: runnable.extra_captures,
12624 },
12625 ))
12626 })
12627 .collect()
12628 }
12629
12630 fn templates_with_tags(
12631 project: &Entity<Project>,
12632 runnable: &mut Runnable,
12633 cx: &mut App,
12634 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12635 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12636 let (worktree_id, file) = project
12637 .buffer_for_id(runnable.buffer, cx)
12638 .and_then(|buffer| buffer.read(cx).file())
12639 .map(|file| (file.worktree_id(cx), file.clone()))
12640 .unzip();
12641
12642 (
12643 project.task_store().read(cx).task_inventory().cloned(),
12644 worktree_id,
12645 file,
12646 )
12647 });
12648
12649 let mut templates_with_tags = mem::take(&mut runnable.tags)
12650 .into_iter()
12651 .flat_map(|RunnableTag(tag)| {
12652 inventory
12653 .as_ref()
12654 .into_iter()
12655 .flat_map(|inventory| {
12656 inventory.read(cx).list_tasks(
12657 file.clone(),
12658 Some(runnable.language.clone()),
12659 worktree_id,
12660 cx,
12661 )
12662 })
12663 .filter(move |(_, template)| {
12664 template.tags.iter().any(|source_tag| source_tag == &tag)
12665 })
12666 })
12667 .sorted_by_key(|(kind, _)| kind.to_owned())
12668 .collect::<Vec<_>>();
12669 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
12670 // Strongest source wins; if we have worktree tag binding, prefer that to
12671 // global and language bindings;
12672 // if we have a global binding, prefer that to language binding.
12673 let first_mismatch = templates_with_tags
12674 .iter()
12675 .position(|(tag_source, _)| tag_source != leading_tag_source);
12676 if let Some(index) = first_mismatch {
12677 templates_with_tags.truncate(index);
12678 }
12679 }
12680
12681 templates_with_tags
12682 }
12683
12684 pub fn move_to_enclosing_bracket(
12685 &mut self,
12686 _: &MoveToEnclosingBracket,
12687 window: &mut Window,
12688 cx: &mut Context<Self>,
12689 ) {
12690 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12691 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12692 s.move_offsets_with(|snapshot, selection| {
12693 let Some(enclosing_bracket_ranges) =
12694 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12695 else {
12696 return;
12697 };
12698
12699 let mut best_length = usize::MAX;
12700 let mut best_inside = false;
12701 let mut best_in_bracket_range = false;
12702 let mut best_destination = None;
12703 for (open, close) in enclosing_bracket_ranges {
12704 let close = close.to_inclusive();
12705 let length = close.end() - open.start;
12706 let inside = selection.start >= open.end && selection.end <= *close.start();
12707 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12708 || close.contains(&selection.head());
12709
12710 // If best is next to a bracket and current isn't, skip
12711 if !in_bracket_range && best_in_bracket_range {
12712 continue;
12713 }
12714
12715 // Prefer smaller lengths unless best is inside and current isn't
12716 if length > best_length && (best_inside || !inside) {
12717 continue;
12718 }
12719
12720 best_length = length;
12721 best_inside = inside;
12722 best_in_bracket_range = in_bracket_range;
12723 best_destination = Some(
12724 if close.contains(&selection.start) && close.contains(&selection.end) {
12725 if inside { open.end } else { open.start }
12726 } else if inside {
12727 *close.start()
12728 } else {
12729 *close.end()
12730 },
12731 );
12732 }
12733
12734 if let Some(destination) = best_destination {
12735 selection.collapse_to(destination, SelectionGoal::None);
12736 }
12737 })
12738 });
12739 }
12740
12741 pub fn undo_selection(
12742 &mut self,
12743 _: &UndoSelection,
12744 window: &mut Window,
12745 cx: &mut Context<Self>,
12746 ) {
12747 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12748 self.end_selection(window, cx);
12749 self.selection_history.mode = SelectionHistoryMode::Undoing;
12750 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12751 self.change_selections(None, window, cx, |s| {
12752 s.select_anchors(entry.selections.to_vec())
12753 });
12754 self.select_next_state = entry.select_next_state;
12755 self.select_prev_state = entry.select_prev_state;
12756 self.add_selections_state = entry.add_selections_state;
12757 self.request_autoscroll(Autoscroll::newest(), cx);
12758 }
12759 self.selection_history.mode = SelectionHistoryMode::Normal;
12760 }
12761
12762 pub fn redo_selection(
12763 &mut self,
12764 _: &RedoSelection,
12765 window: &mut Window,
12766 cx: &mut Context<Self>,
12767 ) {
12768 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12769 self.end_selection(window, cx);
12770 self.selection_history.mode = SelectionHistoryMode::Redoing;
12771 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12772 self.change_selections(None, window, cx, |s| {
12773 s.select_anchors(entry.selections.to_vec())
12774 });
12775 self.select_next_state = entry.select_next_state;
12776 self.select_prev_state = entry.select_prev_state;
12777 self.add_selections_state = entry.add_selections_state;
12778 self.request_autoscroll(Autoscroll::newest(), cx);
12779 }
12780 self.selection_history.mode = SelectionHistoryMode::Normal;
12781 }
12782
12783 pub fn expand_excerpts(
12784 &mut self,
12785 action: &ExpandExcerpts,
12786 _: &mut Window,
12787 cx: &mut Context<Self>,
12788 ) {
12789 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12790 }
12791
12792 pub fn expand_excerpts_down(
12793 &mut self,
12794 action: &ExpandExcerptsDown,
12795 _: &mut Window,
12796 cx: &mut Context<Self>,
12797 ) {
12798 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12799 }
12800
12801 pub fn expand_excerpts_up(
12802 &mut self,
12803 action: &ExpandExcerptsUp,
12804 _: &mut Window,
12805 cx: &mut Context<Self>,
12806 ) {
12807 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12808 }
12809
12810 pub fn expand_excerpts_for_direction(
12811 &mut self,
12812 lines: u32,
12813 direction: ExpandExcerptDirection,
12814
12815 cx: &mut Context<Self>,
12816 ) {
12817 let selections = self.selections.disjoint_anchors();
12818
12819 let lines = if lines == 0 {
12820 EditorSettings::get_global(cx).expand_excerpt_lines
12821 } else {
12822 lines
12823 };
12824
12825 self.buffer.update(cx, |buffer, cx| {
12826 let snapshot = buffer.snapshot(cx);
12827 let mut excerpt_ids = selections
12828 .iter()
12829 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12830 .collect::<Vec<_>>();
12831 excerpt_ids.sort();
12832 excerpt_ids.dedup();
12833 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12834 })
12835 }
12836
12837 pub fn expand_excerpt(
12838 &mut self,
12839 excerpt: ExcerptId,
12840 direction: ExpandExcerptDirection,
12841 window: &mut Window,
12842 cx: &mut Context<Self>,
12843 ) {
12844 let current_scroll_position = self.scroll_position(cx);
12845 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
12846 let mut should_scroll_up = false;
12847
12848 if direction == ExpandExcerptDirection::Down {
12849 let multi_buffer = self.buffer.read(cx);
12850 let snapshot = multi_buffer.snapshot(cx);
12851 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
12852 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
12853 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
12854 let buffer_snapshot = buffer.read(cx).snapshot();
12855 let excerpt_end_row =
12856 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
12857 let last_row = buffer_snapshot.max_point().row;
12858 let lines_below = last_row.saturating_sub(excerpt_end_row);
12859 should_scroll_up = lines_below >= lines_to_expand;
12860 }
12861 }
12862 }
12863 }
12864
12865 self.buffer.update(cx, |buffer, cx| {
12866 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
12867 });
12868
12869 if should_scroll_up {
12870 let new_scroll_position =
12871 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
12872 self.set_scroll_position(new_scroll_position, window, cx);
12873 }
12874 }
12875
12876 pub fn go_to_singleton_buffer_point(
12877 &mut self,
12878 point: Point,
12879 window: &mut Window,
12880 cx: &mut Context<Self>,
12881 ) {
12882 self.go_to_singleton_buffer_range(point..point, window, cx);
12883 }
12884
12885 pub fn go_to_singleton_buffer_range(
12886 &mut self,
12887 range: Range<Point>,
12888 window: &mut Window,
12889 cx: &mut Context<Self>,
12890 ) {
12891 let multibuffer = self.buffer().read(cx);
12892 let Some(buffer) = multibuffer.as_singleton() else {
12893 return;
12894 };
12895 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12896 return;
12897 };
12898 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12899 return;
12900 };
12901 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12902 s.select_anchor_ranges([start..end])
12903 });
12904 }
12905
12906 fn go_to_diagnostic(
12907 &mut self,
12908 _: &GoToDiagnostic,
12909 window: &mut Window,
12910 cx: &mut Context<Self>,
12911 ) {
12912 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12913 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12914 }
12915
12916 fn go_to_prev_diagnostic(
12917 &mut self,
12918 _: &GoToPreviousDiagnostic,
12919 window: &mut Window,
12920 cx: &mut Context<Self>,
12921 ) {
12922 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12923 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12924 }
12925
12926 pub fn go_to_diagnostic_impl(
12927 &mut self,
12928 direction: Direction,
12929 window: &mut Window,
12930 cx: &mut Context<Self>,
12931 ) {
12932 let buffer = self.buffer.read(cx).snapshot(cx);
12933 let selection = self.selections.newest::<usize>(cx);
12934 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12935 if direction == Direction::Next {
12936 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12937 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12938 return;
12939 };
12940 self.activate_diagnostics(
12941 buffer_id,
12942 popover.local_diagnostic.diagnostic.group_id,
12943 window,
12944 cx,
12945 );
12946 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12947 let primary_range_start = active_diagnostics.primary_range.start;
12948 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12949 let mut new_selection = s.newest_anchor().clone();
12950 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12951 s.select_anchors(vec![new_selection.clone()]);
12952 });
12953 self.refresh_inline_completion(false, true, window, cx);
12954 }
12955 return;
12956 }
12957 }
12958
12959 let active_group_id = self
12960 .active_diagnostics
12961 .as_ref()
12962 .map(|active_group| active_group.group_id);
12963 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12964 active_diagnostics
12965 .primary_range
12966 .to_offset(&buffer)
12967 .to_inclusive()
12968 });
12969 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12970 if active_primary_range.contains(&selection.head()) {
12971 *active_primary_range.start()
12972 } else {
12973 selection.head()
12974 }
12975 } else {
12976 selection.head()
12977 };
12978
12979 let snapshot = self.snapshot(window, cx);
12980 let primary_diagnostics_before = buffer
12981 .diagnostics_in_range::<usize>(0..search_start)
12982 .filter(|entry| entry.diagnostic.is_primary)
12983 .filter(|entry| entry.range.start != entry.range.end)
12984 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12985 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12986 .collect::<Vec<_>>();
12987 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12988 primary_diagnostics_before
12989 .iter()
12990 .position(|entry| entry.diagnostic.group_id == active_group_id)
12991 });
12992
12993 let primary_diagnostics_after = buffer
12994 .diagnostics_in_range::<usize>(search_start..buffer.len())
12995 .filter(|entry| entry.diagnostic.is_primary)
12996 .filter(|entry| entry.range.start != entry.range.end)
12997 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12998 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12999 .collect::<Vec<_>>();
13000 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
13001 primary_diagnostics_after
13002 .iter()
13003 .enumerate()
13004 .rev()
13005 .find_map(|(i, entry)| {
13006 if entry.diagnostic.group_id == active_group_id {
13007 Some(i)
13008 } else {
13009 None
13010 }
13011 })
13012 });
13013
13014 let next_primary_diagnostic = match direction {
13015 Direction::Prev => primary_diagnostics_before
13016 .iter()
13017 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
13018 .rev()
13019 .next(),
13020 Direction::Next => primary_diagnostics_after
13021 .iter()
13022 .skip(
13023 last_same_group_diagnostic_after
13024 .map(|index| index + 1)
13025 .unwrap_or(0),
13026 )
13027 .next(),
13028 };
13029
13030 // Cycle around to the start of the buffer, potentially moving back to the start of
13031 // the currently active diagnostic.
13032 let cycle_around = || match direction {
13033 Direction::Prev => primary_diagnostics_after
13034 .iter()
13035 .rev()
13036 .chain(primary_diagnostics_before.iter().rev())
13037 .next(),
13038 Direction::Next => primary_diagnostics_before
13039 .iter()
13040 .chain(primary_diagnostics_after.iter())
13041 .next(),
13042 };
13043
13044 if let Some((primary_range, group_id)) = next_primary_diagnostic
13045 .or_else(cycle_around)
13046 .map(|entry| (&entry.range, entry.diagnostic.group_id))
13047 {
13048 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
13049 return;
13050 };
13051 self.activate_diagnostics(buffer_id, group_id, window, cx);
13052 if self.active_diagnostics.is_some() {
13053 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13054 s.select(vec![Selection {
13055 id: selection.id,
13056 start: primary_range.start,
13057 end: primary_range.start,
13058 reversed: false,
13059 goal: SelectionGoal::None,
13060 }]);
13061 });
13062 self.refresh_inline_completion(false, true, window, cx);
13063 }
13064 }
13065 }
13066
13067 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13068 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13069 let snapshot = self.snapshot(window, cx);
13070 let selection = self.selections.newest::<Point>(cx);
13071 self.go_to_hunk_before_or_after_position(
13072 &snapshot,
13073 selection.head(),
13074 Direction::Next,
13075 window,
13076 cx,
13077 );
13078 }
13079
13080 pub fn go_to_hunk_before_or_after_position(
13081 &mut self,
13082 snapshot: &EditorSnapshot,
13083 position: Point,
13084 direction: Direction,
13085 window: &mut Window,
13086 cx: &mut Context<Editor>,
13087 ) {
13088 let row = if direction == Direction::Next {
13089 self.hunk_after_position(snapshot, position)
13090 .map(|hunk| hunk.row_range.start)
13091 } else {
13092 self.hunk_before_position(snapshot, position)
13093 };
13094
13095 if let Some(row) = row {
13096 let destination = Point::new(row.0, 0);
13097 let autoscroll = Autoscroll::center();
13098
13099 self.unfold_ranges(&[destination..destination], false, false, cx);
13100 self.change_selections(Some(autoscroll), window, cx, |s| {
13101 s.select_ranges([destination..destination]);
13102 });
13103 }
13104 }
13105
13106 fn hunk_after_position(
13107 &mut self,
13108 snapshot: &EditorSnapshot,
13109 position: Point,
13110 ) -> Option<MultiBufferDiffHunk> {
13111 snapshot
13112 .buffer_snapshot
13113 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13114 .find(|hunk| hunk.row_range.start.0 > position.row)
13115 .or_else(|| {
13116 snapshot
13117 .buffer_snapshot
13118 .diff_hunks_in_range(Point::zero()..position)
13119 .find(|hunk| hunk.row_range.end.0 < position.row)
13120 })
13121 }
13122
13123 fn go_to_prev_hunk(
13124 &mut self,
13125 _: &GoToPreviousHunk,
13126 window: &mut Window,
13127 cx: &mut Context<Self>,
13128 ) {
13129 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13130 let snapshot = self.snapshot(window, cx);
13131 let selection = self.selections.newest::<Point>(cx);
13132 self.go_to_hunk_before_or_after_position(
13133 &snapshot,
13134 selection.head(),
13135 Direction::Prev,
13136 window,
13137 cx,
13138 );
13139 }
13140
13141 fn hunk_before_position(
13142 &mut self,
13143 snapshot: &EditorSnapshot,
13144 position: Point,
13145 ) -> Option<MultiBufferRow> {
13146 snapshot
13147 .buffer_snapshot
13148 .diff_hunk_before(position)
13149 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13150 }
13151
13152 fn go_to_line<T: 'static>(
13153 &mut self,
13154 position: Anchor,
13155 highlight_color: Option<Hsla>,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 let snapshot = self.snapshot(window, cx).display_snapshot;
13160 let position = position.to_point(&snapshot.buffer_snapshot);
13161 let start = snapshot
13162 .buffer_snapshot
13163 .clip_point(Point::new(position.row, 0), Bias::Left);
13164 let end = start + Point::new(1, 0);
13165 let start = snapshot.buffer_snapshot.anchor_before(start);
13166 let end = snapshot.buffer_snapshot.anchor_before(end);
13167
13168 self.highlight_rows::<T>(
13169 start..end,
13170 highlight_color
13171 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13172 false,
13173 cx,
13174 );
13175 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13176 }
13177
13178 pub fn go_to_definition(
13179 &mut self,
13180 _: &GoToDefinition,
13181 window: &mut Window,
13182 cx: &mut Context<Self>,
13183 ) -> Task<Result<Navigated>> {
13184 let definition =
13185 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13186 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13187 cx.spawn_in(window, async move |editor, cx| {
13188 if definition.await? == Navigated::Yes {
13189 return Ok(Navigated::Yes);
13190 }
13191 match fallback_strategy {
13192 GoToDefinitionFallback::None => Ok(Navigated::No),
13193 GoToDefinitionFallback::FindAllReferences => {
13194 match editor.update_in(cx, |editor, window, cx| {
13195 editor.find_all_references(&FindAllReferences, window, cx)
13196 })? {
13197 Some(references) => references.await,
13198 None => Ok(Navigated::No),
13199 }
13200 }
13201 }
13202 })
13203 }
13204
13205 pub fn go_to_declaration(
13206 &mut self,
13207 _: &GoToDeclaration,
13208 window: &mut Window,
13209 cx: &mut Context<Self>,
13210 ) -> Task<Result<Navigated>> {
13211 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13212 }
13213
13214 pub fn go_to_declaration_split(
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, true, window, cx)
13221 }
13222
13223 pub fn go_to_implementation(
13224 &mut self,
13225 _: &GoToImplementation,
13226 window: &mut Window,
13227 cx: &mut Context<Self>,
13228 ) -> Task<Result<Navigated>> {
13229 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13230 }
13231
13232 pub fn go_to_implementation_split(
13233 &mut self,
13234 _: &GoToImplementationSplit,
13235 window: &mut Window,
13236 cx: &mut Context<Self>,
13237 ) -> Task<Result<Navigated>> {
13238 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13239 }
13240
13241 pub fn go_to_type_definition(
13242 &mut self,
13243 _: &GoToTypeDefinition,
13244 window: &mut Window,
13245 cx: &mut Context<Self>,
13246 ) -> Task<Result<Navigated>> {
13247 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13248 }
13249
13250 pub fn go_to_definition_split(
13251 &mut self,
13252 _: &GoToDefinitionSplit,
13253 window: &mut Window,
13254 cx: &mut Context<Self>,
13255 ) -> Task<Result<Navigated>> {
13256 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13257 }
13258
13259 pub fn go_to_type_definition_split(
13260 &mut self,
13261 _: &GoToTypeDefinitionSplit,
13262 window: &mut Window,
13263 cx: &mut Context<Self>,
13264 ) -> Task<Result<Navigated>> {
13265 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13266 }
13267
13268 fn go_to_definition_of_kind(
13269 &mut self,
13270 kind: GotoDefinitionKind,
13271 split: bool,
13272 window: &mut Window,
13273 cx: &mut Context<Self>,
13274 ) -> Task<Result<Navigated>> {
13275 let Some(provider) = self.semantics_provider.clone() else {
13276 return Task::ready(Ok(Navigated::No));
13277 };
13278 let head = self.selections.newest::<usize>(cx).head();
13279 let buffer = self.buffer.read(cx);
13280 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13281 text_anchor
13282 } else {
13283 return Task::ready(Ok(Navigated::No));
13284 };
13285
13286 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13287 return Task::ready(Ok(Navigated::No));
13288 };
13289
13290 cx.spawn_in(window, async move |editor, cx| {
13291 let definitions = definitions.await?;
13292 let navigated = editor
13293 .update_in(cx, |editor, window, cx| {
13294 editor.navigate_to_hover_links(
13295 Some(kind),
13296 definitions
13297 .into_iter()
13298 .filter(|location| {
13299 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13300 })
13301 .map(HoverLink::Text)
13302 .collect::<Vec<_>>(),
13303 split,
13304 window,
13305 cx,
13306 )
13307 })?
13308 .await?;
13309 anyhow::Ok(navigated)
13310 })
13311 }
13312
13313 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13314 let selection = self.selections.newest_anchor();
13315 let head = selection.head();
13316 let tail = selection.tail();
13317
13318 let Some((buffer, start_position)) =
13319 self.buffer.read(cx).text_anchor_for_position(head, cx)
13320 else {
13321 return;
13322 };
13323
13324 let end_position = if head != tail {
13325 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13326 return;
13327 };
13328 Some(pos)
13329 } else {
13330 None
13331 };
13332
13333 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13334 let url = if let Some(end_pos) = end_position {
13335 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13336 } else {
13337 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13338 };
13339
13340 if let Some(url) = url {
13341 editor.update(cx, |_, cx| {
13342 cx.open_url(&url);
13343 })
13344 } else {
13345 Ok(())
13346 }
13347 });
13348
13349 url_finder.detach();
13350 }
13351
13352 pub fn open_selected_filename(
13353 &mut self,
13354 _: &OpenSelectedFilename,
13355 window: &mut Window,
13356 cx: &mut Context<Self>,
13357 ) {
13358 let Some(workspace) = self.workspace() else {
13359 return;
13360 };
13361
13362 let position = self.selections.newest_anchor().head();
13363
13364 let Some((buffer, buffer_position)) =
13365 self.buffer.read(cx).text_anchor_for_position(position, cx)
13366 else {
13367 return;
13368 };
13369
13370 let project = self.project.clone();
13371
13372 cx.spawn_in(window, async move |_, cx| {
13373 let result = find_file(&buffer, project, buffer_position, cx).await;
13374
13375 if let Some((_, path)) = result {
13376 workspace
13377 .update_in(cx, |workspace, window, cx| {
13378 workspace.open_resolved_path(path, window, cx)
13379 })?
13380 .await?;
13381 }
13382 anyhow::Ok(())
13383 })
13384 .detach();
13385 }
13386
13387 pub(crate) fn navigate_to_hover_links(
13388 &mut self,
13389 kind: Option<GotoDefinitionKind>,
13390 mut definitions: Vec<HoverLink>,
13391 split: bool,
13392 window: &mut Window,
13393 cx: &mut Context<Editor>,
13394 ) -> Task<Result<Navigated>> {
13395 // If there is one definition, just open it directly
13396 if definitions.len() == 1 {
13397 let definition = definitions.pop().unwrap();
13398
13399 enum TargetTaskResult {
13400 Location(Option<Location>),
13401 AlreadyNavigated,
13402 }
13403
13404 let target_task = match definition {
13405 HoverLink::Text(link) => {
13406 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13407 }
13408 HoverLink::InlayHint(lsp_location, server_id) => {
13409 let computation =
13410 self.compute_target_location(lsp_location, server_id, window, cx);
13411 cx.background_spawn(async move {
13412 let location = computation.await?;
13413 Ok(TargetTaskResult::Location(location))
13414 })
13415 }
13416 HoverLink::Url(url) => {
13417 cx.open_url(&url);
13418 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13419 }
13420 HoverLink::File(path) => {
13421 if let Some(workspace) = self.workspace() {
13422 cx.spawn_in(window, async move |_, cx| {
13423 workspace
13424 .update_in(cx, |workspace, window, cx| {
13425 workspace.open_resolved_path(path, window, cx)
13426 })?
13427 .await
13428 .map(|_| TargetTaskResult::AlreadyNavigated)
13429 })
13430 } else {
13431 Task::ready(Ok(TargetTaskResult::Location(None)))
13432 }
13433 }
13434 };
13435 cx.spawn_in(window, async move |editor, cx| {
13436 let target = match target_task.await.context("target resolution task")? {
13437 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13438 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13439 TargetTaskResult::Location(Some(target)) => target,
13440 };
13441
13442 editor.update_in(cx, |editor, window, cx| {
13443 let Some(workspace) = editor.workspace() else {
13444 return Navigated::No;
13445 };
13446 let pane = workspace.read(cx).active_pane().clone();
13447
13448 let range = target.range.to_point(target.buffer.read(cx));
13449 let range = editor.range_for_match(&range);
13450 let range = collapse_multiline_range(range);
13451
13452 if !split
13453 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13454 {
13455 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13456 } else {
13457 window.defer(cx, move |window, cx| {
13458 let target_editor: Entity<Self> =
13459 workspace.update(cx, |workspace, cx| {
13460 let pane = if split {
13461 workspace.adjacent_pane(window, cx)
13462 } else {
13463 workspace.active_pane().clone()
13464 };
13465
13466 workspace.open_project_item(
13467 pane,
13468 target.buffer.clone(),
13469 true,
13470 true,
13471 window,
13472 cx,
13473 )
13474 });
13475 target_editor.update(cx, |target_editor, cx| {
13476 // When selecting a definition in a different buffer, disable the nav history
13477 // to avoid creating a history entry at the previous cursor location.
13478 pane.update(cx, |pane, _| pane.disable_history());
13479 target_editor.go_to_singleton_buffer_range(range, window, cx);
13480 pane.update(cx, |pane, _| pane.enable_history());
13481 });
13482 });
13483 }
13484 Navigated::Yes
13485 })
13486 })
13487 } else if !definitions.is_empty() {
13488 cx.spawn_in(window, async move |editor, cx| {
13489 let (title, location_tasks, workspace) = editor
13490 .update_in(cx, |editor, window, cx| {
13491 let tab_kind = match kind {
13492 Some(GotoDefinitionKind::Implementation) => "Implementations",
13493 _ => "Definitions",
13494 };
13495 let title = definitions
13496 .iter()
13497 .find_map(|definition| match definition {
13498 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13499 let buffer = origin.buffer.read(cx);
13500 format!(
13501 "{} for {}",
13502 tab_kind,
13503 buffer
13504 .text_for_range(origin.range.clone())
13505 .collect::<String>()
13506 )
13507 }),
13508 HoverLink::InlayHint(_, _) => None,
13509 HoverLink::Url(_) => None,
13510 HoverLink::File(_) => None,
13511 })
13512 .unwrap_or(tab_kind.to_string());
13513 let location_tasks = definitions
13514 .into_iter()
13515 .map(|definition| match definition {
13516 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13517 HoverLink::InlayHint(lsp_location, server_id) => editor
13518 .compute_target_location(lsp_location, server_id, window, cx),
13519 HoverLink::Url(_) => Task::ready(Ok(None)),
13520 HoverLink::File(_) => Task::ready(Ok(None)),
13521 })
13522 .collect::<Vec<_>>();
13523 (title, location_tasks, editor.workspace().clone())
13524 })
13525 .context("location tasks preparation")?;
13526
13527 let locations = future::join_all(location_tasks)
13528 .await
13529 .into_iter()
13530 .filter_map(|location| location.transpose())
13531 .collect::<Result<_>>()
13532 .context("location tasks")?;
13533
13534 let Some(workspace) = workspace else {
13535 return Ok(Navigated::No);
13536 };
13537 let opened = workspace
13538 .update_in(cx, |workspace, window, cx| {
13539 Self::open_locations_in_multibuffer(
13540 workspace,
13541 locations,
13542 title,
13543 split,
13544 MultibufferSelectionMode::First,
13545 window,
13546 cx,
13547 )
13548 })
13549 .ok();
13550
13551 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13552 })
13553 } else {
13554 Task::ready(Ok(Navigated::No))
13555 }
13556 }
13557
13558 fn compute_target_location(
13559 &self,
13560 lsp_location: lsp::Location,
13561 server_id: LanguageServerId,
13562 window: &mut Window,
13563 cx: &mut Context<Self>,
13564 ) -> Task<anyhow::Result<Option<Location>>> {
13565 let Some(project) = self.project.clone() else {
13566 return Task::ready(Ok(None));
13567 };
13568
13569 cx.spawn_in(window, async move |editor, cx| {
13570 let location_task = editor.update(cx, |_, cx| {
13571 project.update(cx, |project, cx| {
13572 let language_server_name = project
13573 .language_server_statuses(cx)
13574 .find(|(id, _)| server_id == *id)
13575 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13576 language_server_name.map(|language_server_name| {
13577 project.open_local_buffer_via_lsp(
13578 lsp_location.uri.clone(),
13579 server_id,
13580 language_server_name,
13581 cx,
13582 )
13583 })
13584 })
13585 })?;
13586 let location = match location_task {
13587 Some(task) => Some({
13588 let target_buffer_handle = task.await.context("open local buffer")?;
13589 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13590 let target_start = target_buffer
13591 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13592 let target_end = target_buffer
13593 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13594 target_buffer.anchor_after(target_start)
13595 ..target_buffer.anchor_before(target_end)
13596 })?;
13597 Location {
13598 buffer: target_buffer_handle,
13599 range,
13600 }
13601 }),
13602 None => None,
13603 };
13604 Ok(location)
13605 })
13606 }
13607
13608 pub fn find_all_references(
13609 &mut self,
13610 _: &FindAllReferences,
13611 window: &mut Window,
13612 cx: &mut Context<Self>,
13613 ) -> Option<Task<Result<Navigated>>> {
13614 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13615
13616 let selection = self.selections.newest::<usize>(cx);
13617 let multi_buffer = self.buffer.read(cx);
13618 let head = selection.head();
13619
13620 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13621 let head_anchor = multi_buffer_snapshot.anchor_at(
13622 head,
13623 if head < selection.tail() {
13624 Bias::Right
13625 } else {
13626 Bias::Left
13627 },
13628 );
13629
13630 match self
13631 .find_all_references_task_sources
13632 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13633 {
13634 Ok(_) => {
13635 log::info!(
13636 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13637 );
13638 return None;
13639 }
13640 Err(i) => {
13641 self.find_all_references_task_sources.insert(i, head_anchor);
13642 }
13643 }
13644
13645 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13646 let workspace = self.workspace()?;
13647 let project = workspace.read(cx).project().clone();
13648 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13649 Some(cx.spawn_in(window, async move |editor, cx| {
13650 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13651 if let Ok(i) = editor
13652 .find_all_references_task_sources
13653 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13654 {
13655 editor.find_all_references_task_sources.remove(i);
13656 }
13657 });
13658
13659 let locations = references.await?;
13660 if locations.is_empty() {
13661 return anyhow::Ok(Navigated::No);
13662 }
13663
13664 workspace.update_in(cx, |workspace, window, cx| {
13665 let title = locations
13666 .first()
13667 .as_ref()
13668 .map(|location| {
13669 let buffer = location.buffer.read(cx);
13670 format!(
13671 "References to `{}`",
13672 buffer
13673 .text_for_range(location.range.clone())
13674 .collect::<String>()
13675 )
13676 })
13677 .unwrap();
13678 Self::open_locations_in_multibuffer(
13679 workspace,
13680 locations,
13681 title,
13682 false,
13683 MultibufferSelectionMode::First,
13684 window,
13685 cx,
13686 );
13687 Navigated::Yes
13688 })
13689 }))
13690 }
13691
13692 /// Opens a multibuffer with the given project locations in it
13693 pub fn open_locations_in_multibuffer(
13694 workspace: &mut Workspace,
13695 mut locations: Vec<Location>,
13696 title: String,
13697 split: bool,
13698 multibuffer_selection_mode: MultibufferSelectionMode,
13699 window: &mut Window,
13700 cx: &mut Context<Workspace>,
13701 ) {
13702 // If there are multiple definitions, open them in a multibuffer
13703 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13704 let mut locations = locations.into_iter().peekable();
13705 let mut ranges: Vec<Range<Anchor>> = Vec::new();
13706 let capability = workspace.project().read(cx).capability();
13707
13708 let excerpt_buffer = cx.new(|cx| {
13709 let mut multibuffer = MultiBuffer::new(capability);
13710 while let Some(location) = locations.next() {
13711 let buffer = location.buffer.read(cx);
13712 let mut ranges_for_buffer = Vec::new();
13713 let range = location.range.to_point(buffer);
13714 ranges_for_buffer.push(range.clone());
13715
13716 while let Some(next_location) = locations.peek() {
13717 if next_location.buffer == location.buffer {
13718 ranges_for_buffer.push(next_location.range.to_point(buffer));
13719 locations.next();
13720 } else {
13721 break;
13722 }
13723 }
13724
13725 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13726 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
13727 PathKey::for_buffer(&location.buffer, cx),
13728 location.buffer.clone(),
13729 ranges_for_buffer,
13730 DEFAULT_MULTIBUFFER_CONTEXT,
13731 cx,
13732 );
13733 ranges.extend(new_ranges)
13734 }
13735
13736 multibuffer.with_title(title)
13737 });
13738
13739 let editor = cx.new(|cx| {
13740 Editor::for_multibuffer(
13741 excerpt_buffer,
13742 Some(workspace.project().clone()),
13743 window,
13744 cx,
13745 )
13746 });
13747 editor.update(cx, |editor, cx| {
13748 match multibuffer_selection_mode {
13749 MultibufferSelectionMode::First => {
13750 if let Some(first_range) = ranges.first() {
13751 editor.change_selections(None, window, cx, |selections| {
13752 selections.clear_disjoint();
13753 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13754 });
13755 }
13756 editor.highlight_background::<Self>(
13757 &ranges,
13758 |theme| theme.editor_highlighted_line_background,
13759 cx,
13760 );
13761 }
13762 MultibufferSelectionMode::All => {
13763 editor.change_selections(None, window, cx, |selections| {
13764 selections.clear_disjoint();
13765 selections.select_anchor_ranges(ranges);
13766 });
13767 }
13768 }
13769 editor.register_buffers_with_language_servers(cx);
13770 });
13771
13772 let item = Box::new(editor);
13773 let item_id = item.item_id();
13774
13775 if split {
13776 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13777 } else {
13778 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13779 let (preview_item_id, preview_item_idx) =
13780 workspace.active_pane().update(cx, |pane, _| {
13781 (pane.preview_item_id(), pane.preview_item_idx())
13782 });
13783
13784 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13785
13786 if let Some(preview_item_id) = preview_item_id {
13787 workspace.active_pane().update(cx, |pane, cx| {
13788 pane.remove_item(preview_item_id, false, false, window, cx);
13789 });
13790 }
13791 } else {
13792 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13793 }
13794 }
13795 workspace.active_pane().update(cx, |pane, cx| {
13796 pane.set_preview_item_id(Some(item_id), cx);
13797 });
13798 }
13799
13800 pub fn rename(
13801 &mut self,
13802 _: &Rename,
13803 window: &mut Window,
13804 cx: &mut Context<Self>,
13805 ) -> Option<Task<Result<()>>> {
13806 use language::ToOffset as _;
13807
13808 let provider = self.semantics_provider.clone()?;
13809 let selection = self.selections.newest_anchor().clone();
13810 let (cursor_buffer, cursor_buffer_position) = self
13811 .buffer
13812 .read(cx)
13813 .text_anchor_for_position(selection.head(), cx)?;
13814 let (tail_buffer, cursor_buffer_position_end) = self
13815 .buffer
13816 .read(cx)
13817 .text_anchor_for_position(selection.tail(), cx)?;
13818 if tail_buffer != cursor_buffer {
13819 return None;
13820 }
13821
13822 let snapshot = cursor_buffer.read(cx).snapshot();
13823 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13824 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13825 let prepare_rename = provider
13826 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13827 .unwrap_or_else(|| Task::ready(Ok(None)));
13828 drop(snapshot);
13829
13830 Some(cx.spawn_in(window, async move |this, cx| {
13831 let rename_range = if let Some(range) = prepare_rename.await? {
13832 Some(range)
13833 } else {
13834 this.update(cx, |this, cx| {
13835 let buffer = this.buffer.read(cx).snapshot(cx);
13836 let mut buffer_highlights = this
13837 .document_highlights_for_position(selection.head(), &buffer)
13838 .filter(|highlight| {
13839 highlight.start.excerpt_id == selection.head().excerpt_id
13840 && highlight.end.excerpt_id == selection.head().excerpt_id
13841 });
13842 buffer_highlights
13843 .next()
13844 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13845 })?
13846 };
13847 if let Some(rename_range) = rename_range {
13848 this.update_in(cx, |this, window, cx| {
13849 let snapshot = cursor_buffer.read(cx).snapshot();
13850 let rename_buffer_range = rename_range.to_offset(&snapshot);
13851 let cursor_offset_in_rename_range =
13852 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13853 let cursor_offset_in_rename_range_end =
13854 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13855
13856 this.take_rename(false, window, cx);
13857 let buffer = this.buffer.read(cx).read(cx);
13858 let cursor_offset = selection.head().to_offset(&buffer);
13859 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13860 let rename_end = rename_start + rename_buffer_range.len();
13861 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13862 let mut old_highlight_id = None;
13863 let old_name: Arc<str> = buffer
13864 .chunks(rename_start..rename_end, true)
13865 .map(|chunk| {
13866 if old_highlight_id.is_none() {
13867 old_highlight_id = chunk.syntax_highlight_id;
13868 }
13869 chunk.text
13870 })
13871 .collect::<String>()
13872 .into();
13873
13874 drop(buffer);
13875
13876 // Position the selection in the rename editor so that it matches the current selection.
13877 this.show_local_selections = false;
13878 let rename_editor = cx.new(|cx| {
13879 let mut editor = Editor::single_line(window, cx);
13880 editor.buffer.update(cx, |buffer, cx| {
13881 buffer.edit([(0..0, old_name.clone())], None, cx)
13882 });
13883 let rename_selection_range = match cursor_offset_in_rename_range
13884 .cmp(&cursor_offset_in_rename_range_end)
13885 {
13886 Ordering::Equal => {
13887 editor.select_all(&SelectAll, window, cx);
13888 return editor;
13889 }
13890 Ordering::Less => {
13891 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13892 }
13893 Ordering::Greater => {
13894 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13895 }
13896 };
13897 if rename_selection_range.end > old_name.len() {
13898 editor.select_all(&SelectAll, window, cx);
13899 } else {
13900 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13901 s.select_ranges([rename_selection_range]);
13902 });
13903 }
13904 editor
13905 });
13906 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13907 if e == &EditorEvent::Focused {
13908 cx.emit(EditorEvent::FocusedIn)
13909 }
13910 })
13911 .detach();
13912
13913 let write_highlights =
13914 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13915 let read_highlights =
13916 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13917 let ranges = write_highlights
13918 .iter()
13919 .flat_map(|(_, ranges)| ranges.iter())
13920 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13921 .cloned()
13922 .collect();
13923
13924 this.highlight_text::<Rename>(
13925 ranges,
13926 HighlightStyle {
13927 fade_out: Some(0.6),
13928 ..Default::default()
13929 },
13930 cx,
13931 );
13932 let rename_focus_handle = rename_editor.focus_handle(cx);
13933 window.focus(&rename_focus_handle);
13934 let block_id = this.insert_blocks(
13935 [BlockProperties {
13936 style: BlockStyle::Flex,
13937 placement: BlockPlacement::Below(range.start),
13938 height: Some(1),
13939 render: Arc::new({
13940 let rename_editor = rename_editor.clone();
13941 move |cx: &mut BlockContext| {
13942 let mut text_style = cx.editor_style.text.clone();
13943 if let Some(highlight_style) = old_highlight_id
13944 .and_then(|h| h.style(&cx.editor_style.syntax))
13945 {
13946 text_style = text_style.highlight(highlight_style);
13947 }
13948 div()
13949 .block_mouse_down()
13950 .pl(cx.anchor_x)
13951 .child(EditorElement::new(
13952 &rename_editor,
13953 EditorStyle {
13954 background: cx.theme().system().transparent,
13955 local_player: cx.editor_style.local_player,
13956 text: text_style,
13957 scrollbar_width: cx.editor_style.scrollbar_width,
13958 syntax: cx.editor_style.syntax.clone(),
13959 status: cx.editor_style.status.clone(),
13960 inlay_hints_style: HighlightStyle {
13961 font_weight: Some(FontWeight::BOLD),
13962 ..make_inlay_hints_style(cx.app)
13963 },
13964 inline_completion_styles: make_suggestion_styles(
13965 cx.app,
13966 ),
13967 ..EditorStyle::default()
13968 },
13969 ))
13970 .into_any_element()
13971 }
13972 }),
13973 priority: 0,
13974 }],
13975 Some(Autoscroll::fit()),
13976 cx,
13977 )[0];
13978 this.pending_rename = Some(RenameState {
13979 range,
13980 old_name,
13981 editor: rename_editor,
13982 block_id,
13983 });
13984 })?;
13985 }
13986
13987 Ok(())
13988 }))
13989 }
13990
13991 pub fn confirm_rename(
13992 &mut self,
13993 _: &ConfirmRename,
13994 window: &mut Window,
13995 cx: &mut Context<Self>,
13996 ) -> Option<Task<Result<()>>> {
13997 let rename = self.take_rename(false, window, cx)?;
13998 let workspace = self.workspace()?.downgrade();
13999 let (buffer, start) = self
14000 .buffer
14001 .read(cx)
14002 .text_anchor_for_position(rename.range.start, cx)?;
14003 let (end_buffer, _) = self
14004 .buffer
14005 .read(cx)
14006 .text_anchor_for_position(rename.range.end, cx)?;
14007 if buffer != end_buffer {
14008 return None;
14009 }
14010
14011 let old_name = rename.old_name;
14012 let new_name = rename.editor.read(cx).text(cx);
14013
14014 let rename = self.semantics_provider.as_ref()?.perform_rename(
14015 &buffer,
14016 start,
14017 new_name.clone(),
14018 cx,
14019 )?;
14020
14021 Some(cx.spawn_in(window, async move |editor, cx| {
14022 let project_transaction = rename.await?;
14023 Self::open_project_transaction(
14024 &editor,
14025 workspace,
14026 project_transaction,
14027 format!("Rename: {} → {}", old_name, new_name),
14028 cx,
14029 )
14030 .await?;
14031
14032 editor.update(cx, |editor, cx| {
14033 editor.refresh_document_highlights(cx);
14034 })?;
14035 Ok(())
14036 }))
14037 }
14038
14039 fn take_rename(
14040 &mut self,
14041 moving_cursor: bool,
14042 window: &mut Window,
14043 cx: &mut Context<Self>,
14044 ) -> Option<RenameState> {
14045 let rename = self.pending_rename.take()?;
14046 if rename.editor.focus_handle(cx).is_focused(window) {
14047 window.focus(&self.focus_handle);
14048 }
14049
14050 self.remove_blocks(
14051 [rename.block_id].into_iter().collect(),
14052 Some(Autoscroll::fit()),
14053 cx,
14054 );
14055 self.clear_highlights::<Rename>(cx);
14056 self.show_local_selections = true;
14057
14058 if moving_cursor {
14059 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14060 editor.selections.newest::<usize>(cx).head()
14061 });
14062
14063 // Update the selection to match the position of the selection inside
14064 // the rename editor.
14065 let snapshot = self.buffer.read(cx).read(cx);
14066 let rename_range = rename.range.to_offset(&snapshot);
14067 let cursor_in_editor = snapshot
14068 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14069 .min(rename_range.end);
14070 drop(snapshot);
14071
14072 self.change_selections(None, window, cx, |s| {
14073 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14074 });
14075 } else {
14076 self.refresh_document_highlights(cx);
14077 }
14078
14079 Some(rename)
14080 }
14081
14082 pub fn pending_rename(&self) -> Option<&RenameState> {
14083 self.pending_rename.as_ref()
14084 }
14085
14086 fn format(
14087 &mut self,
14088 _: &Format,
14089 window: &mut Window,
14090 cx: &mut Context<Self>,
14091 ) -> Option<Task<Result<()>>> {
14092 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14093
14094 let project = match &self.project {
14095 Some(project) => project.clone(),
14096 None => return None,
14097 };
14098
14099 Some(self.perform_format(
14100 project,
14101 FormatTrigger::Manual,
14102 FormatTarget::Buffers,
14103 window,
14104 cx,
14105 ))
14106 }
14107
14108 fn format_selections(
14109 &mut self,
14110 _: &FormatSelections,
14111 window: &mut Window,
14112 cx: &mut Context<Self>,
14113 ) -> Option<Task<Result<()>>> {
14114 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14115
14116 let project = match &self.project {
14117 Some(project) => project.clone(),
14118 None => return None,
14119 };
14120
14121 let ranges = self
14122 .selections
14123 .all_adjusted(cx)
14124 .into_iter()
14125 .map(|selection| selection.range())
14126 .collect_vec();
14127
14128 Some(self.perform_format(
14129 project,
14130 FormatTrigger::Manual,
14131 FormatTarget::Ranges(ranges),
14132 window,
14133 cx,
14134 ))
14135 }
14136
14137 fn perform_format(
14138 &mut self,
14139 project: Entity<Project>,
14140 trigger: FormatTrigger,
14141 target: FormatTarget,
14142 window: &mut Window,
14143 cx: &mut Context<Self>,
14144 ) -> Task<Result<()>> {
14145 let buffer = self.buffer.clone();
14146 let (buffers, target) = match target {
14147 FormatTarget::Buffers => {
14148 let mut buffers = buffer.read(cx).all_buffers();
14149 if trigger == FormatTrigger::Save {
14150 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14151 }
14152 (buffers, LspFormatTarget::Buffers)
14153 }
14154 FormatTarget::Ranges(selection_ranges) => {
14155 let multi_buffer = buffer.read(cx);
14156 let snapshot = multi_buffer.read(cx);
14157 let mut buffers = HashSet::default();
14158 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14159 BTreeMap::new();
14160 for selection_range in selection_ranges {
14161 for (buffer, buffer_range, _) in
14162 snapshot.range_to_buffer_ranges(selection_range)
14163 {
14164 let buffer_id = buffer.remote_id();
14165 let start = buffer.anchor_before(buffer_range.start);
14166 let end = buffer.anchor_after(buffer_range.end);
14167 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14168 buffer_id_to_ranges
14169 .entry(buffer_id)
14170 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14171 .or_insert_with(|| vec![start..end]);
14172 }
14173 }
14174 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14175 }
14176 };
14177
14178 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14179 let format = project.update(cx, |project, cx| {
14180 project.format(buffers, target, true, trigger, cx)
14181 });
14182
14183 cx.spawn_in(window, async move |_, cx| {
14184 let transaction = futures::select_biased! {
14185 transaction = format.log_err().fuse() => transaction,
14186 () = timeout => {
14187 log::warn!("timed out waiting for formatting");
14188 None
14189 }
14190 };
14191
14192 buffer
14193 .update(cx, |buffer, cx| {
14194 if let Some(transaction) = transaction {
14195 if !buffer.is_singleton() {
14196 buffer.push_transaction(&transaction.0, cx);
14197 }
14198 }
14199 cx.notify();
14200 })
14201 .ok();
14202
14203 Ok(())
14204 })
14205 }
14206
14207 fn organize_imports(
14208 &mut self,
14209 _: &OrganizeImports,
14210 window: &mut Window,
14211 cx: &mut Context<Self>,
14212 ) -> Option<Task<Result<()>>> {
14213 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14214 let project = match &self.project {
14215 Some(project) => project.clone(),
14216 None => return None,
14217 };
14218 Some(self.perform_code_action_kind(
14219 project,
14220 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14221 window,
14222 cx,
14223 ))
14224 }
14225
14226 fn perform_code_action_kind(
14227 &mut self,
14228 project: Entity<Project>,
14229 kind: CodeActionKind,
14230 window: &mut Window,
14231 cx: &mut Context<Self>,
14232 ) -> Task<Result<()>> {
14233 let buffer = self.buffer.clone();
14234 let buffers = buffer.read(cx).all_buffers();
14235 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14236 let apply_action = project.update(cx, |project, cx| {
14237 project.apply_code_action_kind(buffers, kind, true, cx)
14238 });
14239 cx.spawn_in(window, async move |_, cx| {
14240 let transaction = futures::select_biased! {
14241 () = timeout => {
14242 log::warn!("timed out waiting for executing code action");
14243 None
14244 }
14245 transaction = apply_action.log_err().fuse() => transaction,
14246 };
14247 buffer
14248 .update(cx, |buffer, cx| {
14249 // check if we need this
14250 if let Some(transaction) = transaction {
14251 if !buffer.is_singleton() {
14252 buffer.push_transaction(&transaction.0, cx);
14253 }
14254 }
14255 cx.notify();
14256 })
14257 .ok();
14258 Ok(())
14259 })
14260 }
14261
14262 fn restart_language_server(
14263 &mut self,
14264 _: &RestartLanguageServer,
14265 _: &mut Window,
14266 cx: &mut Context<Self>,
14267 ) {
14268 if let Some(project) = self.project.clone() {
14269 self.buffer.update(cx, |multi_buffer, cx| {
14270 project.update(cx, |project, cx| {
14271 project.restart_language_servers_for_buffers(
14272 multi_buffer.all_buffers().into_iter().collect(),
14273 cx,
14274 );
14275 });
14276 })
14277 }
14278 }
14279
14280 fn stop_language_server(
14281 &mut self,
14282 _: &StopLanguageServer,
14283 _: &mut Window,
14284 cx: &mut Context<Self>,
14285 ) {
14286 if let Some(project) = self.project.clone() {
14287 self.buffer.update(cx, |multi_buffer, cx| {
14288 project.update(cx, |project, cx| {
14289 project.stop_language_servers_for_buffers(
14290 multi_buffer.all_buffers().into_iter().collect(),
14291 cx,
14292 );
14293 cx.emit(project::Event::RefreshInlayHints);
14294 });
14295 });
14296 }
14297 }
14298
14299 fn cancel_language_server_work(
14300 workspace: &mut Workspace,
14301 _: &actions::CancelLanguageServerWork,
14302 _: &mut Window,
14303 cx: &mut Context<Workspace>,
14304 ) {
14305 let project = workspace.project();
14306 let buffers = workspace
14307 .active_item(cx)
14308 .and_then(|item| item.act_as::<Editor>(cx))
14309 .map_or(HashSet::default(), |editor| {
14310 editor.read(cx).buffer.read(cx).all_buffers()
14311 });
14312 project.update(cx, |project, cx| {
14313 project.cancel_language_server_work_for_buffers(buffers, cx);
14314 });
14315 }
14316
14317 fn show_character_palette(
14318 &mut self,
14319 _: &ShowCharacterPalette,
14320 window: &mut Window,
14321 _: &mut Context<Self>,
14322 ) {
14323 window.show_character_palette();
14324 }
14325
14326 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14327 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
14328 let buffer = self.buffer.read(cx).snapshot(cx);
14329 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
14330 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
14331 let is_valid = buffer
14332 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14333 .any(|entry| {
14334 entry.diagnostic.is_primary
14335 && !entry.range.is_empty()
14336 && entry.range.start == primary_range_start
14337 && entry.diagnostic.message == active_diagnostics.primary_message
14338 });
14339
14340 if is_valid != active_diagnostics.is_valid {
14341 active_diagnostics.is_valid = is_valid;
14342 if is_valid {
14343 let mut new_styles = HashMap::default();
14344 for (block_id, diagnostic) in &active_diagnostics.blocks {
14345 new_styles.insert(
14346 *block_id,
14347 diagnostic_block_renderer(diagnostic.clone(), None, true),
14348 );
14349 }
14350 self.display_map.update(cx, |display_map, _cx| {
14351 display_map.replace_blocks(new_styles);
14352 });
14353 } else {
14354 self.dismiss_diagnostics(cx);
14355 }
14356 }
14357 }
14358 }
14359
14360 fn activate_diagnostics(
14361 &mut self,
14362 buffer_id: BufferId,
14363 group_id: usize,
14364 window: &mut Window,
14365 cx: &mut Context<Self>,
14366 ) {
14367 self.dismiss_diagnostics(cx);
14368 let snapshot = self.snapshot(window, cx);
14369 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
14370 let buffer = self.buffer.read(cx).snapshot(cx);
14371
14372 let mut primary_range = None;
14373 let mut primary_message = None;
14374 let diagnostic_group = buffer
14375 .diagnostic_group(buffer_id, group_id)
14376 .filter_map(|entry| {
14377 let start = entry.range.start;
14378 let end = entry.range.end;
14379 if snapshot.is_line_folded(MultiBufferRow(start.row))
14380 && (start.row == end.row
14381 || snapshot.is_line_folded(MultiBufferRow(end.row)))
14382 {
14383 return None;
14384 }
14385 if entry.diagnostic.is_primary {
14386 primary_range = Some(entry.range.clone());
14387 primary_message = Some(entry.diagnostic.message.clone());
14388 }
14389 Some(entry)
14390 })
14391 .collect::<Vec<_>>();
14392 let primary_range = primary_range?;
14393 let primary_message = primary_message?;
14394
14395 let blocks = display_map
14396 .insert_blocks(
14397 diagnostic_group.iter().map(|entry| {
14398 let diagnostic = entry.diagnostic.clone();
14399 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
14400 BlockProperties {
14401 style: BlockStyle::Fixed,
14402 placement: BlockPlacement::Below(
14403 buffer.anchor_after(entry.range.start),
14404 ),
14405 height: Some(message_height),
14406 render: diagnostic_block_renderer(diagnostic, None, true),
14407 priority: 0,
14408 }
14409 }),
14410 cx,
14411 )
14412 .into_iter()
14413 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
14414 .collect();
14415
14416 Some(ActiveDiagnosticGroup {
14417 primary_range: buffer.anchor_before(primary_range.start)
14418 ..buffer.anchor_after(primary_range.end),
14419 primary_message,
14420 group_id,
14421 blocks,
14422 is_valid: true,
14423 })
14424 });
14425 }
14426
14427 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14428 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
14429 self.display_map.update(cx, |display_map, cx| {
14430 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
14431 });
14432 cx.notify();
14433 }
14434 }
14435
14436 /// Disable inline diagnostics rendering for this editor.
14437 pub fn disable_inline_diagnostics(&mut self) {
14438 self.inline_diagnostics_enabled = false;
14439 self.inline_diagnostics_update = Task::ready(());
14440 self.inline_diagnostics.clear();
14441 }
14442
14443 pub fn inline_diagnostics_enabled(&self) -> bool {
14444 self.inline_diagnostics_enabled
14445 }
14446
14447 pub fn show_inline_diagnostics(&self) -> bool {
14448 self.show_inline_diagnostics
14449 }
14450
14451 pub fn toggle_inline_diagnostics(
14452 &mut self,
14453 _: &ToggleInlineDiagnostics,
14454 window: &mut Window,
14455 cx: &mut Context<Editor>,
14456 ) {
14457 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14458 self.refresh_inline_diagnostics(false, window, cx);
14459 }
14460
14461 fn refresh_inline_diagnostics(
14462 &mut self,
14463 debounce: bool,
14464 window: &mut Window,
14465 cx: &mut Context<Self>,
14466 ) {
14467 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14468 self.inline_diagnostics_update = Task::ready(());
14469 self.inline_diagnostics.clear();
14470 return;
14471 }
14472
14473 let debounce_ms = ProjectSettings::get_global(cx)
14474 .diagnostics
14475 .inline
14476 .update_debounce_ms;
14477 let debounce = if debounce && debounce_ms > 0 {
14478 Some(Duration::from_millis(debounce_ms))
14479 } else {
14480 None
14481 };
14482 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14483 if let Some(debounce) = debounce {
14484 cx.background_executor().timer(debounce).await;
14485 }
14486 let Some(snapshot) = editor
14487 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14488 .ok()
14489 else {
14490 return;
14491 };
14492
14493 let new_inline_diagnostics = cx
14494 .background_spawn(async move {
14495 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14496 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14497 let message = diagnostic_entry
14498 .diagnostic
14499 .message
14500 .split_once('\n')
14501 .map(|(line, _)| line)
14502 .map(SharedString::new)
14503 .unwrap_or_else(|| {
14504 SharedString::from(diagnostic_entry.diagnostic.message)
14505 });
14506 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14507 let (Ok(i) | Err(i)) = inline_diagnostics
14508 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14509 inline_diagnostics.insert(
14510 i,
14511 (
14512 start_anchor,
14513 InlineDiagnostic {
14514 message,
14515 group_id: diagnostic_entry.diagnostic.group_id,
14516 start: diagnostic_entry.range.start.to_point(&snapshot),
14517 is_primary: diagnostic_entry.diagnostic.is_primary,
14518 severity: diagnostic_entry.diagnostic.severity,
14519 },
14520 ),
14521 );
14522 }
14523 inline_diagnostics
14524 })
14525 .await;
14526
14527 editor
14528 .update(cx, |editor, cx| {
14529 editor.inline_diagnostics = new_inline_diagnostics;
14530 cx.notify();
14531 })
14532 .ok();
14533 });
14534 }
14535
14536 pub fn set_selections_from_remote(
14537 &mut self,
14538 selections: Vec<Selection<Anchor>>,
14539 pending_selection: Option<Selection<Anchor>>,
14540 window: &mut Window,
14541 cx: &mut Context<Self>,
14542 ) {
14543 let old_cursor_position = self.selections.newest_anchor().head();
14544 self.selections.change_with(cx, |s| {
14545 s.select_anchors(selections);
14546 if let Some(pending_selection) = pending_selection {
14547 s.set_pending(pending_selection, SelectMode::Character);
14548 } else {
14549 s.clear_pending();
14550 }
14551 });
14552 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14553 }
14554
14555 fn push_to_selection_history(&mut self) {
14556 self.selection_history.push(SelectionHistoryEntry {
14557 selections: self.selections.disjoint_anchors(),
14558 select_next_state: self.select_next_state.clone(),
14559 select_prev_state: self.select_prev_state.clone(),
14560 add_selections_state: self.add_selections_state.clone(),
14561 });
14562 }
14563
14564 pub fn transact(
14565 &mut self,
14566 window: &mut Window,
14567 cx: &mut Context<Self>,
14568 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14569 ) -> Option<TransactionId> {
14570 self.start_transaction_at(Instant::now(), window, cx);
14571 update(self, window, cx);
14572 self.end_transaction_at(Instant::now(), cx)
14573 }
14574
14575 pub fn start_transaction_at(
14576 &mut self,
14577 now: Instant,
14578 window: &mut Window,
14579 cx: &mut Context<Self>,
14580 ) {
14581 self.end_selection(window, cx);
14582 if let Some(tx_id) = self
14583 .buffer
14584 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14585 {
14586 self.selection_history
14587 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14588 cx.emit(EditorEvent::TransactionBegun {
14589 transaction_id: tx_id,
14590 })
14591 }
14592 }
14593
14594 pub fn end_transaction_at(
14595 &mut self,
14596 now: Instant,
14597 cx: &mut Context<Self>,
14598 ) -> Option<TransactionId> {
14599 if let Some(transaction_id) = self
14600 .buffer
14601 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14602 {
14603 if let Some((_, end_selections)) =
14604 self.selection_history.transaction_mut(transaction_id)
14605 {
14606 *end_selections = Some(self.selections.disjoint_anchors());
14607 } else {
14608 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14609 }
14610
14611 cx.emit(EditorEvent::Edited { transaction_id });
14612 Some(transaction_id)
14613 } else {
14614 None
14615 }
14616 }
14617
14618 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14619 if self.selection_mark_mode {
14620 self.change_selections(None, window, cx, |s| {
14621 s.move_with(|_, sel| {
14622 sel.collapse_to(sel.head(), SelectionGoal::None);
14623 });
14624 })
14625 }
14626 self.selection_mark_mode = true;
14627 cx.notify();
14628 }
14629
14630 pub fn swap_selection_ends(
14631 &mut self,
14632 _: &actions::SwapSelectionEnds,
14633 window: &mut Window,
14634 cx: &mut Context<Self>,
14635 ) {
14636 self.change_selections(None, window, cx, |s| {
14637 s.move_with(|_, sel| {
14638 if sel.start != sel.end {
14639 sel.reversed = !sel.reversed
14640 }
14641 });
14642 });
14643 self.request_autoscroll(Autoscroll::newest(), cx);
14644 cx.notify();
14645 }
14646
14647 pub fn toggle_fold(
14648 &mut self,
14649 _: &actions::ToggleFold,
14650 window: &mut Window,
14651 cx: &mut Context<Self>,
14652 ) {
14653 if self.is_singleton(cx) {
14654 let selection = self.selections.newest::<Point>(cx);
14655
14656 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14657 let range = if selection.is_empty() {
14658 let point = selection.head().to_display_point(&display_map);
14659 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14660 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14661 .to_point(&display_map);
14662 start..end
14663 } else {
14664 selection.range()
14665 };
14666 if display_map.folds_in_range(range).next().is_some() {
14667 self.unfold_lines(&Default::default(), window, cx)
14668 } else {
14669 self.fold(&Default::default(), window, cx)
14670 }
14671 } else {
14672 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14673 let buffer_ids: HashSet<_> = self
14674 .selections
14675 .disjoint_anchor_ranges()
14676 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14677 .collect();
14678
14679 let should_unfold = buffer_ids
14680 .iter()
14681 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14682
14683 for buffer_id in buffer_ids {
14684 if should_unfold {
14685 self.unfold_buffer(buffer_id, cx);
14686 } else {
14687 self.fold_buffer(buffer_id, cx);
14688 }
14689 }
14690 }
14691 }
14692
14693 pub fn toggle_fold_recursive(
14694 &mut self,
14695 _: &actions::ToggleFoldRecursive,
14696 window: &mut Window,
14697 cx: &mut Context<Self>,
14698 ) {
14699 let selection = self.selections.newest::<Point>(cx);
14700
14701 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14702 let range = if selection.is_empty() {
14703 let point = selection.head().to_display_point(&display_map);
14704 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14705 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14706 .to_point(&display_map);
14707 start..end
14708 } else {
14709 selection.range()
14710 };
14711 if display_map.folds_in_range(range).next().is_some() {
14712 self.unfold_recursive(&Default::default(), window, cx)
14713 } else {
14714 self.fold_recursive(&Default::default(), window, cx)
14715 }
14716 }
14717
14718 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14719 if self.is_singleton(cx) {
14720 let mut to_fold = Vec::new();
14721 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14722 let selections = self.selections.all_adjusted(cx);
14723
14724 for selection in selections {
14725 let range = selection.range().sorted();
14726 let buffer_start_row = range.start.row;
14727
14728 if range.start.row != range.end.row {
14729 let mut found = false;
14730 let mut row = range.start.row;
14731 while row <= range.end.row {
14732 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14733 {
14734 found = true;
14735 row = crease.range().end.row + 1;
14736 to_fold.push(crease);
14737 } else {
14738 row += 1
14739 }
14740 }
14741 if found {
14742 continue;
14743 }
14744 }
14745
14746 for row in (0..=range.start.row).rev() {
14747 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14748 if crease.range().end.row >= buffer_start_row {
14749 to_fold.push(crease);
14750 if row <= range.start.row {
14751 break;
14752 }
14753 }
14754 }
14755 }
14756 }
14757
14758 self.fold_creases(to_fold, true, window, cx);
14759 } else {
14760 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14761 let buffer_ids = self
14762 .selections
14763 .disjoint_anchor_ranges()
14764 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14765 .collect::<HashSet<_>>();
14766 for buffer_id in buffer_ids {
14767 self.fold_buffer(buffer_id, cx);
14768 }
14769 }
14770 }
14771
14772 fn fold_at_level(
14773 &mut self,
14774 fold_at: &FoldAtLevel,
14775 window: &mut Window,
14776 cx: &mut Context<Self>,
14777 ) {
14778 if !self.buffer.read(cx).is_singleton() {
14779 return;
14780 }
14781
14782 let fold_at_level = fold_at.0;
14783 let snapshot = self.buffer.read(cx).snapshot(cx);
14784 let mut to_fold = Vec::new();
14785 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14786
14787 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14788 while start_row < end_row {
14789 match self
14790 .snapshot(window, cx)
14791 .crease_for_buffer_row(MultiBufferRow(start_row))
14792 {
14793 Some(crease) => {
14794 let nested_start_row = crease.range().start.row + 1;
14795 let nested_end_row = crease.range().end.row;
14796
14797 if current_level < fold_at_level {
14798 stack.push((nested_start_row, nested_end_row, current_level + 1));
14799 } else if current_level == fold_at_level {
14800 to_fold.push(crease);
14801 }
14802
14803 start_row = nested_end_row + 1;
14804 }
14805 None => start_row += 1,
14806 }
14807 }
14808 }
14809
14810 self.fold_creases(to_fold, true, window, cx);
14811 }
14812
14813 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14814 if self.buffer.read(cx).is_singleton() {
14815 let mut fold_ranges = Vec::new();
14816 let snapshot = self.buffer.read(cx).snapshot(cx);
14817
14818 for row in 0..snapshot.max_row().0 {
14819 if let Some(foldable_range) = self
14820 .snapshot(window, cx)
14821 .crease_for_buffer_row(MultiBufferRow(row))
14822 {
14823 fold_ranges.push(foldable_range);
14824 }
14825 }
14826
14827 self.fold_creases(fold_ranges, true, window, cx);
14828 } else {
14829 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14830 editor
14831 .update_in(cx, |editor, _, cx| {
14832 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14833 editor.fold_buffer(buffer_id, cx);
14834 }
14835 })
14836 .ok();
14837 });
14838 }
14839 }
14840
14841 pub fn fold_function_bodies(
14842 &mut self,
14843 _: &actions::FoldFunctionBodies,
14844 window: &mut Window,
14845 cx: &mut Context<Self>,
14846 ) {
14847 let snapshot = self.buffer.read(cx).snapshot(cx);
14848
14849 let ranges = snapshot
14850 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14851 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14852 .collect::<Vec<_>>();
14853
14854 let creases = ranges
14855 .into_iter()
14856 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14857 .collect();
14858
14859 self.fold_creases(creases, true, window, cx);
14860 }
14861
14862 pub fn fold_recursive(
14863 &mut self,
14864 _: &actions::FoldRecursive,
14865 window: &mut Window,
14866 cx: &mut Context<Self>,
14867 ) {
14868 let mut to_fold = Vec::new();
14869 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14870 let selections = self.selections.all_adjusted(cx);
14871
14872 for selection in selections {
14873 let range = selection.range().sorted();
14874 let buffer_start_row = range.start.row;
14875
14876 if range.start.row != range.end.row {
14877 let mut found = false;
14878 for row in range.start.row..=range.end.row {
14879 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14880 found = true;
14881 to_fold.push(crease);
14882 }
14883 }
14884 if found {
14885 continue;
14886 }
14887 }
14888
14889 for row in (0..=range.start.row).rev() {
14890 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14891 if crease.range().end.row >= buffer_start_row {
14892 to_fold.push(crease);
14893 } else {
14894 break;
14895 }
14896 }
14897 }
14898 }
14899
14900 self.fold_creases(to_fold, true, window, cx);
14901 }
14902
14903 pub fn fold_at(
14904 &mut self,
14905 buffer_row: MultiBufferRow,
14906 window: &mut Window,
14907 cx: &mut Context<Self>,
14908 ) {
14909 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14910
14911 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14912 let autoscroll = self
14913 .selections
14914 .all::<Point>(cx)
14915 .iter()
14916 .any(|selection| crease.range().overlaps(&selection.range()));
14917
14918 self.fold_creases(vec![crease], autoscroll, window, cx);
14919 }
14920 }
14921
14922 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14923 if self.is_singleton(cx) {
14924 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14925 let buffer = &display_map.buffer_snapshot;
14926 let selections = self.selections.all::<Point>(cx);
14927 let ranges = selections
14928 .iter()
14929 .map(|s| {
14930 let range = s.display_range(&display_map).sorted();
14931 let mut start = range.start.to_point(&display_map);
14932 let mut end = range.end.to_point(&display_map);
14933 start.column = 0;
14934 end.column = buffer.line_len(MultiBufferRow(end.row));
14935 start..end
14936 })
14937 .collect::<Vec<_>>();
14938
14939 self.unfold_ranges(&ranges, true, true, cx);
14940 } else {
14941 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14942 let buffer_ids = self
14943 .selections
14944 .disjoint_anchor_ranges()
14945 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14946 .collect::<HashSet<_>>();
14947 for buffer_id in buffer_ids {
14948 self.unfold_buffer(buffer_id, cx);
14949 }
14950 }
14951 }
14952
14953 pub fn unfold_recursive(
14954 &mut self,
14955 _: &UnfoldRecursive,
14956 _window: &mut Window,
14957 cx: &mut Context<Self>,
14958 ) {
14959 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14960 let selections = self.selections.all::<Point>(cx);
14961 let ranges = selections
14962 .iter()
14963 .map(|s| {
14964 let mut range = s.display_range(&display_map).sorted();
14965 *range.start.column_mut() = 0;
14966 *range.end.column_mut() = display_map.line_len(range.end.row());
14967 let start = range.start.to_point(&display_map);
14968 let end = range.end.to_point(&display_map);
14969 start..end
14970 })
14971 .collect::<Vec<_>>();
14972
14973 self.unfold_ranges(&ranges, true, true, cx);
14974 }
14975
14976 pub fn unfold_at(
14977 &mut self,
14978 buffer_row: MultiBufferRow,
14979 _window: &mut Window,
14980 cx: &mut Context<Self>,
14981 ) {
14982 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14983
14984 let intersection_range = Point::new(buffer_row.0, 0)
14985 ..Point::new(
14986 buffer_row.0,
14987 display_map.buffer_snapshot.line_len(buffer_row),
14988 );
14989
14990 let autoscroll = self
14991 .selections
14992 .all::<Point>(cx)
14993 .iter()
14994 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14995
14996 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14997 }
14998
14999 pub fn unfold_all(
15000 &mut self,
15001 _: &actions::UnfoldAll,
15002 _window: &mut Window,
15003 cx: &mut Context<Self>,
15004 ) {
15005 if self.buffer.read(cx).is_singleton() {
15006 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15007 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15008 } else {
15009 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15010 editor
15011 .update(cx, |editor, cx| {
15012 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15013 editor.unfold_buffer(buffer_id, cx);
15014 }
15015 })
15016 .ok();
15017 });
15018 }
15019 }
15020
15021 pub fn fold_selected_ranges(
15022 &mut self,
15023 _: &FoldSelectedRanges,
15024 window: &mut Window,
15025 cx: &mut Context<Self>,
15026 ) {
15027 let selections = self.selections.all_adjusted(cx);
15028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15029 let ranges = selections
15030 .into_iter()
15031 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15032 .collect::<Vec<_>>();
15033 self.fold_creases(ranges, true, window, cx);
15034 }
15035
15036 pub fn fold_ranges<T: ToOffset + Clone>(
15037 &mut self,
15038 ranges: Vec<Range<T>>,
15039 auto_scroll: bool,
15040 window: &mut Window,
15041 cx: &mut Context<Self>,
15042 ) {
15043 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15044 let ranges = ranges
15045 .into_iter()
15046 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15047 .collect::<Vec<_>>();
15048 self.fold_creases(ranges, auto_scroll, window, cx);
15049 }
15050
15051 pub fn fold_creases<T: ToOffset + Clone>(
15052 &mut self,
15053 creases: Vec<Crease<T>>,
15054 auto_scroll: bool,
15055 window: &mut Window,
15056 cx: &mut Context<Self>,
15057 ) {
15058 if creases.is_empty() {
15059 return;
15060 }
15061
15062 let mut buffers_affected = HashSet::default();
15063 let multi_buffer = self.buffer().read(cx);
15064 for crease in &creases {
15065 if let Some((_, buffer, _)) =
15066 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15067 {
15068 buffers_affected.insert(buffer.read(cx).remote_id());
15069 };
15070 }
15071
15072 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15073
15074 if auto_scroll {
15075 self.request_autoscroll(Autoscroll::fit(), cx);
15076 }
15077
15078 cx.notify();
15079
15080 if let Some(active_diagnostics) = self.active_diagnostics.take() {
15081 // Clear diagnostics block when folding a range that contains it.
15082 let snapshot = self.snapshot(window, cx);
15083 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
15084 drop(snapshot);
15085 self.active_diagnostics = Some(active_diagnostics);
15086 self.dismiss_diagnostics(cx);
15087 } else {
15088 self.active_diagnostics = Some(active_diagnostics);
15089 }
15090 }
15091
15092 self.scrollbar_marker_state.dirty = true;
15093 self.folds_did_change(cx);
15094 }
15095
15096 /// Removes any folds whose ranges intersect any of the given ranges.
15097 pub fn unfold_ranges<T: ToOffset + Clone>(
15098 &mut self,
15099 ranges: &[Range<T>],
15100 inclusive: bool,
15101 auto_scroll: bool,
15102 cx: &mut Context<Self>,
15103 ) {
15104 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15105 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15106 });
15107 self.folds_did_change(cx);
15108 }
15109
15110 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15111 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15112 return;
15113 }
15114 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15115 self.display_map.update(cx, |display_map, cx| {
15116 display_map.fold_buffers([buffer_id], cx)
15117 });
15118 cx.emit(EditorEvent::BufferFoldToggled {
15119 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15120 folded: true,
15121 });
15122 cx.notify();
15123 }
15124
15125 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15126 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15127 return;
15128 }
15129 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15130 self.display_map.update(cx, |display_map, cx| {
15131 display_map.unfold_buffers([buffer_id], cx);
15132 });
15133 cx.emit(EditorEvent::BufferFoldToggled {
15134 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15135 folded: false,
15136 });
15137 cx.notify();
15138 }
15139
15140 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15141 self.display_map.read(cx).is_buffer_folded(buffer)
15142 }
15143
15144 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15145 self.display_map.read(cx).folded_buffers()
15146 }
15147
15148 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15149 self.display_map.update(cx, |display_map, cx| {
15150 display_map.disable_header_for_buffer(buffer_id, cx);
15151 });
15152 cx.notify();
15153 }
15154
15155 /// Removes any folds with the given ranges.
15156 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15157 &mut self,
15158 ranges: &[Range<T>],
15159 type_id: TypeId,
15160 auto_scroll: bool,
15161 cx: &mut Context<Self>,
15162 ) {
15163 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15164 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15165 });
15166 self.folds_did_change(cx);
15167 }
15168
15169 fn remove_folds_with<T: ToOffset + Clone>(
15170 &mut self,
15171 ranges: &[Range<T>],
15172 auto_scroll: bool,
15173 cx: &mut Context<Self>,
15174 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15175 ) {
15176 if ranges.is_empty() {
15177 return;
15178 }
15179
15180 let mut buffers_affected = HashSet::default();
15181 let multi_buffer = self.buffer().read(cx);
15182 for range in ranges {
15183 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15184 buffers_affected.insert(buffer.read(cx).remote_id());
15185 };
15186 }
15187
15188 self.display_map.update(cx, update);
15189
15190 if auto_scroll {
15191 self.request_autoscroll(Autoscroll::fit(), cx);
15192 }
15193
15194 cx.notify();
15195 self.scrollbar_marker_state.dirty = true;
15196 self.active_indent_guides_state.dirty = true;
15197 }
15198
15199 pub fn update_fold_widths(
15200 &mut self,
15201 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15202 cx: &mut Context<Self>,
15203 ) -> bool {
15204 self.display_map
15205 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15206 }
15207
15208 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15209 self.display_map.read(cx).fold_placeholder.clone()
15210 }
15211
15212 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15213 self.buffer.update(cx, |buffer, cx| {
15214 buffer.set_all_diff_hunks_expanded(cx);
15215 });
15216 }
15217
15218 pub fn expand_all_diff_hunks(
15219 &mut self,
15220 _: &ExpandAllDiffHunks,
15221 _window: &mut Window,
15222 cx: &mut Context<Self>,
15223 ) {
15224 self.buffer.update(cx, |buffer, cx| {
15225 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15226 });
15227 }
15228
15229 pub fn toggle_selected_diff_hunks(
15230 &mut self,
15231 _: &ToggleSelectedDiffHunks,
15232 _window: &mut Window,
15233 cx: &mut Context<Self>,
15234 ) {
15235 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15236 self.toggle_diff_hunks_in_ranges(ranges, cx);
15237 }
15238
15239 pub fn diff_hunks_in_ranges<'a>(
15240 &'a self,
15241 ranges: &'a [Range<Anchor>],
15242 buffer: &'a MultiBufferSnapshot,
15243 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15244 ranges.iter().flat_map(move |range| {
15245 let end_excerpt_id = range.end.excerpt_id;
15246 let range = range.to_point(buffer);
15247 let mut peek_end = range.end;
15248 if range.end.row < buffer.max_row().0 {
15249 peek_end = Point::new(range.end.row + 1, 0);
15250 }
15251 buffer
15252 .diff_hunks_in_range(range.start..peek_end)
15253 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15254 })
15255 }
15256
15257 pub fn has_stageable_diff_hunks_in_ranges(
15258 &self,
15259 ranges: &[Range<Anchor>],
15260 snapshot: &MultiBufferSnapshot,
15261 ) -> bool {
15262 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15263 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15264 }
15265
15266 pub fn toggle_staged_selected_diff_hunks(
15267 &mut self,
15268 _: &::git::ToggleStaged,
15269 _: &mut Window,
15270 cx: &mut Context<Self>,
15271 ) {
15272 let snapshot = self.buffer.read(cx).snapshot(cx);
15273 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15274 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15275 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15276 }
15277
15278 pub fn set_render_diff_hunk_controls(
15279 &mut self,
15280 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15281 cx: &mut Context<Self>,
15282 ) {
15283 self.render_diff_hunk_controls = render_diff_hunk_controls;
15284 cx.notify();
15285 }
15286
15287 pub fn stage_and_next(
15288 &mut self,
15289 _: &::git::StageAndNext,
15290 window: &mut Window,
15291 cx: &mut Context<Self>,
15292 ) {
15293 self.do_stage_or_unstage_and_next(true, window, cx);
15294 }
15295
15296 pub fn unstage_and_next(
15297 &mut self,
15298 _: &::git::UnstageAndNext,
15299 window: &mut Window,
15300 cx: &mut Context<Self>,
15301 ) {
15302 self.do_stage_or_unstage_and_next(false, window, cx);
15303 }
15304
15305 pub fn stage_or_unstage_diff_hunks(
15306 &mut self,
15307 stage: bool,
15308 ranges: Vec<Range<Anchor>>,
15309 cx: &mut Context<Self>,
15310 ) {
15311 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15312 cx.spawn(async move |this, cx| {
15313 task.await?;
15314 this.update(cx, |this, cx| {
15315 let snapshot = this.buffer.read(cx).snapshot(cx);
15316 let chunk_by = this
15317 .diff_hunks_in_ranges(&ranges, &snapshot)
15318 .chunk_by(|hunk| hunk.buffer_id);
15319 for (buffer_id, hunks) in &chunk_by {
15320 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15321 }
15322 })
15323 })
15324 .detach_and_log_err(cx);
15325 }
15326
15327 fn save_buffers_for_ranges_if_needed(
15328 &mut self,
15329 ranges: &[Range<Anchor>],
15330 cx: &mut Context<Editor>,
15331 ) -> Task<Result<()>> {
15332 let multibuffer = self.buffer.read(cx);
15333 let snapshot = multibuffer.read(cx);
15334 let buffer_ids: HashSet<_> = ranges
15335 .iter()
15336 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15337 .collect();
15338 drop(snapshot);
15339
15340 let mut buffers = HashSet::default();
15341 for buffer_id in buffer_ids {
15342 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15343 let buffer = buffer_entity.read(cx);
15344 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15345 {
15346 buffers.insert(buffer_entity);
15347 }
15348 }
15349 }
15350
15351 if let Some(project) = &self.project {
15352 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15353 } else {
15354 Task::ready(Ok(()))
15355 }
15356 }
15357
15358 fn do_stage_or_unstage_and_next(
15359 &mut self,
15360 stage: bool,
15361 window: &mut Window,
15362 cx: &mut Context<Self>,
15363 ) {
15364 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15365
15366 if ranges.iter().any(|range| range.start != range.end) {
15367 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15368 return;
15369 }
15370
15371 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15372 let snapshot = self.snapshot(window, cx);
15373 let position = self.selections.newest::<Point>(cx).head();
15374 let mut row = snapshot
15375 .buffer_snapshot
15376 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15377 .find(|hunk| hunk.row_range.start.0 > position.row)
15378 .map(|hunk| hunk.row_range.start);
15379
15380 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15381 // Outside of the project diff editor, wrap around to the beginning.
15382 if !all_diff_hunks_expanded {
15383 row = row.or_else(|| {
15384 snapshot
15385 .buffer_snapshot
15386 .diff_hunks_in_range(Point::zero()..position)
15387 .find(|hunk| hunk.row_range.end.0 < position.row)
15388 .map(|hunk| hunk.row_range.start)
15389 });
15390 }
15391
15392 if let Some(row) = row {
15393 let destination = Point::new(row.0, 0);
15394 let autoscroll = Autoscroll::center();
15395
15396 self.unfold_ranges(&[destination..destination], false, false, cx);
15397 self.change_selections(Some(autoscroll), window, cx, |s| {
15398 s.select_ranges([destination..destination]);
15399 });
15400 }
15401 }
15402
15403 fn do_stage_or_unstage(
15404 &self,
15405 stage: bool,
15406 buffer_id: BufferId,
15407 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15408 cx: &mut App,
15409 ) -> Option<()> {
15410 let project = self.project.as_ref()?;
15411 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15412 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15413 let buffer_snapshot = buffer.read(cx).snapshot();
15414 let file_exists = buffer_snapshot
15415 .file()
15416 .is_some_and(|file| file.disk_state().exists());
15417 diff.update(cx, |diff, cx| {
15418 diff.stage_or_unstage_hunks(
15419 stage,
15420 &hunks
15421 .map(|hunk| buffer_diff::DiffHunk {
15422 buffer_range: hunk.buffer_range,
15423 diff_base_byte_range: hunk.diff_base_byte_range,
15424 secondary_status: hunk.secondary_status,
15425 range: Point::zero()..Point::zero(), // unused
15426 })
15427 .collect::<Vec<_>>(),
15428 &buffer_snapshot,
15429 file_exists,
15430 cx,
15431 )
15432 });
15433 None
15434 }
15435
15436 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15437 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15438 self.buffer
15439 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15440 }
15441
15442 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15443 self.buffer.update(cx, |buffer, cx| {
15444 let ranges = vec![Anchor::min()..Anchor::max()];
15445 if !buffer.all_diff_hunks_expanded()
15446 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15447 {
15448 buffer.collapse_diff_hunks(ranges, cx);
15449 true
15450 } else {
15451 false
15452 }
15453 })
15454 }
15455
15456 fn toggle_diff_hunks_in_ranges(
15457 &mut self,
15458 ranges: Vec<Range<Anchor>>,
15459 cx: &mut Context<Editor>,
15460 ) {
15461 self.buffer.update(cx, |buffer, cx| {
15462 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15463 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15464 })
15465 }
15466
15467 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15468 self.buffer.update(cx, |buffer, cx| {
15469 let snapshot = buffer.snapshot(cx);
15470 let excerpt_id = range.end.excerpt_id;
15471 let point_range = range.to_point(&snapshot);
15472 let expand = !buffer.single_hunk_is_expanded(range, cx);
15473 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15474 })
15475 }
15476
15477 pub(crate) fn apply_all_diff_hunks(
15478 &mut self,
15479 _: &ApplyAllDiffHunks,
15480 window: &mut Window,
15481 cx: &mut Context<Self>,
15482 ) {
15483 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15484
15485 let buffers = self.buffer.read(cx).all_buffers();
15486 for branch_buffer in buffers {
15487 branch_buffer.update(cx, |branch_buffer, cx| {
15488 branch_buffer.merge_into_base(Vec::new(), cx);
15489 });
15490 }
15491
15492 if let Some(project) = self.project.clone() {
15493 self.save(true, project, window, cx).detach_and_log_err(cx);
15494 }
15495 }
15496
15497 pub(crate) fn apply_selected_diff_hunks(
15498 &mut self,
15499 _: &ApplyDiffHunk,
15500 window: &mut Window,
15501 cx: &mut Context<Self>,
15502 ) {
15503 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15504 let snapshot = self.snapshot(window, cx);
15505 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15506 let mut ranges_by_buffer = HashMap::default();
15507 self.transact(window, cx, |editor, _window, cx| {
15508 for hunk in hunks {
15509 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15510 ranges_by_buffer
15511 .entry(buffer.clone())
15512 .or_insert_with(Vec::new)
15513 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15514 }
15515 }
15516
15517 for (buffer, ranges) in ranges_by_buffer {
15518 buffer.update(cx, |buffer, cx| {
15519 buffer.merge_into_base(ranges, cx);
15520 });
15521 }
15522 });
15523
15524 if let Some(project) = self.project.clone() {
15525 self.save(true, project, window, cx).detach_and_log_err(cx);
15526 }
15527 }
15528
15529 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15530 if hovered != self.gutter_hovered {
15531 self.gutter_hovered = hovered;
15532 cx.notify();
15533 }
15534 }
15535
15536 pub fn insert_blocks(
15537 &mut self,
15538 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15539 autoscroll: Option<Autoscroll>,
15540 cx: &mut Context<Self>,
15541 ) -> Vec<CustomBlockId> {
15542 let blocks = self
15543 .display_map
15544 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15545 if let Some(autoscroll) = autoscroll {
15546 self.request_autoscroll(autoscroll, cx);
15547 }
15548 cx.notify();
15549 blocks
15550 }
15551
15552 pub fn resize_blocks(
15553 &mut self,
15554 heights: HashMap<CustomBlockId, u32>,
15555 autoscroll: Option<Autoscroll>,
15556 cx: &mut Context<Self>,
15557 ) {
15558 self.display_map
15559 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15560 if let Some(autoscroll) = autoscroll {
15561 self.request_autoscroll(autoscroll, cx);
15562 }
15563 cx.notify();
15564 }
15565
15566 pub fn replace_blocks(
15567 &mut self,
15568 renderers: HashMap<CustomBlockId, RenderBlock>,
15569 autoscroll: Option<Autoscroll>,
15570 cx: &mut Context<Self>,
15571 ) {
15572 self.display_map
15573 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15574 if let Some(autoscroll) = autoscroll {
15575 self.request_autoscroll(autoscroll, cx);
15576 }
15577 cx.notify();
15578 }
15579
15580 pub fn remove_blocks(
15581 &mut self,
15582 block_ids: HashSet<CustomBlockId>,
15583 autoscroll: Option<Autoscroll>,
15584 cx: &mut Context<Self>,
15585 ) {
15586 self.display_map.update(cx, |display_map, cx| {
15587 display_map.remove_blocks(block_ids, cx)
15588 });
15589 if let Some(autoscroll) = autoscroll {
15590 self.request_autoscroll(autoscroll, cx);
15591 }
15592 cx.notify();
15593 }
15594
15595 pub fn row_for_block(
15596 &self,
15597 block_id: CustomBlockId,
15598 cx: &mut Context<Self>,
15599 ) -> Option<DisplayRow> {
15600 self.display_map
15601 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15602 }
15603
15604 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15605 self.focused_block = Some(focused_block);
15606 }
15607
15608 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15609 self.focused_block.take()
15610 }
15611
15612 pub fn insert_creases(
15613 &mut self,
15614 creases: impl IntoIterator<Item = Crease<Anchor>>,
15615 cx: &mut Context<Self>,
15616 ) -> Vec<CreaseId> {
15617 self.display_map
15618 .update(cx, |map, cx| map.insert_creases(creases, cx))
15619 }
15620
15621 pub fn remove_creases(
15622 &mut self,
15623 ids: impl IntoIterator<Item = CreaseId>,
15624 cx: &mut Context<Self>,
15625 ) {
15626 self.display_map
15627 .update(cx, |map, cx| map.remove_creases(ids, cx));
15628 }
15629
15630 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15631 self.display_map
15632 .update(cx, |map, cx| map.snapshot(cx))
15633 .longest_row()
15634 }
15635
15636 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15637 self.display_map
15638 .update(cx, |map, cx| map.snapshot(cx))
15639 .max_point()
15640 }
15641
15642 pub fn text(&self, cx: &App) -> String {
15643 self.buffer.read(cx).read(cx).text()
15644 }
15645
15646 pub fn is_empty(&self, cx: &App) -> bool {
15647 self.buffer.read(cx).read(cx).is_empty()
15648 }
15649
15650 pub fn text_option(&self, cx: &App) -> Option<String> {
15651 let text = self.text(cx);
15652 let text = text.trim();
15653
15654 if text.is_empty() {
15655 return None;
15656 }
15657
15658 Some(text.to_string())
15659 }
15660
15661 pub fn set_text(
15662 &mut self,
15663 text: impl Into<Arc<str>>,
15664 window: &mut Window,
15665 cx: &mut Context<Self>,
15666 ) {
15667 self.transact(window, cx, |this, _, cx| {
15668 this.buffer
15669 .read(cx)
15670 .as_singleton()
15671 .expect("you can only call set_text on editors for singleton buffers")
15672 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15673 });
15674 }
15675
15676 pub fn display_text(&self, cx: &mut App) -> String {
15677 self.display_map
15678 .update(cx, |map, cx| map.snapshot(cx))
15679 .text()
15680 }
15681
15682 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15683 let mut wrap_guides = smallvec::smallvec![];
15684
15685 if self.show_wrap_guides == Some(false) {
15686 return wrap_guides;
15687 }
15688
15689 let settings = self.buffer.read(cx).language_settings(cx);
15690 if settings.show_wrap_guides {
15691 match self.soft_wrap_mode(cx) {
15692 SoftWrap::Column(soft_wrap) => {
15693 wrap_guides.push((soft_wrap as usize, true));
15694 }
15695 SoftWrap::Bounded(soft_wrap) => {
15696 wrap_guides.push((soft_wrap as usize, true));
15697 }
15698 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15699 }
15700 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15701 }
15702
15703 wrap_guides
15704 }
15705
15706 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15707 let settings = self.buffer.read(cx).language_settings(cx);
15708 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15709 match mode {
15710 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15711 SoftWrap::None
15712 }
15713 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15714 language_settings::SoftWrap::PreferredLineLength => {
15715 SoftWrap::Column(settings.preferred_line_length)
15716 }
15717 language_settings::SoftWrap::Bounded => {
15718 SoftWrap::Bounded(settings.preferred_line_length)
15719 }
15720 }
15721 }
15722
15723 pub fn set_soft_wrap_mode(
15724 &mut self,
15725 mode: language_settings::SoftWrap,
15726
15727 cx: &mut Context<Self>,
15728 ) {
15729 self.soft_wrap_mode_override = Some(mode);
15730 cx.notify();
15731 }
15732
15733 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15734 self.hard_wrap = hard_wrap;
15735 cx.notify();
15736 }
15737
15738 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15739 self.text_style_refinement = Some(style);
15740 }
15741
15742 /// called by the Element so we know what style we were most recently rendered with.
15743 pub(crate) fn set_style(
15744 &mut self,
15745 style: EditorStyle,
15746 window: &mut Window,
15747 cx: &mut Context<Self>,
15748 ) {
15749 let rem_size = window.rem_size();
15750 self.display_map.update(cx, |map, cx| {
15751 map.set_font(
15752 style.text.font(),
15753 style.text.font_size.to_pixels(rem_size),
15754 cx,
15755 )
15756 });
15757 self.style = Some(style);
15758 }
15759
15760 pub fn style(&self) -> Option<&EditorStyle> {
15761 self.style.as_ref()
15762 }
15763
15764 // Called by the element. This method is not designed to be called outside of the editor
15765 // element's layout code because it does not notify when rewrapping is computed synchronously.
15766 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15767 self.display_map
15768 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15769 }
15770
15771 pub fn set_soft_wrap(&mut self) {
15772 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15773 }
15774
15775 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15776 if self.soft_wrap_mode_override.is_some() {
15777 self.soft_wrap_mode_override.take();
15778 } else {
15779 let soft_wrap = match self.soft_wrap_mode(cx) {
15780 SoftWrap::GitDiff => return,
15781 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15782 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15783 language_settings::SoftWrap::None
15784 }
15785 };
15786 self.soft_wrap_mode_override = Some(soft_wrap);
15787 }
15788 cx.notify();
15789 }
15790
15791 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15792 let Some(workspace) = self.workspace() else {
15793 return;
15794 };
15795 let fs = workspace.read(cx).app_state().fs.clone();
15796 let current_show = TabBarSettings::get_global(cx).show;
15797 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15798 setting.show = Some(!current_show);
15799 });
15800 }
15801
15802 pub fn toggle_indent_guides(
15803 &mut self,
15804 _: &ToggleIndentGuides,
15805 _: &mut Window,
15806 cx: &mut Context<Self>,
15807 ) {
15808 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15809 self.buffer
15810 .read(cx)
15811 .language_settings(cx)
15812 .indent_guides
15813 .enabled
15814 });
15815 self.show_indent_guides = Some(!currently_enabled);
15816 cx.notify();
15817 }
15818
15819 fn should_show_indent_guides(&self) -> Option<bool> {
15820 self.show_indent_guides
15821 }
15822
15823 pub fn toggle_line_numbers(
15824 &mut self,
15825 _: &ToggleLineNumbers,
15826 _: &mut Window,
15827 cx: &mut Context<Self>,
15828 ) {
15829 let mut editor_settings = EditorSettings::get_global(cx).clone();
15830 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15831 EditorSettings::override_global(editor_settings, cx);
15832 }
15833
15834 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15835 if let Some(show_line_numbers) = self.show_line_numbers {
15836 return show_line_numbers;
15837 }
15838 EditorSettings::get_global(cx).gutter.line_numbers
15839 }
15840
15841 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15842 self.use_relative_line_numbers
15843 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15844 }
15845
15846 pub fn toggle_relative_line_numbers(
15847 &mut self,
15848 _: &ToggleRelativeLineNumbers,
15849 _: &mut Window,
15850 cx: &mut Context<Self>,
15851 ) {
15852 let is_relative = self.should_use_relative_line_numbers(cx);
15853 self.set_relative_line_number(Some(!is_relative), cx)
15854 }
15855
15856 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15857 self.use_relative_line_numbers = is_relative;
15858 cx.notify();
15859 }
15860
15861 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15862 self.show_gutter = show_gutter;
15863 cx.notify();
15864 }
15865
15866 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15867 self.show_scrollbars = show_scrollbars;
15868 cx.notify();
15869 }
15870
15871 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15872 self.show_line_numbers = Some(show_line_numbers);
15873 cx.notify();
15874 }
15875
15876 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15877 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15878 cx.notify();
15879 }
15880
15881 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15882 self.show_code_actions = Some(show_code_actions);
15883 cx.notify();
15884 }
15885
15886 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15887 self.show_runnables = Some(show_runnables);
15888 cx.notify();
15889 }
15890
15891 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15892 self.show_breakpoints = Some(show_breakpoints);
15893 cx.notify();
15894 }
15895
15896 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15897 if self.display_map.read(cx).masked != masked {
15898 self.display_map.update(cx, |map, _| map.masked = masked);
15899 }
15900 cx.notify()
15901 }
15902
15903 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15904 self.show_wrap_guides = Some(show_wrap_guides);
15905 cx.notify();
15906 }
15907
15908 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15909 self.show_indent_guides = Some(show_indent_guides);
15910 cx.notify();
15911 }
15912
15913 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15914 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15915 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15916 if let Some(dir) = file.abs_path(cx).parent() {
15917 return Some(dir.to_owned());
15918 }
15919 }
15920
15921 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15922 return Some(project_path.path.to_path_buf());
15923 }
15924 }
15925
15926 None
15927 }
15928
15929 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15930 self.active_excerpt(cx)?
15931 .1
15932 .read(cx)
15933 .file()
15934 .and_then(|f| f.as_local())
15935 }
15936
15937 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15938 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15939 let buffer = buffer.read(cx);
15940 if let Some(project_path) = buffer.project_path(cx) {
15941 let project = self.project.as_ref()?.read(cx);
15942 project.absolute_path(&project_path, cx)
15943 } else {
15944 buffer
15945 .file()
15946 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15947 }
15948 })
15949 }
15950
15951 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15952 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15953 let project_path = buffer.read(cx).project_path(cx)?;
15954 let project = self.project.as_ref()?.read(cx);
15955 let entry = project.entry_for_path(&project_path, cx)?;
15956 let path = entry.path.to_path_buf();
15957 Some(path)
15958 })
15959 }
15960
15961 pub fn reveal_in_finder(
15962 &mut self,
15963 _: &RevealInFileManager,
15964 _window: &mut Window,
15965 cx: &mut Context<Self>,
15966 ) {
15967 if let Some(target) = self.target_file(cx) {
15968 cx.reveal_path(&target.abs_path(cx));
15969 }
15970 }
15971
15972 pub fn copy_path(
15973 &mut self,
15974 _: &zed_actions::workspace::CopyPath,
15975 _window: &mut Window,
15976 cx: &mut Context<Self>,
15977 ) {
15978 if let Some(path) = self.target_file_abs_path(cx) {
15979 if let Some(path) = path.to_str() {
15980 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15981 }
15982 }
15983 }
15984
15985 pub fn copy_relative_path(
15986 &mut self,
15987 _: &zed_actions::workspace::CopyRelativePath,
15988 _window: &mut Window,
15989 cx: &mut Context<Self>,
15990 ) {
15991 if let Some(path) = self.target_file_path(cx) {
15992 if let Some(path) = path.to_str() {
15993 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15994 }
15995 }
15996 }
15997
15998 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
15999 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16000 buffer.read(cx).project_path(cx)
16001 } else {
16002 None
16003 }
16004 }
16005
16006 // Returns true if the editor handled a go-to-line request
16007 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16008 maybe!({
16009 let breakpoint_store = self.breakpoint_store.as_ref()?;
16010
16011 let Some((_, _, active_position)) =
16012 breakpoint_store.read(cx).active_position().cloned()
16013 else {
16014 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16015 return None;
16016 };
16017
16018 let snapshot = self
16019 .project
16020 .as_ref()?
16021 .read(cx)
16022 .buffer_for_id(active_position.buffer_id?, cx)?
16023 .read(cx)
16024 .snapshot();
16025
16026 let mut handled = false;
16027 for (id, ExcerptRange { context, .. }) in self
16028 .buffer
16029 .read(cx)
16030 .excerpts_for_buffer(active_position.buffer_id?, cx)
16031 {
16032 if context.start.cmp(&active_position, &snapshot).is_ge()
16033 || context.end.cmp(&active_position, &snapshot).is_lt()
16034 {
16035 continue;
16036 }
16037 let snapshot = self.buffer.read(cx).snapshot(cx);
16038 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
16039
16040 handled = true;
16041 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16042 self.go_to_line::<DebugCurrentRowHighlight>(
16043 multibuffer_anchor,
16044 Some(cx.theme().colors().editor_debugger_active_line_background),
16045 window,
16046 cx,
16047 );
16048
16049 cx.notify();
16050 }
16051 handled.then_some(())
16052 })
16053 .is_some()
16054 }
16055
16056 pub fn copy_file_name_without_extension(
16057 &mut self,
16058 _: &CopyFileNameWithoutExtension,
16059 _: &mut Window,
16060 cx: &mut Context<Self>,
16061 ) {
16062 if let Some(file) = self.target_file(cx) {
16063 if let Some(file_stem) = file.path().file_stem() {
16064 if let Some(name) = file_stem.to_str() {
16065 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16066 }
16067 }
16068 }
16069 }
16070
16071 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16072 if let Some(file) = self.target_file(cx) {
16073 if let Some(file_name) = file.path().file_name() {
16074 if let Some(name) = file_name.to_str() {
16075 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16076 }
16077 }
16078 }
16079 }
16080
16081 pub fn toggle_git_blame(
16082 &mut self,
16083 _: &::git::Blame,
16084 window: &mut Window,
16085 cx: &mut Context<Self>,
16086 ) {
16087 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16088
16089 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16090 self.start_git_blame(true, window, cx);
16091 }
16092
16093 cx.notify();
16094 }
16095
16096 pub fn toggle_git_blame_inline(
16097 &mut self,
16098 _: &ToggleGitBlameInline,
16099 window: &mut Window,
16100 cx: &mut Context<Self>,
16101 ) {
16102 self.toggle_git_blame_inline_internal(true, window, cx);
16103 cx.notify();
16104 }
16105
16106 pub fn open_git_blame_commit(
16107 &mut self,
16108 _: &OpenGitBlameCommit,
16109 window: &mut Window,
16110 cx: &mut Context<Self>,
16111 ) {
16112 self.open_git_blame_commit_internal(window, cx);
16113 }
16114
16115 fn open_git_blame_commit_internal(
16116 &mut self,
16117 window: &mut Window,
16118 cx: &mut Context<Self>,
16119 ) -> Option<()> {
16120 let blame = self.blame.as_ref()?;
16121 let snapshot = self.snapshot(window, cx);
16122 let cursor = self.selections.newest::<Point>(cx).head();
16123 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16124 let blame_entry = blame
16125 .update(cx, |blame, cx| {
16126 blame
16127 .blame_for_rows(
16128 &[RowInfo {
16129 buffer_id: Some(buffer.remote_id()),
16130 buffer_row: Some(point.row),
16131 ..Default::default()
16132 }],
16133 cx,
16134 )
16135 .next()
16136 })
16137 .flatten()?;
16138 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16139 let repo = blame.read(cx).repository(cx)?;
16140 let workspace = self.workspace()?.downgrade();
16141 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16142 None
16143 }
16144
16145 pub fn git_blame_inline_enabled(&self) -> bool {
16146 self.git_blame_inline_enabled
16147 }
16148
16149 pub fn toggle_selection_menu(
16150 &mut self,
16151 _: &ToggleSelectionMenu,
16152 _: &mut Window,
16153 cx: &mut Context<Self>,
16154 ) {
16155 self.show_selection_menu = self
16156 .show_selection_menu
16157 .map(|show_selections_menu| !show_selections_menu)
16158 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16159
16160 cx.notify();
16161 }
16162
16163 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16164 self.show_selection_menu
16165 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16166 }
16167
16168 fn start_git_blame(
16169 &mut self,
16170 user_triggered: bool,
16171 window: &mut Window,
16172 cx: &mut Context<Self>,
16173 ) {
16174 if let Some(project) = self.project.as_ref() {
16175 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16176 return;
16177 };
16178
16179 if buffer.read(cx).file().is_none() {
16180 return;
16181 }
16182
16183 let focused = self.focus_handle(cx).contains_focused(window, cx);
16184
16185 let project = project.clone();
16186 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16187 self.blame_subscription =
16188 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16189 self.blame = Some(blame);
16190 }
16191 }
16192
16193 fn toggle_git_blame_inline_internal(
16194 &mut self,
16195 user_triggered: bool,
16196 window: &mut Window,
16197 cx: &mut Context<Self>,
16198 ) {
16199 if self.git_blame_inline_enabled {
16200 self.git_blame_inline_enabled = false;
16201 self.show_git_blame_inline = false;
16202 self.show_git_blame_inline_delay_task.take();
16203 } else {
16204 self.git_blame_inline_enabled = true;
16205 self.start_git_blame_inline(user_triggered, window, cx);
16206 }
16207
16208 cx.notify();
16209 }
16210
16211 fn start_git_blame_inline(
16212 &mut self,
16213 user_triggered: bool,
16214 window: &mut Window,
16215 cx: &mut Context<Self>,
16216 ) {
16217 self.start_git_blame(user_triggered, window, cx);
16218
16219 if ProjectSettings::get_global(cx)
16220 .git
16221 .inline_blame_delay()
16222 .is_some()
16223 {
16224 self.start_inline_blame_timer(window, cx);
16225 } else {
16226 self.show_git_blame_inline = true
16227 }
16228 }
16229
16230 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16231 self.blame.as_ref()
16232 }
16233
16234 pub fn show_git_blame_gutter(&self) -> bool {
16235 self.show_git_blame_gutter
16236 }
16237
16238 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16239 self.show_git_blame_gutter && self.has_blame_entries(cx)
16240 }
16241
16242 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16243 self.show_git_blame_inline
16244 && (self.focus_handle.is_focused(window)
16245 || self
16246 .git_blame_inline_tooltip
16247 .as_ref()
16248 .and_then(|t| t.upgrade())
16249 .is_some())
16250 && !self.newest_selection_head_on_empty_line(cx)
16251 && self.has_blame_entries(cx)
16252 }
16253
16254 fn has_blame_entries(&self, cx: &App) -> bool {
16255 self.blame()
16256 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16257 }
16258
16259 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16260 let cursor_anchor = self.selections.newest_anchor().head();
16261
16262 let snapshot = self.buffer.read(cx).snapshot(cx);
16263 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16264
16265 snapshot.line_len(buffer_row) == 0
16266 }
16267
16268 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16269 let buffer_and_selection = maybe!({
16270 let selection = self.selections.newest::<Point>(cx);
16271 let selection_range = selection.range();
16272
16273 let multi_buffer = self.buffer().read(cx);
16274 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16275 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16276
16277 let (buffer, range, _) = if selection.reversed {
16278 buffer_ranges.first()
16279 } else {
16280 buffer_ranges.last()
16281 }?;
16282
16283 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16284 ..text::ToPoint::to_point(&range.end, &buffer).row;
16285 Some((
16286 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16287 selection,
16288 ))
16289 });
16290
16291 let Some((buffer, selection)) = buffer_and_selection else {
16292 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16293 };
16294
16295 let Some(project) = self.project.as_ref() else {
16296 return Task::ready(Err(anyhow!("editor does not have project")));
16297 };
16298
16299 project.update(cx, |project, cx| {
16300 project.get_permalink_to_line(&buffer, selection, cx)
16301 })
16302 }
16303
16304 pub fn copy_permalink_to_line(
16305 &mut self,
16306 _: &CopyPermalinkToLine,
16307 window: &mut Window,
16308 cx: &mut Context<Self>,
16309 ) {
16310 let permalink_task = self.get_permalink_to_line(cx);
16311 let workspace = self.workspace();
16312
16313 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16314 Ok(permalink) => {
16315 cx.update(|_, cx| {
16316 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16317 })
16318 .ok();
16319 }
16320 Err(err) => {
16321 let message = format!("Failed to copy permalink: {err}");
16322
16323 Err::<(), anyhow::Error>(err).log_err();
16324
16325 if let Some(workspace) = workspace {
16326 workspace
16327 .update_in(cx, |workspace, _, cx| {
16328 struct CopyPermalinkToLine;
16329
16330 workspace.show_toast(
16331 Toast::new(
16332 NotificationId::unique::<CopyPermalinkToLine>(),
16333 message,
16334 ),
16335 cx,
16336 )
16337 })
16338 .ok();
16339 }
16340 }
16341 })
16342 .detach();
16343 }
16344
16345 pub fn copy_file_location(
16346 &mut self,
16347 _: &CopyFileLocation,
16348 _: &mut Window,
16349 cx: &mut Context<Self>,
16350 ) {
16351 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16352 if let Some(file) = self.target_file(cx) {
16353 if let Some(path) = file.path().to_str() {
16354 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16355 }
16356 }
16357 }
16358
16359 pub fn open_permalink_to_line(
16360 &mut self,
16361 _: &OpenPermalinkToLine,
16362 window: &mut Window,
16363 cx: &mut Context<Self>,
16364 ) {
16365 let permalink_task = self.get_permalink_to_line(cx);
16366 let workspace = self.workspace();
16367
16368 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16369 Ok(permalink) => {
16370 cx.update(|_, cx| {
16371 cx.open_url(permalink.as_ref());
16372 })
16373 .ok();
16374 }
16375 Err(err) => {
16376 let message = format!("Failed to open permalink: {err}");
16377
16378 Err::<(), anyhow::Error>(err).log_err();
16379
16380 if let Some(workspace) = workspace {
16381 workspace
16382 .update(cx, |workspace, cx| {
16383 struct OpenPermalinkToLine;
16384
16385 workspace.show_toast(
16386 Toast::new(
16387 NotificationId::unique::<OpenPermalinkToLine>(),
16388 message,
16389 ),
16390 cx,
16391 )
16392 })
16393 .ok();
16394 }
16395 }
16396 })
16397 .detach();
16398 }
16399
16400 pub fn insert_uuid_v4(
16401 &mut self,
16402 _: &InsertUuidV4,
16403 window: &mut Window,
16404 cx: &mut Context<Self>,
16405 ) {
16406 self.insert_uuid(UuidVersion::V4, window, cx);
16407 }
16408
16409 pub fn insert_uuid_v7(
16410 &mut self,
16411 _: &InsertUuidV7,
16412 window: &mut Window,
16413 cx: &mut Context<Self>,
16414 ) {
16415 self.insert_uuid(UuidVersion::V7, window, cx);
16416 }
16417
16418 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16419 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16420 self.transact(window, cx, |this, window, cx| {
16421 let edits = this
16422 .selections
16423 .all::<Point>(cx)
16424 .into_iter()
16425 .map(|selection| {
16426 let uuid = match version {
16427 UuidVersion::V4 => uuid::Uuid::new_v4(),
16428 UuidVersion::V7 => uuid::Uuid::now_v7(),
16429 };
16430
16431 (selection.range(), uuid.to_string())
16432 });
16433 this.edit(edits, cx);
16434 this.refresh_inline_completion(true, false, window, cx);
16435 });
16436 }
16437
16438 pub fn open_selections_in_multibuffer(
16439 &mut self,
16440 _: &OpenSelectionsInMultibuffer,
16441 window: &mut Window,
16442 cx: &mut Context<Self>,
16443 ) {
16444 let multibuffer = self.buffer.read(cx);
16445
16446 let Some(buffer) = multibuffer.as_singleton() else {
16447 return;
16448 };
16449
16450 let Some(workspace) = self.workspace() else {
16451 return;
16452 };
16453
16454 let locations = self
16455 .selections
16456 .disjoint_anchors()
16457 .iter()
16458 .map(|range| Location {
16459 buffer: buffer.clone(),
16460 range: range.start.text_anchor..range.end.text_anchor,
16461 })
16462 .collect::<Vec<_>>();
16463
16464 let title = multibuffer.title(cx).to_string();
16465
16466 cx.spawn_in(window, async move |_, cx| {
16467 workspace.update_in(cx, |workspace, window, cx| {
16468 Self::open_locations_in_multibuffer(
16469 workspace,
16470 locations,
16471 format!("Selections for '{title}'"),
16472 false,
16473 MultibufferSelectionMode::All,
16474 window,
16475 cx,
16476 );
16477 })
16478 })
16479 .detach();
16480 }
16481
16482 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16483 /// last highlight added will be used.
16484 ///
16485 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16486 pub fn highlight_rows<T: 'static>(
16487 &mut self,
16488 range: Range<Anchor>,
16489 color: Hsla,
16490 should_autoscroll: bool,
16491 cx: &mut Context<Self>,
16492 ) {
16493 let snapshot = self.buffer().read(cx).snapshot(cx);
16494 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16495 let ix = row_highlights.binary_search_by(|highlight| {
16496 Ordering::Equal
16497 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16498 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16499 });
16500
16501 if let Err(mut ix) = ix {
16502 let index = post_inc(&mut self.highlight_order);
16503
16504 // If this range intersects with the preceding highlight, then merge it with
16505 // the preceding highlight. Otherwise insert a new highlight.
16506 let mut merged = false;
16507 if ix > 0 {
16508 let prev_highlight = &mut row_highlights[ix - 1];
16509 if prev_highlight
16510 .range
16511 .end
16512 .cmp(&range.start, &snapshot)
16513 .is_ge()
16514 {
16515 ix -= 1;
16516 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16517 prev_highlight.range.end = range.end;
16518 }
16519 merged = true;
16520 prev_highlight.index = index;
16521 prev_highlight.color = color;
16522 prev_highlight.should_autoscroll = should_autoscroll;
16523 }
16524 }
16525
16526 if !merged {
16527 row_highlights.insert(
16528 ix,
16529 RowHighlight {
16530 range: range.clone(),
16531 index,
16532 color,
16533 should_autoscroll,
16534 },
16535 );
16536 }
16537
16538 // If any of the following highlights intersect with this one, merge them.
16539 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16540 let highlight = &row_highlights[ix];
16541 if next_highlight
16542 .range
16543 .start
16544 .cmp(&highlight.range.end, &snapshot)
16545 .is_le()
16546 {
16547 if next_highlight
16548 .range
16549 .end
16550 .cmp(&highlight.range.end, &snapshot)
16551 .is_gt()
16552 {
16553 row_highlights[ix].range.end = next_highlight.range.end;
16554 }
16555 row_highlights.remove(ix + 1);
16556 } else {
16557 break;
16558 }
16559 }
16560 }
16561 }
16562
16563 /// Remove any highlighted row ranges of the given type that intersect the
16564 /// given ranges.
16565 pub fn remove_highlighted_rows<T: 'static>(
16566 &mut self,
16567 ranges_to_remove: Vec<Range<Anchor>>,
16568 cx: &mut Context<Self>,
16569 ) {
16570 let snapshot = self.buffer().read(cx).snapshot(cx);
16571 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16572 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16573 row_highlights.retain(|highlight| {
16574 while let Some(range_to_remove) = ranges_to_remove.peek() {
16575 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16576 Ordering::Less | Ordering::Equal => {
16577 ranges_to_remove.next();
16578 }
16579 Ordering::Greater => {
16580 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16581 Ordering::Less | Ordering::Equal => {
16582 return false;
16583 }
16584 Ordering::Greater => break,
16585 }
16586 }
16587 }
16588 }
16589
16590 true
16591 })
16592 }
16593
16594 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16595 pub fn clear_row_highlights<T: 'static>(&mut self) {
16596 self.highlighted_rows.remove(&TypeId::of::<T>());
16597 }
16598
16599 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16600 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16601 self.highlighted_rows
16602 .get(&TypeId::of::<T>())
16603 .map_or(&[] as &[_], |vec| vec.as_slice())
16604 .iter()
16605 .map(|highlight| (highlight.range.clone(), highlight.color))
16606 }
16607
16608 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16609 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16610 /// Allows to ignore certain kinds of highlights.
16611 pub fn highlighted_display_rows(
16612 &self,
16613 window: &mut Window,
16614 cx: &mut App,
16615 ) -> BTreeMap<DisplayRow, LineHighlight> {
16616 let snapshot = self.snapshot(window, cx);
16617 let mut used_highlight_orders = HashMap::default();
16618 self.highlighted_rows
16619 .iter()
16620 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16621 .fold(
16622 BTreeMap::<DisplayRow, LineHighlight>::new(),
16623 |mut unique_rows, highlight| {
16624 let start = highlight.range.start.to_display_point(&snapshot);
16625 let end = highlight.range.end.to_display_point(&snapshot);
16626 let start_row = start.row().0;
16627 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16628 && end.column() == 0
16629 {
16630 end.row().0.saturating_sub(1)
16631 } else {
16632 end.row().0
16633 };
16634 for row in start_row..=end_row {
16635 let used_index =
16636 used_highlight_orders.entry(row).or_insert(highlight.index);
16637 if highlight.index >= *used_index {
16638 *used_index = highlight.index;
16639 unique_rows.insert(DisplayRow(row), highlight.color.into());
16640 }
16641 }
16642 unique_rows
16643 },
16644 )
16645 }
16646
16647 pub fn highlighted_display_row_for_autoscroll(
16648 &self,
16649 snapshot: &DisplaySnapshot,
16650 ) -> Option<DisplayRow> {
16651 self.highlighted_rows
16652 .values()
16653 .flat_map(|highlighted_rows| highlighted_rows.iter())
16654 .filter_map(|highlight| {
16655 if highlight.should_autoscroll {
16656 Some(highlight.range.start.to_display_point(snapshot).row())
16657 } else {
16658 None
16659 }
16660 })
16661 .min()
16662 }
16663
16664 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16665 self.highlight_background::<SearchWithinRange>(
16666 ranges,
16667 |colors| colors.editor_document_highlight_read_background,
16668 cx,
16669 )
16670 }
16671
16672 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16673 self.breadcrumb_header = Some(new_header);
16674 }
16675
16676 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16677 self.clear_background_highlights::<SearchWithinRange>(cx);
16678 }
16679
16680 pub fn highlight_background<T: 'static>(
16681 &mut self,
16682 ranges: &[Range<Anchor>],
16683 color_fetcher: fn(&ThemeColors) -> Hsla,
16684 cx: &mut Context<Self>,
16685 ) {
16686 self.background_highlights
16687 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16688 self.scrollbar_marker_state.dirty = true;
16689 cx.notify();
16690 }
16691
16692 pub fn clear_background_highlights<T: 'static>(
16693 &mut self,
16694 cx: &mut Context<Self>,
16695 ) -> Option<BackgroundHighlight> {
16696 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16697 if !text_highlights.1.is_empty() {
16698 self.scrollbar_marker_state.dirty = true;
16699 cx.notify();
16700 }
16701 Some(text_highlights)
16702 }
16703
16704 pub fn highlight_gutter<T: 'static>(
16705 &mut self,
16706 ranges: &[Range<Anchor>],
16707 color_fetcher: fn(&App) -> Hsla,
16708 cx: &mut Context<Self>,
16709 ) {
16710 self.gutter_highlights
16711 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16712 cx.notify();
16713 }
16714
16715 pub fn clear_gutter_highlights<T: 'static>(
16716 &mut self,
16717 cx: &mut Context<Self>,
16718 ) -> Option<GutterHighlight> {
16719 cx.notify();
16720 self.gutter_highlights.remove(&TypeId::of::<T>())
16721 }
16722
16723 #[cfg(feature = "test-support")]
16724 pub fn all_text_background_highlights(
16725 &self,
16726 window: &mut Window,
16727 cx: &mut Context<Self>,
16728 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16729 let snapshot = self.snapshot(window, cx);
16730 let buffer = &snapshot.buffer_snapshot;
16731 let start = buffer.anchor_before(0);
16732 let end = buffer.anchor_after(buffer.len());
16733 let theme = cx.theme().colors();
16734 self.background_highlights_in_range(start..end, &snapshot, theme)
16735 }
16736
16737 #[cfg(feature = "test-support")]
16738 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16739 let snapshot = self.buffer().read(cx).snapshot(cx);
16740
16741 let highlights = self
16742 .background_highlights
16743 .get(&TypeId::of::<items::BufferSearchHighlights>());
16744
16745 if let Some((_color, ranges)) = highlights {
16746 ranges
16747 .iter()
16748 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16749 .collect_vec()
16750 } else {
16751 vec![]
16752 }
16753 }
16754
16755 fn document_highlights_for_position<'a>(
16756 &'a self,
16757 position: Anchor,
16758 buffer: &'a MultiBufferSnapshot,
16759 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16760 let read_highlights = self
16761 .background_highlights
16762 .get(&TypeId::of::<DocumentHighlightRead>())
16763 .map(|h| &h.1);
16764 let write_highlights = self
16765 .background_highlights
16766 .get(&TypeId::of::<DocumentHighlightWrite>())
16767 .map(|h| &h.1);
16768 let left_position = position.bias_left(buffer);
16769 let right_position = position.bias_right(buffer);
16770 read_highlights
16771 .into_iter()
16772 .chain(write_highlights)
16773 .flat_map(move |ranges| {
16774 let start_ix = match ranges.binary_search_by(|probe| {
16775 let cmp = probe.end.cmp(&left_position, buffer);
16776 if cmp.is_ge() {
16777 Ordering::Greater
16778 } else {
16779 Ordering::Less
16780 }
16781 }) {
16782 Ok(i) | Err(i) => i,
16783 };
16784
16785 ranges[start_ix..]
16786 .iter()
16787 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16788 })
16789 }
16790
16791 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16792 self.background_highlights
16793 .get(&TypeId::of::<T>())
16794 .map_or(false, |(_, highlights)| !highlights.is_empty())
16795 }
16796
16797 pub fn background_highlights_in_range(
16798 &self,
16799 search_range: Range<Anchor>,
16800 display_snapshot: &DisplaySnapshot,
16801 theme: &ThemeColors,
16802 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16803 let mut results = Vec::new();
16804 for (color_fetcher, ranges) in self.background_highlights.values() {
16805 let color = color_fetcher(theme);
16806 let start_ix = match ranges.binary_search_by(|probe| {
16807 let cmp = probe
16808 .end
16809 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16810 if cmp.is_gt() {
16811 Ordering::Greater
16812 } else {
16813 Ordering::Less
16814 }
16815 }) {
16816 Ok(i) | Err(i) => i,
16817 };
16818 for range in &ranges[start_ix..] {
16819 if range
16820 .start
16821 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16822 .is_ge()
16823 {
16824 break;
16825 }
16826
16827 let start = range.start.to_display_point(display_snapshot);
16828 let end = range.end.to_display_point(display_snapshot);
16829 results.push((start..end, color))
16830 }
16831 }
16832 results
16833 }
16834
16835 pub fn background_highlight_row_ranges<T: 'static>(
16836 &self,
16837 search_range: Range<Anchor>,
16838 display_snapshot: &DisplaySnapshot,
16839 count: usize,
16840 ) -> Vec<RangeInclusive<DisplayPoint>> {
16841 let mut results = Vec::new();
16842 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16843 return vec![];
16844 };
16845
16846 let start_ix = match ranges.binary_search_by(|probe| {
16847 let cmp = probe
16848 .end
16849 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16850 if cmp.is_gt() {
16851 Ordering::Greater
16852 } else {
16853 Ordering::Less
16854 }
16855 }) {
16856 Ok(i) | Err(i) => i,
16857 };
16858 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16859 if let (Some(start_display), Some(end_display)) = (start, end) {
16860 results.push(
16861 start_display.to_display_point(display_snapshot)
16862 ..=end_display.to_display_point(display_snapshot),
16863 );
16864 }
16865 };
16866 let mut start_row: Option<Point> = None;
16867 let mut end_row: Option<Point> = None;
16868 if ranges.len() > count {
16869 return Vec::new();
16870 }
16871 for range in &ranges[start_ix..] {
16872 if range
16873 .start
16874 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16875 .is_ge()
16876 {
16877 break;
16878 }
16879 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16880 if let Some(current_row) = &end_row {
16881 if end.row == current_row.row {
16882 continue;
16883 }
16884 }
16885 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16886 if start_row.is_none() {
16887 assert_eq!(end_row, None);
16888 start_row = Some(start);
16889 end_row = Some(end);
16890 continue;
16891 }
16892 if let Some(current_end) = end_row.as_mut() {
16893 if start.row > current_end.row + 1 {
16894 push_region(start_row, end_row);
16895 start_row = Some(start);
16896 end_row = Some(end);
16897 } else {
16898 // Merge two hunks.
16899 *current_end = end;
16900 }
16901 } else {
16902 unreachable!();
16903 }
16904 }
16905 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16906 push_region(start_row, end_row);
16907 results
16908 }
16909
16910 pub fn gutter_highlights_in_range(
16911 &self,
16912 search_range: Range<Anchor>,
16913 display_snapshot: &DisplaySnapshot,
16914 cx: &App,
16915 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16916 let mut results = Vec::new();
16917 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16918 let color = color_fetcher(cx);
16919 let start_ix = match ranges.binary_search_by(|probe| {
16920 let cmp = probe
16921 .end
16922 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16923 if cmp.is_gt() {
16924 Ordering::Greater
16925 } else {
16926 Ordering::Less
16927 }
16928 }) {
16929 Ok(i) | Err(i) => i,
16930 };
16931 for range in &ranges[start_ix..] {
16932 if range
16933 .start
16934 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16935 .is_ge()
16936 {
16937 break;
16938 }
16939
16940 let start = range.start.to_display_point(display_snapshot);
16941 let end = range.end.to_display_point(display_snapshot);
16942 results.push((start..end, color))
16943 }
16944 }
16945 results
16946 }
16947
16948 /// Get the text ranges corresponding to the redaction query
16949 pub fn redacted_ranges(
16950 &self,
16951 search_range: Range<Anchor>,
16952 display_snapshot: &DisplaySnapshot,
16953 cx: &App,
16954 ) -> Vec<Range<DisplayPoint>> {
16955 display_snapshot
16956 .buffer_snapshot
16957 .redacted_ranges(search_range, |file| {
16958 if let Some(file) = file {
16959 file.is_private()
16960 && EditorSettings::get(
16961 Some(SettingsLocation {
16962 worktree_id: file.worktree_id(cx),
16963 path: file.path().as_ref(),
16964 }),
16965 cx,
16966 )
16967 .redact_private_values
16968 } else {
16969 false
16970 }
16971 })
16972 .map(|range| {
16973 range.start.to_display_point(display_snapshot)
16974 ..range.end.to_display_point(display_snapshot)
16975 })
16976 .collect()
16977 }
16978
16979 pub fn highlight_text<T: 'static>(
16980 &mut self,
16981 ranges: Vec<Range<Anchor>>,
16982 style: HighlightStyle,
16983 cx: &mut Context<Self>,
16984 ) {
16985 self.display_map.update(cx, |map, _| {
16986 map.highlight_text(TypeId::of::<T>(), ranges, style)
16987 });
16988 cx.notify();
16989 }
16990
16991 pub(crate) fn highlight_inlays<T: 'static>(
16992 &mut self,
16993 highlights: Vec<InlayHighlight>,
16994 style: HighlightStyle,
16995 cx: &mut Context<Self>,
16996 ) {
16997 self.display_map.update(cx, |map, _| {
16998 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16999 });
17000 cx.notify();
17001 }
17002
17003 pub fn text_highlights<'a, T: 'static>(
17004 &'a self,
17005 cx: &'a App,
17006 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17007 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17008 }
17009
17010 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17011 let cleared = self
17012 .display_map
17013 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17014 if cleared {
17015 cx.notify();
17016 }
17017 }
17018
17019 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17020 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17021 && self.focus_handle.is_focused(window)
17022 }
17023
17024 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17025 self.show_cursor_when_unfocused = is_enabled;
17026 cx.notify();
17027 }
17028
17029 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17030 cx.notify();
17031 }
17032
17033 fn on_buffer_event(
17034 &mut self,
17035 multibuffer: &Entity<MultiBuffer>,
17036 event: &multi_buffer::Event,
17037 window: &mut Window,
17038 cx: &mut Context<Self>,
17039 ) {
17040 match event {
17041 multi_buffer::Event::Edited {
17042 singleton_buffer_edited,
17043 edited_buffer: buffer_edited,
17044 } => {
17045 self.scrollbar_marker_state.dirty = true;
17046 self.active_indent_guides_state.dirty = true;
17047 self.refresh_active_diagnostics(cx);
17048 self.refresh_code_actions(window, cx);
17049 if self.has_active_inline_completion() {
17050 self.update_visible_inline_completion(window, cx);
17051 }
17052 if let Some(buffer) = buffer_edited {
17053 let buffer_id = buffer.read(cx).remote_id();
17054 if !self.registered_buffers.contains_key(&buffer_id) {
17055 if let Some(project) = self.project.as_ref() {
17056 project.update(cx, |project, cx| {
17057 self.registered_buffers.insert(
17058 buffer_id,
17059 project.register_buffer_with_language_servers(&buffer, cx),
17060 );
17061 })
17062 }
17063 }
17064 }
17065 cx.emit(EditorEvent::BufferEdited);
17066 cx.emit(SearchEvent::MatchesInvalidated);
17067 if *singleton_buffer_edited {
17068 if let Some(project) = &self.project {
17069 #[allow(clippy::mutable_key_type)]
17070 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17071 multibuffer
17072 .all_buffers()
17073 .into_iter()
17074 .filter_map(|buffer| {
17075 buffer.update(cx, |buffer, cx| {
17076 let language = buffer.language()?;
17077 let should_discard = project.update(cx, |project, cx| {
17078 project.is_local()
17079 && !project.has_language_servers_for(buffer, cx)
17080 });
17081 should_discard.not().then_some(language.clone())
17082 })
17083 })
17084 .collect::<HashSet<_>>()
17085 });
17086 if !languages_affected.is_empty() {
17087 self.refresh_inlay_hints(
17088 InlayHintRefreshReason::BufferEdited(languages_affected),
17089 cx,
17090 );
17091 }
17092 }
17093 }
17094
17095 let Some(project) = &self.project else { return };
17096 let (telemetry, is_via_ssh) = {
17097 let project = project.read(cx);
17098 let telemetry = project.client().telemetry().clone();
17099 let is_via_ssh = project.is_via_ssh();
17100 (telemetry, is_via_ssh)
17101 };
17102 refresh_linked_ranges(self, window, cx);
17103 telemetry.log_edit_event("editor", is_via_ssh);
17104 }
17105 multi_buffer::Event::ExcerptsAdded {
17106 buffer,
17107 predecessor,
17108 excerpts,
17109 } => {
17110 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17111 let buffer_id = buffer.read(cx).remote_id();
17112 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17113 if let Some(project) = &self.project {
17114 get_uncommitted_diff_for_buffer(
17115 project,
17116 [buffer.clone()],
17117 self.buffer.clone(),
17118 cx,
17119 )
17120 .detach();
17121 }
17122 }
17123 cx.emit(EditorEvent::ExcerptsAdded {
17124 buffer: buffer.clone(),
17125 predecessor: *predecessor,
17126 excerpts: excerpts.clone(),
17127 });
17128 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17129 }
17130 multi_buffer::Event::ExcerptsRemoved { ids } => {
17131 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17132 let buffer = self.buffer.read(cx);
17133 self.registered_buffers
17134 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17135 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17136 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
17137 }
17138 multi_buffer::Event::ExcerptsEdited {
17139 excerpt_ids,
17140 buffer_ids,
17141 } => {
17142 self.display_map.update(cx, |map, cx| {
17143 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17144 });
17145 cx.emit(EditorEvent::ExcerptsEdited {
17146 ids: excerpt_ids.clone(),
17147 })
17148 }
17149 multi_buffer::Event::ExcerptsExpanded { ids } => {
17150 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17151 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17152 }
17153 multi_buffer::Event::Reparsed(buffer_id) => {
17154 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17155 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17156
17157 cx.emit(EditorEvent::Reparsed(*buffer_id));
17158 }
17159 multi_buffer::Event::DiffHunksToggled => {
17160 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17161 }
17162 multi_buffer::Event::LanguageChanged(buffer_id) => {
17163 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17164 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17165 cx.emit(EditorEvent::Reparsed(*buffer_id));
17166 cx.notify();
17167 }
17168 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17169 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17170 multi_buffer::Event::FileHandleChanged
17171 | multi_buffer::Event::Reloaded
17172 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17173 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17174 multi_buffer::Event::DiagnosticsUpdated => {
17175 self.refresh_active_diagnostics(cx);
17176 self.refresh_inline_diagnostics(true, window, cx);
17177 self.scrollbar_marker_state.dirty = true;
17178 cx.notify();
17179 }
17180 _ => {}
17181 };
17182 }
17183
17184 fn on_display_map_changed(
17185 &mut self,
17186 _: Entity<DisplayMap>,
17187 _: &mut Window,
17188 cx: &mut Context<Self>,
17189 ) {
17190 cx.notify();
17191 }
17192
17193 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17194 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17195 self.update_edit_prediction_settings(cx);
17196 self.refresh_inline_completion(true, false, window, cx);
17197 self.refresh_inlay_hints(
17198 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17199 self.selections.newest_anchor().head(),
17200 &self.buffer.read(cx).snapshot(cx),
17201 cx,
17202 )),
17203 cx,
17204 );
17205
17206 let old_cursor_shape = self.cursor_shape;
17207
17208 {
17209 let editor_settings = EditorSettings::get_global(cx);
17210 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17211 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17212 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17213 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17214 }
17215
17216 if old_cursor_shape != self.cursor_shape {
17217 cx.emit(EditorEvent::CursorShapeChanged);
17218 }
17219
17220 let project_settings = ProjectSettings::get_global(cx);
17221 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17222
17223 if self.mode == EditorMode::Full {
17224 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17225 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17226 if self.show_inline_diagnostics != show_inline_diagnostics {
17227 self.show_inline_diagnostics = show_inline_diagnostics;
17228 self.refresh_inline_diagnostics(false, window, cx);
17229 }
17230
17231 if self.git_blame_inline_enabled != inline_blame_enabled {
17232 self.toggle_git_blame_inline_internal(false, window, cx);
17233 }
17234 }
17235
17236 cx.notify();
17237 }
17238
17239 pub fn set_searchable(&mut self, searchable: bool) {
17240 self.searchable = searchable;
17241 }
17242
17243 pub fn searchable(&self) -> bool {
17244 self.searchable
17245 }
17246
17247 fn open_proposed_changes_editor(
17248 &mut self,
17249 _: &OpenProposedChangesEditor,
17250 window: &mut Window,
17251 cx: &mut Context<Self>,
17252 ) {
17253 let Some(workspace) = self.workspace() else {
17254 cx.propagate();
17255 return;
17256 };
17257
17258 let selections = self.selections.all::<usize>(cx);
17259 let multi_buffer = self.buffer.read(cx);
17260 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17261 let mut new_selections_by_buffer = HashMap::default();
17262 for selection in selections {
17263 for (buffer, range, _) in
17264 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17265 {
17266 let mut range = range.to_point(buffer);
17267 range.start.column = 0;
17268 range.end.column = buffer.line_len(range.end.row);
17269 new_selections_by_buffer
17270 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17271 .or_insert(Vec::new())
17272 .push(range)
17273 }
17274 }
17275
17276 let proposed_changes_buffers = new_selections_by_buffer
17277 .into_iter()
17278 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17279 .collect::<Vec<_>>();
17280 let proposed_changes_editor = cx.new(|cx| {
17281 ProposedChangesEditor::new(
17282 "Proposed changes",
17283 proposed_changes_buffers,
17284 self.project.clone(),
17285 window,
17286 cx,
17287 )
17288 });
17289
17290 window.defer(cx, move |window, cx| {
17291 workspace.update(cx, |workspace, cx| {
17292 workspace.active_pane().update(cx, |pane, cx| {
17293 pane.add_item(
17294 Box::new(proposed_changes_editor),
17295 true,
17296 true,
17297 None,
17298 window,
17299 cx,
17300 );
17301 });
17302 });
17303 });
17304 }
17305
17306 pub fn open_excerpts_in_split(
17307 &mut self,
17308 _: &OpenExcerptsSplit,
17309 window: &mut Window,
17310 cx: &mut Context<Self>,
17311 ) {
17312 self.open_excerpts_common(None, true, window, cx)
17313 }
17314
17315 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17316 self.open_excerpts_common(None, false, window, cx)
17317 }
17318
17319 fn open_excerpts_common(
17320 &mut self,
17321 jump_data: Option<JumpData>,
17322 split: bool,
17323 window: &mut Window,
17324 cx: &mut Context<Self>,
17325 ) {
17326 let Some(workspace) = self.workspace() else {
17327 cx.propagate();
17328 return;
17329 };
17330
17331 if self.buffer.read(cx).is_singleton() {
17332 cx.propagate();
17333 return;
17334 }
17335
17336 let mut new_selections_by_buffer = HashMap::default();
17337 match &jump_data {
17338 Some(JumpData::MultiBufferPoint {
17339 excerpt_id,
17340 position,
17341 anchor,
17342 line_offset_from_top,
17343 }) => {
17344 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17345 if let Some(buffer) = multi_buffer_snapshot
17346 .buffer_id_for_excerpt(*excerpt_id)
17347 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17348 {
17349 let buffer_snapshot = buffer.read(cx).snapshot();
17350 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17351 language::ToPoint::to_point(anchor, &buffer_snapshot)
17352 } else {
17353 buffer_snapshot.clip_point(*position, Bias::Left)
17354 };
17355 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17356 new_selections_by_buffer.insert(
17357 buffer,
17358 (
17359 vec![jump_to_offset..jump_to_offset],
17360 Some(*line_offset_from_top),
17361 ),
17362 );
17363 }
17364 }
17365 Some(JumpData::MultiBufferRow {
17366 row,
17367 line_offset_from_top,
17368 }) => {
17369 let point = MultiBufferPoint::new(row.0, 0);
17370 if let Some((buffer, buffer_point, _)) =
17371 self.buffer.read(cx).point_to_buffer_point(point, cx)
17372 {
17373 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17374 new_selections_by_buffer
17375 .entry(buffer)
17376 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17377 .0
17378 .push(buffer_offset..buffer_offset)
17379 }
17380 }
17381 None => {
17382 let selections = self.selections.all::<usize>(cx);
17383 let multi_buffer = self.buffer.read(cx);
17384 for selection in selections {
17385 for (snapshot, range, _, anchor) in multi_buffer
17386 .snapshot(cx)
17387 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17388 {
17389 if let Some(anchor) = anchor {
17390 // selection is in a deleted hunk
17391 let Some(buffer_id) = anchor.buffer_id else {
17392 continue;
17393 };
17394 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17395 continue;
17396 };
17397 let offset = text::ToOffset::to_offset(
17398 &anchor.text_anchor,
17399 &buffer_handle.read(cx).snapshot(),
17400 );
17401 let range = offset..offset;
17402 new_selections_by_buffer
17403 .entry(buffer_handle)
17404 .or_insert((Vec::new(), None))
17405 .0
17406 .push(range)
17407 } else {
17408 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17409 else {
17410 continue;
17411 };
17412 new_selections_by_buffer
17413 .entry(buffer_handle)
17414 .or_insert((Vec::new(), None))
17415 .0
17416 .push(range)
17417 }
17418 }
17419 }
17420 }
17421 }
17422
17423 new_selections_by_buffer
17424 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
17425
17426 if new_selections_by_buffer.is_empty() {
17427 return;
17428 }
17429
17430 // We defer the pane interaction because we ourselves are a workspace item
17431 // and activating a new item causes the pane to call a method on us reentrantly,
17432 // which panics if we're on the stack.
17433 window.defer(cx, move |window, cx| {
17434 workspace.update(cx, |workspace, cx| {
17435 let pane = if split {
17436 workspace.adjacent_pane(window, cx)
17437 } else {
17438 workspace.active_pane().clone()
17439 };
17440
17441 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17442 let editor = buffer
17443 .read(cx)
17444 .file()
17445 .is_none()
17446 .then(|| {
17447 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17448 // so `workspace.open_project_item` will never find them, always opening a new editor.
17449 // Instead, we try to activate the existing editor in the pane first.
17450 let (editor, pane_item_index) =
17451 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17452 let editor = item.downcast::<Editor>()?;
17453 let singleton_buffer =
17454 editor.read(cx).buffer().read(cx).as_singleton()?;
17455 if singleton_buffer == buffer {
17456 Some((editor, i))
17457 } else {
17458 None
17459 }
17460 })?;
17461 pane.update(cx, |pane, cx| {
17462 pane.activate_item(pane_item_index, true, true, window, cx)
17463 });
17464 Some(editor)
17465 })
17466 .flatten()
17467 .unwrap_or_else(|| {
17468 workspace.open_project_item::<Self>(
17469 pane.clone(),
17470 buffer,
17471 true,
17472 true,
17473 window,
17474 cx,
17475 )
17476 });
17477
17478 editor.update(cx, |editor, cx| {
17479 let autoscroll = match scroll_offset {
17480 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17481 None => Autoscroll::newest(),
17482 };
17483 let nav_history = editor.nav_history.take();
17484 editor.change_selections(Some(autoscroll), window, cx, |s| {
17485 s.select_ranges(ranges);
17486 });
17487 editor.nav_history = nav_history;
17488 });
17489 }
17490 })
17491 });
17492 }
17493
17494 // For now, don't allow opening excerpts in buffers that aren't backed by
17495 // regular project files.
17496 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
17497 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
17498 }
17499
17500 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17501 let snapshot = self.buffer.read(cx).read(cx);
17502 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17503 Some(
17504 ranges
17505 .iter()
17506 .map(move |range| {
17507 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17508 })
17509 .collect(),
17510 )
17511 }
17512
17513 fn selection_replacement_ranges(
17514 &self,
17515 range: Range<OffsetUtf16>,
17516 cx: &mut App,
17517 ) -> Vec<Range<OffsetUtf16>> {
17518 let selections = self.selections.all::<OffsetUtf16>(cx);
17519 let newest_selection = selections
17520 .iter()
17521 .max_by_key(|selection| selection.id)
17522 .unwrap();
17523 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17524 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17525 let snapshot = self.buffer.read(cx).read(cx);
17526 selections
17527 .into_iter()
17528 .map(|mut selection| {
17529 selection.start.0 =
17530 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17531 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17532 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17533 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17534 })
17535 .collect()
17536 }
17537
17538 fn report_editor_event(
17539 &self,
17540 event_type: &'static str,
17541 file_extension: Option<String>,
17542 cx: &App,
17543 ) {
17544 if cfg!(any(test, feature = "test-support")) {
17545 return;
17546 }
17547
17548 let Some(project) = &self.project else { return };
17549
17550 // If None, we are in a file without an extension
17551 let file = self
17552 .buffer
17553 .read(cx)
17554 .as_singleton()
17555 .and_then(|b| b.read(cx).file());
17556 let file_extension = file_extension.or(file
17557 .as_ref()
17558 .and_then(|file| Path::new(file.file_name(cx)).extension())
17559 .and_then(|e| e.to_str())
17560 .map(|a| a.to_string()));
17561
17562 let vim_mode = cx
17563 .global::<SettingsStore>()
17564 .raw_user_settings()
17565 .get("vim_mode")
17566 == Some(&serde_json::Value::Bool(true));
17567
17568 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17569 let copilot_enabled = edit_predictions_provider
17570 == language::language_settings::EditPredictionProvider::Copilot;
17571 let copilot_enabled_for_language = self
17572 .buffer
17573 .read(cx)
17574 .language_settings(cx)
17575 .show_edit_predictions;
17576
17577 let project = project.read(cx);
17578 telemetry::event!(
17579 event_type,
17580 file_extension,
17581 vim_mode,
17582 copilot_enabled,
17583 copilot_enabled_for_language,
17584 edit_predictions_provider,
17585 is_via_ssh = project.is_via_ssh(),
17586 );
17587 }
17588
17589 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17590 /// with each line being an array of {text, highlight} objects.
17591 fn copy_highlight_json(
17592 &mut self,
17593 _: &CopyHighlightJson,
17594 window: &mut Window,
17595 cx: &mut Context<Self>,
17596 ) {
17597 #[derive(Serialize)]
17598 struct Chunk<'a> {
17599 text: String,
17600 highlight: Option<&'a str>,
17601 }
17602
17603 let snapshot = self.buffer.read(cx).snapshot(cx);
17604 let range = self
17605 .selected_text_range(false, window, cx)
17606 .and_then(|selection| {
17607 if selection.range.is_empty() {
17608 None
17609 } else {
17610 Some(selection.range)
17611 }
17612 })
17613 .unwrap_or_else(|| 0..snapshot.len());
17614
17615 let chunks = snapshot.chunks(range, true);
17616 let mut lines = Vec::new();
17617 let mut line: VecDeque<Chunk> = VecDeque::new();
17618
17619 let Some(style) = self.style.as_ref() else {
17620 return;
17621 };
17622
17623 for chunk in chunks {
17624 let highlight = chunk
17625 .syntax_highlight_id
17626 .and_then(|id| id.name(&style.syntax));
17627 let mut chunk_lines = chunk.text.split('\n').peekable();
17628 while let Some(text) = chunk_lines.next() {
17629 let mut merged_with_last_token = false;
17630 if let Some(last_token) = line.back_mut() {
17631 if last_token.highlight == highlight {
17632 last_token.text.push_str(text);
17633 merged_with_last_token = true;
17634 }
17635 }
17636
17637 if !merged_with_last_token {
17638 line.push_back(Chunk {
17639 text: text.into(),
17640 highlight,
17641 });
17642 }
17643
17644 if chunk_lines.peek().is_some() {
17645 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17646 line.pop_front();
17647 }
17648 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17649 line.pop_back();
17650 }
17651
17652 lines.push(mem::take(&mut line));
17653 }
17654 }
17655 }
17656
17657 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17658 return;
17659 };
17660 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17661 }
17662
17663 pub fn open_context_menu(
17664 &mut self,
17665 _: &OpenContextMenu,
17666 window: &mut Window,
17667 cx: &mut Context<Self>,
17668 ) {
17669 self.request_autoscroll(Autoscroll::newest(), cx);
17670 let position = self.selections.newest_display(cx).start;
17671 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17672 }
17673
17674 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17675 &self.inlay_hint_cache
17676 }
17677
17678 pub fn replay_insert_event(
17679 &mut self,
17680 text: &str,
17681 relative_utf16_range: Option<Range<isize>>,
17682 window: &mut Window,
17683 cx: &mut Context<Self>,
17684 ) {
17685 if !self.input_enabled {
17686 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17687 return;
17688 }
17689 if let Some(relative_utf16_range) = relative_utf16_range {
17690 let selections = self.selections.all::<OffsetUtf16>(cx);
17691 self.change_selections(None, window, cx, |s| {
17692 let new_ranges = selections.into_iter().map(|range| {
17693 let start = OffsetUtf16(
17694 range
17695 .head()
17696 .0
17697 .saturating_add_signed(relative_utf16_range.start),
17698 );
17699 let end = OffsetUtf16(
17700 range
17701 .head()
17702 .0
17703 .saturating_add_signed(relative_utf16_range.end),
17704 );
17705 start..end
17706 });
17707 s.select_ranges(new_ranges);
17708 });
17709 }
17710
17711 self.handle_input(text, window, cx);
17712 }
17713
17714 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17715 let Some(provider) = self.semantics_provider.as_ref() else {
17716 return false;
17717 };
17718
17719 let mut supports = false;
17720 self.buffer().update(cx, |this, cx| {
17721 this.for_each_buffer(|buffer| {
17722 supports |= provider.supports_inlay_hints(buffer, cx);
17723 });
17724 });
17725
17726 supports
17727 }
17728
17729 pub fn is_focused(&self, window: &Window) -> bool {
17730 self.focus_handle.is_focused(window)
17731 }
17732
17733 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17734 cx.emit(EditorEvent::Focused);
17735
17736 if let Some(descendant) = self
17737 .last_focused_descendant
17738 .take()
17739 .and_then(|descendant| descendant.upgrade())
17740 {
17741 window.focus(&descendant);
17742 } else {
17743 if let Some(blame) = self.blame.as_ref() {
17744 blame.update(cx, GitBlame::focus)
17745 }
17746
17747 self.blink_manager.update(cx, BlinkManager::enable);
17748 self.show_cursor_names(window, cx);
17749 self.buffer.update(cx, |buffer, cx| {
17750 buffer.finalize_last_transaction(cx);
17751 if self.leader_peer_id.is_none() {
17752 buffer.set_active_selections(
17753 &self.selections.disjoint_anchors(),
17754 self.selections.line_mode,
17755 self.cursor_shape,
17756 cx,
17757 );
17758 }
17759 });
17760 }
17761 }
17762
17763 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17764 cx.emit(EditorEvent::FocusedIn)
17765 }
17766
17767 fn handle_focus_out(
17768 &mut self,
17769 event: FocusOutEvent,
17770 _window: &mut Window,
17771 cx: &mut Context<Self>,
17772 ) {
17773 if event.blurred != self.focus_handle {
17774 self.last_focused_descendant = Some(event.blurred);
17775 }
17776 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17777 }
17778
17779 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17780 self.blink_manager.update(cx, BlinkManager::disable);
17781 self.buffer
17782 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17783
17784 if let Some(blame) = self.blame.as_ref() {
17785 blame.update(cx, GitBlame::blur)
17786 }
17787 if !self.hover_state.focused(window, cx) {
17788 hide_hover(self, cx);
17789 }
17790 if !self
17791 .context_menu
17792 .borrow()
17793 .as_ref()
17794 .is_some_and(|context_menu| context_menu.focused(window, cx))
17795 {
17796 self.hide_context_menu(window, cx);
17797 }
17798 self.discard_inline_completion(false, cx);
17799 cx.emit(EditorEvent::Blurred);
17800 cx.notify();
17801 }
17802
17803 pub fn register_action<A: Action>(
17804 &mut self,
17805 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17806 ) -> Subscription {
17807 let id = self.next_editor_action_id.post_inc();
17808 let listener = Arc::new(listener);
17809 self.editor_actions.borrow_mut().insert(
17810 id,
17811 Box::new(move |window, _| {
17812 let listener = listener.clone();
17813 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17814 let action = action.downcast_ref().unwrap();
17815 if phase == DispatchPhase::Bubble {
17816 listener(action, window, cx)
17817 }
17818 })
17819 }),
17820 );
17821
17822 let editor_actions = self.editor_actions.clone();
17823 Subscription::new(move || {
17824 editor_actions.borrow_mut().remove(&id);
17825 })
17826 }
17827
17828 pub fn file_header_size(&self) -> u32 {
17829 FILE_HEADER_HEIGHT
17830 }
17831
17832 pub fn restore(
17833 &mut self,
17834 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17835 window: &mut Window,
17836 cx: &mut Context<Self>,
17837 ) {
17838 let workspace = self.workspace();
17839 let project = self.project.as_ref();
17840 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17841 let mut tasks = Vec::new();
17842 for (buffer_id, changes) in revert_changes {
17843 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17844 buffer.update(cx, |buffer, cx| {
17845 buffer.edit(
17846 changes
17847 .into_iter()
17848 .map(|(range, text)| (range, text.to_string())),
17849 None,
17850 cx,
17851 );
17852 });
17853
17854 if let Some(project) =
17855 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17856 {
17857 project.update(cx, |project, cx| {
17858 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17859 })
17860 }
17861 }
17862 }
17863 tasks
17864 });
17865 cx.spawn_in(window, async move |_, cx| {
17866 for (buffer, task) in save_tasks {
17867 let result = task.await;
17868 if result.is_err() {
17869 let Some(path) = buffer
17870 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17871 .ok()
17872 else {
17873 continue;
17874 };
17875 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17876 let Some(task) = cx
17877 .update_window_entity(&workspace, |workspace, window, cx| {
17878 workspace
17879 .open_path_preview(path, None, false, false, false, window, cx)
17880 })
17881 .ok()
17882 else {
17883 continue;
17884 };
17885 task.await.log_err();
17886 }
17887 }
17888 }
17889 })
17890 .detach();
17891 self.change_selections(None, window, cx, |selections| selections.refresh());
17892 }
17893
17894 pub fn to_pixel_point(
17895 &self,
17896 source: multi_buffer::Anchor,
17897 editor_snapshot: &EditorSnapshot,
17898 window: &mut Window,
17899 ) -> Option<gpui::Point<Pixels>> {
17900 let source_point = source.to_display_point(editor_snapshot);
17901 self.display_to_pixel_point(source_point, editor_snapshot, window)
17902 }
17903
17904 pub fn display_to_pixel_point(
17905 &self,
17906 source: DisplayPoint,
17907 editor_snapshot: &EditorSnapshot,
17908 window: &mut Window,
17909 ) -> Option<gpui::Point<Pixels>> {
17910 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17911 let text_layout_details = self.text_layout_details(window);
17912 let scroll_top = text_layout_details
17913 .scroll_anchor
17914 .scroll_position(editor_snapshot)
17915 .y;
17916
17917 if source.row().as_f32() < scroll_top.floor() {
17918 return None;
17919 }
17920 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17921 let source_y = line_height * (source.row().as_f32() - scroll_top);
17922 Some(gpui::Point::new(source_x, source_y))
17923 }
17924
17925 pub fn has_visible_completions_menu(&self) -> bool {
17926 !self.edit_prediction_preview_is_active()
17927 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17928 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17929 })
17930 }
17931
17932 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17933 self.addons
17934 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17935 }
17936
17937 pub fn unregister_addon<T: Addon>(&mut self) {
17938 self.addons.remove(&std::any::TypeId::of::<T>());
17939 }
17940
17941 pub fn addon<T: Addon>(&self) -> Option<&T> {
17942 let type_id = std::any::TypeId::of::<T>();
17943 self.addons
17944 .get(&type_id)
17945 .and_then(|item| item.to_any().downcast_ref::<T>())
17946 }
17947
17948 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17949 let text_layout_details = self.text_layout_details(window);
17950 let style = &text_layout_details.editor_style;
17951 let font_id = window.text_system().resolve_font(&style.text.font());
17952 let font_size = style.text.font_size.to_pixels(window.rem_size());
17953 let line_height = style.text.line_height_in_pixels(window.rem_size());
17954 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17955
17956 gpui::Size::new(em_width, line_height)
17957 }
17958
17959 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17960 self.load_diff_task.clone()
17961 }
17962
17963 fn read_metadata_from_db(
17964 &mut self,
17965 item_id: u64,
17966 workspace_id: WorkspaceId,
17967 window: &mut Window,
17968 cx: &mut Context<Editor>,
17969 ) {
17970 if self.is_singleton(cx)
17971 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
17972 {
17973 let buffer_snapshot = OnceCell::new();
17974
17975 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
17976 if !folds.is_empty() {
17977 let snapshot =
17978 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17979 self.fold_ranges(
17980 folds
17981 .into_iter()
17982 .map(|(start, end)| {
17983 snapshot.clip_offset(start, Bias::Left)
17984 ..snapshot.clip_offset(end, Bias::Right)
17985 })
17986 .collect(),
17987 false,
17988 window,
17989 cx,
17990 );
17991 }
17992 }
17993
17994 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
17995 if !selections.is_empty() {
17996 let snapshot =
17997 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17998 self.change_selections(None, window, cx, |s| {
17999 s.select_ranges(selections.into_iter().map(|(start, end)| {
18000 snapshot.clip_offset(start, Bias::Left)
18001 ..snapshot.clip_offset(end, Bias::Right)
18002 }));
18003 });
18004 }
18005 };
18006 }
18007
18008 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18009 }
18010}
18011
18012// Consider user intent and default settings
18013fn choose_completion_range(
18014 completion: &Completion,
18015 intent: CompletionIntent,
18016 buffer: &Entity<Buffer>,
18017 cx: &mut Context<Editor>,
18018) -> Range<usize> {
18019 fn should_replace(
18020 completion: &Completion,
18021 insert_range: &Range<text::Anchor>,
18022 intent: CompletionIntent,
18023 completion_mode_setting: LspInsertMode,
18024 buffer: &Buffer,
18025 ) -> bool {
18026 // specific actions take precedence over settings
18027 match intent {
18028 CompletionIntent::CompleteWithInsert => return false,
18029 CompletionIntent::CompleteWithReplace => return true,
18030 CompletionIntent::Complete | CompletionIntent::Compose => {}
18031 }
18032
18033 match completion_mode_setting {
18034 LspInsertMode::Insert => false,
18035 LspInsertMode::Replace => true,
18036 LspInsertMode::ReplaceSubsequence => {
18037 let mut text_to_replace = buffer.chars_for_range(
18038 buffer.anchor_before(completion.replace_range.start)
18039 ..buffer.anchor_after(completion.replace_range.end),
18040 );
18041 let mut completion_text = completion.new_text.chars();
18042
18043 // is `text_to_replace` a subsequence of `completion_text`
18044 text_to_replace
18045 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18046 }
18047 LspInsertMode::ReplaceSuffix => {
18048 let range_after_cursor = insert_range.end..completion.replace_range.end;
18049
18050 let text_after_cursor = buffer
18051 .text_for_range(
18052 buffer.anchor_before(range_after_cursor.start)
18053 ..buffer.anchor_after(range_after_cursor.end),
18054 )
18055 .collect::<String>();
18056 completion.new_text.ends_with(&text_after_cursor)
18057 }
18058 }
18059 }
18060
18061 let buffer = buffer.read(cx);
18062
18063 if let CompletionSource::Lsp {
18064 insert_range: Some(insert_range),
18065 ..
18066 } = &completion.source
18067 {
18068 let completion_mode_setting =
18069 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18070 .completions
18071 .lsp_insert_mode;
18072
18073 if !should_replace(
18074 completion,
18075 &insert_range,
18076 intent,
18077 completion_mode_setting,
18078 buffer,
18079 ) {
18080 return insert_range.to_offset(buffer);
18081 }
18082 }
18083
18084 completion.replace_range.to_offset(buffer)
18085}
18086
18087fn insert_extra_newline_brackets(
18088 buffer: &MultiBufferSnapshot,
18089 range: Range<usize>,
18090 language: &language::LanguageScope,
18091) -> bool {
18092 let leading_whitespace_len = buffer
18093 .reversed_chars_at(range.start)
18094 .take_while(|c| c.is_whitespace() && *c != '\n')
18095 .map(|c| c.len_utf8())
18096 .sum::<usize>();
18097 let trailing_whitespace_len = buffer
18098 .chars_at(range.end)
18099 .take_while(|c| c.is_whitespace() && *c != '\n')
18100 .map(|c| c.len_utf8())
18101 .sum::<usize>();
18102 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18103
18104 language.brackets().any(|(pair, enabled)| {
18105 let pair_start = pair.start.trim_end();
18106 let pair_end = pair.end.trim_start();
18107
18108 enabled
18109 && pair.newline
18110 && buffer.contains_str_at(range.end, pair_end)
18111 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18112 })
18113}
18114
18115fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18116 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18117 [(buffer, range, _)] => (*buffer, range.clone()),
18118 _ => return false,
18119 };
18120 let pair = {
18121 let mut result: Option<BracketMatch> = None;
18122
18123 for pair in buffer
18124 .all_bracket_ranges(range.clone())
18125 .filter(move |pair| {
18126 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18127 })
18128 {
18129 let len = pair.close_range.end - pair.open_range.start;
18130
18131 if let Some(existing) = &result {
18132 let existing_len = existing.close_range.end - existing.open_range.start;
18133 if len > existing_len {
18134 continue;
18135 }
18136 }
18137
18138 result = Some(pair);
18139 }
18140
18141 result
18142 };
18143 let Some(pair) = pair else {
18144 return false;
18145 };
18146 pair.newline_only
18147 && buffer
18148 .chars_for_range(pair.open_range.end..range.start)
18149 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18150 .all(|c| c.is_whitespace() && c != '\n')
18151}
18152
18153fn get_uncommitted_diff_for_buffer(
18154 project: &Entity<Project>,
18155 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18156 buffer: Entity<MultiBuffer>,
18157 cx: &mut App,
18158) -> Task<()> {
18159 let mut tasks = Vec::new();
18160 project.update(cx, |project, cx| {
18161 for buffer in buffers {
18162 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18163 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18164 }
18165 }
18166 });
18167 cx.spawn(async move |cx| {
18168 let diffs = future::join_all(tasks).await;
18169 buffer
18170 .update(cx, |buffer, cx| {
18171 for diff in diffs.into_iter().flatten() {
18172 buffer.add_diff(diff, cx);
18173 }
18174 })
18175 .ok();
18176 })
18177}
18178
18179fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18180 let tab_size = tab_size.get() as usize;
18181 let mut width = offset;
18182
18183 for ch in text.chars() {
18184 width += if ch == '\t' {
18185 tab_size - (width % tab_size)
18186 } else {
18187 1
18188 };
18189 }
18190
18191 width - offset
18192}
18193
18194#[cfg(test)]
18195mod tests {
18196 use super::*;
18197
18198 #[test]
18199 fn test_string_size_with_expanded_tabs() {
18200 let nz = |val| NonZeroU32::new(val).unwrap();
18201 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18202 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18203 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18204 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18205 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18206 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18207 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18208 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18209 }
18210}
18211
18212/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18213struct WordBreakingTokenizer<'a> {
18214 input: &'a str,
18215}
18216
18217impl<'a> WordBreakingTokenizer<'a> {
18218 fn new(input: &'a str) -> Self {
18219 Self { input }
18220 }
18221}
18222
18223fn is_char_ideographic(ch: char) -> bool {
18224 use unicode_script::Script::*;
18225 use unicode_script::UnicodeScript;
18226 matches!(ch.script(), Han | Tangut | Yi)
18227}
18228
18229fn is_grapheme_ideographic(text: &str) -> bool {
18230 text.chars().any(is_char_ideographic)
18231}
18232
18233fn is_grapheme_whitespace(text: &str) -> bool {
18234 text.chars().any(|x| x.is_whitespace())
18235}
18236
18237fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18238 text.chars().next().map_or(false, |ch| {
18239 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18240 })
18241}
18242
18243#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18244enum WordBreakToken<'a> {
18245 Word { token: &'a str, grapheme_len: usize },
18246 InlineWhitespace { token: &'a str, grapheme_len: usize },
18247 Newline,
18248}
18249
18250impl<'a> Iterator for WordBreakingTokenizer<'a> {
18251 /// Yields a span, the count of graphemes in the token, and whether it was
18252 /// whitespace. Note that it also breaks at word boundaries.
18253 type Item = WordBreakToken<'a>;
18254
18255 fn next(&mut self) -> Option<Self::Item> {
18256 use unicode_segmentation::UnicodeSegmentation;
18257 if self.input.is_empty() {
18258 return None;
18259 }
18260
18261 let mut iter = self.input.graphemes(true).peekable();
18262 let mut offset = 0;
18263 let mut grapheme_len = 0;
18264 if let Some(first_grapheme) = iter.next() {
18265 let is_newline = first_grapheme == "\n";
18266 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18267 offset += first_grapheme.len();
18268 grapheme_len += 1;
18269 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18270 if let Some(grapheme) = iter.peek().copied() {
18271 if should_stay_with_preceding_ideograph(grapheme) {
18272 offset += grapheme.len();
18273 grapheme_len += 1;
18274 }
18275 }
18276 } else {
18277 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18278 let mut next_word_bound = words.peek().copied();
18279 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18280 next_word_bound = words.next();
18281 }
18282 while let Some(grapheme) = iter.peek().copied() {
18283 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18284 break;
18285 };
18286 if is_grapheme_whitespace(grapheme) != is_whitespace
18287 || (grapheme == "\n") != is_newline
18288 {
18289 break;
18290 };
18291 offset += grapheme.len();
18292 grapheme_len += 1;
18293 iter.next();
18294 }
18295 }
18296 let token = &self.input[..offset];
18297 self.input = &self.input[offset..];
18298 if token == "\n" {
18299 Some(WordBreakToken::Newline)
18300 } else if is_whitespace {
18301 Some(WordBreakToken::InlineWhitespace {
18302 token,
18303 grapheme_len,
18304 })
18305 } else {
18306 Some(WordBreakToken::Word {
18307 token,
18308 grapheme_len,
18309 })
18310 }
18311 } else {
18312 None
18313 }
18314 }
18315}
18316
18317#[test]
18318fn test_word_breaking_tokenizer() {
18319 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18320 ("", &[]),
18321 (" ", &[whitespace(" ", 2)]),
18322 ("Ʒ", &[word("Ʒ", 1)]),
18323 ("Ǽ", &[word("Ǽ", 1)]),
18324 ("⋑", &[word("⋑", 1)]),
18325 ("⋑⋑", &[word("⋑⋑", 2)]),
18326 (
18327 "原理,进而",
18328 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18329 ),
18330 (
18331 "hello world",
18332 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18333 ),
18334 (
18335 "hello, world",
18336 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18337 ),
18338 (
18339 " hello world",
18340 &[
18341 whitespace(" ", 2),
18342 word("hello", 5),
18343 whitespace(" ", 1),
18344 word("world", 5),
18345 ],
18346 ),
18347 (
18348 "这是什么 \n 钢笔",
18349 &[
18350 word("这", 1),
18351 word("是", 1),
18352 word("什", 1),
18353 word("么", 1),
18354 whitespace(" ", 1),
18355 newline(),
18356 whitespace(" ", 1),
18357 word("钢", 1),
18358 word("笔", 1),
18359 ],
18360 ),
18361 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
18362 ];
18363
18364 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18365 WordBreakToken::Word {
18366 token,
18367 grapheme_len,
18368 }
18369 }
18370
18371 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18372 WordBreakToken::InlineWhitespace {
18373 token,
18374 grapheme_len,
18375 }
18376 }
18377
18378 fn newline() -> WordBreakToken<'static> {
18379 WordBreakToken::Newline
18380 }
18381
18382 for (input, result) in tests {
18383 assert_eq!(
18384 WordBreakingTokenizer::new(input)
18385 .collect::<Vec<_>>()
18386 .as_slice(),
18387 *result,
18388 );
18389 }
18390}
18391
18392fn wrap_with_prefix(
18393 line_prefix: String,
18394 unwrapped_text: String,
18395 wrap_column: usize,
18396 tab_size: NonZeroU32,
18397 preserve_existing_whitespace: bool,
18398) -> String {
18399 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18400 let mut wrapped_text = String::new();
18401 let mut current_line = line_prefix.clone();
18402
18403 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18404 let mut current_line_len = line_prefix_len;
18405 let mut in_whitespace = false;
18406 for token in tokenizer {
18407 let have_preceding_whitespace = in_whitespace;
18408 match token {
18409 WordBreakToken::Word {
18410 token,
18411 grapheme_len,
18412 } => {
18413 in_whitespace = false;
18414 if current_line_len + grapheme_len > wrap_column
18415 && current_line_len != line_prefix_len
18416 {
18417 wrapped_text.push_str(current_line.trim_end());
18418 wrapped_text.push('\n');
18419 current_line.truncate(line_prefix.len());
18420 current_line_len = line_prefix_len;
18421 }
18422 current_line.push_str(token);
18423 current_line_len += grapheme_len;
18424 }
18425 WordBreakToken::InlineWhitespace {
18426 mut token,
18427 mut grapheme_len,
18428 } => {
18429 in_whitespace = true;
18430 if have_preceding_whitespace && !preserve_existing_whitespace {
18431 continue;
18432 }
18433 if !preserve_existing_whitespace {
18434 token = " ";
18435 grapheme_len = 1;
18436 }
18437 if current_line_len + grapheme_len > wrap_column {
18438 wrapped_text.push_str(current_line.trim_end());
18439 wrapped_text.push('\n');
18440 current_line.truncate(line_prefix.len());
18441 current_line_len = line_prefix_len;
18442 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18443 current_line.push_str(token);
18444 current_line_len += grapheme_len;
18445 }
18446 }
18447 WordBreakToken::Newline => {
18448 in_whitespace = true;
18449 if preserve_existing_whitespace {
18450 wrapped_text.push_str(current_line.trim_end());
18451 wrapped_text.push('\n');
18452 current_line.truncate(line_prefix.len());
18453 current_line_len = line_prefix_len;
18454 } else if have_preceding_whitespace {
18455 continue;
18456 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18457 {
18458 wrapped_text.push_str(current_line.trim_end());
18459 wrapped_text.push('\n');
18460 current_line.truncate(line_prefix.len());
18461 current_line_len = line_prefix_len;
18462 } else if current_line_len != line_prefix_len {
18463 current_line.push(' ');
18464 current_line_len += 1;
18465 }
18466 }
18467 }
18468 }
18469
18470 if !current_line.is_empty() {
18471 wrapped_text.push_str(¤t_line);
18472 }
18473 wrapped_text
18474}
18475
18476#[test]
18477fn test_wrap_with_prefix() {
18478 assert_eq!(
18479 wrap_with_prefix(
18480 "# ".to_string(),
18481 "abcdefg".to_string(),
18482 4,
18483 NonZeroU32::new(4).unwrap(),
18484 false,
18485 ),
18486 "# abcdefg"
18487 );
18488 assert_eq!(
18489 wrap_with_prefix(
18490 "".to_string(),
18491 "\thello world".to_string(),
18492 8,
18493 NonZeroU32::new(4).unwrap(),
18494 false,
18495 ),
18496 "hello\nworld"
18497 );
18498 assert_eq!(
18499 wrap_with_prefix(
18500 "// ".to_string(),
18501 "xx \nyy zz aa bb cc".to_string(),
18502 12,
18503 NonZeroU32::new(4).unwrap(),
18504 false,
18505 ),
18506 "// xx yy zz\n// aa bb cc"
18507 );
18508 assert_eq!(
18509 wrap_with_prefix(
18510 String::new(),
18511 "这是什么 \n 钢笔".to_string(),
18512 3,
18513 NonZeroU32::new(4).unwrap(),
18514 false,
18515 ),
18516 "这是什\n么 钢\n笔"
18517 );
18518}
18519
18520pub trait CollaborationHub {
18521 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18522 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18523 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18524}
18525
18526impl CollaborationHub for Entity<Project> {
18527 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18528 self.read(cx).collaborators()
18529 }
18530
18531 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18532 self.read(cx).user_store().read(cx).participant_indices()
18533 }
18534
18535 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18536 let this = self.read(cx);
18537 let user_ids = this.collaborators().values().map(|c| c.user_id);
18538 this.user_store().read_with(cx, |user_store, cx| {
18539 user_store.participant_names(user_ids, cx)
18540 })
18541 }
18542}
18543
18544pub trait SemanticsProvider {
18545 fn hover(
18546 &self,
18547 buffer: &Entity<Buffer>,
18548 position: text::Anchor,
18549 cx: &mut App,
18550 ) -> Option<Task<Vec<project::Hover>>>;
18551
18552 fn inlay_hints(
18553 &self,
18554 buffer_handle: Entity<Buffer>,
18555 range: Range<text::Anchor>,
18556 cx: &mut App,
18557 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18558
18559 fn resolve_inlay_hint(
18560 &self,
18561 hint: InlayHint,
18562 buffer_handle: Entity<Buffer>,
18563 server_id: LanguageServerId,
18564 cx: &mut App,
18565 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18566
18567 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18568
18569 fn document_highlights(
18570 &self,
18571 buffer: &Entity<Buffer>,
18572 position: text::Anchor,
18573 cx: &mut App,
18574 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18575
18576 fn definitions(
18577 &self,
18578 buffer: &Entity<Buffer>,
18579 position: text::Anchor,
18580 kind: GotoDefinitionKind,
18581 cx: &mut App,
18582 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18583
18584 fn range_for_rename(
18585 &self,
18586 buffer: &Entity<Buffer>,
18587 position: text::Anchor,
18588 cx: &mut App,
18589 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18590
18591 fn perform_rename(
18592 &self,
18593 buffer: &Entity<Buffer>,
18594 position: text::Anchor,
18595 new_name: String,
18596 cx: &mut App,
18597 ) -> Option<Task<Result<ProjectTransaction>>>;
18598}
18599
18600pub trait CompletionProvider {
18601 fn completions(
18602 &self,
18603 excerpt_id: ExcerptId,
18604 buffer: &Entity<Buffer>,
18605 buffer_position: text::Anchor,
18606 trigger: CompletionContext,
18607 window: &mut Window,
18608 cx: &mut Context<Editor>,
18609 ) -> Task<Result<Option<Vec<Completion>>>>;
18610
18611 fn resolve_completions(
18612 &self,
18613 buffer: Entity<Buffer>,
18614 completion_indices: Vec<usize>,
18615 completions: Rc<RefCell<Box<[Completion]>>>,
18616 cx: &mut Context<Editor>,
18617 ) -> Task<Result<bool>>;
18618
18619 fn apply_additional_edits_for_completion(
18620 &self,
18621 _buffer: Entity<Buffer>,
18622 _completions: Rc<RefCell<Box<[Completion]>>>,
18623 _completion_index: usize,
18624 _push_to_history: bool,
18625 _cx: &mut Context<Editor>,
18626 ) -> Task<Result<Option<language::Transaction>>> {
18627 Task::ready(Ok(None))
18628 }
18629
18630 fn is_completion_trigger(
18631 &self,
18632 buffer: &Entity<Buffer>,
18633 position: language::Anchor,
18634 text: &str,
18635 trigger_in_words: bool,
18636 cx: &mut Context<Editor>,
18637 ) -> bool;
18638
18639 fn sort_completions(&self) -> bool {
18640 true
18641 }
18642
18643 fn filter_completions(&self) -> bool {
18644 true
18645 }
18646}
18647
18648pub trait CodeActionProvider {
18649 fn id(&self) -> Arc<str>;
18650
18651 fn code_actions(
18652 &self,
18653 buffer: &Entity<Buffer>,
18654 range: Range<text::Anchor>,
18655 window: &mut Window,
18656 cx: &mut App,
18657 ) -> Task<Result<Vec<CodeAction>>>;
18658
18659 fn apply_code_action(
18660 &self,
18661 buffer_handle: Entity<Buffer>,
18662 action: CodeAction,
18663 excerpt_id: ExcerptId,
18664 push_to_history: bool,
18665 window: &mut Window,
18666 cx: &mut App,
18667 ) -> Task<Result<ProjectTransaction>>;
18668}
18669
18670impl CodeActionProvider for Entity<Project> {
18671 fn id(&self) -> Arc<str> {
18672 "project".into()
18673 }
18674
18675 fn code_actions(
18676 &self,
18677 buffer: &Entity<Buffer>,
18678 range: Range<text::Anchor>,
18679 _window: &mut Window,
18680 cx: &mut App,
18681 ) -> Task<Result<Vec<CodeAction>>> {
18682 self.update(cx, |project, cx| {
18683 let code_lens = project.code_lens(buffer, range.clone(), cx);
18684 let code_actions = project.code_actions(buffer, range, None, cx);
18685 cx.background_spawn(async move {
18686 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18687 Ok(code_lens
18688 .context("code lens fetch")?
18689 .into_iter()
18690 .chain(code_actions.context("code action fetch")?)
18691 .collect())
18692 })
18693 })
18694 }
18695
18696 fn apply_code_action(
18697 &self,
18698 buffer_handle: Entity<Buffer>,
18699 action: CodeAction,
18700 _excerpt_id: ExcerptId,
18701 push_to_history: bool,
18702 _window: &mut Window,
18703 cx: &mut App,
18704 ) -> Task<Result<ProjectTransaction>> {
18705 self.update(cx, |project, cx| {
18706 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18707 })
18708 }
18709}
18710
18711fn snippet_completions(
18712 project: &Project,
18713 buffer: &Entity<Buffer>,
18714 buffer_position: text::Anchor,
18715 cx: &mut App,
18716) -> Task<Result<Vec<Completion>>> {
18717 let language = buffer.read(cx).language_at(buffer_position);
18718 let language_name = language.as_ref().map(|language| language.lsp_id());
18719 let snippet_store = project.snippets().read(cx);
18720 let snippets = snippet_store.snippets_for(language_name, cx);
18721
18722 if snippets.is_empty() {
18723 return Task::ready(Ok(vec![]));
18724 }
18725 let snapshot = buffer.read(cx).text_snapshot();
18726 let chars: String = snapshot
18727 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18728 .collect();
18729
18730 let scope = language.map(|language| language.default_scope());
18731 let executor = cx.background_executor().clone();
18732
18733 cx.background_spawn(async move {
18734 let classifier = CharClassifier::new(scope).for_completion(true);
18735 let mut last_word = chars
18736 .chars()
18737 .take_while(|c| classifier.is_word(*c))
18738 .collect::<String>();
18739 last_word = last_word.chars().rev().collect();
18740
18741 if last_word.is_empty() {
18742 return Ok(vec![]);
18743 }
18744
18745 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18746 let to_lsp = |point: &text::Anchor| {
18747 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18748 point_to_lsp(end)
18749 };
18750 let lsp_end = to_lsp(&buffer_position);
18751
18752 let candidates = snippets
18753 .iter()
18754 .enumerate()
18755 .flat_map(|(ix, snippet)| {
18756 snippet
18757 .prefix
18758 .iter()
18759 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18760 })
18761 .collect::<Vec<StringMatchCandidate>>();
18762
18763 let mut matches = fuzzy::match_strings(
18764 &candidates,
18765 &last_word,
18766 last_word.chars().any(|c| c.is_uppercase()),
18767 100,
18768 &Default::default(),
18769 executor,
18770 )
18771 .await;
18772
18773 // Remove all candidates where the query's start does not match the start of any word in the candidate
18774 if let Some(query_start) = last_word.chars().next() {
18775 matches.retain(|string_match| {
18776 split_words(&string_match.string).any(|word| {
18777 // Check that the first codepoint of the word as lowercase matches the first
18778 // codepoint of the query as lowercase
18779 word.chars()
18780 .flat_map(|codepoint| codepoint.to_lowercase())
18781 .zip(query_start.to_lowercase())
18782 .all(|(word_cp, query_cp)| word_cp == query_cp)
18783 })
18784 });
18785 }
18786
18787 let matched_strings = matches
18788 .into_iter()
18789 .map(|m| m.string)
18790 .collect::<HashSet<_>>();
18791
18792 let result: Vec<Completion> = snippets
18793 .into_iter()
18794 .filter_map(|snippet| {
18795 let matching_prefix = snippet
18796 .prefix
18797 .iter()
18798 .find(|prefix| matched_strings.contains(*prefix))?;
18799 let start = as_offset - last_word.len();
18800 let start = snapshot.anchor_before(start);
18801 let range = start..buffer_position;
18802 let lsp_start = to_lsp(&start);
18803 let lsp_range = lsp::Range {
18804 start: lsp_start,
18805 end: lsp_end,
18806 };
18807 Some(Completion {
18808 replace_range: range,
18809 new_text: snippet.body.clone(),
18810 source: CompletionSource::Lsp {
18811 insert_range: None,
18812 server_id: LanguageServerId(usize::MAX),
18813 resolved: true,
18814 lsp_completion: Box::new(lsp::CompletionItem {
18815 label: snippet.prefix.first().unwrap().clone(),
18816 kind: Some(CompletionItemKind::SNIPPET),
18817 label_details: snippet.description.as_ref().map(|description| {
18818 lsp::CompletionItemLabelDetails {
18819 detail: Some(description.clone()),
18820 description: None,
18821 }
18822 }),
18823 insert_text_format: Some(InsertTextFormat::SNIPPET),
18824 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18825 lsp::InsertReplaceEdit {
18826 new_text: snippet.body.clone(),
18827 insert: lsp_range,
18828 replace: lsp_range,
18829 },
18830 )),
18831 filter_text: Some(snippet.body.clone()),
18832 sort_text: Some(char::MAX.to_string()),
18833 ..lsp::CompletionItem::default()
18834 }),
18835 lsp_defaults: None,
18836 },
18837 label: CodeLabel {
18838 text: matching_prefix.clone(),
18839 runs: Vec::new(),
18840 filter_range: 0..matching_prefix.len(),
18841 },
18842 icon_path: None,
18843 documentation: snippet
18844 .description
18845 .clone()
18846 .map(|description| CompletionDocumentation::SingleLine(description.into())),
18847 insert_text_mode: None,
18848 confirm: None,
18849 })
18850 })
18851 .collect();
18852
18853 Ok(result)
18854 })
18855}
18856
18857impl CompletionProvider for Entity<Project> {
18858 fn completions(
18859 &self,
18860 _excerpt_id: ExcerptId,
18861 buffer: &Entity<Buffer>,
18862 buffer_position: text::Anchor,
18863 options: CompletionContext,
18864 _window: &mut Window,
18865 cx: &mut Context<Editor>,
18866 ) -> Task<Result<Option<Vec<Completion>>>> {
18867 self.update(cx, |project, cx| {
18868 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18869 let project_completions = project.completions(buffer, buffer_position, options, cx);
18870 cx.background_spawn(async move {
18871 let snippets_completions = snippets.await?;
18872 match project_completions.await? {
18873 Some(mut completions) => {
18874 completions.extend(snippets_completions);
18875 Ok(Some(completions))
18876 }
18877 None => {
18878 if snippets_completions.is_empty() {
18879 Ok(None)
18880 } else {
18881 Ok(Some(snippets_completions))
18882 }
18883 }
18884 }
18885 })
18886 })
18887 }
18888
18889 fn resolve_completions(
18890 &self,
18891 buffer: Entity<Buffer>,
18892 completion_indices: Vec<usize>,
18893 completions: Rc<RefCell<Box<[Completion]>>>,
18894 cx: &mut Context<Editor>,
18895 ) -> Task<Result<bool>> {
18896 self.update(cx, |project, cx| {
18897 project.lsp_store().update(cx, |lsp_store, cx| {
18898 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18899 })
18900 })
18901 }
18902
18903 fn apply_additional_edits_for_completion(
18904 &self,
18905 buffer: Entity<Buffer>,
18906 completions: Rc<RefCell<Box<[Completion]>>>,
18907 completion_index: usize,
18908 push_to_history: bool,
18909 cx: &mut Context<Editor>,
18910 ) -> Task<Result<Option<language::Transaction>>> {
18911 self.update(cx, |project, cx| {
18912 project.lsp_store().update(cx, |lsp_store, cx| {
18913 lsp_store.apply_additional_edits_for_completion(
18914 buffer,
18915 completions,
18916 completion_index,
18917 push_to_history,
18918 cx,
18919 )
18920 })
18921 })
18922 }
18923
18924 fn is_completion_trigger(
18925 &self,
18926 buffer: &Entity<Buffer>,
18927 position: language::Anchor,
18928 text: &str,
18929 trigger_in_words: bool,
18930 cx: &mut Context<Editor>,
18931 ) -> bool {
18932 let mut chars = text.chars();
18933 let char = if let Some(char) = chars.next() {
18934 char
18935 } else {
18936 return false;
18937 };
18938 if chars.next().is_some() {
18939 return false;
18940 }
18941
18942 let buffer = buffer.read(cx);
18943 let snapshot = buffer.snapshot();
18944 if !snapshot.settings_at(position, cx).show_completions_on_input {
18945 return false;
18946 }
18947 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18948 if trigger_in_words && classifier.is_word(char) {
18949 return true;
18950 }
18951
18952 buffer.completion_triggers().contains(text)
18953 }
18954}
18955
18956impl SemanticsProvider for Entity<Project> {
18957 fn hover(
18958 &self,
18959 buffer: &Entity<Buffer>,
18960 position: text::Anchor,
18961 cx: &mut App,
18962 ) -> Option<Task<Vec<project::Hover>>> {
18963 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18964 }
18965
18966 fn document_highlights(
18967 &self,
18968 buffer: &Entity<Buffer>,
18969 position: text::Anchor,
18970 cx: &mut App,
18971 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18972 Some(self.update(cx, |project, cx| {
18973 project.document_highlights(buffer, position, cx)
18974 }))
18975 }
18976
18977 fn definitions(
18978 &self,
18979 buffer: &Entity<Buffer>,
18980 position: text::Anchor,
18981 kind: GotoDefinitionKind,
18982 cx: &mut App,
18983 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18984 Some(self.update(cx, |project, cx| match kind {
18985 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18986 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18987 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18988 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18989 }))
18990 }
18991
18992 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18993 // TODO: make this work for remote projects
18994 self.update(cx, |this, cx| {
18995 buffer.update(cx, |buffer, cx| {
18996 this.any_language_server_supports_inlay_hints(buffer, cx)
18997 })
18998 })
18999 }
19000
19001 fn inlay_hints(
19002 &self,
19003 buffer_handle: Entity<Buffer>,
19004 range: Range<text::Anchor>,
19005 cx: &mut App,
19006 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19007 Some(self.update(cx, |project, cx| {
19008 project.inlay_hints(buffer_handle, range, cx)
19009 }))
19010 }
19011
19012 fn resolve_inlay_hint(
19013 &self,
19014 hint: InlayHint,
19015 buffer_handle: Entity<Buffer>,
19016 server_id: LanguageServerId,
19017 cx: &mut App,
19018 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19019 Some(self.update(cx, |project, cx| {
19020 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19021 }))
19022 }
19023
19024 fn range_for_rename(
19025 &self,
19026 buffer: &Entity<Buffer>,
19027 position: text::Anchor,
19028 cx: &mut App,
19029 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19030 Some(self.update(cx, |project, cx| {
19031 let buffer = buffer.clone();
19032 let task = project.prepare_rename(buffer.clone(), position, cx);
19033 cx.spawn(async move |_, cx| {
19034 Ok(match task.await? {
19035 PrepareRenameResponse::Success(range) => Some(range),
19036 PrepareRenameResponse::InvalidPosition => None,
19037 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19038 // Fallback on using TreeSitter info to determine identifier range
19039 buffer.update(cx, |buffer, _| {
19040 let snapshot = buffer.snapshot();
19041 let (range, kind) = snapshot.surrounding_word(position);
19042 if kind != Some(CharKind::Word) {
19043 return None;
19044 }
19045 Some(
19046 snapshot.anchor_before(range.start)
19047 ..snapshot.anchor_after(range.end),
19048 )
19049 })?
19050 }
19051 })
19052 })
19053 }))
19054 }
19055
19056 fn perform_rename(
19057 &self,
19058 buffer: &Entity<Buffer>,
19059 position: text::Anchor,
19060 new_name: String,
19061 cx: &mut App,
19062 ) -> Option<Task<Result<ProjectTransaction>>> {
19063 Some(self.update(cx, |project, cx| {
19064 project.perform_rename(buffer.clone(), position, new_name, cx)
19065 }))
19066 }
19067}
19068
19069fn inlay_hint_settings(
19070 location: Anchor,
19071 snapshot: &MultiBufferSnapshot,
19072 cx: &mut Context<Editor>,
19073) -> InlayHintSettings {
19074 let file = snapshot.file_at(location);
19075 let language = snapshot.language_at(location).map(|l| l.name());
19076 language_settings(language, file, cx).inlay_hints
19077}
19078
19079fn consume_contiguous_rows(
19080 contiguous_row_selections: &mut Vec<Selection<Point>>,
19081 selection: &Selection<Point>,
19082 display_map: &DisplaySnapshot,
19083 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19084) -> (MultiBufferRow, MultiBufferRow) {
19085 contiguous_row_selections.push(selection.clone());
19086 let start_row = MultiBufferRow(selection.start.row);
19087 let mut end_row = ending_row(selection, display_map);
19088
19089 while let Some(next_selection) = selections.peek() {
19090 if next_selection.start.row <= end_row.0 {
19091 end_row = ending_row(next_selection, display_map);
19092 contiguous_row_selections.push(selections.next().unwrap().clone());
19093 } else {
19094 break;
19095 }
19096 }
19097 (start_row, end_row)
19098}
19099
19100fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19101 if next_selection.end.column > 0 || next_selection.is_empty() {
19102 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19103 } else {
19104 MultiBufferRow(next_selection.end.row)
19105 }
19106}
19107
19108impl EditorSnapshot {
19109 pub fn remote_selections_in_range<'a>(
19110 &'a self,
19111 range: &'a Range<Anchor>,
19112 collaboration_hub: &dyn CollaborationHub,
19113 cx: &'a App,
19114 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19115 let participant_names = collaboration_hub.user_names(cx);
19116 let participant_indices = collaboration_hub.user_participant_indices(cx);
19117 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19118 let collaborators_by_replica_id = collaborators_by_peer_id
19119 .iter()
19120 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19121 .collect::<HashMap<_, _>>();
19122 self.buffer_snapshot
19123 .selections_in_range(range, false)
19124 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19125 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19126 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19127 let user_name = participant_names.get(&collaborator.user_id).cloned();
19128 Some(RemoteSelection {
19129 replica_id,
19130 selection,
19131 cursor_shape,
19132 line_mode,
19133 participant_index,
19134 peer_id: collaborator.peer_id,
19135 user_name,
19136 })
19137 })
19138 }
19139
19140 pub fn hunks_for_ranges(
19141 &self,
19142 ranges: impl IntoIterator<Item = Range<Point>>,
19143 ) -> Vec<MultiBufferDiffHunk> {
19144 let mut hunks = Vec::new();
19145 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19146 HashMap::default();
19147 for query_range in ranges {
19148 let query_rows =
19149 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19150 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19151 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19152 ) {
19153 // Include deleted hunks that are adjacent to the query range, because
19154 // otherwise they would be missed.
19155 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19156 if hunk.status().is_deleted() {
19157 intersects_range |= hunk.row_range.start == query_rows.end;
19158 intersects_range |= hunk.row_range.end == query_rows.start;
19159 }
19160 if intersects_range {
19161 if !processed_buffer_rows
19162 .entry(hunk.buffer_id)
19163 .or_default()
19164 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19165 {
19166 continue;
19167 }
19168 hunks.push(hunk);
19169 }
19170 }
19171 }
19172
19173 hunks
19174 }
19175
19176 fn display_diff_hunks_for_rows<'a>(
19177 &'a self,
19178 display_rows: Range<DisplayRow>,
19179 folded_buffers: &'a HashSet<BufferId>,
19180 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19181 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19182 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19183
19184 self.buffer_snapshot
19185 .diff_hunks_in_range(buffer_start..buffer_end)
19186 .filter_map(|hunk| {
19187 if folded_buffers.contains(&hunk.buffer_id) {
19188 return None;
19189 }
19190
19191 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19192 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19193
19194 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19195 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19196
19197 let display_hunk = if hunk_display_start.column() != 0 {
19198 DisplayDiffHunk::Folded {
19199 display_row: hunk_display_start.row(),
19200 }
19201 } else {
19202 let mut end_row = hunk_display_end.row();
19203 if hunk_display_end.column() > 0 {
19204 end_row.0 += 1;
19205 }
19206 let is_created_file = hunk.is_created_file();
19207 DisplayDiffHunk::Unfolded {
19208 status: hunk.status(),
19209 diff_base_byte_range: hunk.diff_base_byte_range,
19210 display_row_range: hunk_display_start.row()..end_row,
19211 multi_buffer_range: Anchor::range_in_buffer(
19212 hunk.excerpt_id,
19213 hunk.buffer_id,
19214 hunk.buffer_range,
19215 ),
19216 is_created_file,
19217 }
19218 };
19219
19220 Some(display_hunk)
19221 })
19222 }
19223
19224 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19225 self.display_snapshot.buffer_snapshot.language_at(position)
19226 }
19227
19228 pub fn is_focused(&self) -> bool {
19229 self.is_focused
19230 }
19231
19232 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19233 self.placeholder_text.as_ref()
19234 }
19235
19236 pub fn scroll_position(&self) -> gpui::Point<f32> {
19237 self.scroll_anchor.scroll_position(&self.display_snapshot)
19238 }
19239
19240 fn gutter_dimensions(
19241 &self,
19242 font_id: FontId,
19243 font_size: Pixels,
19244 max_line_number_width: Pixels,
19245 cx: &App,
19246 ) -> Option<GutterDimensions> {
19247 if !self.show_gutter {
19248 return None;
19249 }
19250
19251 let descent = cx.text_system().descent(font_id, font_size);
19252 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19253 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19254
19255 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19256 matches!(
19257 ProjectSettings::get_global(cx).git.git_gutter,
19258 Some(GitGutterSetting::TrackedFiles)
19259 )
19260 });
19261 let gutter_settings = EditorSettings::get_global(cx).gutter;
19262 let show_line_numbers = self
19263 .show_line_numbers
19264 .unwrap_or(gutter_settings.line_numbers);
19265 let line_gutter_width = if show_line_numbers {
19266 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19267 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19268 max_line_number_width.max(min_width_for_number_on_gutter)
19269 } else {
19270 0.0.into()
19271 };
19272
19273 let show_code_actions = self
19274 .show_code_actions
19275 .unwrap_or(gutter_settings.code_actions);
19276
19277 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19278 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19279
19280 let git_blame_entries_width =
19281 self.git_blame_gutter_max_author_length
19282 .map(|max_author_length| {
19283 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19284 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19285
19286 /// The number of characters to dedicate to gaps and margins.
19287 const SPACING_WIDTH: usize = 4;
19288
19289 let max_char_count = max_author_length.min(renderer.max_author_length())
19290 + ::git::SHORT_SHA_LENGTH
19291 + MAX_RELATIVE_TIMESTAMP.len()
19292 + SPACING_WIDTH;
19293
19294 em_advance * max_char_count
19295 });
19296
19297 let is_singleton = self.buffer_snapshot.is_singleton();
19298
19299 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19300 left_padding += if !is_singleton {
19301 em_width * 4.0
19302 } else if show_code_actions || show_runnables || show_breakpoints {
19303 em_width * 3.0
19304 } else if show_git_gutter && show_line_numbers {
19305 em_width * 2.0
19306 } else if show_git_gutter || show_line_numbers {
19307 em_width
19308 } else {
19309 px(0.)
19310 };
19311
19312 let shows_folds = is_singleton && gutter_settings.folds;
19313
19314 let right_padding = if shows_folds && show_line_numbers {
19315 em_width * 4.0
19316 } else if shows_folds || (!is_singleton && show_line_numbers) {
19317 em_width * 3.0
19318 } else if show_line_numbers {
19319 em_width
19320 } else {
19321 px(0.)
19322 };
19323
19324 Some(GutterDimensions {
19325 left_padding,
19326 right_padding,
19327 width: line_gutter_width + left_padding + right_padding,
19328 margin: -descent,
19329 git_blame_entries_width,
19330 })
19331 }
19332
19333 pub fn render_crease_toggle(
19334 &self,
19335 buffer_row: MultiBufferRow,
19336 row_contains_cursor: bool,
19337 editor: Entity<Editor>,
19338 window: &mut Window,
19339 cx: &mut App,
19340 ) -> Option<AnyElement> {
19341 let folded = self.is_line_folded(buffer_row);
19342 let mut is_foldable = false;
19343
19344 if let Some(crease) = self
19345 .crease_snapshot
19346 .query_row(buffer_row, &self.buffer_snapshot)
19347 {
19348 is_foldable = true;
19349 match crease {
19350 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
19351 if let Some(render_toggle) = render_toggle {
19352 let toggle_callback =
19353 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
19354 if folded {
19355 editor.update(cx, |editor, cx| {
19356 editor.fold_at(buffer_row, window, cx)
19357 });
19358 } else {
19359 editor.update(cx, |editor, cx| {
19360 editor.unfold_at(buffer_row, window, cx)
19361 });
19362 }
19363 });
19364 return Some((render_toggle)(
19365 buffer_row,
19366 folded,
19367 toggle_callback,
19368 window,
19369 cx,
19370 ));
19371 }
19372 }
19373 }
19374 }
19375
19376 is_foldable |= self.starts_indent(buffer_row);
19377
19378 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19379 Some(
19380 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19381 .toggle_state(folded)
19382 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19383 if folded {
19384 this.unfold_at(buffer_row, window, cx);
19385 } else {
19386 this.fold_at(buffer_row, window, cx);
19387 }
19388 }))
19389 .into_any_element(),
19390 )
19391 } else {
19392 None
19393 }
19394 }
19395
19396 pub fn render_crease_trailer(
19397 &self,
19398 buffer_row: MultiBufferRow,
19399 window: &mut Window,
19400 cx: &mut App,
19401 ) -> Option<AnyElement> {
19402 let folded = self.is_line_folded(buffer_row);
19403 if let Crease::Inline { render_trailer, .. } = self
19404 .crease_snapshot
19405 .query_row(buffer_row, &self.buffer_snapshot)?
19406 {
19407 let render_trailer = render_trailer.as_ref()?;
19408 Some(render_trailer(buffer_row, folded, window, cx))
19409 } else {
19410 None
19411 }
19412 }
19413}
19414
19415impl Deref for EditorSnapshot {
19416 type Target = DisplaySnapshot;
19417
19418 fn deref(&self) -> &Self::Target {
19419 &self.display_snapshot
19420 }
19421}
19422
19423#[derive(Clone, Debug, PartialEq, Eq)]
19424pub enum EditorEvent {
19425 InputIgnored {
19426 text: Arc<str>,
19427 },
19428 InputHandled {
19429 utf16_range_to_replace: Option<Range<isize>>,
19430 text: Arc<str>,
19431 },
19432 ExcerptsAdded {
19433 buffer: Entity<Buffer>,
19434 predecessor: ExcerptId,
19435 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19436 },
19437 ExcerptsRemoved {
19438 ids: Vec<ExcerptId>,
19439 },
19440 BufferFoldToggled {
19441 ids: Vec<ExcerptId>,
19442 folded: bool,
19443 },
19444 ExcerptsEdited {
19445 ids: Vec<ExcerptId>,
19446 },
19447 ExcerptsExpanded {
19448 ids: Vec<ExcerptId>,
19449 },
19450 BufferEdited,
19451 Edited {
19452 transaction_id: clock::Lamport,
19453 },
19454 Reparsed(BufferId),
19455 Focused,
19456 FocusedIn,
19457 Blurred,
19458 DirtyChanged,
19459 Saved,
19460 TitleChanged,
19461 DiffBaseChanged,
19462 SelectionsChanged {
19463 local: bool,
19464 },
19465 ScrollPositionChanged {
19466 local: bool,
19467 autoscroll: bool,
19468 },
19469 Closed,
19470 TransactionUndone {
19471 transaction_id: clock::Lamport,
19472 },
19473 TransactionBegun {
19474 transaction_id: clock::Lamport,
19475 },
19476 Reloaded,
19477 CursorShapeChanged,
19478 PushedToNavHistory {
19479 anchor: Anchor,
19480 is_deactivate: bool,
19481 },
19482}
19483
19484impl EventEmitter<EditorEvent> for Editor {}
19485
19486impl Focusable for Editor {
19487 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19488 self.focus_handle.clone()
19489 }
19490}
19491
19492impl Render for Editor {
19493 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19494 let settings = ThemeSettings::get_global(cx);
19495
19496 let mut text_style = match self.mode {
19497 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19498 color: cx.theme().colors().editor_foreground,
19499 font_family: settings.ui_font.family.clone(),
19500 font_features: settings.ui_font.features.clone(),
19501 font_fallbacks: settings.ui_font.fallbacks.clone(),
19502 font_size: rems(0.875).into(),
19503 font_weight: settings.ui_font.weight,
19504 line_height: relative(settings.buffer_line_height.value()),
19505 ..Default::default()
19506 },
19507 EditorMode::Full => TextStyle {
19508 color: cx.theme().colors().editor_foreground,
19509 font_family: settings.buffer_font.family.clone(),
19510 font_features: settings.buffer_font.features.clone(),
19511 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19512 font_size: settings.buffer_font_size(cx).into(),
19513 font_weight: settings.buffer_font.weight,
19514 line_height: relative(settings.buffer_line_height.value()),
19515 ..Default::default()
19516 },
19517 };
19518 if let Some(text_style_refinement) = &self.text_style_refinement {
19519 text_style.refine(text_style_refinement)
19520 }
19521
19522 let background = match self.mode {
19523 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19524 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19525 EditorMode::Full => cx.theme().colors().editor_background,
19526 };
19527
19528 EditorElement::new(
19529 &cx.entity(),
19530 EditorStyle {
19531 background,
19532 local_player: cx.theme().players().local(),
19533 text: text_style,
19534 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19535 syntax: cx.theme().syntax().clone(),
19536 status: cx.theme().status().clone(),
19537 inlay_hints_style: make_inlay_hints_style(cx),
19538 inline_completion_styles: make_suggestion_styles(cx),
19539 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19540 },
19541 )
19542 }
19543}
19544
19545impl EntityInputHandler for Editor {
19546 fn text_for_range(
19547 &mut self,
19548 range_utf16: Range<usize>,
19549 adjusted_range: &mut Option<Range<usize>>,
19550 _: &mut Window,
19551 cx: &mut Context<Self>,
19552 ) -> Option<String> {
19553 let snapshot = self.buffer.read(cx).read(cx);
19554 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19555 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19556 if (start.0..end.0) != range_utf16 {
19557 adjusted_range.replace(start.0..end.0);
19558 }
19559 Some(snapshot.text_for_range(start..end).collect())
19560 }
19561
19562 fn selected_text_range(
19563 &mut self,
19564 ignore_disabled_input: bool,
19565 _: &mut Window,
19566 cx: &mut Context<Self>,
19567 ) -> Option<UTF16Selection> {
19568 // Prevent the IME menu from appearing when holding down an alphabetic key
19569 // while input is disabled.
19570 if !ignore_disabled_input && !self.input_enabled {
19571 return None;
19572 }
19573
19574 let selection = self.selections.newest::<OffsetUtf16>(cx);
19575 let range = selection.range();
19576
19577 Some(UTF16Selection {
19578 range: range.start.0..range.end.0,
19579 reversed: selection.reversed,
19580 })
19581 }
19582
19583 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19584 let snapshot = self.buffer.read(cx).read(cx);
19585 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19586 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19587 }
19588
19589 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19590 self.clear_highlights::<InputComposition>(cx);
19591 self.ime_transaction.take();
19592 }
19593
19594 fn replace_text_in_range(
19595 &mut self,
19596 range_utf16: Option<Range<usize>>,
19597 text: &str,
19598 window: &mut Window,
19599 cx: &mut Context<Self>,
19600 ) {
19601 if !self.input_enabled {
19602 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19603 return;
19604 }
19605
19606 self.transact(window, cx, |this, window, cx| {
19607 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19608 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19609 Some(this.selection_replacement_ranges(range_utf16, cx))
19610 } else {
19611 this.marked_text_ranges(cx)
19612 };
19613
19614 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19615 let newest_selection_id = this.selections.newest_anchor().id;
19616 this.selections
19617 .all::<OffsetUtf16>(cx)
19618 .iter()
19619 .zip(ranges_to_replace.iter())
19620 .find_map(|(selection, range)| {
19621 if selection.id == newest_selection_id {
19622 Some(
19623 (range.start.0 as isize - selection.head().0 as isize)
19624 ..(range.end.0 as isize - selection.head().0 as isize),
19625 )
19626 } else {
19627 None
19628 }
19629 })
19630 });
19631
19632 cx.emit(EditorEvent::InputHandled {
19633 utf16_range_to_replace: range_to_replace,
19634 text: text.into(),
19635 });
19636
19637 if let Some(new_selected_ranges) = new_selected_ranges {
19638 this.change_selections(None, window, cx, |selections| {
19639 selections.select_ranges(new_selected_ranges)
19640 });
19641 this.backspace(&Default::default(), window, cx);
19642 }
19643
19644 this.handle_input(text, window, cx);
19645 });
19646
19647 if let Some(transaction) = self.ime_transaction {
19648 self.buffer.update(cx, |buffer, cx| {
19649 buffer.group_until_transaction(transaction, cx);
19650 });
19651 }
19652
19653 self.unmark_text(window, cx);
19654 }
19655
19656 fn replace_and_mark_text_in_range(
19657 &mut self,
19658 range_utf16: Option<Range<usize>>,
19659 text: &str,
19660 new_selected_range_utf16: Option<Range<usize>>,
19661 window: &mut Window,
19662 cx: &mut Context<Self>,
19663 ) {
19664 if !self.input_enabled {
19665 return;
19666 }
19667
19668 let transaction = self.transact(window, cx, |this, window, cx| {
19669 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19670 let snapshot = this.buffer.read(cx).read(cx);
19671 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19672 for marked_range in &mut marked_ranges {
19673 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19674 marked_range.start.0 += relative_range_utf16.start;
19675 marked_range.start =
19676 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19677 marked_range.end =
19678 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19679 }
19680 }
19681 Some(marked_ranges)
19682 } else if let Some(range_utf16) = range_utf16 {
19683 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19684 Some(this.selection_replacement_ranges(range_utf16, cx))
19685 } else {
19686 None
19687 };
19688
19689 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19690 let newest_selection_id = this.selections.newest_anchor().id;
19691 this.selections
19692 .all::<OffsetUtf16>(cx)
19693 .iter()
19694 .zip(ranges_to_replace.iter())
19695 .find_map(|(selection, range)| {
19696 if selection.id == newest_selection_id {
19697 Some(
19698 (range.start.0 as isize - selection.head().0 as isize)
19699 ..(range.end.0 as isize - selection.head().0 as isize),
19700 )
19701 } else {
19702 None
19703 }
19704 })
19705 });
19706
19707 cx.emit(EditorEvent::InputHandled {
19708 utf16_range_to_replace: range_to_replace,
19709 text: text.into(),
19710 });
19711
19712 if let Some(ranges) = ranges_to_replace {
19713 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19714 }
19715
19716 let marked_ranges = {
19717 let snapshot = this.buffer.read(cx).read(cx);
19718 this.selections
19719 .disjoint_anchors()
19720 .iter()
19721 .map(|selection| {
19722 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19723 })
19724 .collect::<Vec<_>>()
19725 };
19726
19727 if text.is_empty() {
19728 this.unmark_text(window, cx);
19729 } else {
19730 this.highlight_text::<InputComposition>(
19731 marked_ranges.clone(),
19732 HighlightStyle {
19733 underline: Some(UnderlineStyle {
19734 thickness: px(1.),
19735 color: None,
19736 wavy: false,
19737 }),
19738 ..Default::default()
19739 },
19740 cx,
19741 );
19742 }
19743
19744 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19745 let use_autoclose = this.use_autoclose;
19746 let use_auto_surround = this.use_auto_surround;
19747 this.set_use_autoclose(false);
19748 this.set_use_auto_surround(false);
19749 this.handle_input(text, window, cx);
19750 this.set_use_autoclose(use_autoclose);
19751 this.set_use_auto_surround(use_auto_surround);
19752
19753 if let Some(new_selected_range) = new_selected_range_utf16 {
19754 let snapshot = this.buffer.read(cx).read(cx);
19755 let new_selected_ranges = marked_ranges
19756 .into_iter()
19757 .map(|marked_range| {
19758 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19759 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19760 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19761 snapshot.clip_offset_utf16(new_start, Bias::Left)
19762 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19763 })
19764 .collect::<Vec<_>>();
19765
19766 drop(snapshot);
19767 this.change_selections(None, window, cx, |selections| {
19768 selections.select_ranges(new_selected_ranges)
19769 });
19770 }
19771 });
19772
19773 self.ime_transaction = self.ime_transaction.or(transaction);
19774 if let Some(transaction) = self.ime_transaction {
19775 self.buffer.update(cx, |buffer, cx| {
19776 buffer.group_until_transaction(transaction, cx);
19777 });
19778 }
19779
19780 if self.text_highlights::<InputComposition>(cx).is_none() {
19781 self.ime_transaction.take();
19782 }
19783 }
19784
19785 fn bounds_for_range(
19786 &mut self,
19787 range_utf16: Range<usize>,
19788 element_bounds: gpui::Bounds<Pixels>,
19789 window: &mut Window,
19790 cx: &mut Context<Self>,
19791 ) -> Option<gpui::Bounds<Pixels>> {
19792 let text_layout_details = self.text_layout_details(window);
19793 let gpui::Size {
19794 width: em_width,
19795 height: line_height,
19796 } = self.character_size(window);
19797
19798 let snapshot = self.snapshot(window, cx);
19799 let scroll_position = snapshot.scroll_position();
19800 let scroll_left = scroll_position.x * em_width;
19801
19802 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19803 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19804 + self.gutter_dimensions.width
19805 + self.gutter_dimensions.margin;
19806 let y = line_height * (start.row().as_f32() - scroll_position.y);
19807
19808 Some(Bounds {
19809 origin: element_bounds.origin + point(x, y),
19810 size: size(em_width, line_height),
19811 })
19812 }
19813
19814 fn character_index_for_point(
19815 &mut self,
19816 point: gpui::Point<Pixels>,
19817 _window: &mut Window,
19818 _cx: &mut Context<Self>,
19819 ) -> Option<usize> {
19820 let position_map = self.last_position_map.as_ref()?;
19821 if !position_map.text_hitbox.contains(&point) {
19822 return None;
19823 }
19824 let display_point = position_map.point_for_position(point).previous_valid;
19825 let anchor = position_map
19826 .snapshot
19827 .display_point_to_anchor(display_point, Bias::Left);
19828 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19829 Some(utf16_offset.0)
19830 }
19831}
19832
19833trait SelectionExt {
19834 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19835 fn spanned_rows(
19836 &self,
19837 include_end_if_at_line_start: bool,
19838 map: &DisplaySnapshot,
19839 ) -> Range<MultiBufferRow>;
19840}
19841
19842impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19843 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19844 let start = self
19845 .start
19846 .to_point(&map.buffer_snapshot)
19847 .to_display_point(map);
19848 let end = self
19849 .end
19850 .to_point(&map.buffer_snapshot)
19851 .to_display_point(map);
19852 if self.reversed {
19853 end..start
19854 } else {
19855 start..end
19856 }
19857 }
19858
19859 fn spanned_rows(
19860 &self,
19861 include_end_if_at_line_start: bool,
19862 map: &DisplaySnapshot,
19863 ) -> Range<MultiBufferRow> {
19864 let start = self.start.to_point(&map.buffer_snapshot);
19865 let mut end = self.end.to_point(&map.buffer_snapshot);
19866 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19867 end.row -= 1;
19868 }
19869
19870 let buffer_start = map.prev_line_boundary(start).0;
19871 let buffer_end = map.next_line_boundary(end).0;
19872 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19873 }
19874}
19875
19876impl<T: InvalidationRegion> InvalidationStack<T> {
19877 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19878 where
19879 S: Clone + ToOffset,
19880 {
19881 while let Some(region) = self.last() {
19882 let all_selections_inside_invalidation_ranges =
19883 if selections.len() == region.ranges().len() {
19884 selections
19885 .iter()
19886 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19887 .all(|(selection, invalidation_range)| {
19888 let head = selection.head().to_offset(buffer);
19889 invalidation_range.start <= head && invalidation_range.end >= head
19890 })
19891 } else {
19892 false
19893 };
19894
19895 if all_selections_inside_invalidation_ranges {
19896 break;
19897 } else {
19898 self.pop();
19899 }
19900 }
19901 }
19902}
19903
19904impl<T> Default for InvalidationStack<T> {
19905 fn default() -> Self {
19906 Self(Default::default())
19907 }
19908}
19909
19910impl<T> Deref for InvalidationStack<T> {
19911 type Target = Vec<T>;
19912
19913 fn deref(&self) -> &Self::Target {
19914 &self.0
19915 }
19916}
19917
19918impl<T> DerefMut for InvalidationStack<T> {
19919 fn deref_mut(&mut self) -> &mut Self::Target {
19920 &mut self.0
19921 }
19922}
19923
19924impl InvalidationRegion for SnippetState {
19925 fn ranges(&self) -> &[Range<Anchor>] {
19926 &self.ranges[self.active_index]
19927 }
19928}
19929
19930pub fn diagnostic_block_renderer(
19931 diagnostic: Diagnostic,
19932 max_message_rows: Option<u8>,
19933 allow_closing: bool,
19934) -> RenderBlock {
19935 let (text_without_backticks, code_ranges) =
19936 highlight_diagnostic_message(&diagnostic, max_message_rows);
19937
19938 Arc::new(move |cx: &mut BlockContext| {
19939 let group_id: SharedString = cx.block_id.to_string().into();
19940
19941 let mut text_style = cx.window.text_style().clone();
19942 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19943 let theme_settings = ThemeSettings::get_global(cx);
19944 text_style.font_family = theme_settings.buffer_font.family.clone();
19945 text_style.font_style = theme_settings.buffer_font.style;
19946 text_style.font_features = theme_settings.buffer_font.features.clone();
19947 text_style.font_weight = theme_settings.buffer_font.weight;
19948
19949 let multi_line_diagnostic = diagnostic.message.contains('\n');
19950
19951 let buttons = |diagnostic: &Diagnostic| {
19952 if multi_line_diagnostic {
19953 v_flex()
19954 } else {
19955 h_flex()
19956 }
19957 .when(allow_closing, |div| {
19958 div.children(diagnostic.is_primary.then(|| {
19959 IconButton::new("close-block", IconName::XCircle)
19960 .icon_color(Color::Muted)
19961 .size(ButtonSize::Compact)
19962 .style(ButtonStyle::Transparent)
19963 .visible_on_hover(group_id.clone())
19964 .on_click(move |_click, window, cx| {
19965 window.dispatch_action(Box::new(Cancel), cx)
19966 })
19967 .tooltip(|window, cx| {
19968 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19969 })
19970 }))
19971 })
19972 .child(
19973 IconButton::new("copy-block", IconName::Copy)
19974 .icon_color(Color::Muted)
19975 .size(ButtonSize::Compact)
19976 .style(ButtonStyle::Transparent)
19977 .visible_on_hover(group_id.clone())
19978 .on_click({
19979 let message = diagnostic.message.clone();
19980 move |_click, _, cx| {
19981 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19982 }
19983 })
19984 .tooltip(Tooltip::text("Copy diagnostic message")),
19985 )
19986 };
19987
19988 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19989 AvailableSpace::min_size(),
19990 cx.window,
19991 cx.app,
19992 );
19993
19994 h_flex()
19995 .id(cx.block_id)
19996 .group(group_id.clone())
19997 .relative()
19998 .size_full()
19999 .block_mouse_down()
20000 .pl(cx.gutter_dimensions.width)
20001 .w(cx.max_width - cx.gutter_dimensions.full_width())
20002 .child(
20003 div()
20004 .flex()
20005 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
20006 .flex_shrink(),
20007 )
20008 .child(buttons(&diagnostic))
20009 .child(div().flex().flex_shrink_0().child(
20010 StyledText::new(text_without_backticks.clone()).with_default_highlights(
20011 &text_style,
20012 code_ranges.iter().map(|range| {
20013 (
20014 range.clone(),
20015 HighlightStyle {
20016 font_weight: Some(FontWeight::BOLD),
20017 ..Default::default()
20018 },
20019 )
20020 }),
20021 ),
20022 ))
20023 .into_any_element()
20024 })
20025}
20026
20027fn inline_completion_edit_text(
20028 current_snapshot: &BufferSnapshot,
20029 edits: &[(Range<Anchor>, String)],
20030 edit_preview: &EditPreview,
20031 include_deletions: bool,
20032 cx: &App,
20033) -> HighlightedText {
20034 let edits = edits
20035 .iter()
20036 .map(|(anchor, text)| {
20037 (
20038 anchor.start.text_anchor..anchor.end.text_anchor,
20039 text.clone(),
20040 )
20041 })
20042 .collect::<Vec<_>>();
20043
20044 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20045}
20046
20047pub fn highlight_diagnostic_message(
20048 diagnostic: &Diagnostic,
20049 mut max_message_rows: Option<u8>,
20050) -> (SharedString, Vec<Range<usize>>) {
20051 let mut text_without_backticks = String::new();
20052 let mut code_ranges = Vec::new();
20053
20054 if let Some(source) = &diagnostic.source {
20055 text_without_backticks.push_str(source);
20056 code_ranges.push(0..source.len());
20057 text_without_backticks.push_str(": ");
20058 }
20059
20060 let mut prev_offset = 0;
20061 let mut in_code_block = false;
20062 let has_row_limit = max_message_rows.is_some();
20063 let mut newline_indices = diagnostic
20064 .message
20065 .match_indices('\n')
20066 .filter(|_| has_row_limit)
20067 .map(|(ix, _)| ix)
20068 .fuse()
20069 .peekable();
20070
20071 for (quote_ix, _) in diagnostic
20072 .message
20073 .match_indices('`')
20074 .chain([(diagnostic.message.len(), "")])
20075 {
20076 let mut first_newline_ix = None;
20077 let mut last_newline_ix = None;
20078 while let Some(newline_ix) = newline_indices.peek() {
20079 if *newline_ix < quote_ix {
20080 if first_newline_ix.is_none() {
20081 first_newline_ix = Some(*newline_ix);
20082 }
20083 last_newline_ix = Some(*newline_ix);
20084
20085 if let Some(rows_left) = &mut max_message_rows {
20086 if *rows_left == 0 {
20087 break;
20088 } else {
20089 *rows_left -= 1;
20090 }
20091 }
20092 let _ = newline_indices.next();
20093 } else {
20094 break;
20095 }
20096 }
20097 let prev_len = text_without_backticks.len();
20098 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
20099 text_without_backticks.push_str(new_text);
20100 if in_code_block {
20101 code_ranges.push(prev_len..text_without_backticks.len());
20102 }
20103 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
20104 in_code_block = !in_code_block;
20105 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
20106 text_without_backticks.push_str("...");
20107 break;
20108 }
20109 }
20110
20111 (text_without_backticks.into(), code_ranges)
20112}
20113
20114fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20115 match severity {
20116 DiagnosticSeverity::ERROR => colors.error,
20117 DiagnosticSeverity::WARNING => colors.warning,
20118 DiagnosticSeverity::INFORMATION => colors.info,
20119 DiagnosticSeverity::HINT => colors.info,
20120 _ => colors.ignored,
20121 }
20122}
20123
20124pub fn styled_runs_for_code_label<'a>(
20125 label: &'a CodeLabel,
20126 syntax_theme: &'a theme::SyntaxTheme,
20127) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20128 let fade_out = HighlightStyle {
20129 fade_out: Some(0.35),
20130 ..Default::default()
20131 };
20132
20133 let mut prev_end = label.filter_range.end;
20134 label
20135 .runs
20136 .iter()
20137 .enumerate()
20138 .flat_map(move |(ix, (range, highlight_id))| {
20139 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20140 style
20141 } else {
20142 return Default::default();
20143 };
20144 let mut muted_style = style;
20145 muted_style.highlight(fade_out);
20146
20147 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20148 if range.start >= label.filter_range.end {
20149 if range.start > prev_end {
20150 runs.push((prev_end..range.start, fade_out));
20151 }
20152 runs.push((range.clone(), muted_style));
20153 } else if range.end <= label.filter_range.end {
20154 runs.push((range.clone(), style));
20155 } else {
20156 runs.push((range.start..label.filter_range.end, style));
20157 runs.push((label.filter_range.end..range.end, muted_style));
20158 }
20159 prev_end = cmp::max(prev_end, range.end);
20160
20161 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20162 runs.push((prev_end..label.text.len(), fade_out));
20163 }
20164
20165 runs
20166 })
20167}
20168
20169pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20170 let mut prev_index = 0;
20171 let mut prev_codepoint: Option<char> = None;
20172 text.char_indices()
20173 .chain([(text.len(), '\0')])
20174 .filter_map(move |(index, codepoint)| {
20175 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20176 let is_boundary = index == text.len()
20177 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20178 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20179 if is_boundary {
20180 let chunk = &text[prev_index..index];
20181 prev_index = index;
20182 Some(chunk)
20183 } else {
20184 None
20185 }
20186 })
20187}
20188
20189pub trait RangeToAnchorExt: Sized {
20190 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20191
20192 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20193 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20194 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20195 }
20196}
20197
20198impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20199 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20200 let start_offset = self.start.to_offset(snapshot);
20201 let end_offset = self.end.to_offset(snapshot);
20202 if start_offset == end_offset {
20203 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20204 } else {
20205 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20206 }
20207 }
20208}
20209
20210pub trait RowExt {
20211 fn as_f32(&self) -> f32;
20212
20213 fn next_row(&self) -> Self;
20214
20215 fn previous_row(&self) -> Self;
20216
20217 fn minus(&self, other: Self) -> u32;
20218}
20219
20220impl RowExt for DisplayRow {
20221 fn as_f32(&self) -> f32 {
20222 self.0 as f32
20223 }
20224
20225 fn next_row(&self) -> Self {
20226 Self(self.0 + 1)
20227 }
20228
20229 fn previous_row(&self) -> Self {
20230 Self(self.0.saturating_sub(1))
20231 }
20232
20233 fn minus(&self, other: Self) -> u32 {
20234 self.0 - other.0
20235 }
20236}
20237
20238impl RowExt for MultiBufferRow {
20239 fn as_f32(&self) -> f32 {
20240 self.0 as f32
20241 }
20242
20243 fn next_row(&self) -> Self {
20244 Self(self.0 + 1)
20245 }
20246
20247 fn previous_row(&self) -> Self {
20248 Self(self.0.saturating_sub(1))
20249 }
20250
20251 fn minus(&self, other: Self) -> u32 {
20252 self.0 - other.0
20253 }
20254}
20255
20256trait RowRangeExt {
20257 type Row;
20258
20259 fn len(&self) -> usize;
20260
20261 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20262}
20263
20264impl RowRangeExt for Range<MultiBufferRow> {
20265 type Row = MultiBufferRow;
20266
20267 fn len(&self) -> usize {
20268 (self.end.0 - self.start.0) as usize
20269 }
20270
20271 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20272 (self.start.0..self.end.0).map(MultiBufferRow)
20273 }
20274}
20275
20276impl RowRangeExt for Range<DisplayRow> {
20277 type Row = DisplayRow;
20278
20279 fn len(&self) -> usize {
20280 (self.end.0 - self.start.0) as usize
20281 }
20282
20283 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20284 (self.start.0..self.end.0).map(DisplayRow)
20285 }
20286}
20287
20288/// If select range has more than one line, we
20289/// just point the cursor to range.start.
20290fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20291 if range.start.row == range.end.row {
20292 range
20293 } else {
20294 range.start..range.start
20295 }
20296}
20297pub struct KillRing(ClipboardItem);
20298impl Global for KillRing {}
20299
20300const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20301
20302enum BreakpointPromptEditAction {
20303 Log,
20304 Condition,
20305 HitCondition,
20306}
20307
20308struct BreakpointPromptEditor {
20309 pub(crate) prompt: Entity<Editor>,
20310 editor: WeakEntity<Editor>,
20311 breakpoint_anchor: Anchor,
20312 breakpoint: Breakpoint,
20313 edit_action: BreakpointPromptEditAction,
20314 block_ids: HashSet<CustomBlockId>,
20315 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20316 _subscriptions: Vec<Subscription>,
20317}
20318
20319impl BreakpointPromptEditor {
20320 const MAX_LINES: u8 = 4;
20321
20322 fn new(
20323 editor: WeakEntity<Editor>,
20324 breakpoint_anchor: Anchor,
20325 breakpoint: Breakpoint,
20326 edit_action: BreakpointPromptEditAction,
20327 window: &mut Window,
20328 cx: &mut Context<Self>,
20329 ) -> Self {
20330 let base_text = match edit_action {
20331 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20332 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20333 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20334 }
20335 .map(|msg| msg.to_string())
20336 .unwrap_or_default();
20337
20338 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20339 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20340
20341 let prompt = cx.new(|cx| {
20342 let mut prompt = Editor::new(
20343 EditorMode::AutoHeight {
20344 max_lines: Self::MAX_LINES as usize,
20345 },
20346 buffer,
20347 None,
20348 window,
20349 cx,
20350 );
20351 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20352 prompt.set_show_cursor_when_unfocused(false, cx);
20353 prompt.set_placeholder_text(
20354 match edit_action {
20355 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20356 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20357 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20358 },
20359 cx,
20360 );
20361
20362 prompt
20363 });
20364
20365 Self {
20366 prompt,
20367 editor,
20368 breakpoint_anchor,
20369 breakpoint,
20370 edit_action,
20371 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20372 block_ids: Default::default(),
20373 _subscriptions: vec![],
20374 }
20375 }
20376
20377 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20378 self.block_ids.extend(block_ids)
20379 }
20380
20381 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20382 if let Some(editor) = self.editor.upgrade() {
20383 let message = self
20384 .prompt
20385 .read(cx)
20386 .buffer
20387 .read(cx)
20388 .as_singleton()
20389 .expect("A multi buffer in breakpoint prompt isn't possible")
20390 .read(cx)
20391 .as_rope()
20392 .to_string();
20393
20394 editor.update(cx, |editor, cx| {
20395 editor.edit_breakpoint_at_anchor(
20396 self.breakpoint_anchor,
20397 self.breakpoint.clone(),
20398 match self.edit_action {
20399 BreakpointPromptEditAction::Log => {
20400 BreakpointEditAction::EditLogMessage(message.into())
20401 }
20402 BreakpointPromptEditAction::Condition => {
20403 BreakpointEditAction::EditCondition(message.into())
20404 }
20405 BreakpointPromptEditAction::HitCondition => {
20406 BreakpointEditAction::EditHitCondition(message.into())
20407 }
20408 },
20409 cx,
20410 );
20411
20412 editor.remove_blocks(self.block_ids.clone(), None, cx);
20413 cx.focus_self(window);
20414 });
20415 }
20416 }
20417
20418 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20419 self.editor
20420 .update(cx, |editor, cx| {
20421 editor.remove_blocks(self.block_ids.clone(), None, cx);
20422 window.focus(&editor.focus_handle);
20423 })
20424 .log_err();
20425 }
20426
20427 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20428 let settings = ThemeSettings::get_global(cx);
20429 let text_style = TextStyle {
20430 color: if self.prompt.read(cx).read_only(cx) {
20431 cx.theme().colors().text_disabled
20432 } else {
20433 cx.theme().colors().text
20434 },
20435 font_family: settings.buffer_font.family.clone(),
20436 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20437 font_size: settings.buffer_font_size(cx).into(),
20438 font_weight: settings.buffer_font.weight,
20439 line_height: relative(settings.buffer_line_height.value()),
20440 ..Default::default()
20441 };
20442 EditorElement::new(
20443 &self.prompt,
20444 EditorStyle {
20445 background: cx.theme().colors().editor_background,
20446 local_player: cx.theme().players().local(),
20447 text: text_style,
20448 ..Default::default()
20449 },
20450 )
20451 }
20452}
20453
20454impl Render for BreakpointPromptEditor {
20455 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20456 let gutter_dimensions = *self.gutter_dimensions.lock();
20457 h_flex()
20458 .key_context("Editor")
20459 .bg(cx.theme().colors().editor_background)
20460 .border_y_1()
20461 .border_color(cx.theme().status().info_border)
20462 .size_full()
20463 .py(window.line_height() / 2.5)
20464 .on_action(cx.listener(Self::confirm))
20465 .on_action(cx.listener(Self::cancel))
20466 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20467 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20468 }
20469}
20470
20471impl Focusable for BreakpointPromptEditor {
20472 fn focus_handle(&self, cx: &App) -> FocusHandle {
20473 self.prompt.focus_handle(cx)
20474 }
20475}
20476
20477fn all_edits_insertions_or_deletions(
20478 edits: &Vec<(Range<Anchor>, String)>,
20479 snapshot: &MultiBufferSnapshot,
20480) -> bool {
20481 let mut all_insertions = true;
20482 let mut all_deletions = true;
20483
20484 for (range, new_text) in edits.iter() {
20485 let range_is_empty = range.to_offset(&snapshot).is_empty();
20486 let text_is_empty = new_text.is_empty();
20487
20488 if range_is_empty != text_is_empty {
20489 if range_is_empty {
20490 all_deletions = false;
20491 } else {
20492 all_insertions = false;
20493 }
20494 } else {
20495 return false;
20496 }
20497
20498 if !all_insertions && !all_deletions {
20499 return false;
20500 }
20501 }
20502 all_insertions || all_deletions
20503}
20504
20505struct MissingEditPredictionKeybindingTooltip;
20506
20507impl Render for MissingEditPredictionKeybindingTooltip {
20508 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20509 ui::tooltip_container(window, cx, |container, _, cx| {
20510 container
20511 .flex_shrink_0()
20512 .max_w_80()
20513 .min_h(rems_from_px(124.))
20514 .justify_between()
20515 .child(
20516 v_flex()
20517 .flex_1()
20518 .text_ui_sm(cx)
20519 .child(Label::new("Conflict with Accept Keybinding"))
20520 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20521 )
20522 .child(
20523 h_flex()
20524 .pb_1()
20525 .gap_1()
20526 .items_end()
20527 .w_full()
20528 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20529 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20530 }))
20531 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20532 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20533 })),
20534 )
20535 })
20536 }
20537}
20538
20539#[derive(Debug, Clone, Copy, PartialEq)]
20540pub struct LineHighlight {
20541 pub background: Background,
20542 pub border: Option<gpui::Hsla>,
20543}
20544
20545impl From<Hsla> for LineHighlight {
20546 fn from(hsla: Hsla) -> Self {
20547 Self {
20548 background: hsla.into(),
20549 border: None,
20550 }
20551 }
20552}
20553
20554impl From<Background> for LineHighlight {
20555 fn from(background: Background) -> Self {
20556 Self {
20557 background,
20558 border: None,
20559 }
20560 }
20561}
20562
20563fn render_diff_hunk_controls(
20564 row: u32,
20565 status: &DiffHunkStatus,
20566 hunk_range: Range<Anchor>,
20567 is_created_file: bool,
20568 line_height: Pixels,
20569 editor: &Entity<Editor>,
20570 _window: &mut Window,
20571 cx: &mut App,
20572) -> AnyElement {
20573 h_flex()
20574 .h(line_height)
20575 .mr_1()
20576 .gap_1()
20577 .px_0p5()
20578 .pb_1()
20579 .border_x_1()
20580 .border_b_1()
20581 .border_color(cx.theme().colors().border_variant)
20582 .rounded_b_lg()
20583 .bg(cx.theme().colors().editor_background)
20584 .gap_1()
20585 .occlude()
20586 .shadow_md()
20587 .child(if status.has_secondary_hunk() {
20588 Button::new(("stage", row as u64), "Stage")
20589 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20590 .tooltip({
20591 let focus_handle = editor.focus_handle(cx);
20592 move |window, cx| {
20593 Tooltip::for_action_in(
20594 "Stage Hunk",
20595 &::git::ToggleStaged,
20596 &focus_handle,
20597 window,
20598 cx,
20599 )
20600 }
20601 })
20602 .on_click({
20603 let editor = editor.clone();
20604 move |_event, _window, cx| {
20605 editor.update(cx, |editor, cx| {
20606 editor.stage_or_unstage_diff_hunks(
20607 true,
20608 vec![hunk_range.start..hunk_range.start],
20609 cx,
20610 );
20611 });
20612 }
20613 })
20614 } else {
20615 Button::new(("unstage", row as u64), "Unstage")
20616 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20617 .tooltip({
20618 let focus_handle = editor.focus_handle(cx);
20619 move |window, cx| {
20620 Tooltip::for_action_in(
20621 "Unstage Hunk",
20622 &::git::ToggleStaged,
20623 &focus_handle,
20624 window,
20625 cx,
20626 )
20627 }
20628 })
20629 .on_click({
20630 let editor = editor.clone();
20631 move |_event, _window, cx| {
20632 editor.update(cx, |editor, cx| {
20633 editor.stage_or_unstage_diff_hunks(
20634 false,
20635 vec![hunk_range.start..hunk_range.start],
20636 cx,
20637 );
20638 });
20639 }
20640 })
20641 })
20642 .child(
20643 Button::new(("restore", row as u64), "Restore")
20644 .tooltip({
20645 let focus_handle = editor.focus_handle(cx);
20646 move |window, cx| {
20647 Tooltip::for_action_in(
20648 "Restore Hunk",
20649 &::git::Restore,
20650 &focus_handle,
20651 window,
20652 cx,
20653 )
20654 }
20655 })
20656 .on_click({
20657 let editor = editor.clone();
20658 move |_event, window, cx| {
20659 editor.update(cx, |editor, cx| {
20660 let snapshot = editor.snapshot(window, cx);
20661 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20662 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20663 });
20664 }
20665 })
20666 .disabled(is_created_file),
20667 )
20668 .when(
20669 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20670 |el| {
20671 el.child(
20672 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20673 .shape(IconButtonShape::Square)
20674 .icon_size(IconSize::Small)
20675 // .disabled(!has_multiple_hunks)
20676 .tooltip({
20677 let focus_handle = editor.focus_handle(cx);
20678 move |window, cx| {
20679 Tooltip::for_action_in(
20680 "Next Hunk",
20681 &GoToHunk,
20682 &focus_handle,
20683 window,
20684 cx,
20685 )
20686 }
20687 })
20688 .on_click({
20689 let editor = editor.clone();
20690 move |_event, window, cx| {
20691 editor.update(cx, |editor, cx| {
20692 let snapshot = editor.snapshot(window, cx);
20693 let position =
20694 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20695 editor.go_to_hunk_before_or_after_position(
20696 &snapshot,
20697 position,
20698 Direction::Next,
20699 window,
20700 cx,
20701 );
20702 editor.expand_selected_diff_hunks(cx);
20703 });
20704 }
20705 }),
20706 )
20707 .child(
20708 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20709 .shape(IconButtonShape::Square)
20710 .icon_size(IconSize::Small)
20711 // .disabled(!has_multiple_hunks)
20712 .tooltip({
20713 let focus_handle = editor.focus_handle(cx);
20714 move |window, cx| {
20715 Tooltip::for_action_in(
20716 "Previous Hunk",
20717 &GoToPreviousHunk,
20718 &focus_handle,
20719 window,
20720 cx,
20721 )
20722 }
20723 })
20724 .on_click({
20725 let editor = editor.clone();
20726 move |_event, window, cx| {
20727 editor.update(cx, |editor, cx| {
20728 let snapshot = editor.snapshot(window, cx);
20729 let point =
20730 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20731 editor.go_to_hunk_before_or_after_position(
20732 &snapshot,
20733 point,
20734 Direction::Prev,
20735 window,
20736 cx,
20737 );
20738 editor.expand_selected_diff_hunks(cx);
20739 });
20740 }
20741 }),
20742 )
20743 },
20744 )
20745 .into_any_element()
20746}