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
1266 }
1267
1268 pub fn new(
1269 mode: EditorMode,
1270 buffer: Entity<MultiBuffer>,
1271 project: Option<Entity<Project>>,
1272 window: &mut Window,
1273 cx: &mut Context<Self>,
1274 ) -> Self {
1275 let style = window.text_style();
1276 let font_size = style.font_size.to_pixels(window.rem_size());
1277 let editor = cx.entity().downgrade();
1278 let fold_placeholder = FoldPlaceholder {
1279 constrain_width: true,
1280 render: Arc::new(move |fold_id, fold_range, cx| {
1281 let editor = editor.clone();
1282 div()
1283 .id(fold_id)
1284 .bg(cx.theme().colors().ghost_element_background)
1285 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1286 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1287 .rounded_xs()
1288 .size_full()
1289 .cursor_pointer()
1290 .child("⋯")
1291 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1292 .on_click(move |_, _window, cx| {
1293 editor
1294 .update(cx, |editor, cx| {
1295 editor.unfold_ranges(
1296 &[fold_range.start..fold_range.end],
1297 true,
1298 false,
1299 cx,
1300 );
1301 cx.stop_propagation();
1302 })
1303 .ok();
1304 })
1305 .into_any()
1306 }),
1307 merge_adjacent: true,
1308 ..Default::default()
1309 };
1310 let display_map = cx.new(|cx| {
1311 DisplayMap::new(
1312 buffer.clone(),
1313 style.font(),
1314 font_size,
1315 None,
1316 FILE_HEADER_HEIGHT,
1317 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1318 fold_placeholder,
1319 cx,
1320 )
1321 });
1322
1323 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1324
1325 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1326
1327 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1328 .then(|| language_settings::SoftWrap::None);
1329
1330 let mut project_subscriptions = Vec::new();
1331 if mode == EditorMode::Full {
1332 if let Some(project) = project.as_ref() {
1333 project_subscriptions.push(cx.subscribe_in(
1334 project,
1335 window,
1336 |editor, _, event, window, cx| match event {
1337 project::Event::RefreshCodeLens => {
1338 // we always query lens with actions, without storing them, always refreshing them
1339 }
1340 project::Event::RefreshInlayHints => {
1341 editor
1342 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1343 }
1344 project::Event::SnippetEdit(id, snippet_edits) => {
1345 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1346 let focus_handle = editor.focus_handle(cx);
1347 if focus_handle.is_focused(window) {
1348 let snapshot = buffer.read(cx).snapshot();
1349 for (range, snippet) in snippet_edits {
1350 let editor_range =
1351 language::range_from_lsp(*range).to_offset(&snapshot);
1352 editor
1353 .insert_snippet(
1354 &[editor_range],
1355 snippet.clone(),
1356 window,
1357 cx,
1358 )
1359 .ok();
1360 }
1361 }
1362 }
1363 }
1364 _ => {}
1365 },
1366 ));
1367 if let Some(task_inventory) = project
1368 .read(cx)
1369 .task_store()
1370 .read(cx)
1371 .task_inventory()
1372 .cloned()
1373 {
1374 project_subscriptions.push(cx.observe_in(
1375 &task_inventory,
1376 window,
1377 |editor, _, window, cx| {
1378 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1379 },
1380 ));
1381 };
1382
1383 project_subscriptions.push(cx.subscribe_in(
1384 &project.read(cx).breakpoint_store(),
1385 window,
1386 |editor, _, event, window, cx| match event {
1387 BreakpointStoreEvent::ActiveDebugLineChanged => {
1388 if editor.go_to_active_debug_line(window, cx) {
1389 cx.stop_propagation();
1390 }
1391 }
1392 _ => {}
1393 },
1394 ));
1395 }
1396 }
1397
1398 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1399
1400 let inlay_hint_settings =
1401 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1402 let focus_handle = cx.focus_handle();
1403 cx.on_focus(&focus_handle, window, Self::handle_focus)
1404 .detach();
1405 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1406 .detach();
1407 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1408 .detach();
1409 cx.on_blur(&focus_handle, window, Self::handle_blur)
1410 .detach();
1411
1412 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1413 Some(false)
1414 } else {
1415 None
1416 };
1417
1418 let breakpoint_store = match (mode, project.as_ref()) {
1419 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1420 _ => None,
1421 };
1422
1423 let mut code_action_providers = Vec::new();
1424 let mut load_uncommitted_diff = None;
1425 if let Some(project) = project.clone() {
1426 load_uncommitted_diff = Some(
1427 get_uncommitted_diff_for_buffer(
1428 &project,
1429 buffer.read(cx).all_buffers(),
1430 buffer.clone(),
1431 cx,
1432 )
1433 .shared(),
1434 );
1435 code_action_providers.push(Rc::new(project) as Rc<_>);
1436 }
1437
1438 let mut this = Self {
1439 focus_handle,
1440 show_cursor_when_unfocused: false,
1441 last_focused_descendant: None,
1442 buffer: buffer.clone(),
1443 display_map: display_map.clone(),
1444 selections,
1445 scroll_manager: ScrollManager::new(cx),
1446 columnar_selection_tail: None,
1447 add_selections_state: None,
1448 select_next_state: None,
1449 select_prev_state: None,
1450 selection_history: Default::default(),
1451 autoclose_regions: Default::default(),
1452 snippet_stack: Default::default(),
1453 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1454 ime_transaction: Default::default(),
1455 active_diagnostics: None,
1456 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1457 inline_diagnostics_update: Task::ready(()),
1458 inline_diagnostics: Vec::new(),
1459 soft_wrap_mode_override,
1460 hard_wrap: None,
1461 completion_provider: project.clone().map(|project| Box::new(project) as _),
1462 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1463 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1464 project,
1465 blink_manager: blink_manager.clone(),
1466 show_local_selections: true,
1467 show_scrollbars: true,
1468 mode,
1469 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1470 show_gutter: mode == EditorMode::Full,
1471 show_line_numbers: None,
1472 use_relative_line_numbers: None,
1473 show_git_diff_gutter: None,
1474 show_code_actions: None,
1475 show_runnables: None,
1476 show_breakpoints: None,
1477 show_wrap_guides: None,
1478 show_indent_guides,
1479 placeholder_text: None,
1480 highlight_order: 0,
1481 highlighted_rows: HashMap::default(),
1482 background_highlights: Default::default(),
1483 gutter_highlights: TreeMap::default(),
1484 scrollbar_marker_state: ScrollbarMarkerState::default(),
1485 active_indent_guides_state: ActiveIndentGuidesState::default(),
1486 nav_history: None,
1487 context_menu: RefCell::new(None),
1488 context_menu_options: None,
1489 mouse_context_menu: None,
1490 completion_tasks: Default::default(),
1491 signature_help_state: SignatureHelpState::default(),
1492 auto_signature_help: None,
1493 find_all_references_task_sources: Vec::new(),
1494 next_completion_id: 0,
1495 next_inlay_id: 0,
1496 code_action_providers,
1497 available_code_actions: Default::default(),
1498 code_actions_task: Default::default(),
1499 selection_highlight_task: Default::default(),
1500 document_highlights_task: Default::default(),
1501 linked_editing_range_task: Default::default(),
1502 pending_rename: Default::default(),
1503 searchable: true,
1504 cursor_shape: EditorSettings::get_global(cx)
1505 .cursor_shape
1506 .unwrap_or_default(),
1507 current_line_highlight: None,
1508 autoindent_mode: Some(AutoindentMode::EachLine),
1509 collapse_matches: false,
1510 workspace: None,
1511 input_enabled: true,
1512 use_modal_editing: mode == EditorMode::Full,
1513 read_only: false,
1514 use_autoclose: true,
1515 use_auto_surround: true,
1516 auto_replace_emoji_shortcode: false,
1517 jsx_tag_auto_close_enabled_in_any_buffer: false,
1518 leader_peer_id: None,
1519 remote_id: None,
1520 hover_state: Default::default(),
1521 pending_mouse_down: None,
1522 hovered_link_state: Default::default(),
1523 edit_prediction_provider: None,
1524 active_inline_completion: None,
1525 stale_inline_completion_in_menu: None,
1526 edit_prediction_preview: EditPredictionPreview::Inactive {
1527 released_too_fast: false,
1528 },
1529 inline_diagnostics_enabled: mode == EditorMode::Full,
1530 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1531
1532 gutter_hovered: false,
1533 pixel_position_of_newest_cursor: None,
1534 last_bounds: None,
1535 last_position_map: None,
1536 expect_bounds_change: None,
1537 gutter_dimensions: GutterDimensions::default(),
1538 style: None,
1539 show_cursor_names: false,
1540 hovered_cursors: Default::default(),
1541 next_editor_action_id: EditorActionId::default(),
1542 editor_actions: Rc::default(),
1543 inline_completions_hidden_for_vim_mode: false,
1544 show_inline_completions_override: None,
1545 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1546 edit_prediction_settings: EditPredictionSettings::Disabled,
1547 edit_prediction_indent_conflict: false,
1548 edit_prediction_requires_modifier_in_indent_conflict: true,
1549 custom_context_menu: None,
1550 show_git_blame_gutter: false,
1551 show_git_blame_inline: false,
1552 show_selection_menu: None,
1553 show_git_blame_inline_delay_task: None,
1554 git_blame_inline_tooltip: None,
1555 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1556 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1557 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1558 .session
1559 .restore_unsaved_buffers,
1560 blame: None,
1561 blame_subscription: None,
1562 tasks: Default::default(),
1563
1564 breakpoint_store,
1565 gutter_breakpoint_indicator: (None, None),
1566 _subscriptions: vec![
1567 cx.observe(&buffer, Self::on_buffer_changed),
1568 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1569 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1570 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1571 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1572 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1573 cx.observe_window_activation(window, |editor, window, cx| {
1574 let active = window.is_window_active();
1575 editor.blink_manager.update(cx, |blink_manager, cx| {
1576 if active {
1577 blink_manager.enable(cx);
1578 } else {
1579 blink_manager.disable(cx);
1580 }
1581 });
1582 }),
1583 ],
1584 tasks_update_task: None,
1585 linked_edit_ranges: Default::default(),
1586 in_project_search: false,
1587 previous_search_ranges: None,
1588 breadcrumb_header: None,
1589 focused_block: None,
1590 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1591 addons: HashMap::default(),
1592 registered_buffers: HashMap::default(),
1593 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1594 selection_mark_mode: false,
1595 toggle_fold_multiple_buffers: Task::ready(()),
1596 serialize_selections: Task::ready(()),
1597 serialize_folds: Task::ready(()),
1598 text_style_refinement: None,
1599 load_diff_task: load_uncommitted_diff,
1600 mouse_cursor_hidden: false,
1601 hide_mouse_mode: EditorSettings::get_global(cx)
1602 .hide_mouse
1603 .unwrap_or_default(),
1604 };
1605 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1606 this._subscriptions
1607 .push(cx.observe(breakpoints, |_, _, cx| {
1608 cx.notify();
1609 }));
1610 }
1611 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1612 this._subscriptions.extend(project_subscriptions);
1613
1614 this._subscriptions.push(cx.subscribe_in(
1615 &cx.entity(),
1616 window,
1617 |editor, _, e: &EditorEvent, window, cx| {
1618 if let EditorEvent::SelectionsChanged { local } = e {
1619 if *local {
1620 let new_anchor = editor.scroll_manager.anchor();
1621 let snapshot = editor.snapshot(window, cx);
1622 editor.update_restoration_data(cx, move |data| {
1623 data.scroll_position = (
1624 new_anchor.top_row(&snapshot.buffer_snapshot),
1625 new_anchor.offset,
1626 );
1627 });
1628 }
1629 }
1630 },
1631 ));
1632
1633 this.end_selection(window, cx);
1634 this.scroll_manager.show_scrollbars(window, cx);
1635 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1636
1637 if mode == EditorMode::Full {
1638 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1639 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1640
1641 if this.git_blame_inline_enabled {
1642 this.git_blame_inline_enabled = true;
1643 this.start_git_blame_inline(false, window, cx);
1644 }
1645
1646 this.go_to_active_debug_line(window, cx);
1647
1648 if let Some(buffer) = buffer.read(cx).as_singleton() {
1649 if let Some(project) = this.project.as_ref() {
1650 let handle = project.update(cx, |project, cx| {
1651 project.register_buffer_with_language_servers(&buffer, cx)
1652 });
1653 this.registered_buffers
1654 .insert(buffer.read(cx).remote_id(), handle);
1655 }
1656 }
1657 }
1658
1659 this.report_editor_event("Editor Opened", None, cx);
1660 this
1661 }
1662
1663 pub fn deploy_mouse_context_menu(
1664 &mut self,
1665 position: gpui::Point<Pixels>,
1666 context_menu: Entity<ContextMenu>,
1667 window: &mut Window,
1668 cx: &mut Context<Self>,
1669 ) {
1670 self.mouse_context_menu = Some(MouseContextMenu::new(
1671 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1672 context_menu,
1673 window,
1674 cx,
1675 ));
1676 }
1677
1678 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1679 self.mouse_context_menu
1680 .as_ref()
1681 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1682 }
1683
1684 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1685 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1686 }
1687
1688 fn key_context_internal(
1689 &self,
1690 has_active_edit_prediction: bool,
1691 window: &Window,
1692 cx: &App,
1693 ) -> KeyContext {
1694 let mut key_context = KeyContext::new_with_defaults();
1695 key_context.add("Editor");
1696 let mode = match self.mode {
1697 EditorMode::SingleLine { .. } => "single_line",
1698 EditorMode::AutoHeight { .. } => "auto_height",
1699 EditorMode::Full => "full",
1700 };
1701
1702 if EditorSettings::jupyter_enabled(cx) {
1703 key_context.add("jupyter");
1704 }
1705
1706 key_context.set("mode", mode);
1707 if self.pending_rename.is_some() {
1708 key_context.add("renaming");
1709 }
1710
1711 match self.context_menu.borrow().as_ref() {
1712 Some(CodeContextMenu::Completions(_)) => {
1713 key_context.add("menu");
1714 key_context.add("showing_completions");
1715 }
1716 Some(CodeContextMenu::CodeActions(_)) => {
1717 key_context.add("menu");
1718 key_context.add("showing_code_actions")
1719 }
1720 None => {}
1721 }
1722
1723 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1724 if !self.focus_handle(cx).contains_focused(window, cx)
1725 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1726 {
1727 for addon in self.addons.values() {
1728 addon.extend_key_context(&mut key_context, cx)
1729 }
1730 }
1731
1732 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1733 if let Some(extension) = singleton_buffer
1734 .read(cx)
1735 .file()
1736 .and_then(|file| file.path().extension()?.to_str())
1737 {
1738 key_context.set("extension", extension.to_string());
1739 }
1740 } else {
1741 key_context.add("multibuffer");
1742 }
1743
1744 if has_active_edit_prediction {
1745 if self.edit_prediction_in_conflict() {
1746 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1747 } else {
1748 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1749 key_context.add("copilot_suggestion");
1750 }
1751 }
1752
1753 if self.selection_mark_mode {
1754 key_context.add("selection_mode");
1755 }
1756
1757 key_context
1758 }
1759
1760 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1761 self.mouse_cursor_hidden = match origin {
1762 HideMouseCursorOrigin::TypingAction => {
1763 matches!(
1764 self.hide_mouse_mode,
1765 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1766 )
1767 }
1768 HideMouseCursorOrigin::MovementAction => {
1769 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
1770 }
1771 };
1772 }
1773
1774 pub fn edit_prediction_in_conflict(&self) -> bool {
1775 if !self.show_edit_predictions_in_menu() {
1776 return false;
1777 }
1778
1779 let showing_completions = self
1780 .context_menu
1781 .borrow()
1782 .as_ref()
1783 .map_or(false, |context| {
1784 matches!(context, CodeContextMenu::Completions(_))
1785 });
1786
1787 showing_completions
1788 || self.edit_prediction_requires_modifier()
1789 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1790 // bindings to insert tab characters.
1791 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1792 }
1793
1794 pub fn accept_edit_prediction_keybind(
1795 &self,
1796 window: &Window,
1797 cx: &App,
1798 ) -> AcceptEditPredictionBinding {
1799 let key_context = self.key_context_internal(true, window, cx);
1800 let in_conflict = self.edit_prediction_in_conflict();
1801
1802 AcceptEditPredictionBinding(
1803 window
1804 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1805 .into_iter()
1806 .filter(|binding| {
1807 !in_conflict
1808 || binding
1809 .keystrokes()
1810 .first()
1811 .map_or(false, |keystroke| keystroke.modifiers.modified())
1812 })
1813 .rev()
1814 .min_by_key(|binding| {
1815 binding
1816 .keystrokes()
1817 .first()
1818 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1819 }),
1820 )
1821 }
1822
1823 pub fn new_file(
1824 workspace: &mut Workspace,
1825 _: &workspace::NewFile,
1826 window: &mut Window,
1827 cx: &mut Context<Workspace>,
1828 ) {
1829 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1830 "Failed to create buffer",
1831 window,
1832 cx,
1833 |e, _, _| match e.error_code() {
1834 ErrorCode::RemoteUpgradeRequired => Some(format!(
1835 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1836 e.error_tag("required").unwrap_or("the latest version")
1837 )),
1838 _ => None,
1839 },
1840 );
1841 }
1842
1843 pub fn new_in_workspace(
1844 workspace: &mut Workspace,
1845 window: &mut Window,
1846 cx: &mut Context<Workspace>,
1847 ) -> Task<Result<Entity<Editor>>> {
1848 let project = workspace.project().clone();
1849 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1850
1851 cx.spawn_in(window, async move |workspace, cx| {
1852 let buffer = create.await?;
1853 workspace.update_in(cx, |workspace, window, cx| {
1854 let editor =
1855 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1856 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1857 editor
1858 })
1859 })
1860 }
1861
1862 fn new_file_vertical(
1863 workspace: &mut Workspace,
1864 _: &workspace::NewFileSplitVertical,
1865 window: &mut Window,
1866 cx: &mut Context<Workspace>,
1867 ) {
1868 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1869 }
1870
1871 fn new_file_horizontal(
1872 workspace: &mut Workspace,
1873 _: &workspace::NewFileSplitHorizontal,
1874 window: &mut Window,
1875 cx: &mut Context<Workspace>,
1876 ) {
1877 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1878 }
1879
1880 fn new_file_in_direction(
1881 workspace: &mut Workspace,
1882 direction: SplitDirection,
1883 window: &mut Window,
1884 cx: &mut Context<Workspace>,
1885 ) {
1886 let project = workspace.project().clone();
1887 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1888
1889 cx.spawn_in(window, async move |workspace, cx| {
1890 let buffer = create.await?;
1891 workspace.update_in(cx, move |workspace, window, cx| {
1892 workspace.split_item(
1893 direction,
1894 Box::new(
1895 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1896 ),
1897 window,
1898 cx,
1899 )
1900 })?;
1901 anyhow::Ok(())
1902 })
1903 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1904 match e.error_code() {
1905 ErrorCode::RemoteUpgradeRequired => Some(format!(
1906 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1907 e.error_tag("required").unwrap_or("the latest version")
1908 )),
1909 _ => None,
1910 }
1911 });
1912 }
1913
1914 pub fn leader_peer_id(&self) -> Option<PeerId> {
1915 self.leader_peer_id
1916 }
1917
1918 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1919 &self.buffer
1920 }
1921
1922 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1923 self.workspace.as_ref()?.0.upgrade()
1924 }
1925
1926 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1927 self.buffer().read(cx).title(cx)
1928 }
1929
1930 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1931 let git_blame_gutter_max_author_length = self
1932 .render_git_blame_gutter(cx)
1933 .then(|| {
1934 if let Some(blame) = self.blame.as_ref() {
1935 let max_author_length =
1936 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1937 Some(max_author_length)
1938 } else {
1939 None
1940 }
1941 })
1942 .flatten();
1943
1944 EditorSnapshot {
1945 mode: self.mode,
1946 show_gutter: self.show_gutter,
1947 show_line_numbers: self.show_line_numbers,
1948 show_git_diff_gutter: self.show_git_diff_gutter,
1949 show_code_actions: self.show_code_actions,
1950 show_runnables: self.show_runnables,
1951 show_breakpoints: self.show_breakpoints,
1952 git_blame_gutter_max_author_length,
1953 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1954 scroll_anchor: self.scroll_manager.anchor(),
1955 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1956 placeholder_text: self.placeholder_text.clone(),
1957 is_focused: self.focus_handle.is_focused(window),
1958 current_line_highlight: self
1959 .current_line_highlight
1960 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1961 gutter_hovered: self.gutter_hovered,
1962 }
1963 }
1964
1965 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1966 self.buffer.read(cx).language_at(point, cx)
1967 }
1968
1969 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1970 self.buffer.read(cx).read(cx).file_at(point).cloned()
1971 }
1972
1973 pub fn active_excerpt(
1974 &self,
1975 cx: &App,
1976 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1977 self.buffer
1978 .read(cx)
1979 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1980 }
1981
1982 pub fn mode(&self) -> EditorMode {
1983 self.mode
1984 }
1985
1986 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1987 self.collaboration_hub.as_deref()
1988 }
1989
1990 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1991 self.collaboration_hub = Some(hub);
1992 }
1993
1994 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1995 self.in_project_search = in_project_search;
1996 }
1997
1998 pub fn set_custom_context_menu(
1999 &mut self,
2000 f: impl 'static
2001 + Fn(
2002 &mut Self,
2003 DisplayPoint,
2004 &mut Window,
2005 &mut Context<Self>,
2006 ) -> Option<Entity<ui::ContextMenu>>,
2007 ) {
2008 self.custom_context_menu = Some(Box::new(f))
2009 }
2010
2011 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2012 self.completion_provider = provider;
2013 }
2014
2015 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2016 self.semantics_provider.clone()
2017 }
2018
2019 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2020 self.semantics_provider = provider;
2021 }
2022
2023 pub fn set_edit_prediction_provider<T>(
2024 &mut self,
2025 provider: Option<Entity<T>>,
2026 window: &mut Window,
2027 cx: &mut Context<Self>,
2028 ) where
2029 T: EditPredictionProvider,
2030 {
2031 self.edit_prediction_provider =
2032 provider.map(|provider| RegisteredInlineCompletionProvider {
2033 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2034 if this.focus_handle.is_focused(window) {
2035 this.update_visible_inline_completion(window, cx);
2036 }
2037 }),
2038 provider: Arc::new(provider),
2039 });
2040 self.update_edit_prediction_settings(cx);
2041 self.refresh_inline_completion(false, false, window, cx);
2042 }
2043
2044 pub fn placeholder_text(&self) -> Option<&str> {
2045 self.placeholder_text.as_deref()
2046 }
2047
2048 pub fn set_placeholder_text(
2049 &mut self,
2050 placeholder_text: impl Into<Arc<str>>,
2051 cx: &mut Context<Self>,
2052 ) {
2053 let placeholder_text = Some(placeholder_text.into());
2054 if self.placeholder_text != placeholder_text {
2055 self.placeholder_text = placeholder_text;
2056 cx.notify();
2057 }
2058 }
2059
2060 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2061 self.cursor_shape = cursor_shape;
2062
2063 // Disrupt blink for immediate user feedback that the cursor shape has changed
2064 self.blink_manager.update(cx, BlinkManager::show_cursor);
2065
2066 cx.notify();
2067 }
2068
2069 pub fn set_current_line_highlight(
2070 &mut self,
2071 current_line_highlight: Option<CurrentLineHighlight>,
2072 ) {
2073 self.current_line_highlight = current_line_highlight;
2074 }
2075
2076 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2077 self.collapse_matches = collapse_matches;
2078 }
2079
2080 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2081 let buffers = self.buffer.read(cx).all_buffers();
2082 let Some(project) = self.project.as_ref() else {
2083 return;
2084 };
2085 project.update(cx, |project, cx| {
2086 for buffer in buffers {
2087 self.registered_buffers
2088 .entry(buffer.read(cx).remote_id())
2089 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2090 }
2091 })
2092 }
2093
2094 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2095 if self.collapse_matches {
2096 return range.start..range.start;
2097 }
2098 range.clone()
2099 }
2100
2101 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2102 if self.display_map.read(cx).clip_at_line_ends != clip {
2103 self.display_map
2104 .update(cx, |map, _| map.clip_at_line_ends = clip);
2105 }
2106 }
2107
2108 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2109 self.input_enabled = input_enabled;
2110 }
2111
2112 pub fn set_inline_completions_hidden_for_vim_mode(
2113 &mut self,
2114 hidden: bool,
2115 window: &mut Window,
2116 cx: &mut Context<Self>,
2117 ) {
2118 if hidden != self.inline_completions_hidden_for_vim_mode {
2119 self.inline_completions_hidden_for_vim_mode = hidden;
2120 if hidden {
2121 self.update_visible_inline_completion(window, cx);
2122 } else {
2123 self.refresh_inline_completion(true, false, window, cx);
2124 }
2125 }
2126 }
2127
2128 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2129 self.menu_inline_completions_policy = value;
2130 }
2131
2132 pub fn set_autoindent(&mut self, autoindent: bool) {
2133 if autoindent {
2134 self.autoindent_mode = Some(AutoindentMode::EachLine);
2135 } else {
2136 self.autoindent_mode = None;
2137 }
2138 }
2139
2140 pub fn read_only(&self, cx: &App) -> bool {
2141 self.read_only || self.buffer.read(cx).read_only()
2142 }
2143
2144 pub fn set_read_only(&mut self, read_only: bool) {
2145 self.read_only = read_only;
2146 }
2147
2148 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2149 self.use_autoclose = autoclose;
2150 }
2151
2152 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2153 self.use_auto_surround = auto_surround;
2154 }
2155
2156 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2157 self.auto_replace_emoji_shortcode = auto_replace;
2158 }
2159
2160 pub fn toggle_edit_predictions(
2161 &mut self,
2162 _: &ToggleEditPrediction,
2163 window: &mut Window,
2164 cx: &mut Context<Self>,
2165 ) {
2166 if self.show_inline_completions_override.is_some() {
2167 self.set_show_edit_predictions(None, window, cx);
2168 } else {
2169 let show_edit_predictions = !self.edit_predictions_enabled();
2170 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2171 }
2172 }
2173
2174 pub fn set_show_edit_predictions(
2175 &mut self,
2176 show_edit_predictions: Option<bool>,
2177 window: &mut Window,
2178 cx: &mut Context<Self>,
2179 ) {
2180 self.show_inline_completions_override = show_edit_predictions;
2181 self.update_edit_prediction_settings(cx);
2182
2183 if let Some(false) = show_edit_predictions {
2184 self.discard_inline_completion(false, cx);
2185 } else {
2186 self.refresh_inline_completion(false, true, window, cx);
2187 }
2188 }
2189
2190 fn inline_completions_disabled_in_scope(
2191 &self,
2192 buffer: &Entity<Buffer>,
2193 buffer_position: language::Anchor,
2194 cx: &App,
2195 ) -> bool {
2196 let snapshot = buffer.read(cx).snapshot();
2197 let settings = snapshot.settings_at(buffer_position, cx);
2198
2199 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2200 return false;
2201 };
2202
2203 scope.override_name().map_or(false, |scope_name| {
2204 settings
2205 .edit_predictions_disabled_in
2206 .iter()
2207 .any(|s| s == scope_name)
2208 })
2209 }
2210
2211 pub fn set_use_modal_editing(&mut self, to: bool) {
2212 self.use_modal_editing = to;
2213 }
2214
2215 pub fn use_modal_editing(&self) -> bool {
2216 self.use_modal_editing
2217 }
2218
2219 fn selections_did_change(
2220 &mut self,
2221 local: bool,
2222 old_cursor_position: &Anchor,
2223 show_completions: bool,
2224 window: &mut Window,
2225 cx: &mut Context<Self>,
2226 ) {
2227 window.invalidate_character_coordinates();
2228
2229 // Copy selections to primary selection buffer
2230 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2231 if local {
2232 let selections = self.selections.all::<usize>(cx);
2233 let buffer_handle = self.buffer.read(cx).read(cx);
2234
2235 let mut text = String::new();
2236 for (index, selection) in selections.iter().enumerate() {
2237 let text_for_selection = buffer_handle
2238 .text_for_range(selection.start..selection.end)
2239 .collect::<String>();
2240
2241 text.push_str(&text_for_selection);
2242 if index != selections.len() - 1 {
2243 text.push('\n');
2244 }
2245 }
2246
2247 if !text.is_empty() {
2248 cx.write_to_primary(ClipboardItem::new_string(text));
2249 }
2250 }
2251
2252 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2253 self.buffer.update(cx, |buffer, cx| {
2254 buffer.set_active_selections(
2255 &self.selections.disjoint_anchors(),
2256 self.selections.line_mode,
2257 self.cursor_shape,
2258 cx,
2259 )
2260 });
2261 }
2262 let display_map = self
2263 .display_map
2264 .update(cx, |display_map, cx| display_map.snapshot(cx));
2265 let buffer = &display_map.buffer_snapshot;
2266 self.add_selections_state = None;
2267 self.select_next_state = None;
2268 self.select_prev_state = None;
2269 self.select_syntax_node_history.try_clear();
2270 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2271 self.snippet_stack
2272 .invalidate(&self.selections.disjoint_anchors(), buffer);
2273 self.take_rename(false, window, cx);
2274
2275 let new_cursor_position = self.selections.newest_anchor().head();
2276
2277 self.push_to_nav_history(
2278 *old_cursor_position,
2279 Some(new_cursor_position.to_point(buffer)),
2280 false,
2281 cx,
2282 );
2283
2284 if local {
2285 let new_cursor_position = self.selections.newest_anchor().head();
2286 let mut context_menu = self.context_menu.borrow_mut();
2287 let completion_menu = match context_menu.as_ref() {
2288 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2289 _ => {
2290 *context_menu = None;
2291 None
2292 }
2293 };
2294 if let Some(buffer_id) = new_cursor_position.buffer_id {
2295 if !self.registered_buffers.contains_key(&buffer_id) {
2296 if let Some(project) = self.project.as_ref() {
2297 project.update(cx, |project, cx| {
2298 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2299 return;
2300 };
2301 self.registered_buffers.insert(
2302 buffer_id,
2303 project.register_buffer_with_language_servers(&buffer, cx),
2304 );
2305 })
2306 }
2307 }
2308 }
2309
2310 if let Some(completion_menu) = completion_menu {
2311 let cursor_position = new_cursor_position.to_offset(buffer);
2312 let (word_range, kind) =
2313 buffer.surrounding_word(completion_menu.initial_position, true);
2314 if kind == Some(CharKind::Word)
2315 && word_range.to_inclusive().contains(&cursor_position)
2316 {
2317 let mut completion_menu = completion_menu.clone();
2318 drop(context_menu);
2319
2320 let query = Self::completion_query(buffer, cursor_position);
2321 cx.spawn(async move |this, cx| {
2322 completion_menu
2323 .filter(query.as_deref(), cx.background_executor().clone())
2324 .await;
2325
2326 this.update(cx, |this, cx| {
2327 let mut context_menu = this.context_menu.borrow_mut();
2328 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2329 else {
2330 return;
2331 };
2332
2333 if menu.id > completion_menu.id {
2334 return;
2335 }
2336
2337 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2338 drop(context_menu);
2339 cx.notify();
2340 })
2341 })
2342 .detach();
2343
2344 if show_completions {
2345 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2346 }
2347 } else {
2348 drop(context_menu);
2349 self.hide_context_menu(window, cx);
2350 }
2351 } else {
2352 drop(context_menu);
2353 }
2354
2355 hide_hover(self, cx);
2356
2357 if old_cursor_position.to_display_point(&display_map).row()
2358 != new_cursor_position.to_display_point(&display_map).row()
2359 {
2360 self.available_code_actions.take();
2361 }
2362 self.refresh_code_actions(window, cx);
2363 self.refresh_document_highlights(cx);
2364 self.refresh_selected_text_highlights(window, cx);
2365 refresh_matching_bracket_highlights(self, window, cx);
2366 self.update_visible_inline_completion(window, cx);
2367 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2368 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2369 if self.git_blame_inline_enabled {
2370 self.start_inline_blame_timer(window, cx);
2371 }
2372 }
2373
2374 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2375 cx.emit(EditorEvent::SelectionsChanged { local });
2376
2377 let selections = &self.selections.disjoint;
2378 if selections.len() == 1 {
2379 cx.emit(SearchEvent::ActiveMatchChanged)
2380 }
2381 if local {
2382 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2383 let inmemory_selections = selections
2384 .iter()
2385 .map(|s| {
2386 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2387 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2388 })
2389 .collect();
2390 self.update_restoration_data(cx, |data| {
2391 data.selections = inmemory_selections;
2392 });
2393
2394 if WorkspaceSettings::get(None, cx).restore_on_startup
2395 != RestoreOnStartupBehavior::None
2396 {
2397 if let Some(workspace_id) =
2398 self.workspace.as_ref().and_then(|workspace| workspace.1)
2399 {
2400 let snapshot = self.buffer().read(cx).snapshot(cx);
2401 let selections = selections.clone();
2402 let background_executor = cx.background_executor().clone();
2403 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2404 self.serialize_selections = cx.background_spawn(async move {
2405 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2406 let db_selections = selections
2407 .iter()
2408 .map(|selection| {
2409 (
2410 selection.start.to_offset(&snapshot),
2411 selection.end.to_offset(&snapshot),
2412 )
2413 })
2414 .collect();
2415
2416 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2417 .await
2418 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2419 .log_err();
2420 });
2421 }
2422 }
2423 }
2424 }
2425
2426 cx.notify();
2427 }
2428
2429 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2430 use text::ToOffset as _;
2431 use text::ToPoint as _;
2432
2433 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2434 return;
2435 }
2436
2437 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2438 return;
2439 };
2440
2441 let snapshot = singleton.read(cx).snapshot();
2442 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2443 let display_snapshot = display_map.snapshot(cx);
2444
2445 display_snapshot
2446 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2447 .map(|fold| {
2448 fold.range.start.text_anchor.to_point(&snapshot)
2449 ..fold.range.end.text_anchor.to_point(&snapshot)
2450 })
2451 .collect()
2452 });
2453 self.update_restoration_data(cx, |data| {
2454 data.folds = inmemory_folds;
2455 });
2456
2457 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2458 return;
2459 };
2460 let background_executor = cx.background_executor().clone();
2461 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2462 let db_folds = self.display_map.update(cx, |display_map, cx| {
2463 display_map
2464 .snapshot(cx)
2465 .folds_in_range(0..snapshot.len())
2466 .map(|fold| {
2467 (
2468 fold.range.start.text_anchor.to_offset(&snapshot),
2469 fold.range.end.text_anchor.to_offset(&snapshot),
2470 )
2471 })
2472 .collect()
2473 });
2474 self.serialize_folds = cx.background_spawn(async move {
2475 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2476 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2477 .await
2478 .with_context(|| {
2479 format!(
2480 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2481 )
2482 })
2483 .log_err();
2484 });
2485 }
2486
2487 pub fn sync_selections(
2488 &mut self,
2489 other: Entity<Editor>,
2490 cx: &mut Context<Self>,
2491 ) -> gpui::Subscription {
2492 let other_selections = other.read(cx).selections.disjoint.to_vec();
2493 self.selections.change_with(cx, |selections| {
2494 selections.select_anchors(other_selections);
2495 });
2496
2497 let other_subscription =
2498 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2499 EditorEvent::SelectionsChanged { local: true } => {
2500 let other_selections = other.read(cx).selections.disjoint.to_vec();
2501 if other_selections.is_empty() {
2502 return;
2503 }
2504 this.selections.change_with(cx, |selections| {
2505 selections.select_anchors(other_selections);
2506 });
2507 }
2508 _ => {}
2509 });
2510
2511 let this_subscription =
2512 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2513 EditorEvent::SelectionsChanged { local: true } => {
2514 let these_selections = this.selections.disjoint.to_vec();
2515 if these_selections.is_empty() {
2516 return;
2517 }
2518 other.update(cx, |other_editor, cx| {
2519 other_editor.selections.change_with(cx, |selections| {
2520 selections.select_anchors(these_selections);
2521 })
2522 });
2523 }
2524 _ => {}
2525 });
2526
2527 Subscription::join(other_subscription, this_subscription)
2528 }
2529
2530 pub fn change_selections<R>(
2531 &mut self,
2532 autoscroll: Option<Autoscroll>,
2533 window: &mut Window,
2534 cx: &mut Context<Self>,
2535 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2536 ) -> R {
2537 self.change_selections_inner(autoscroll, true, window, cx, change)
2538 }
2539
2540 fn change_selections_inner<R>(
2541 &mut self,
2542 autoscroll: Option<Autoscroll>,
2543 request_completions: bool,
2544 window: &mut Window,
2545 cx: &mut Context<Self>,
2546 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2547 ) -> R {
2548 let old_cursor_position = self.selections.newest_anchor().head();
2549 self.push_to_selection_history();
2550
2551 let (changed, result) = self.selections.change_with(cx, change);
2552
2553 if changed {
2554 if let Some(autoscroll) = autoscroll {
2555 self.request_autoscroll(autoscroll, cx);
2556 }
2557 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2558
2559 if self.should_open_signature_help_automatically(
2560 &old_cursor_position,
2561 self.signature_help_state.backspace_pressed(),
2562 cx,
2563 ) {
2564 self.show_signature_help(&ShowSignatureHelp, window, cx);
2565 }
2566 self.signature_help_state.set_backspace_pressed(false);
2567 }
2568
2569 result
2570 }
2571
2572 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2573 where
2574 I: IntoIterator<Item = (Range<S>, T)>,
2575 S: ToOffset,
2576 T: Into<Arc<str>>,
2577 {
2578 if self.read_only(cx) {
2579 return;
2580 }
2581
2582 self.buffer
2583 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2584 }
2585
2586 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2587 where
2588 I: IntoIterator<Item = (Range<S>, T)>,
2589 S: ToOffset,
2590 T: Into<Arc<str>>,
2591 {
2592 if self.read_only(cx) {
2593 return;
2594 }
2595
2596 self.buffer.update(cx, |buffer, cx| {
2597 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2598 });
2599 }
2600
2601 pub fn edit_with_block_indent<I, S, T>(
2602 &mut self,
2603 edits: I,
2604 original_indent_columns: Vec<Option<u32>>,
2605 cx: &mut Context<Self>,
2606 ) where
2607 I: IntoIterator<Item = (Range<S>, T)>,
2608 S: ToOffset,
2609 T: Into<Arc<str>>,
2610 {
2611 if self.read_only(cx) {
2612 return;
2613 }
2614
2615 self.buffer.update(cx, |buffer, cx| {
2616 buffer.edit(
2617 edits,
2618 Some(AutoindentMode::Block {
2619 original_indent_columns,
2620 }),
2621 cx,
2622 )
2623 });
2624 }
2625
2626 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2627 self.hide_context_menu(window, cx);
2628
2629 match phase {
2630 SelectPhase::Begin {
2631 position,
2632 add,
2633 click_count,
2634 } => self.begin_selection(position, add, click_count, window, cx),
2635 SelectPhase::BeginColumnar {
2636 position,
2637 goal_column,
2638 reset,
2639 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2640 SelectPhase::Extend {
2641 position,
2642 click_count,
2643 } => self.extend_selection(position, click_count, window, cx),
2644 SelectPhase::Update {
2645 position,
2646 goal_column,
2647 scroll_delta,
2648 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2649 SelectPhase::End => self.end_selection(window, cx),
2650 }
2651 }
2652
2653 fn extend_selection(
2654 &mut self,
2655 position: DisplayPoint,
2656 click_count: usize,
2657 window: &mut Window,
2658 cx: &mut Context<Self>,
2659 ) {
2660 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2661 let tail = self.selections.newest::<usize>(cx).tail();
2662 self.begin_selection(position, false, click_count, window, cx);
2663
2664 let position = position.to_offset(&display_map, Bias::Left);
2665 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2666
2667 let mut pending_selection = self
2668 .selections
2669 .pending_anchor()
2670 .expect("extend_selection not called with pending selection");
2671 if position >= tail {
2672 pending_selection.start = tail_anchor;
2673 } else {
2674 pending_selection.end = tail_anchor;
2675 pending_selection.reversed = true;
2676 }
2677
2678 let mut pending_mode = self.selections.pending_mode().unwrap();
2679 match &mut pending_mode {
2680 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2681 _ => {}
2682 }
2683
2684 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2685 s.set_pending(pending_selection, pending_mode)
2686 });
2687 }
2688
2689 fn begin_selection(
2690 &mut self,
2691 position: DisplayPoint,
2692 add: bool,
2693 click_count: usize,
2694 window: &mut Window,
2695 cx: &mut Context<Self>,
2696 ) {
2697 if !self.focus_handle.is_focused(window) {
2698 self.last_focused_descendant = None;
2699 window.focus(&self.focus_handle);
2700 }
2701
2702 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2703 let buffer = &display_map.buffer_snapshot;
2704 let newest_selection = self.selections.newest_anchor().clone();
2705 let position = display_map.clip_point(position, Bias::Left);
2706
2707 let start;
2708 let end;
2709 let mode;
2710 let mut auto_scroll;
2711 match click_count {
2712 1 => {
2713 start = buffer.anchor_before(position.to_point(&display_map));
2714 end = start;
2715 mode = SelectMode::Character;
2716 auto_scroll = true;
2717 }
2718 2 => {
2719 let range = movement::surrounding_word(&display_map, position);
2720 start = buffer.anchor_before(range.start.to_point(&display_map));
2721 end = buffer.anchor_before(range.end.to_point(&display_map));
2722 mode = SelectMode::Word(start..end);
2723 auto_scroll = true;
2724 }
2725 3 => {
2726 let position = display_map
2727 .clip_point(position, Bias::Left)
2728 .to_point(&display_map);
2729 let line_start = display_map.prev_line_boundary(position).0;
2730 let next_line_start = buffer.clip_point(
2731 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2732 Bias::Left,
2733 );
2734 start = buffer.anchor_before(line_start);
2735 end = buffer.anchor_before(next_line_start);
2736 mode = SelectMode::Line(start..end);
2737 auto_scroll = true;
2738 }
2739 _ => {
2740 start = buffer.anchor_before(0);
2741 end = buffer.anchor_before(buffer.len());
2742 mode = SelectMode::All;
2743 auto_scroll = false;
2744 }
2745 }
2746 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2747
2748 let point_to_delete: Option<usize> = {
2749 let selected_points: Vec<Selection<Point>> =
2750 self.selections.disjoint_in_range(start..end, cx);
2751
2752 if !add || click_count > 1 {
2753 None
2754 } else if !selected_points.is_empty() {
2755 Some(selected_points[0].id)
2756 } else {
2757 let clicked_point_already_selected =
2758 self.selections.disjoint.iter().find(|selection| {
2759 selection.start.to_point(buffer) == start.to_point(buffer)
2760 || selection.end.to_point(buffer) == end.to_point(buffer)
2761 });
2762
2763 clicked_point_already_selected.map(|selection| selection.id)
2764 }
2765 };
2766
2767 let selections_count = self.selections.count();
2768
2769 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2770 if let Some(point_to_delete) = point_to_delete {
2771 s.delete(point_to_delete);
2772
2773 if selections_count == 1 {
2774 s.set_pending_anchor_range(start..end, mode);
2775 }
2776 } else {
2777 if !add {
2778 s.clear_disjoint();
2779 } else if click_count > 1 {
2780 s.delete(newest_selection.id)
2781 }
2782
2783 s.set_pending_anchor_range(start..end, mode);
2784 }
2785 });
2786 }
2787
2788 fn begin_columnar_selection(
2789 &mut self,
2790 position: DisplayPoint,
2791 goal_column: u32,
2792 reset: bool,
2793 window: &mut Window,
2794 cx: &mut Context<Self>,
2795 ) {
2796 if !self.focus_handle.is_focused(window) {
2797 self.last_focused_descendant = None;
2798 window.focus(&self.focus_handle);
2799 }
2800
2801 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2802
2803 if reset {
2804 let pointer_position = display_map
2805 .buffer_snapshot
2806 .anchor_before(position.to_point(&display_map));
2807
2808 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2809 s.clear_disjoint();
2810 s.set_pending_anchor_range(
2811 pointer_position..pointer_position,
2812 SelectMode::Character,
2813 );
2814 });
2815 }
2816
2817 let tail = self.selections.newest::<Point>(cx).tail();
2818 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2819
2820 if !reset {
2821 self.select_columns(
2822 tail.to_display_point(&display_map),
2823 position,
2824 goal_column,
2825 &display_map,
2826 window,
2827 cx,
2828 );
2829 }
2830 }
2831
2832 fn update_selection(
2833 &mut self,
2834 position: DisplayPoint,
2835 goal_column: u32,
2836 scroll_delta: gpui::Point<f32>,
2837 window: &mut Window,
2838 cx: &mut Context<Self>,
2839 ) {
2840 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2841
2842 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2843 let tail = tail.to_display_point(&display_map);
2844 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2845 } else if let Some(mut pending) = self.selections.pending_anchor() {
2846 let buffer = self.buffer.read(cx).snapshot(cx);
2847 let head;
2848 let tail;
2849 let mode = self.selections.pending_mode().unwrap();
2850 match &mode {
2851 SelectMode::Character => {
2852 head = position.to_point(&display_map);
2853 tail = pending.tail().to_point(&buffer);
2854 }
2855 SelectMode::Word(original_range) => {
2856 let original_display_range = original_range.start.to_display_point(&display_map)
2857 ..original_range.end.to_display_point(&display_map);
2858 let original_buffer_range = original_display_range.start.to_point(&display_map)
2859 ..original_display_range.end.to_point(&display_map);
2860 if movement::is_inside_word(&display_map, position)
2861 || original_display_range.contains(&position)
2862 {
2863 let word_range = movement::surrounding_word(&display_map, position);
2864 if word_range.start < original_display_range.start {
2865 head = word_range.start.to_point(&display_map);
2866 } else {
2867 head = word_range.end.to_point(&display_map);
2868 }
2869 } else {
2870 head = position.to_point(&display_map);
2871 }
2872
2873 if head <= original_buffer_range.start {
2874 tail = original_buffer_range.end;
2875 } else {
2876 tail = original_buffer_range.start;
2877 }
2878 }
2879 SelectMode::Line(original_range) => {
2880 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2881
2882 let position = display_map
2883 .clip_point(position, Bias::Left)
2884 .to_point(&display_map);
2885 let line_start = display_map.prev_line_boundary(position).0;
2886 let next_line_start = buffer.clip_point(
2887 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2888 Bias::Left,
2889 );
2890
2891 if line_start < original_range.start {
2892 head = line_start
2893 } else {
2894 head = next_line_start
2895 }
2896
2897 if head <= original_range.start {
2898 tail = original_range.end;
2899 } else {
2900 tail = original_range.start;
2901 }
2902 }
2903 SelectMode::All => {
2904 return;
2905 }
2906 };
2907
2908 if head < tail {
2909 pending.start = buffer.anchor_before(head);
2910 pending.end = buffer.anchor_before(tail);
2911 pending.reversed = true;
2912 } else {
2913 pending.start = buffer.anchor_before(tail);
2914 pending.end = buffer.anchor_before(head);
2915 pending.reversed = false;
2916 }
2917
2918 self.change_selections(None, window, cx, |s| {
2919 s.set_pending(pending, mode);
2920 });
2921 } else {
2922 log::error!("update_selection dispatched with no pending selection");
2923 return;
2924 }
2925
2926 self.apply_scroll_delta(scroll_delta, window, cx);
2927 cx.notify();
2928 }
2929
2930 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2931 self.columnar_selection_tail.take();
2932 if self.selections.pending_anchor().is_some() {
2933 let selections = self.selections.all::<usize>(cx);
2934 self.change_selections(None, window, cx, |s| {
2935 s.select(selections);
2936 s.clear_pending();
2937 });
2938 }
2939 }
2940
2941 fn select_columns(
2942 &mut self,
2943 tail: DisplayPoint,
2944 head: DisplayPoint,
2945 goal_column: u32,
2946 display_map: &DisplaySnapshot,
2947 window: &mut Window,
2948 cx: &mut Context<Self>,
2949 ) {
2950 let start_row = cmp::min(tail.row(), head.row());
2951 let end_row = cmp::max(tail.row(), head.row());
2952 let start_column = cmp::min(tail.column(), goal_column);
2953 let end_column = cmp::max(tail.column(), goal_column);
2954 let reversed = start_column < tail.column();
2955
2956 let selection_ranges = (start_row.0..=end_row.0)
2957 .map(DisplayRow)
2958 .filter_map(|row| {
2959 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2960 let start = display_map
2961 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2962 .to_point(display_map);
2963 let end = display_map
2964 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2965 .to_point(display_map);
2966 if reversed {
2967 Some(end..start)
2968 } else {
2969 Some(start..end)
2970 }
2971 } else {
2972 None
2973 }
2974 })
2975 .collect::<Vec<_>>();
2976
2977 self.change_selections(None, window, cx, |s| {
2978 s.select_ranges(selection_ranges);
2979 });
2980 cx.notify();
2981 }
2982
2983 pub fn has_pending_nonempty_selection(&self) -> bool {
2984 let pending_nonempty_selection = match self.selections.pending_anchor() {
2985 Some(Selection { start, end, .. }) => start != end,
2986 None => false,
2987 };
2988
2989 pending_nonempty_selection
2990 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2991 }
2992
2993 pub fn has_pending_selection(&self) -> bool {
2994 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2995 }
2996
2997 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2998 self.selection_mark_mode = false;
2999
3000 if self.clear_expanded_diff_hunks(cx) {
3001 cx.notify();
3002 return;
3003 }
3004 if self.dismiss_menus_and_popups(true, window, cx) {
3005 return;
3006 }
3007
3008 if self.mode == EditorMode::Full
3009 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3010 {
3011 return;
3012 }
3013
3014 cx.propagate();
3015 }
3016
3017 pub fn dismiss_menus_and_popups(
3018 &mut self,
3019 is_user_requested: bool,
3020 window: &mut Window,
3021 cx: &mut Context<Self>,
3022 ) -> bool {
3023 if self.take_rename(false, window, cx).is_some() {
3024 return true;
3025 }
3026
3027 if hide_hover(self, cx) {
3028 return true;
3029 }
3030
3031 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3032 return true;
3033 }
3034
3035 if self.hide_context_menu(window, cx).is_some() {
3036 return true;
3037 }
3038
3039 if self.mouse_context_menu.take().is_some() {
3040 return true;
3041 }
3042
3043 if is_user_requested && self.discard_inline_completion(true, cx) {
3044 return true;
3045 }
3046
3047 if self.snippet_stack.pop().is_some() {
3048 return true;
3049 }
3050
3051 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
3052 self.dismiss_diagnostics(cx);
3053 return true;
3054 }
3055
3056 false
3057 }
3058
3059 fn linked_editing_ranges_for(
3060 &self,
3061 selection: Range<text::Anchor>,
3062 cx: &App,
3063 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3064 if self.linked_edit_ranges.is_empty() {
3065 return None;
3066 }
3067 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3068 selection.end.buffer_id.and_then(|end_buffer_id| {
3069 if selection.start.buffer_id != Some(end_buffer_id) {
3070 return None;
3071 }
3072 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3073 let snapshot = buffer.read(cx).snapshot();
3074 self.linked_edit_ranges
3075 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3076 .map(|ranges| (ranges, snapshot, buffer))
3077 })?;
3078 use text::ToOffset as TO;
3079 // find offset from the start of current range to current cursor position
3080 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3081
3082 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3083 let start_difference = start_offset - start_byte_offset;
3084 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3085 let end_difference = end_offset - start_byte_offset;
3086 // Current range has associated linked ranges.
3087 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3088 for range in linked_ranges.iter() {
3089 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3090 let end_offset = start_offset + end_difference;
3091 let start_offset = start_offset + start_difference;
3092 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3093 continue;
3094 }
3095 if self.selections.disjoint_anchor_ranges().any(|s| {
3096 if s.start.buffer_id != selection.start.buffer_id
3097 || s.end.buffer_id != selection.end.buffer_id
3098 {
3099 return false;
3100 }
3101 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3102 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3103 }) {
3104 continue;
3105 }
3106 let start = buffer_snapshot.anchor_after(start_offset);
3107 let end = buffer_snapshot.anchor_after(end_offset);
3108 linked_edits
3109 .entry(buffer.clone())
3110 .or_default()
3111 .push(start..end);
3112 }
3113 Some(linked_edits)
3114 }
3115
3116 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3117 let text: Arc<str> = text.into();
3118
3119 if self.read_only(cx) {
3120 return;
3121 }
3122
3123 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3124
3125 let selections = self.selections.all_adjusted(cx);
3126 let mut bracket_inserted = false;
3127 let mut edits = Vec::new();
3128 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3129 let mut new_selections = Vec::with_capacity(selections.len());
3130 let mut new_autoclose_regions = Vec::new();
3131 let snapshot = self.buffer.read(cx).read(cx);
3132
3133 for (selection, autoclose_region) in
3134 self.selections_with_autoclose_regions(selections, &snapshot)
3135 {
3136 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3137 // Determine if the inserted text matches the opening or closing
3138 // bracket of any of this language's bracket pairs.
3139 let mut bracket_pair = None;
3140 let mut is_bracket_pair_start = false;
3141 let mut is_bracket_pair_end = false;
3142 if !text.is_empty() {
3143 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3144 // and they are removing the character that triggered IME popup.
3145 for (pair, enabled) in scope.brackets() {
3146 if !pair.close && !pair.surround {
3147 continue;
3148 }
3149
3150 if enabled && pair.start.ends_with(text.as_ref()) {
3151 let prefix_len = pair.start.len() - text.len();
3152 let preceding_text_matches_prefix = prefix_len == 0
3153 || (selection.start.column >= (prefix_len as u32)
3154 && snapshot.contains_str_at(
3155 Point::new(
3156 selection.start.row,
3157 selection.start.column - (prefix_len as u32),
3158 ),
3159 &pair.start[..prefix_len],
3160 ));
3161 if preceding_text_matches_prefix {
3162 bracket_pair = Some(pair.clone());
3163 is_bracket_pair_start = true;
3164 break;
3165 }
3166 }
3167 if pair.end.as_str() == text.as_ref() {
3168 bracket_pair = Some(pair.clone());
3169 is_bracket_pair_end = true;
3170 break;
3171 }
3172 }
3173 }
3174
3175 if let Some(bracket_pair) = bracket_pair {
3176 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3177 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3178 let auto_surround =
3179 self.use_auto_surround && snapshot_settings.use_auto_surround;
3180 if selection.is_empty() {
3181 if is_bracket_pair_start {
3182 // If the inserted text is a suffix of an opening bracket and the
3183 // selection is preceded by the rest of the opening bracket, then
3184 // insert the closing bracket.
3185 let following_text_allows_autoclose = snapshot
3186 .chars_at(selection.start)
3187 .next()
3188 .map_or(true, |c| scope.should_autoclose_before(c));
3189
3190 let preceding_text_allows_autoclose = selection.start.column == 0
3191 || snapshot.reversed_chars_at(selection.start).next().map_or(
3192 true,
3193 |c| {
3194 bracket_pair.start != bracket_pair.end
3195 || !snapshot
3196 .char_classifier_at(selection.start)
3197 .is_word(c)
3198 },
3199 );
3200
3201 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3202 && bracket_pair.start.len() == 1
3203 {
3204 let target = bracket_pair.start.chars().next().unwrap();
3205 let current_line_count = snapshot
3206 .reversed_chars_at(selection.start)
3207 .take_while(|&c| c != '\n')
3208 .filter(|&c| c == target)
3209 .count();
3210 current_line_count % 2 == 1
3211 } else {
3212 false
3213 };
3214
3215 if autoclose
3216 && bracket_pair.close
3217 && following_text_allows_autoclose
3218 && preceding_text_allows_autoclose
3219 && !is_closing_quote
3220 {
3221 let anchor = snapshot.anchor_before(selection.end);
3222 new_selections.push((selection.map(|_| anchor), text.len()));
3223 new_autoclose_regions.push((
3224 anchor,
3225 text.len(),
3226 selection.id,
3227 bracket_pair.clone(),
3228 ));
3229 edits.push((
3230 selection.range(),
3231 format!("{}{}", text, bracket_pair.end).into(),
3232 ));
3233 bracket_inserted = true;
3234 continue;
3235 }
3236 }
3237
3238 if let Some(region) = autoclose_region {
3239 // If the selection is followed by an auto-inserted closing bracket,
3240 // then don't insert that closing bracket again; just move the selection
3241 // past the closing bracket.
3242 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3243 && text.as_ref() == region.pair.end.as_str();
3244 if should_skip {
3245 let anchor = snapshot.anchor_after(selection.end);
3246 new_selections
3247 .push((selection.map(|_| anchor), region.pair.end.len()));
3248 continue;
3249 }
3250 }
3251
3252 let always_treat_brackets_as_autoclosed = snapshot
3253 .language_settings_at(selection.start, cx)
3254 .always_treat_brackets_as_autoclosed;
3255 if always_treat_brackets_as_autoclosed
3256 && is_bracket_pair_end
3257 && snapshot.contains_str_at(selection.end, text.as_ref())
3258 {
3259 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3260 // and the inserted text is a closing bracket and the selection is followed
3261 // by the closing bracket then move the selection past the closing bracket.
3262 let anchor = snapshot.anchor_after(selection.end);
3263 new_selections.push((selection.map(|_| anchor), text.len()));
3264 continue;
3265 }
3266 }
3267 // If an opening bracket is 1 character long and is typed while
3268 // text is selected, then surround that text with the bracket pair.
3269 else if auto_surround
3270 && bracket_pair.surround
3271 && is_bracket_pair_start
3272 && bracket_pair.start.chars().count() == 1
3273 {
3274 edits.push((selection.start..selection.start, text.clone()));
3275 edits.push((
3276 selection.end..selection.end,
3277 bracket_pair.end.as_str().into(),
3278 ));
3279 bracket_inserted = true;
3280 new_selections.push((
3281 Selection {
3282 id: selection.id,
3283 start: snapshot.anchor_after(selection.start),
3284 end: snapshot.anchor_before(selection.end),
3285 reversed: selection.reversed,
3286 goal: selection.goal,
3287 },
3288 0,
3289 ));
3290 continue;
3291 }
3292 }
3293 }
3294
3295 if self.auto_replace_emoji_shortcode
3296 && selection.is_empty()
3297 && text.as_ref().ends_with(':')
3298 {
3299 if let Some(possible_emoji_short_code) =
3300 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3301 {
3302 if !possible_emoji_short_code.is_empty() {
3303 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3304 let emoji_shortcode_start = Point::new(
3305 selection.start.row,
3306 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3307 );
3308
3309 // Remove shortcode from buffer
3310 edits.push((
3311 emoji_shortcode_start..selection.start,
3312 "".to_string().into(),
3313 ));
3314 new_selections.push((
3315 Selection {
3316 id: selection.id,
3317 start: snapshot.anchor_after(emoji_shortcode_start),
3318 end: snapshot.anchor_before(selection.start),
3319 reversed: selection.reversed,
3320 goal: selection.goal,
3321 },
3322 0,
3323 ));
3324
3325 // Insert emoji
3326 let selection_start_anchor = snapshot.anchor_after(selection.start);
3327 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3328 edits.push((selection.start..selection.end, emoji.to_string().into()));
3329
3330 continue;
3331 }
3332 }
3333 }
3334 }
3335
3336 // If not handling any auto-close operation, then just replace the selected
3337 // text with the given input and move the selection to the end of the
3338 // newly inserted text.
3339 let anchor = snapshot.anchor_after(selection.end);
3340 if !self.linked_edit_ranges.is_empty() {
3341 let start_anchor = snapshot.anchor_before(selection.start);
3342
3343 let is_word_char = text.chars().next().map_or(true, |char| {
3344 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3345 classifier.is_word(char)
3346 });
3347
3348 if is_word_char {
3349 if let Some(ranges) = self
3350 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3351 {
3352 for (buffer, edits) in ranges {
3353 linked_edits
3354 .entry(buffer.clone())
3355 .or_default()
3356 .extend(edits.into_iter().map(|range| (range, text.clone())));
3357 }
3358 }
3359 }
3360 }
3361
3362 new_selections.push((selection.map(|_| anchor), 0));
3363 edits.push((selection.start..selection.end, text.clone()));
3364 }
3365
3366 drop(snapshot);
3367
3368 self.transact(window, cx, |this, window, cx| {
3369 let initial_buffer_versions =
3370 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3371
3372 this.buffer.update(cx, |buffer, cx| {
3373 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3374 });
3375 for (buffer, edits) in linked_edits {
3376 buffer.update(cx, |buffer, cx| {
3377 let snapshot = buffer.snapshot();
3378 let edits = edits
3379 .into_iter()
3380 .map(|(range, text)| {
3381 use text::ToPoint as TP;
3382 let end_point = TP::to_point(&range.end, &snapshot);
3383 let start_point = TP::to_point(&range.start, &snapshot);
3384 (start_point..end_point, text)
3385 })
3386 .sorted_by_key(|(range, _)| range.start);
3387 buffer.edit(edits, None, cx);
3388 })
3389 }
3390 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3391 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3392 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3393 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3394 .zip(new_selection_deltas)
3395 .map(|(selection, delta)| Selection {
3396 id: selection.id,
3397 start: selection.start + delta,
3398 end: selection.end + delta,
3399 reversed: selection.reversed,
3400 goal: SelectionGoal::None,
3401 })
3402 .collect::<Vec<_>>();
3403
3404 let mut i = 0;
3405 for (position, delta, selection_id, pair) in new_autoclose_regions {
3406 let position = position.to_offset(&map.buffer_snapshot) + delta;
3407 let start = map.buffer_snapshot.anchor_before(position);
3408 let end = map.buffer_snapshot.anchor_after(position);
3409 while let Some(existing_state) = this.autoclose_regions.get(i) {
3410 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3411 Ordering::Less => i += 1,
3412 Ordering::Greater => break,
3413 Ordering::Equal => {
3414 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3415 Ordering::Less => i += 1,
3416 Ordering::Equal => break,
3417 Ordering::Greater => break,
3418 }
3419 }
3420 }
3421 }
3422 this.autoclose_regions.insert(
3423 i,
3424 AutocloseRegion {
3425 selection_id,
3426 range: start..end,
3427 pair,
3428 },
3429 );
3430 }
3431
3432 let had_active_inline_completion = this.has_active_inline_completion();
3433 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3434 s.select(new_selections)
3435 });
3436
3437 if !bracket_inserted {
3438 if let Some(on_type_format_task) =
3439 this.trigger_on_type_formatting(text.to_string(), window, cx)
3440 {
3441 on_type_format_task.detach_and_log_err(cx);
3442 }
3443 }
3444
3445 let editor_settings = EditorSettings::get_global(cx);
3446 if bracket_inserted
3447 && (editor_settings.auto_signature_help
3448 || editor_settings.show_signature_help_after_edits)
3449 {
3450 this.show_signature_help(&ShowSignatureHelp, window, cx);
3451 }
3452
3453 let trigger_in_words =
3454 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3455 if this.hard_wrap.is_some() {
3456 let latest: Range<Point> = this.selections.newest(cx).range();
3457 if latest.is_empty()
3458 && this
3459 .buffer()
3460 .read(cx)
3461 .snapshot(cx)
3462 .line_len(MultiBufferRow(latest.start.row))
3463 == latest.start.column
3464 {
3465 this.rewrap_impl(
3466 RewrapOptions {
3467 override_language_settings: true,
3468 preserve_existing_whitespace: true,
3469 },
3470 cx,
3471 )
3472 }
3473 }
3474 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3475 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3476 this.refresh_inline_completion(true, false, window, cx);
3477 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3478 });
3479 }
3480
3481 fn find_possible_emoji_shortcode_at_position(
3482 snapshot: &MultiBufferSnapshot,
3483 position: Point,
3484 ) -> Option<String> {
3485 let mut chars = Vec::new();
3486 let mut found_colon = false;
3487 for char in snapshot.reversed_chars_at(position).take(100) {
3488 // Found a possible emoji shortcode in the middle of the buffer
3489 if found_colon {
3490 if char.is_whitespace() {
3491 chars.reverse();
3492 return Some(chars.iter().collect());
3493 }
3494 // If the previous character is not a whitespace, we are in the middle of a word
3495 // and we only want to complete the shortcode if the word is made up of other emojis
3496 let mut containing_word = String::new();
3497 for ch in snapshot
3498 .reversed_chars_at(position)
3499 .skip(chars.len() + 1)
3500 .take(100)
3501 {
3502 if ch.is_whitespace() {
3503 break;
3504 }
3505 containing_word.push(ch);
3506 }
3507 let containing_word = containing_word.chars().rev().collect::<String>();
3508 if util::word_consists_of_emojis(containing_word.as_str()) {
3509 chars.reverse();
3510 return Some(chars.iter().collect());
3511 }
3512 }
3513
3514 if char.is_whitespace() || !char.is_ascii() {
3515 return None;
3516 }
3517 if char == ':' {
3518 found_colon = true;
3519 } else {
3520 chars.push(char);
3521 }
3522 }
3523 // Found a possible emoji shortcode at the beginning of the buffer
3524 chars.reverse();
3525 Some(chars.iter().collect())
3526 }
3527
3528 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3529 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3530 self.transact(window, cx, |this, window, cx| {
3531 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3532 let selections = this.selections.all::<usize>(cx);
3533 let multi_buffer = this.buffer.read(cx);
3534 let buffer = multi_buffer.snapshot(cx);
3535 selections
3536 .iter()
3537 .map(|selection| {
3538 let start_point = selection.start.to_point(&buffer);
3539 let mut indent =
3540 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3541 indent.len = cmp::min(indent.len, start_point.column);
3542 let start = selection.start;
3543 let end = selection.end;
3544 let selection_is_empty = start == end;
3545 let language_scope = buffer.language_scope_at(start);
3546 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3547 &language_scope
3548 {
3549 let insert_extra_newline =
3550 insert_extra_newline_brackets(&buffer, start..end, language)
3551 || insert_extra_newline_tree_sitter(&buffer, start..end);
3552
3553 // Comment extension on newline is allowed only for cursor selections
3554 let comment_delimiter = maybe!({
3555 if !selection_is_empty {
3556 return None;
3557 }
3558
3559 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3560 return None;
3561 }
3562
3563 let delimiters = language.line_comment_prefixes();
3564 let max_len_of_delimiter =
3565 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3566 let (snapshot, range) =
3567 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3568
3569 let mut index_of_first_non_whitespace = 0;
3570 let comment_candidate = snapshot
3571 .chars_for_range(range)
3572 .skip_while(|c| {
3573 let should_skip = c.is_whitespace();
3574 if should_skip {
3575 index_of_first_non_whitespace += 1;
3576 }
3577 should_skip
3578 })
3579 .take(max_len_of_delimiter)
3580 .collect::<String>();
3581 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3582 comment_candidate.starts_with(comment_prefix.as_ref())
3583 })?;
3584 let cursor_is_placed_after_comment_marker =
3585 index_of_first_non_whitespace + comment_prefix.len()
3586 <= start_point.column as usize;
3587 if cursor_is_placed_after_comment_marker {
3588 Some(comment_prefix.clone())
3589 } else {
3590 None
3591 }
3592 });
3593 (comment_delimiter, insert_extra_newline)
3594 } else {
3595 (None, false)
3596 };
3597
3598 let capacity_for_delimiter = comment_delimiter
3599 .as_deref()
3600 .map(str::len)
3601 .unwrap_or_default();
3602 let mut new_text =
3603 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3604 new_text.push('\n');
3605 new_text.extend(indent.chars());
3606 if let Some(delimiter) = &comment_delimiter {
3607 new_text.push_str(delimiter);
3608 }
3609 if insert_extra_newline {
3610 new_text = new_text.repeat(2);
3611 }
3612
3613 let anchor = buffer.anchor_after(end);
3614 let new_selection = selection.map(|_| anchor);
3615 (
3616 (start..end, new_text),
3617 (insert_extra_newline, new_selection),
3618 )
3619 })
3620 .unzip()
3621 };
3622
3623 this.edit_with_autoindent(edits, cx);
3624 let buffer = this.buffer.read(cx).snapshot(cx);
3625 let new_selections = selection_fixup_info
3626 .into_iter()
3627 .map(|(extra_newline_inserted, new_selection)| {
3628 let mut cursor = new_selection.end.to_point(&buffer);
3629 if extra_newline_inserted {
3630 cursor.row -= 1;
3631 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3632 }
3633 new_selection.map(|_| cursor)
3634 })
3635 .collect();
3636
3637 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3638 s.select(new_selections)
3639 });
3640 this.refresh_inline_completion(true, false, window, cx);
3641 });
3642 }
3643
3644 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3645 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3646
3647 let buffer = self.buffer.read(cx);
3648 let snapshot = buffer.snapshot(cx);
3649
3650 let mut edits = Vec::new();
3651 let mut rows = Vec::new();
3652
3653 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3654 let cursor = selection.head();
3655 let row = cursor.row;
3656
3657 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3658
3659 let newline = "\n".to_string();
3660 edits.push((start_of_line..start_of_line, newline));
3661
3662 rows.push(row + rows_inserted as u32);
3663 }
3664
3665 self.transact(window, cx, |editor, window, cx| {
3666 editor.edit(edits, cx);
3667
3668 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3669 let mut index = 0;
3670 s.move_cursors_with(|map, _, _| {
3671 let row = rows[index];
3672 index += 1;
3673
3674 let point = Point::new(row, 0);
3675 let boundary = map.next_line_boundary(point).1;
3676 let clipped = map.clip_point(boundary, Bias::Left);
3677
3678 (clipped, SelectionGoal::None)
3679 });
3680 });
3681
3682 let mut indent_edits = Vec::new();
3683 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3684 for row in rows {
3685 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3686 for (row, indent) in indents {
3687 if indent.len == 0 {
3688 continue;
3689 }
3690
3691 let text = match indent.kind {
3692 IndentKind::Space => " ".repeat(indent.len as usize),
3693 IndentKind::Tab => "\t".repeat(indent.len as usize),
3694 };
3695 let point = Point::new(row.0, 0);
3696 indent_edits.push((point..point, text));
3697 }
3698 }
3699 editor.edit(indent_edits, cx);
3700 });
3701 }
3702
3703 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3704 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3705
3706 let buffer = self.buffer.read(cx);
3707 let snapshot = buffer.snapshot(cx);
3708
3709 let mut edits = Vec::new();
3710 let mut rows = Vec::new();
3711 let mut rows_inserted = 0;
3712
3713 for selection in self.selections.all_adjusted(cx) {
3714 let cursor = selection.head();
3715 let row = cursor.row;
3716
3717 let point = Point::new(row + 1, 0);
3718 let start_of_line = snapshot.clip_point(point, Bias::Left);
3719
3720 let newline = "\n".to_string();
3721 edits.push((start_of_line..start_of_line, newline));
3722
3723 rows_inserted += 1;
3724 rows.push(row + rows_inserted);
3725 }
3726
3727 self.transact(window, cx, |editor, window, cx| {
3728 editor.edit(edits, cx);
3729
3730 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3731 let mut index = 0;
3732 s.move_cursors_with(|map, _, _| {
3733 let row = rows[index];
3734 index += 1;
3735
3736 let point = Point::new(row, 0);
3737 let boundary = map.next_line_boundary(point).1;
3738 let clipped = map.clip_point(boundary, Bias::Left);
3739
3740 (clipped, SelectionGoal::None)
3741 });
3742 });
3743
3744 let mut indent_edits = Vec::new();
3745 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3746 for row in rows {
3747 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3748 for (row, indent) in indents {
3749 if indent.len == 0 {
3750 continue;
3751 }
3752
3753 let text = match indent.kind {
3754 IndentKind::Space => " ".repeat(indent.len as usize),
3755 IndentKind::Tab => "\t".repeat(indent.len as usize),
3756 };
3757 let point = Point::new(row.0, 0);
3758 indent_edits.push((point..point, text));
3759 }
3760 }
3761 editor.edit(indent_edits, cx);
3762 });
3763 }
3764
3765 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3766 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3767 original_indent_columns: Vec::new(),
3768 });
3769 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3770 }
3771
3772 fn insert_with_autoindent_mode(
3773 &mut self,
3774 text: &str,
3775 autoindent_mode: Option<AutoindentMode>,
3776 window: &mut Window,
3777 cx: &mut Context<Self>,
3778 ) {
3779 if self.read_only(cx) {
3780 return;
3781 }
3782
3783 let text: Arc<str> = text.into();
3784 self.transact(window, cx, |this, window, cx| {
3785 let old_selections = this.selections.all_adjusted(cx);
3786 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3787 let anchors = {
3788 let snapshot = buffer.read(cx);
3789 old_selections
3790 .iter()
3791 .map(|s| {
3792 let anchor = snapshot.anchor_after(s.head());
3793 s.map(|_| anchor)
3794 })
3795 .collect::<Vec<_>>()
3796 };
3797 buffer.edit(
3798 old_selections
3799 .iter()
3800 .map(|s| (s.start..s.end, text.clone())),
3801 autoindent_mode,
3802 cx,
3803 );
3804 anchors
3805 });
3806
3807 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3808 s.select_anchors(selection_anchors);
3809 });
3810
3811 cx.notify();
3812 });
3813 }
3814
3815 fn trigger_completion_on_input(
3816 &mut self,
3817 text: &str,
3818 trigger_in_words: bool,
3819 window: &mut Window,
3820 cx: &mut Context<Self>,
3821 ) {
3822 let ignore_completion_provider = self
3823 .context_menu
3824 .borrow()
3825 .as_ref()
3826 .map(|menu| match menu {
3827 CodeContextMenu::Completions(completions_menu) => {
3828 completions_menu.ignore_completion_provider
3829 }
3830 CodeContextMenu::CodeActions(_) => false,
3831 })
3832 .unwrap_or(false);
3833
3834 if ignore_completion_provider {
3835 self.show_word_completions(&ShowWordCompletions, window, cx);
3836 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3837 self.show_completions(
3838 &ShowCompletions {
3839 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3840 },
3841 window,
3842 cx,
3843 );
3844 } else {
3845 self.hide_context_menu(window, cx);
3846 }
3847 }
3848
3849 fn is_completion_trigger(
3850 &self,
3851 text: &str,
3852 trigger_in_words: bool,
3853 cx: &mut Context<Self>,
3854 ) -> bool {
3855 let position = self.selections.newest_anchor().head();
3856 let multibuffer = self.buffer.read(cx);
3857 let Some(buffer) = position
3858 .buffer_id
3859 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3860 else {
3861 return false;
3862 };
3863
3864 if let Some(completion_provider) = &self.completion_provider {
3865 completion_provider.is_completion_trigger(
3866 &buffer,
3867 position.text_anchor,
3868 text,
3869 trigger_in_words,
3870 cx,
3871 )
3872 } else {
3873 false
3874 }
3875 }
3876
3877 /// If any empty selections is touching the start of its innermost containing autoclose
3878 /// region, expand it to select the brackets.
3879 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3880 let selections = self.selections.all::<usize>(cx);
3881 let buffer = self.buffer.read(cx).read(cx);
3882 let new_selections = self
3883 .selections_with_autoclose_regions(selections, &buffer)
3884 .map(|(mut selection, region)| {
3885 if !selection.is_empty() {
3886 return selection;
3887 }
3888
3889 if let Some(region) = region {
3890 let mut range = region.range.to_offset(&buffer);
3891 if selection.start == range.start && range.start >= region.pair.start.len() {
3892 range.start -= region.pair.start.len();
3893 if buffer.contains_str_at(range.start, ®ion.pair.start)
3894 && buffer.contains_str_at(range.end, ®ion.pair.end)
3895 {
3896 range.end += region.pair.end.len();
3897 selection.start = range.start;
3898 selection.end = range.end;
3899
3900 return selection;
3901 }
3902 }
3903 }
3904
3905 let always_treat_brackets_as_autoclosed = buffer
3906 .language_settings_at(selection.start, cx)
3907 .always_treat_brackets_as_autoclosed;
3908
3909 if !always_treat_brackets_as_autoclosed {
3910 return selection;
3911 }
3912
3913 if let Some(scope) = buffer.language_scope_at(selection.start) {
3914 for (pair, enabled) in scope.brackets() {
3915 if !enabled || !pair.close {
3916 continue;
3917 }
3918
3919 if buffer.contains_str_at(selection.start, &pair.end) {
3920 let pair_start_len = pair.start.len();
3921 if buffer.contains_str_at(
3922 selection.start.saturating_sub(pair_start_len),
3923 &pair.start,
3924 ) {
3925 selection.start -= pair_start_len;
3926 selection.end += pair.end.len();
3927
3928 return selection;
3929 }
3930 }
3931 }
3932 }
3933
3934 selection
3935 })
3936 .collect();
3937
3938 drop(buffer);
3939 self.change_selections(None, window, cx, |selections| {
3940 selections.select(new_selections)
3941 });
3942 }
3943
3944 /// Iterate the given selections, and for each one, find the smallest surrounding
3945 /// autoclose region. This uses the ordering of the selections and the autoclose
3946 /// regions to avoid repeated comparisons.
3947 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3948 &'a self,
3949 selections: impl IntoIterator<Item = Selection<D>>,
3950 buffer: &'a MultiBufferSnapshot,
3951 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3952 let mut i = 0;
3953 let mut regions = self.autoclose_regions.as_slice();
3954 selections.into_iter().map(move |selection| {
3955 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3956
3957 let mut enclosing = None;
3958 while let Some(pair_state) = regions.get(i) {
3959 if pair_state.range.end.to_offset(buffer) < range.start {
3960 regions = ®ions[i + 1..];
3961 i = 0;
3962 } else if pair_state.range.start.to_offset(buffer) > range.end {
3963 break;
3964 } else {
3965 if pair_state.selection_id == selection.id {
3966 enclosing = Some(pair_state);
3967 }
3968 i += 1;
3969 }
3970 }
3971
3972 (selection, enclosing)
3973 })
3974 }
3975
3976 /// Remove any autoclose regions that no longer contain their selection.
3977 fn invalidate_autoclose_regions(
3978 &mut self,
3979 mut selections: &[Selection<Anchor>],
3980 buffer: &MultiBufferSnapshot,
3981 ) {
3982 self.autoclose_regions.retain(|state| {
3983 let mut i = 0;
3984 while let Some(selection) = selections.get(i) {
3985 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3986 selections = &selections[1..];
3987 continue;
3988 }
3989 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3990 break;
3991 }
3992 if selection.id == state.selection_id {
3993 return true;
3994 } else {
3995 i += 1;
3996 }
3997 }
3998 false
3999 });
4000 }
4001
4002 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4003 let offset = position.to_offset(buffer);
4004 let (word_range, kind) = buffer.surrounding_word(offset, true);
4005 if offset > word_range.start && kind == Some(CharKind::Word) {
4006 Some(
4007 buffer
4008 .text_for_range(word_range.start..offset)
4009 .collect::<String>(),
4010 )
4011 } else {
4012 None
4013 }
4014 }
4015
4016 pub fn toggle_inlay_hints(
4017 &mut self,
4018 _: &ToggleInlayHints,
4019 _: &mut Window,
4020 cx: &mut Context<Self>,
4021 ) {
4022 self.refresh_inlay_hints(
4023 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4024 cx,
4025 );
4026 }
4027
4028 pub fn inlay_hints_enabled(&self) -> bool {
4029 self.inlay_hint_cache.enabled
4030 }
4031
4032 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4033 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
4034 return;
4035 }
4036
4037 let reason_description = reason.description();
4038 let ignore_debounce = matches!(
4039 reason,
4040 InlayHintRefreshReason::SettingsChange(_)
4041 | InlayHintRefreshReason::Toggle(_)
4042 | InlayHintRefreshReason::ExcerptsRemoved(_)
4043 | InlayHintRefreshReason::ModifiersChanged(_)
4044 );
4045 let (invalidate_cache, required_languages) = match reason {
4046 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4047 match self.inlay_hint_cache.modifiers_override(enabled) {
4048 Some(enabled) => {
4049 if enabled {
4050 (InvalidationStrategy::RefreshRequested, None)
4051 } else {
4052 self.splice_inlays(
4053 &self
4054 .visible_inlay_hints(cx)
4055 .iter()
4056 .map(|inlay| inlay.id)
4057 .collect::<Vec<InlayId>>(),
4058 Vec::new(),
4059 cx,
4060 );
4061 return;
4062 }
4063 }
4064 None => return,
4065 }
4066 }
4067 InlayHintRefreshReason::Toggle(enabled) => {
4068 if self.inlay_hint_cache.toggle(enabled) {
4069 if enabled {
4070 (InvalidationStrategy::RefreshRequested, None)
4071 } else {
4072 self.splice_inlays(
4073 &self
4074 .visible_inlay_hints(cx)
4075 .iter()
4076 .map(|inlay| inlay.id)
4077 .collect::<Vec<InlayId>>(),
4078 Vec::new(),
4079 cx,
4080 );
4081 return;
4082 }
4083 } else {
4084 return;
4085 }
4086 }
4087 InlayHintRefreshReason::SettingsChange(new_settings) => {
4088 match self.inlay_hint_cache.update_settings(
4089 &self.buffer,
4090 new_settings,
4091 self.visible_inlay_hints(cx),
4092 cx,
4093 ) {
4094 ControlFlow::Break(Some(InlaySplice {
4095 to_remove,
4096 to_insert,
4097 })) => {
4098 self.splice_inlays(&to_remove, to_insert, cx);
4099 return;
4100 }
4101 ControlFlow::Break(None) => return,
4102 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4103 }
4104 }
4105 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4106 if let Some(InlaySplice {
4107 to_remove,
4108 to_insert,
4109 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
4110 {
4111 self.splice_inlays(&to_remove, to_insert, cx);
4112 }
4113 return;
4114 }
4115 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4116 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4117 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4118 }
4119 InlayHintRefreshReason::RefreshRequested => {
4120 (InvalidationStrategy::RefreshRequested, None)
4121 }
4122 };
4123
4124 if let Some(InlaySplice {
4125 to_remove,
4126 to_insert,
4127 }) = self.inlay_hint_cache.spawn_hint_refresh(
4128 reason_description,
4129 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4130 invalidate_cache,
4131 ignore_debounce,
4132 cx,
4133 ) {
4134 self.splice_inlays(&to_remove, to_insert, cx);
4135 }
4136 }
4137
4138 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4139 self.display_map
4140 .read(cx)
4141 .current_inlays()
4142 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4143 .cloned()
4144 .collect()
4145 }
4146
4147 pub fn excerpts_for_inlay_hints_query(
4148 &self,
4149 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4150 cx: &mut Context<Editor>,
4151 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4152 let Some(project) = self.project.as_ref() else {
4153 return HashMap::default();
4154 };
4155 let project = project.read(cx);
4156 let multi_buffer = self.buffer().read(cx);
4157 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4158 let multi_buffer_visible_start = self
4159 .scroll_manager
4160 .anchor()
4161 .anchor
4162 .to_point(&multi_buffer_snapshot);
4163 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4164 multi_buffer_visible_start
4165 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4166 Bias::Left,
4167 );
4168 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4169 multi_buffer_snapshot
4170 .range_to_buffer_ranges(multi_buffer_visible_range)
4171 .into_iter()
4172 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4173 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4174 let buffer_file = project::File::from_dyn(buffer.file())?;
4175 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4176 let worktree_entry = buffer_worktree
4177 .read(cx)
4178 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4179 if worktree_entry.is_ignored {
4180 return None;
4181 }
4182
4183 let language = buffer.language()?;
4184 if let Some(restrict_to_languages) = restrict_to_languages {
4185 if !restrict_to_languages.contains(language) {
4186 return None;
4187 }
4188 }
4189 Some((
4190 excerpt_id,
4191 (
4192 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4193 buffer.version().clone(),
4194 excerpt_visible_range,
4195 ),
4196 ))
4197 })
4198 .collect()
4199 }
4200
4201 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4202 TextLayoutDetails {
4203 text_system: window.text_system().clone(),
4204 editor_style: self.style.clone().unwrap(),
4205 rem_size: window.rem_size(),
4206 scroll_anchor: self.scroll_manager.anchor(),
4207 visible_rows: self.visible_line_count(),
4208 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4209 }
4210 }
4211
4212 pub fn splice_inlays(
4213 &self,
4214 to_remove: &[InlayId],
4215 to_insert: Vec<Inlay>,
4216 cx: &mut Context<Self>,
4217 ) {
4218 self.display_map.update(cx, |display_map, cx| {
4219 display_map.splice_inlays(to_remove, to_insert, cx)
4220 });
4221 cx.notify();
4222 }
4223
4224 fn trigger_on_type_formatting(
4225 &self,
4226 input: String,
4227 window: &mut Window,
4228 cx: &mut Context<Self>,
4229 ) -> Option<Task<Result<()>>> {
4230 if input.len() != 1 {
4231 return None;
4232 }
4233
4234 let project = self.project.as_ref()?;
4235 let position = self.selections.newest_anchor().head();
4236 let (buffer, buffer_position) = self
4237 .buffer
4238 .read(cx)
4239 .text_anchor_for_position(position, cx)?;
4240
4241 let settings = language_settings::language_settings(
4242 buffer
4243 .read(cx)
4244 .language_at(buffer_position)
4245 .map(|l| l.name()),
4246 buffer.read(cx).file(),
4247 cx,
4248 );
4249 if !settings.use_on_type_format {
4250 return None;
4251 }
4252
4253 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4254 // hence we do LSP request & edit on host side only — add formats to host's history.
4255 let push_to_lsp_host_history = true;
4256 // If this is not the host, append its history with new edits.
4257 let push_to_client_history = project.read(cx).is_via_collab();
4258
4259 let on_type_formatting = project.update(cx, |project, cx| {
4260 project.on_type_format(
4261 buffer.clone(),
4262 buffer_position,
4263 input,
4264 push_to_lsp_host_history,
4265 cx,
4266 )
4267 });
4268 Some(cx.spawn_in(window, async move |editor, cx| {
4269 if let Some(transaction) = on_type_formatting.await? {
4270 if push_to_client_history {
4271 buffer
4272 .update(cx, |buffer, _| {
4273 buffer.push_transaction(transaction, Instant::now());
4274 })
4275 .ok();
4276 }
4277 editor.update(cx, |editor, cx| {
4278 editor.refresh_document_highlights(cx);
4279 })?;
4280 }
4281 Ok(())
4282 }))
4283 }
4284
4285 pub fn show_word_completions(
4286 &mut self,
4287 _: &ShowWordCompletions,
4288 window: &mut Window,
4289 cx: &mut Context<Self>,
4290 ) {
4291 self.open_completions_menu(true, None, window, cx);
4292 }
4293
4294 pub fn show_completions(
4295 &mut self,
4296 options: &ShowCompletions,
4297 window: &mut Window,
4298 cx: &mut Context<Self>,
4299 ) {
4300 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4301 }
4302
4303 fn open_completions_menu(
4304 &mut self,
4305 ignore_completion_provider: bool,
4306 trigger: Option<&str>,
4307 window: &mut Window,
4308 cx: &mut Context<Self>,
4309 ) {
4310 if self.pending_rename.is_some() {
4311 return;
4312 }
4313 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4314 return;
4315 }
4316
4317 let position = self.selections.newest_anchor().head();
4318 if position.diff_base_anchor.is_some() {
4319 return;
4320 }
4321 let (buffer, buffer_position) =
4322 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4323 output
4324 } else {
4325 return;
4326 };
4327 let buffer_snapshot = buffer.read(cx).snapshot();
4328 let show_completion_documentation = buffer_snapshot
4329 .settings_at(buffer_position, cx)
4330 .show_completion_documentation;
4331
4332 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4333
4334 let trigger_kind = match trigger {
4335 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4336 CompletionTriggerKind::TRIGGER_CHARACTER
4337 }
4338 _ => CompletionTriggerKind::INVOKED,
4339 };
4340 let completion_context = CompletionContext {
4341 trigger_character: trigger.and_then(|trigger| {
4342 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4343 Some(String::from(trigger))
4344 } else {
4345 None
4346 }
4347 }),
4348 trigger_kind,
4349 };
4350
4351 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4352 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4353 let word_to_exclude = buffer_snapshot
4354 .text_for_range(old_range.clone())
4355 .collect::<String>();
4356 (
4357 buffer_snapshot.anchor_before(old_range.start)
4358 ..buffer_snapshot.anchor_after(old_range.end),
4359 Some(word_to_exclude),
4360 )
4361 } else {
4362 (buffer_position..buffer_position, None)
4363 };
4364
4365 let completion_settings = language_settings(
4366 buffer_snapshot
4367 .language_at(buffer_position)
4368 .map(|language| language.name()),
4369 buffer_snapshot.file(),
4370 cx,
4371 )
4372 .completions;
4373
4374 // The document can be large, so stay in reasonable bounds when searching for words,
4375 // otherwise completion pop-up might be slow to appear.
4376 const WORD_LOOKUP_ROWS: u32 = 5_000;
4377 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4378 let min_word_search = buffer_snapshot.clip_point(
4379 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4380 Bias::Left,
4381 );
4382 let max_word_search = buffer_snapshot.clip_point(
4383 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4384 Bias::Right,
4385 );
4386 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4387 ..buffer_snapshot.point_to_offset(max_word_search);
4388
4389 let provider = self
4390 .completion_provider
4391 .as_ref()
4392 .filter(|_| !ignore_completion_provider);
4393 let skip_digits = query
4394 .as_ref()
4395 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4396
4397 let (mut words, provided_completions) = match provider {
4398 Some(provider) => {
4399 let completions = provider.completions(
4400 position.excerpt_id,
4401 &buffer,
4402 buffer_position,
4403 completion_context,
4404 window,
4405 cx,
4406 );
4407
4408 let words = match completion_settings.words {
4409 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4410 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4411 .background_spawn(async move {
4412 buffer_snapshot.words_in_range(WordsQuery {
4413 fuzzy_contents: None,
4414 range: word_search_range,
4415 skip_digits,
4416 })
4417 }),
4418 };
4419
4420 (words, completions)
4421 }
4422 None => (
4423 cx.background_spawn(async move {
4424 buffer_snapshot.words_in_range(WordsQuery {
4425 fuzzy_contents: None,
4426 range: word_search_range,
4427 skip_digits,
4428 })
4429 }),
4430 Task::ready(Ok(None)),
4431 ),
4432 };
4433
4434 let sort_completions = provider
4435 .as_ref()
4436 .map_or(false, |provider| provider.sort_completions());
4437
4438 let filter_completions = provider
4439 .as_ref()
4440 .map_or(true, |provider| provider.filter_completions());
4441
4442 let id = post_inc(&mut self.next_completion_id);
4443 let task = cx.spawn_in(window, async move |editor, cx| {
4444 async move {
4445 editor.update(cx, |this, _| {
4446 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4447 })?;
4448
4449 let mut completions = Vec::new();
4450 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4451 completions.extend(provided_completions);
4452 if completion_settings.words == WordsCompletionMode::Fallback {
4453 words = Task::ready(BTreeMap::default());
4454 }
4455 }
4456
4457 let mut words = words.await;
4458 if let Some(word_to_exclude) = &word_to_exclude {
4459 words.remove(word_to_exclude);
4460 }
4461 for lsp_completion in &completions {
4462 words.remove(&lsp_completion.new_text);
4463 }
4464 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4465 replace_range: old_range.clone(),
4466 new_text: word.clone(),
4467 label: CodeLabel::plain(word, None),
4468 icon_path: None,
4469 documentation: None,
4470 source: CompletionSource::BufferWord {
4471 word_range,
4472 resolved: false,
4473 },
4474 insert_text_mode: Some(InsertTextMode::AS_IS),
4475 confirm: None,
4476 }));
4477
4478 let menu = if completions.is_empty() {
4479 None
4480 } else {
4481 let mut menu = CompletionsMenu::new(
4482 id,
4483 sort_completions,
4484 show_completion_documentation,
4485 ignore_completion_provider,
4486 position,
4487 buffer.clone(),
4488 completions.into(),
4489 );
4490
4491 menu.filter(
4492 if filter_completions {
4493 query.as_deref()
4494 } else {
4495 None
4496 },
4497 cx.background_executor().clone(),
4498 )
4499 .await;
4500
4501 menu.visible().then_some(menu)
4502 };
4503
4504 editor.update_in(cx, |editor, window, cx| {
4505 match editor.context_menu.borrow().as_ref() {
4506 None => {}
4507 Some(CodeContextMenu::Completions(prev_menu)) => {
4508 if prev_menu.id > id {
4509 return;
4510 }
4511 }
4512 _ => return,
4513 }
4514
4515 if editor.focus_handle.is_focused(window) && menu.is_some() {
4516 let mut menu = menu.unwrap();
4517 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4518
4519 *editor.context_menu.borrow_mut() =
4520 Some(CodeContextMenu::Completions(menu));
4521
4522 if editor.show_edit_predictions_in_menu() {
4523 editor.update_visible_inline_completion(window, cx);
4524 } else {
4525 editor.discard_inline_completion(false, cx);
4526 }
4527
4528 cx.notify();
4529 } else if editor.completion_tasks.len() <= 1 {
4530 // If there are no more completion tasks and the last menu was
4531 // empty, we should hide it.
4532 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4533 // If it was already hidden and we don't show inline
4534 // completions in the menu, we should also show the
4535 // inline-completion when available.
4536 if was_hidden && editor.show_edit_predictions_in_menu() {
4537 editor.update_visible_inline_completion(window, cx);
4538 }
4539 }
4540 })?;
4541
4542 anyhow::Ok(())
4543 }
4544 .log_err()
4545 .await
4546 });
4547
4548 self.completion_tasks.push((id, task));
4549 }
4550
4551 #[cfg(feature = "test-support")]
4552 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4553 let menu = self.context_menu.borrow();
4554 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4555 let completions = menu.completions.borrow();
4556 Some(completions.to_vec())
4557 } else {
4558 None
4559 }
4560 }
4561
4562 pub fn confirm_completion(
4563 &mut self,
4564 action: &ConfirmCompletion,
4565 window: &mut Window,
4566 cx: &mut Context<Self>,
4567 ) -> Option<Task<Result<()>>> {
4568 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4569 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4570 }
4571
4572 pub fn confirm_completion_insert(
4573 &mut self,
4574 _: &ConfirmCompletionInsert,
4575 window: &mut Window,
4576 cx: &mut Context<Self>,
4577 ) -> Option<Task<Result<()>>> {
4578 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4579 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4580 }
4581
4582 pub fn confirm_completion_replace(
4583 &mut self,
4584 _: &ConfirmCompletionReplace,
4585 window: &mut Window,
4586 cx: &mut Context<Self>,
4587 ) -> Option<Task<Result<()>>> {
4588 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4589 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4590 }
4591
4592 pub fn compose_completion(
4593 &mut self,
4594 action: &ComposeCompletion,
4595 window: &mut Window,
4596 cx: &mut Context<Self>,
4597 ) -> Option<Task<Result<()>>> {
4598 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4599 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4600 }
4601
4602 fn do_completion(
4603 &mut self,
4604 item_ix: Option<usize>,
4605 intent: CompletionIntent,
4606 window: &mut Window,
4607 cx: &mut Context<Editor>,
4608 ) -> Option<Task<Result<()>>> {
4609 use language::ToOffset as _;
4610
4611 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4612 else {
4613 return None;
4614 };
4615
4616 let candidate_id = {
4617 let entries = completions_menu.entries.borrow();
4618 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4619 if self.show_edit_predictions_in_menu() {
4620 self.discard_inline_completion(true, cx);
4621 }
4622 mat.candidate_id
4623 };
4624
4625 let buffer_handle = completions_menu.buffer;
4626 let completion = completions_menu
4627 .completions
4628 .borrow()
4629 .get(candidate_id)?
4630 .clone();
4631 cx.stop_propagation();
4632
4633 let snippet;
4634 let new_text;
4635 if completion.is_snippet() {
4636 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4637 new_text = snippet.as_ref().unwrap().text.clone();
4638 } else {
4639 snippet = None;
4640 new_text = completion.new_text.clone();
4641 };
4642 let selections = self.selections.all::<usize>(cx);
4643
4644 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4645 let buffer = buffer_handle.read(cx);
4646 let old_text = buffer
4647 .text_for_range(replace_range.clone())
4648 .collect::<String>();
4649
4650 let newest_selection = self.selections.newest_anchor();
4651 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4652 return None;
4653 }
4654
4655 let lookbehind = newest_selection
4656 .start
4657 .text_anchor
4658 .to_offset(buffer)
4659 .saturating_sub(replace_range.start);
4660 let lookahead = replace_range
4661 .end
4662 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4663 let mut common_prefix_len = 0;
4664 for (a, b) in old_text.chars().zip(new_text.chars()) {
4665 if a == b {
4666 common_prefix_len += a.len_utf8();
4667 } else {
4668 break;
4669 }
4670 }
4671
4672 let snapshot = self.buffer.read(cx).snapshot(cx);
4673 let mut range_to_replace: Option<Range<usize>> = None;
4674 let mut ranges = Vec::new();
4675 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4676 for selection in &selections {
4677 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4678 let start = selection.start.saturating_sub(lookbehind);
4679 let end = selection.end + lookahead;
4680 if selection.id == newest_selection.id {
4681 range_to_replace = Some(start + common_prefix_len..end);
4682 }
4683 ranges.push(start + common_prefix_len..end);
4684 } else {
4685 common_prefix_len = 0;
4686 ranges.clear();
4687 ranges.extend(selections.iter().map(|s| {
4688 if s.id == newest_selection.id {
4689 range_to_replace = Some(replace_range.clone());
4690 replace_range.clone()
4691 } else {
4692 s.start..s.end
4693 }
4694 }));
4695 break;
4696 }
4697 if !self.linked_edit_ranges.is_empty() {
4698 let start_anchor = snapshot.anchor_before(selection.head());
4699 let end_anchor = snapshot.anchor_after(selection.tail());
4700 if let Some(ranges) = self
4701 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4702 {
4703 for (buffer, edits) in ranges {
4704 linked_edits.entry(buffer.clone()).or_default().extend(
4705 edits
4706 .into_iter()
4707 .map(|range| (range, new_text[common_prefix_len..].to_owned())),
4708 );
4709 }
4710 }
4711 }
4712 }
4713 let text = &new_text[common_prefix_len..];
4714
4715 let utf16_range_to_replace = range_to_replace.map(|range| {
4716 let newest_selection = self.selections.newest::<OffsetUtf16>(cx).range();
4717 let selection_start_utf16 = newest_selection.start.0 as isize;
4718
4719 range.start.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16
4720 ..range.end.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16
4721 });
4722 cx.emit(EditorEvent::InputHandled {
4723 utf16_range_to_replace,
4724 text: text.into(),
4725 });
4726
4727 self.transact(window, cx, |this, window, cx| {
4728 if let Some(mut snippet) = snippet {
4729 snippet.text = text.to_string();
4730 for tabstop in snippet
4731 .tabstops
4732 .iter_mut()
4733 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4734 {
4735 tabstop.start -= common_prefix_len as isize;
4736 tabstop.end -= common_prefix_len as isize;
4737 }
4738
4739 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4740 } else {
4741 this.buffer.update(cx, |buffer, cx| {
4742 let edits = ranges.iter().map(|range| (range.clone(), text));
4743 let auto_indent = if completion.insert_text_mode == Some(InsertTextMode::AS_IS)
4744 {
4745 None
4746 } else {
4747 this.autoindent_mode.clone()
4748 };
4749 buffer.edit(edits, auto_indent, cx);
4750 });
4751 }
4752 for (buffer, edits) in linked_edits {
4753 buffer.update(cx, |buffer, cx| {
4754 let snapshot = buffer.snapshot();
4755 let edits = edits
4756 .into_iter()
4757 .map(|(range, text)| {
4758 use text::ToPoint as TP;
4759 let end_point = TP::to_point(&range.end, &snapshot);
4760 let start_point = TP::to_point(&range.start, &snapshot);
4761 (start_point..end_point, text)
4762 })
4763 .sorted_by_key(|(range, _)| range.start);
4764 buffer.edit(edits, None, cx);
4765 })
4766 }
4767
4768 this.refresh_inline_completion(true, false, window, cx);
4769 });
4770
4771 let show_new_completions_on_confirm = completion
4772 .confirm
4773 .as_ref()
4774 .map_or(false, |confirm| confirm(intent, window, cx));
4775 if show_new_completions_on_confirm {
4776 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4777 }
4778
4779 let provider = self.completion_provider.as_ref()?;
4780 drop(completion);
4781 let apply_edits = provider.apply_additional_edits_for_completion(
4782 buffer_handle,
4783 completions_menu.completions.clone(),
4784 candidate_id,
4785 true,
4786 cx,
4787 );
4788
4789 let editor_settings = EditorSettings::get_global(cx);
4790 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4791 // After the code completion is finished, users often want to know what signatures are needed.
4792 // so we should automatically call signature_help
4793 self.show_signature_help(&ShowSignatureHelp, window, cx);
4794 }
4795
4796 Some(cx.foreground_executor().spawn(async move {
4797 apply_edits.await?;
4798 Ok(())
4799 }))
4800 }
4801
4802 pub fn toggle_code_actions(
4803 &mut self,
4804 action: &ToggleCodeActions,
4805 window: &mut Window,
4806 cx: &mut Context<Self>,
4807 ) {
4808 let mut context_menu = self.context_menu.borrow_mut();
4809 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4810 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4811 // Toggle if we're selecting the same one
4812 *context_menu = None;
4813 cx.notify();
4814 return;
4815 } else {
4816 // Otherwise, clear it and start a new one
4817 *context_menu = None;
4818 cx.notify();
4819 }
4820 }
4821 drop(context_menu);
4822 let snapshot = self.snapshot(window, cx);
4823 let deployed_from_indicator = action.deployed_from_indicator;
4824 let mut task = self.code_actions_task.take();
4825 let action = action.clone();
4826 cx.spawn_in(window, async move |editor, cx| {
4827 while let Some(prev_task) = task {
4828 prev_task.await.log_err();
4829 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4830 }
4831
4832 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4833 if editor.focus_handle.is_focused(window) {
4834 let multibuffer_point = action
4835 .deployed_from_indicator
4836 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4837 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4838 let (buffer, buffer_row) = snapshot
4839 .buffer_snapshot
4840 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4841 .and_then(|(buffer_snapshot, range)| {
4842 editor
4843 .buffer
4844 .read(cx)
4845 .buffer(buffer_snapshot.remote_id())
4846 .map(|buffer| (buffer, range.start.row))
4847 })?;
4848 let (_, code_actions) = editor
4849 .available_code_actions
4850 .clone()
4851 .and_then(|(location, code_actions)| {
4852 let snapshot = location.buffer.read(cx).snapshot();
4853 let point_range = location.range.to_point(&snapshot);
4854 let point_range = point_range.start.row..=point_range.end.row;
4855 if point_range.contains(&buffer_row) {
4856 Some((location, code_actions))
4857 } else {
4858 None
4859 }
4860 })
4861 .unzip();
4862 let buffer_id = buffer.read(cx).remote_id();
4863 let tasks = editor
4864 .tasks
4865 .get(&(buffer_id, buffer_row))
4866 .map(|t| Arc::new(t.to_owned()));
4867 if tasks.is_none() && code_actions.is_none() {
4868 return None;
4869 }
4870
4871 editor.completion_tasks.clear();
4872 editor.discard_inline_completion(false, cx);
4873 let task_context =
4874 tasks
4875 .as_ref()
4876 .zip(editor.project.clone())
4877 .map(|(tasks, project)| {
4878 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4879 });
4880
4881 let debugger_flag = cx.has_flag::<Debugger>();
4882
4883 Some(cx.spawn_in(window, async move |editor, cx| {
4884 let task_context = match task_context {
4885 Some(task_context) => task_context.await,
4886 None => None,
4887 };
4888 let resolved_tasks =
4889 tasks.zip(task_context).map(|(tasks, task_context)| {
4890 Rc::new(ResolvedTasks {
4891 templates: tasks.resolve(&task_context).collect(),
4892 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4893 multibuffer_point.row,
4894 tasks.column,
4895 )),
4896 })
4897 });
4898 let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
4899 tasks
4900 .templates
4901 .iter()
4902 .filter(|task| {
4903 if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
4904 debugger_flag
4905 } else {
4906 true
4907 }
4908 })
4909 .count()
4910 == 1
4911 }) && code_actions
4912 .as_ref()
4913 .map_or(true, |actions| actions.is_empty());
4914 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4915 *editor.context_menu.borrow_mut() =
4916 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4917 buffer,
4918 actions: CodeActionContents {
4919 tasks: resolved_tasks,
4920 actions: code_actions,
4921 },
4922 selected_item: Default::default(),
4923 scroll_handle: UniformListScrollHandle::default(),
4924 deployed_from_indicator,
4925 }));
4926 if spawn_straight_away {
4927 if let Some(task) = editor.confirm_code_action(
4928 &ConfirmCodeAction { item_ix: Some(0) },
4929 window,
4930 cx,
4931 ) {
4932 cx.notify();
4933 return task;
4934 }
4935 }
4936 cx.notify();
4937 Task::ready(Ok(()))
4938 }) {
4939 task.await
4940 } else {
4941 Ok(())
4942 }
4943 }))
4944 } else {
4945 Some(Task::ready(Ok(())))
4946 }
4947 })?;
4948 if let Some(task) = spawned_test_task {
4949 task.await?;
4950 }
4951
4952 Ok::<_, anyhow::Error>(())
4953 })
4954 .detach_and_log_err(cx);
4955 }
4956
4957 pub fn confirm_code_action(
4958 &mut self,
4959 action: &ConfirmCodeAction,
4960 window: &mut Window,
4961 cx: &mut Context<Self>,
4962 ) -> Option<Task<Result<()>>> {
4963 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4964
4965 let actions_menu =
4966 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4967 menu
4968 } else {
4969 return None;
4970 };
4971
4972 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4973 let action = actions_menu.actions.get(action_ix)?;
4974 let title = action.label();
4975 let buffer = actions_menu.buffer;
4976 let workspace = self.workspace()?;
4977
4978 match action {
4979 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4980 match resolved_task.task_type() {
4981 task::TaskType::Script => workspace.update(cx, |workspace, cx| {
4982 workspace::tasks::schedule_resolved_task(
4983 workspace,
4984 task_source_kind,
4985 resolved_task,
4986 false,
4987 cx,
4988 );
4989
4990 Some(Task::ready(Ok(())))
4991 }),
4992 task::TaskType::Debug(debug_args) => {
4993 if debug_args.locator.is_some() {
4994 workspace.update(cx, |workspace, cx| {
4995 workspace::tasks::schedule_resolved_task(
4996 workspace,
4997 task_source_kind,
4998 resolved_task,
4999 false,
5000 cx,
5001 );
5002 });
5003
5004 return Some(Task::ready(Ok(())));
5005 }
5006
5007 if let Some(project) = self.project.as_ref() {
5008 project
5009 .update(cx, |project, cx| {
5010 project.start_debug_session(
5011 resolved_task.resolved_debug_adapter_config().unwrap(),
5012 cx,
5013 )
5014 })
5015 .detach_and_log_err(cx);
5016 Some(Task::ready(Ok(())))
5017 } else {
5018 Some(Task::ready(Ok(())))
5019 }
5020 }
5021 }
5022 }
5023 CodeActionsItem::CodeAction {
5024 excerpt_id,
5025 action,
5026 provider,
5027 } => {
5028 let apply_code_action =
5029 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5030 let workspace = workspace.downgrade();
5031 Some(cx.spawn_in(window, async move |editor, cx| {
5032 let project_transaction = apply_code_action.await?;
5033 Self::open_project_transaction(
5034 &editor,
5035 workspace,
5036 project_transaction,
5037 title,
5038 cx,
5039 )
5040 .await
5041 }))
5042 }
5043 }
5044 }
5045
5046 pub async fn open_project_transaction(
5047 this: &WeakEntity<Editor>,
5048 workspace: WeakEntity<Workspace>,
5049 transaction: ProjectTransaction,
5050 title: String,
5051 cx: &mut AsyncWindowContext,
5052 ) -> Result<()> {
5053 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5054 cx.update(|_, cx| {
5055 entries.sort_unstable_by_key(|(buffer, _)| {
5056 buffer.read(cx).file().map(|f| f.path().clone())
5057 });
5058 })?;
5059
5060 // If the project transaction's edits are all contained within this editor, then
5061 // avoid opening a new editor to display them.
5062
5063 if let Some((buffer, transaction)) = entries.first() {
5064 if entries.len() == 1 {
5065 let excerpt = this.update(cx, |editor, cx| {
5066 editor
5067 .buffer()
5068 .read(cx)
5069 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5070 })?;
5071 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5072 if excerpted_buffer == *buffer {
5073 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5074 let excerpt_range = excerpt_range.to_offset(buffer);
5075 buffer
5076 .edited_ranges_for_transaction::<usize>(transaction)
5077 .all(|range| {
5078 excerpt_range.start <= range.start
5079 && excerpt_range.end >= range.end
5080 })
5081 })?;
5082
5083 if all_edits_within_excerpt {
5084 return Ok(());
5085 }
5086 }
5087 }
5088 }
5089 } else {
5090 return Ok(());
5091 }
5092
5093 let mut ranges_to_highlight = Vec::new();
5094 let excerpt_buffer = cx.new(|cx| {
5095 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5096 for (buffer_handle, transaction) in &entries {
5097 let edited_ranges = buffer_handle
5098 .read(cx)
5099 .edited_ranges_for_transaction::<Point>(transaction)
5100 .collect::<Vec<_>>();
5101 let (ranges, _) = multibuffer.set_excerpts_for_path(
5102 PathKey::for_buffer(buffer_handle, cx),
5103 buffer_handle.clone(),
5104 edited_ranges,
5105 DEFAULT_MULTIBUFFER_CONTEXT,
5106 cx,
5107 );
5108
5109 ranges_to_highlight.extend(ranges);
5110 }
5111 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5112 multibuffer
5113 })?;
5114
5115 workspace.update_in(cx, |workspace, window, cx| {
5116 let project = workspace.project().clone();
5117 let editor =
5118 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5119 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5120 editor.update(cx, |editor, cx| {
5121 editor.highlight_background::<Self>(
5122 &ranges_to_highlight,
5123 |theme| theme.editor_highlighted_line_background,
5124 cx,
5125 );
5126 });
5127 })?;
5128
5129 Ok(())
5130 }
5131
5132 pub fn clear_code_action_providers(&mut self) {
5133 self.code_action_providers.clear();
5134 self.available_code_actions.take();
5135 }
5136
5137 pub fn add_code_action_provider(
5138 &mut self,
5139 provider: Rc<dyn CodeActionProvider>,
5140 window: &mut Window,
5141 cx: &mut Context<Self>,
5142 ) {
5143 if self
5144 .code_action_providers
5145 .iter()
5146 .any(|existing_provider| existing_provider.id() == provider.id())
5147 {
5148 return;
5149 }
5150
5151 self.code_action_providers.push(provider);
5152 self.refresh_code_actions(window, cx);
5153 }
5154
5155 pub fn remove_code_action_provider(
5156 &mut self,
5157 id: Arc<str>,
5158 window: &mut Window,
5159 cx: &mut Context<Self>,
5160 ) {
5161 self.code_action_providers
5162 .retain(|provider| provider.id() != id);
5163 self.refresh_code_actions(window, cx);
5164 }
5165
5166 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5167 let buffer = self.buffer.read(cx);
5168 let newest_selection = self.selections.newest_anchor().clone();
5169 if newest_selection.head().diff_base_anchor.is_some() {
5170 return None;
5171 }
5172 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
5173 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
5174 if start_buffer != end_buffer {
5175 return None;
5176 }
5177
5178 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5179 cx.background_executor()
5180 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5181 .await;
5182
5183 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5184 let providers = this.code_action_providers.clone();
5185 let tasks = this
5186 .code_action_providers
5187 .iter()
5188 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5189 .collect::<Vec<_>>();
5190 (providers, tasks)
5191 })?;
5192
5193 let mut actions = Vec::new();
5194 for (provider, provider_actions) in
5195 providers.into_iter().zip(future::join_all(tasks).await)
5196 {
5197 if let Some(provider_actions) = provider_actions.log_err() {
5198 actions.extend(provider_actions.into_iter().map(|action| {
5199 AvailableCodeAction {
5200 excerpt_id: newest_selection.start.excerpt_id,
5201 action,
5202 provider: provider.clone(),
5203 }
5204 }));
5205 }
5206 }
5207
5208 this.update(cx, |this, cx| {
5209 this.available_code_actions = if actions.is_empty() {
5210 None
5211 } else {
5212 Some((
5213 Location {
5214 buffer: start_buffer,
5215 range: start..end,
5216 },
5217 actions.into(),
5218 ))
5219 };
5220 cx.notify();
5221 })
5222 }));
5223 None
5224 }
5225
5226 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5227 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5228 self.show_git_blame_inline = false;
5229
5230 self.show_git_blame_inline_delay_task =
5231 Some(cx.spawn_in(window, async move |this, cx| {
5232 cx.background_executor().timer(delay).await;
5233
5234 this.update(cx, |this, cx| {
5235 this.show_git_blame_inline = true;
5236 cx.notify();
5237 })
5238 .log_err();
5239 }));
5240 }
5241 }
5242
5243 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5244 if self.pending_rename.is_some() {
5245 return None;
5246 }
5247
5248 let provider = self.semantics_provider.clone()?;
5249 let buffer = self.buffer.read(cx);
5250 let newest_selection = self.selections.newest_anchor().clone();
5251 let cursor_position = newest_selection.head();
5252 let (cursor_buffer, cursor_buffer_position) =
5253 buffer.text_anchor_for_position(cursor_position, cx)?;
5254 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5255 if cursor_buffer != tail_buffer {
5256 return None;
5257 }
5258 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5259 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5260 cx.background_executor()
5261 .timer(Duration::from_millis(debounce))
5262 .await;
5263
5264 let highlights = if let Some(highlights) = cx
5265 .update(|cx| {
5266 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5267 })
5268 .ok()
5269 .flatten()
5270 {
5271 highlights.await.log_err()
5272 } else {
5273 None
5274 };
5275
5276 if let Some(highlights) = highlights {
5277 this.update(cx, |this, cx| {
5278 if this.pending_rename.is_some() {
5279 return;
5280 }
5281
5282 let buffer_id = cursor_position.buffer_id;
5283 let buffer = this.buffer.read(cx);
5284 if !buffer
5285 .text_anchor_for_position(cursor_position, cx)
5286 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5287 {
5288 return;
5289 }
5290
5291 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5292 let mut write_ranges = Vec::new();
5293 let mut read_ranges = Vec::new();
5294 for highlight in highlights {
5295 for (excerpt_id, excerpt_range) in
5296 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5297 {
5298 let start = highlight
5299 .range
5300 .start
5301 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5302 let end = highlight
5303 .range
5304 .end
5305 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5306 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5307 continue;
5308 }
5309
5310 let range = Anchor {
5311 buffer_id,
5312 excerpt_id,
5313 text_anchor: start,
5314 diff_base_anchor: None,
5315 }..Anchor {
5316 buffer_id,
5317 excerpt_id,
5318 text_anchor: end,
5319 diff_base_anchor: None,
5320 };
5321 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5322 write_ranges.push(range);
5323 } else {
5324 read_ranges.push(range);
5325 }
5326 }
5327 }
5328
5329 this.highlight_background::<DocumentHighlightRead>(
5330 &read_ranges,
5331 |theme| theme.editor_document_highlight_read_background,
5332 cx,
5333 );
5334 this.highlight_background::<DocumentHighlightWrite>(
5335 &write_ranges,
5336 |theme| theme.editor_document_highlight_write_background,
5337 cx,
5338 );
5339 cx.notify();
5340 })
5341 .log_err();
5342 }
5343 }));
5344 None
5345 }
5346
5347 pub fn refresh_selected_text_highlights(
5348 &mut self,
5349 window: &mut Window,
5350 cx: &mut Context<Editor>,
5351 ) {
5352 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5353 return;
5354 }
5355 self.selection_highlight_task.take();
5356 if !EditorSettings::get_global(cx).selection_highlight {
5357 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5358 return;
5359 }
5360 if self.selections.count() != 1 || self.selections.line_mode {
5361 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5362 return;
5363 }
5364 let selection = self.selections.newest::<Point>(cx);
5365 if selection.is_empty() || selection.start.row != selection.end.row {
5366 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5367 return;
5368 }
5369 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5370 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5371 cx.background_executor()
5372 .timer(Duration::from_millis(debounce))
5373 .await;
5374 let Some(Some(matches_task)) = editor
5375 .update_in(cx, |editor, _, cx| {
5376 if editor.selections.count() != 1 || editor.selections.line_mode {
5377 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5378 return None;
5379 }
5380 let selection = editor.selections.newest::<Point>(cx);
5381 if selection.is_empty() || selection.start.row != selection.end.row {
5382 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5383 return None;
5384 }
5385 let buffer = editor.buffer().read(cx).snapshot(cx);
5386 let query = buffer.text_for_range(selection.range()).collect::<String>();
5387 if query.trim().is_empty() {
5388 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5389 return None;
5390 }
5391 Some(cx.background_spawn(async move {
5392 let mut ranges = Vec::new();
5393 let selection_anchors = selection.range().to_anchors(&buffer);
5394 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5395 for (search_buffer, search_range, excerpt_id) in
5396 buffer.range_to_buffer_ranges(range)
5397 {
5398 ranges.extend(
5399 project::search::SearchQuery::text(
5400 query.clone(),
5401 false,
5402 false,
5403 false,
5404 Default::default(),
5405 Default::default(),
5406 None,
5407 )
5408 .unwrap()
5409 .search(search_buffer, Some(search_range.clone()))
5410 .await
5411 .into_iter()
5412 .filter_map(
5413 |match_range| {
5414 let start = search_buffer.anchor_after(
5415 search_range.start + match_range.start,
5416 );
5417 let end = search_buffer.anchor_before(
5418 search_range.start + match_range.end,
5419 );
5420 let range = Anchor::range_in_buffer(
5421 excerpt_id,
5422 search_buffer.remote_id(),
5423 start..end,
5424 );
5425 (range != selection_anchors).then_some(range)
5426 },
5427 ),
5428 );
5429 }
5430 }
5431 ranges
5432 }))
5433 })
5434 .log_err()
5435 else {
5436 return;
5437 };
5438 let matches = matches_task.await;
5439 editor
5440 .update_in(cx, |editor, _, cx| {
5441 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5442 if !matches.is_empty() {
5443 editor.highlight_background::<SelectedTextHighlight>(
5444 &matches,
5445 |theme| theme.editor_document_highlight_bracket_background,
5446 cx,
5447 )
5448 }
5449 })
5450 .log_err();
5451 }));
5452 }
5453
5454 pub fn refresh_inline_completion(
5455 &mut self,
5456 debounce: bool,
5457 user_requested: bool,
5458 window: &mut Window,
5459 cx: &mut Context<Self>,
5460 ) -> Option<()> {
5461 let provider = self.edit_prediction_provider()?;
5462 let cursor = self.selections.newest_anchor().head();
5463 let (buffer, cursor_buffer_position) =
5464 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5465
5466 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5467 self.discard_inline_completion(false, cx);
5468 return None;
5469 }
5470
5471 if !user_requested
5472 && (!self.should_show_edit_predictions()
5473 || !self.is_focused(window)
5474 || buffer.read(cx).is_empty())
5475 {
5476 self.discard_inline_completion(false, cx);
5477 return None;
5478 }
5479
5480 self.update_visible_inline_completion(window, cx);
5481 provider.refresh(
5482 self.project.clone(),
5483 buffer,
5484 cursor_buffer_position,
5485 debounce,
5486 cx,
5487 );
5488 Some(())
5489 }
5490
5491 fn show_edit_predictions_in_menu(&self) -> bool {
5492 match self.edit_prediction_settings {
5493 EditPredictionSettings::Disabled => false,
5494 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5495 }
5496 }
5497
5498 pub fn edit_predictions_enabled(&self) -> bool {
5499 match self.edit_prediction_settings {
5500 EditPredictionSettings::Disabled => false,
5501 EditPredictionSettings::Enabled { .. } => true,
5502 }
5503 }
5504
5505 fn edit_prediction_requires_modifier(&self) -> bool {
5506 match self.edit_prediction_settings {
5507 EditPredictionSettings::Disabled => false,
5508 EditPredictionSettings::Enabled {
5509 preview_requires_modifier,
5510 ..
5511 } => preview_requires_modifier,
5512 }
5513 }
5514
5515 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5516 if self.edit_prediction_provider.is_none() {
5517 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5518 } else {
5519 let selection = self.selections.newest_anchor();
5520 let cursor = selection.head();
5521
5522 if let Some((buffer, cursor_buffer_position)) =
5523 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5524 {
5525 self.edit_prediction_settings =
5526 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5527 }
5528 }
5529 }
5530
5531 fn edit_prediction_settings_at_position(
5532 &self,
5533 buffer: &Entity<Buffer>,
5534 buffer_position: language::Anchor,
5535 cx: &App,
5536 ) -> EditPredictionSettings {
5537 if self.mode != EditorMode::Full
5538 || !self.show_inline_completions_override.unwrap_or(true)
5539 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5540 {
5541 return EditPredictionSettings::Disabled;
5542 }
5543
5544 let buffer = buffer.read(cx);
5545
5546 let file = buffer.file();
5547
5548 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5549 return EditPredictionSettings::Disabled;
5550 };
5551
5552 let by_provider = matches!(
5553 self.menu_inline_completions_policy,
5554 MenuInlineCompletionsPolicy::ByProvider
5555 );
5556
5557 let show_in_menu = by_provider
5558 && self
5559 .edit_prediction_provider
5560 .as_ref()
5561 .map_or(false, |provider| {
5562 provider.provider.show_completions_in_menu()
5563 });
5564
5565 let preview_requires_modifier =
5566 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5567
5568 EditPredictionSettings::Enabled {
5569 show_in_menu,
5570 preview_requires_modifier,
5571 }
5572 }
5573
5574 fn should_show_edit_predictions(&self) -> bool {
5575 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5576 }
5577
5578 pub fn edit_prediction_preview_is_active(&self) -> bool {
5579 matches!(
5580 self.edit_prediction_preview,
5581 EditPredictionPreview::Active { .. }
5582 )
5583 }
5584
5585 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5586 let cursor = self.selections.newest_anchor().head();
5587 if let Some((buffer, cursor_position)) =
5588 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5589 {
5590 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5591 } else {
5592 false
5593 }
5594 }
5595
5596 fn edit_predictions_enabled_in_buffer(
5597 &self,
5598 buffer: &Entity<Buffer>,
5599 buffer_position: language::Anchor,
5600 cx: &App,
5601 ) -> bool {
5602 maybe!({
5603 if self.read_only(cx) {
5604 return Some(false);
5605 }
5606 let provider = self.edit_prediction_provider()?;
5607 if !provider.is_enabled(&buffer, buffer_position, cx) {
5608 return Some(false);
5609 }
5610 let buffer = buffer.read(cx);
5611 let Some(file) = buffer.file() else {
5612 return Some(true);
5613 };
5614 let settings = all_language_settings(Some(file), cx);
5615 Some(settings.edit_predictions_enabled_for_file(file, cx))
5616 })
5617 .unwrap_or(false)
5618 }
5619
5620 fn cycle_inline_completion(
5621 &mut self,
5622 direction: Direction,
5623 window: &mut Window,
5624 cx: &mut Context<Self>,
5625 ) -> Option<()> {
5626 let provider = self.edit_prediction_provider()?;
5627 let cursor = self.selections.newest_anchor().head();
5628 let (buffer, cursor_buffer_position) =
5629 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5630 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5631 return None;
5632 }
5633
5634 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5635 self.update_visible_inline_completion(window, cx);
5636
5637 Some(())
5638 }
5639
5640 pub fn show_inline_completion(
5641 &mut self,
5642 _: &ShowEditPrediction,
5643 window: &mut Window,
5644 cx: &mut Context<Self>,
5645 ) {
5646 if !self.has_active_inline_completion() {
5647 self.refresh_inline_completion(false, true, window, cx);
5648 return;
5649 }
5650
5651 self.update_visible_inline_completion(window, cx);
5652 }
5653
5654 pub fn display_cursor_names(
5655 &mut self,
5656 _: &DisplayCursorNames,
5657 window: &mut Window,
5658 cx: &mut Context<Self>,
5659 ) {
5660 self.show_cursor_names(window, cx);
5661 }
5662
5663 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5664 self.show_cursor_names = true;
5665 cx.notify();
5666 cx.spawn_in(window, async move |this, cx| {
5667 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5668 this.update(cx, |this, cx| {
5669 this.show_cursor_names = false;
5670 cx.notify()
5671 })
5672 .ok()
5673 })
5674 .detach();
5675 }
5676
5677 pub fn next_edit_prediction(
5678 &mut self,
5679 _: &NextEditPrediction,
5680 window: &mut Window,
5681 cx: &mut Context<Self>,
5682 ) {
5683 if self.has_active_inline_completion() {
5684 self.cycle_inline_completion(Direction::Next, window, cx);
5685 } else {
5686 let is_copilot_disabled = self
5687 .refresh_inline_completion(false, true, window, cx)
5688 .is_none();
5689 if is_copilot_disabled {
5690 cx.propagate();
5691 }
5692 }
5693 }
5694
5695 pub fn previous_edit_prediction(
5696 &mut self,
5697 _: &PreviousEditPrediction,
5698 window: &mut Window,
5699 cx: &mut Context<Self>,
5700 ) {
5701 if self.has_active_inline_completion() {
5702 self.cycle_inline_completion(Direction::Prev, window, cx);
5703 } else {
5704 let is_copilot_disabled = self
5705 .refresh_inline_completion(false, true, window, cx)
5706 .is_none();
5707 if is_copilot_disabled {
5708 cx.propagate();
5709 }
5710 }
5711 }
5712
5713 pub fn accept_edit_prediction(
5714 &mut self,
5715 _: &AcceptEditPrediction,
5716 window: &mut Window,
5717 cx: &mut Context<Self>,
5718 ) {
5719 if self.show_edit_predictions_in_menu() {
5720 self.hide_context_menu(window, cx);
5721 }
5722
5723 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5724 return;
5725 };
5726
5727 self.report_inline_completion_event(
5728 active_inline_completion.completion_id.clone(),
5729 true,
5730 cx,
5731 );
5732
5733 match &active_inline_completion.completion {
5734 InlineCompletion::Move { target, .. } => {
5735 let target = *target;
5736
5737 if let Some(position_map) = &self.last_position_map {
5738 if position_map
5739 .visible_row_range
5740 .contains(&target.to_display_point(&position_map.snapshot).row())
5741 || !self.edit_prediction_requires_modifier()
5742 {
5743 self.unfold_ranges(&[target..target], true, false, cx);
5744 // Note that this is also done in vim's handler of the Tab action.
5745 self.change_selections(
5746 Some(Autoscroll::newest()),
5747 window,
5748 cx,
5749 |selections| {
5750 selections.select_anchor_ranges([target..target]);
5751 },
5752 );
5753 self.clear_row_highlights::<EditPredictionPreview>();
5754
5755 self.edit_prediction_preview
5756 .set_previous_scroll_position(None);
5757 } else {
5758 self.edit_prediction_preview
5759 .set_previous_scroll_position(Some(
5760 position_map.snapshot.scroll_anchor,
5761 ));
5762
5763 self.highlight_rows::<EditPredictionPreview>(
5764 target..target,
5765 cx.theme().colors().editor_highlighted_line_background,
5766 true,
5767 cx,
5768 );
5769 self.request_autoscroll(Autoscroll::fit(), cx);
5770 }
5771 }
5772 }
5773 InlineCompletion::Edit { edits, .. } => {
5774 if let Some(provider) = self.edit_prediction_provider() {
5775 provider.accept(cx);
5776 }
5777
5778 let snapshot = self.buffer.read(cx).snapshot(cx);
5779 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5780
5781 self.buffer.update(cx, |buffer, cx| {
5782 buffer.edit(edits.iter().cloned(), None, cx)
5783 });
5784
5785 self.change_selections(None, window, cx, |s| {
5786 s.select_anchor_ranges([last_edit_end..last_edit_end])
5787 });
5788
5789 self.update_visible_inline_completion(window, cx);
5790 if self.active_inline_completion.is_none() {
5791 self.refresh_inline_completion(true, true, window, cx);
5792 }
5793
5794 cx.notify();
5795 }
5796 }
5797
5798 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5799 }
5800
5801 pub fn accept_partial_inline_completion(
5802 &mut self,
5803 _: &AcceptPartialEditPrediction,
5804 window: &mut Window,
5805 cx: &mut Context<Self>,
5806 ) {
5807 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5808 return;
5809 };
5810 if self.selections.count() != 1 {
5811 return;
5812 }
5813
5814 self.report_inline_completion_event(
5815 active_inline_completion.completion_id.clone(),
5816 true,
5817 cx,
5818 );
5819
5820 match &active_inline_completion.completion {
5821 InlineCompletion::Move { target, .. } => {
5822 let target = *target;
5823 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5824 selections.select_anchor_ranges([target..target]);
5825 });
5826 }
5827 InlineCompletion::Edit { edits, .. } => {
5828 // Find an insertion that starts at the cursor position.
5829 let snapshot = self.buffer.read(cx).snapshot(cx);
5830 let cursor_offset = self.selections.newest::<usize>(cx).head();
5831 let insertion = edits.iter().find_map(|(range, text)| {
5832 let range = range.to_offset(&snapshot);
5833 if range.is_empty() && range.start == cursor_offset {
5834 Some(text)
5835 } else {
5836 None
5837 }
5838 });
5839
5840 if let Some(text) = insertion {
5841 let mut partial_completion = text
5842 .chars()
5843 .by_ref()
5844 .take_while(|c| c.is_alphabetic())
5845 .collect::<String>();
5846 if partial_completion.is_empty() {
5847 partial_completion = text
5848 .chars()
5849 .by_ref()
5850 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5851 .collect::<String>();
5852 }
5853
5854 cx.emit(EditorEvent::InputHandled {
5855 utf16_range_to_replace: None,
5856 text: partial_completion.clone().into(),
5857 });
5858
5859 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5860
5861 self.refresh_inline_completion(true, true, window, cx);
5862 cx.notify();
5863 } else {
5864 self.accept_edit_prediction(&Default::default(), window, cx);
5865 }
5866 }
5867 }
5868 }
5869
5870 fn discard_inline_completion(
5871 &mut self,
5872 should_report_inline_completion_event: bool,
5873 cx: &mut Context<Self>,
5874 ) -> bool {
5875 if should_report_inline_completion_event {
5876 let completion_id = self
5877 .active_inline_completion
5878 .as_ref()
5879 .and_then(|active_completion| active_completion.completion_id.clone());
5880
5881 self.report_inline_completion_event(completion_id, false, cx);
5882 }
5883
5884 if let Some(provider) = self.edit_prediction_provider() {
5885 provider.discard(cx);
5886 }
5887
5888 self.take_active_inline_completion(cx)
5889 }
5890
5891 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5892 let Some(provider) = self.edit_prediction_provider() else {
5893 return;
5894 };
5895
5896 let Some((_, buffer, _)) = self
5897 .buffer
5898 .read(cx)
5899 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5900 else {
5901 return;
5902 };
5903
5904 let extension = buffer
5905 .read(cx)
5906 .file()
5907 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5908
5909 let event_type = match accepted {
5910 true => "Edit Prediction Accepted",
5911 false => "Edit Prediction Discarded",
5912 };
5913 telemetry::event!(
5914 event_type,
5915 provider = provider.name(),
5916 prediction_id = id,
5917 suggestion_accepted = accepted,
5918 file_extension = extension,
5919 );
5920 }
5921
5922 pub fn has_active_inline_completion(&self) -> bool {
5923 self.active_inline_completion.is_some()
5924 }
5925
5926 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5927 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5928 return false;
5929 };
5930
5931 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5932 self.clear_highlights::<InlineCompletionHighlight>(cx);
5933 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5934 true
5935 }
5936
5937 /// Returns true when we're displaying the edit prediction popover below the cursor
5938 /// like we are not previewing and the LSP autocomplete menu is visible
5939 /// or we are in `when_holding_modifier` mode.
5940 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5941 if self.edit_prediction_preview_is_active()
5942 || !self.show_edit_predictions_in_menu()
5943 || !self.edit_predictions_enabled()
5944 {
5945 return false;
5946 }
5947
5948 if self.has_visible_completions_menu() {
5949 return true;
5950 }
5951
5952 has_completion && self.edit_prediction_requires_modifier()
5953 }
5954
5955 fn handle_modifiers_changed(
5956 &mut self,
5957 modifiers: Modifiers,
5958 position_map: &PositionMap,
5959 window: &mut Window,
5960 cx: &mut Context<Self>,
5961 ) {
5962 if self.show_edit_predictions_in_menu() {
5963 self.update_edit_prediction_preview(&modifiers, window, cx);
5964 }
5965
5966 self.update_selection_mode(&modifiers, position_map, window, cx);
5967
5968 let mouse_position = window.mouse_position();
5969 if !position_map.text_hitbox.is_hovered(window) {
5970 return;
5971 }
5972
5973 self.update_hovered_link(
5974 position_map.point_for_position(mouse_position),
5975 &position_map.snapshot,
5976 modifiers,
5977 window,
5978 cx,
5979 )
5980 }
5981
5982 fn update_selection_mode(
5983 &mut self,
5984 modifiers: &Modifiers,
5985 position_map: &PositionMap,
5986 window: &mut Window,
5987 cx: &mut Context<Self>,
5988 ) {
5989 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
5990 return;
5991 }
5992
5993 let mouse_position = window.mouse_position();
5994 let point_for_position = position_map.point_for_position(mouse_position);
5995 let position = point_for_position.previous_valid;
5996
5997 self.select(
5998 SelectPhase::BeginColumnar {
5999 position,
6000 reset: false,
6001 goal_column: point_for_position.exact_unclipped.column(),
6002 },
6003 window,
6004 cx,
6005 );
6006 }
6007
6008 fn update_edit_prediction_preview(
6009 &mut self,
6010 modifiers: &Modifiers,
6011 window: &mut Window,
6012 cx: &mut Context<Self>,
6013 ) {
6014 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6015 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6016 return;
6017 };
6018
6019 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6020 if matches!(
6021 self.edit_prediction_preview,
6022 EditPredictionPreview::Inactive { .. }
6023 ) {
6024 self.edit_prediction_preview = EditPredictionPreview::Active {
6025 previous_scroll_position: None,
6026 since: Instant::now(),
6027 };
6028
6029 self.update_visible_inline_completion(window, cx);
6030 cx.notify();
6031 }
6032 } else if let EditPredictionPreview::Active {
6033 previous_scroll_position,
6034 since,
6035 } = self.edit_prediction_preview
6036 {
6037 if let (Some(previous_scroll_position), Some(position_map)) =
6038 (previous_scroll_position, self.last_position_map.as_ref())
6039 {
6040 self.set_scroll_position(
6041 previous_scroll_position
6042 .scroll_position(&position_map.snapshot.display_snapshot),
6043 window,
6044 cx,
6045 );
6046 }
6047
6048 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6049 released_too_fast: since.elapsed() < Duration::from_millis(200),
6050 };
6051 self.clear_row_highlights::<EditPredictionPreview>();
6052 self.update_visible_inline_completion(window, cx);
6053 cx.notify();
6054 }
6055 }
6056
6057 fn update_visible_inline_completion(
6058 &mut self,
6059 _window: &mut Window,
6060 cx: &mut Context<Self>,
6061 ) -> Option<()> {
6062 let selection = self.selections.newest_anchor();
6063 let cursor = selection.head();
6064 let multibuffer = self.buffer.read(cx).snapshot(cx);
6065 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6066 let excerpt_id = cursor.excerpt_id;
6067
6068 let show_in_menu = self.show_edit_predictions_in_menu();
6069 let completions_menu_has_precedence = !show_in_menu
6070 && (self.context_menu.borrow().is_some()
6071 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6072
6073 if completions_menu_has_precedence
6074 || !offset_selection.is_empty()
6075 || self
6076 .active_inline_completion
6077 .as_ref()
6078 .map_or(false, |completion| {
6079 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6080 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6081 !invalidation_range.contains(&offset_selection.head())
6082 })
6083 {
6084 self.discard_inline_completion(false, cx);
6085 return None;
6086 }
6087
6088 self.take_active_inline_completion(cx);
6089 let Some(provider) = self.edit_prediction_provider() else {
6090 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6091 return None;
6092 };
6093
6094 let (buffer, cursor_buffer_position) =
6095 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6096
6097 self.edit_prediction_settings =
6098 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6099
6100 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6101
6102 if self.edit_prediction_indent_conflict {
6103 let cursor_point = cursor.to_point(&multibuffer);
6104
6105 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6106
6107 if let Some((_, indent)) = indents.iter().next() {
6108 if indent.len == cursor_point.column {
6109 self.edit_prediction_indent_conflict = false;
6110 }
6111 }
6112 }
6113
6114 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6115 let edits = inline_completion
6116 .edits
6117 .into_iter()
6118 .flat_map(|(range, new_text)| {
6119 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6120 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6121 Some((start..end, new_text))
6122 })
6123 .collect::<Vec<_>>();
6124 if edits.is_empty() {
6125 return None;
6126 }
6127
6128 let first_edit_start = edits.first().unwrap().0.start;
6129 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6130 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6131
6132 let last_edit_end = edits.last().unwrap().0.end;
6133 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6134 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6135
6136 let cursor_row = cursor.to_point(&multibuffer).row;
6137
6138 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6139
6140 let mut inlay_ids = Vec::new();
6141 let invalidation_row_range;
6142 let move_invalidation_row_range = if cursor_row < edit_start_row {
6143 Some(cursor_row..edit_end_row)
6144 } else if cursor_row > edit_end_row {
6145 Some(edit_start_row..cursor_row)
6146 } else {
6147 None
6148 };
6149 let is_move =
6150 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6151 let completion = if is_move {
6152 invalidation_row_range =
6153 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6154 let target = first_edit_start;
6155 InlineCompletion::Move { target, snapshot }
6156 } else {
6157 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6158 && !self.inline_completions_hidden_for_vim_mode;
6159
6160 if show_completions_in_buffer {
6161 if edits
6162 .iter()
6163 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6164 {
6165 let mut inlays = Vec::new();
6166 for (range, new_text) in &edits {
6167 let inlay = Inlay::inline_completion(
6168 post_inc(&mut self.next_inlay_id),
6169 range.start,
6170 new_text.as_str(),
6171 );
6172 inlay_ids.push(inlay.id);
6173 inlays.push(inlay);
6174 }
6175
6176 self.splice_inlays(&[], inlays, cx);
6177 } else {
6178 let background_color = cx.theme().status().deleted_background;
6179 self.highlight_text::<InlineCompletionHighlight>(
6180 edits.iter().map(|(range, _)| range.clone()).collect(),
6181 HighlightStyle {
6182 background_color: Some(background_color),
6183 ..Default::default()
6184 },
6185 cx,
6186 );
6187 }
6188 }
6189
6190 invalidation_row_range = edit_start_row..edit_end_row;
6191
6192 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6193 if provider.show_tab_accept_marker() {
6194 EditDisplayMode::TabAccept
6195 } else {
6196 EditDisplayMode::Inline
6197 }
6198 } else {
6199 EditDisplayMode::DiffPopover
6200 };
6201
6202 InlineCompletion::Edit {
6203 edits,
6204 edit_preview: inline_completion.edit_preview,
6205 display_mode,
6206 snapshot,
6207 }
6208 };
6209
6210 let invalidation_range = multibuffer
6211 .anchor_before(Point::new(invalidation_row_range.start, 0))
6212 ..multibuffer.anchor_after(Point::new(
6213 invalidation_row_range.end,
6214 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6215 ));
6216
6217 self.stale_inline_completion_in_menu = None;
6218 self.active_inline_completion = Some(InlineCompletionState {
6219 inlay_ids,
6220 completion,
6221 completion_id: inline_completion.id,
6222 invalidation_range,
6223 });
6224
6225 cx.notify();
6226
6227 Some(())
6228 }
6229
6230 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6231 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6232 }
6233
6234 fn render_code_actions_indicator(
6235 &self,
6236 _style: &EditorStyle,
6237 row: DisplayRow,
6238 is_active: bool,
6239 breakpoint: Option<&(Anchor, Breakpoint)>,
6240 cx: &mut Context<Self>,
6241 ) -> Option<IconButton> {
6242 let color = Color::Muted;
6243 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6244 let show_tooltip = !self.context_menu_visible();
6245
6246 if self.available_code_actions.is_some() {
6247 Some(
6248 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6249 .shape(ui::IconButtonShape::Square)
6250 .icon_size(IconSize::XSmall)
6251 .icon_color(color)
6252 .toggle_state(is_active)
6253 .when(show_tooltip, |this| {
6254 this.tooltip({
6255 let focus_handle = self.focus_handle.clone();
6256 move |window, cx| {
6257 Tooltip::for_action_in(
6258 "Toggle Code Actions",
6259 &ToggleCodeActions {
6260 deployed_from_indicator: None,
6261 },
6262 &focus_handle,
6263 window,
6264 cx,
6265 )
6266 }
6267 })
6268 })
6269 .on_click(cx.listener(move |editor, _e, window, cx| {
6270 window.focus(&editor.focus_handle(cx));
6271 editor.toggle_code_actions(
6272 &ToggleCodeActions {
6273 deployed_from_indicator: Some(row),
6274 },
6275 window,
6276 cx,
6277 );
6278 }))
6279 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6280 editor.set_breakpoint_context_menu(
6281 row,
6282 position,
6283 event.down.position,
6284 window,
6285 cx,
6286 );
6287 })),
6288 )
6289 } else {
6290 None
6291 }
6292 }
6293
6294 fn clear_tasks(&mut self) {
6295 self.tasks.clear()
6296 }
6297
6298 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6299 if self.tasks.insert(key, value).is_some() {
6300 // This case should hopefully be rare, but just in case...
6301 log::error!(
6302 "multiple different run targets found on a single line, only the last target will be rendered"
6303 )
6304 }
6305 }
6306
6307 /// Get all display points of breakpoints that will be rendered within editor
6308 ///
6309 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6310 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6311 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6312 fn active_breakpoints(
6313 &self,
6314 range: Range<DisplayRow>,
6315 window: &mut Window,
6316 cx: &mut Context<Self>,
6317 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6318 let mut breakpoint_display_points = HashMap::default();
6319
6320 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6321 return breakpoint_display_points;
6322 };
6323
6324 let snapshot = self.snapshot(window, cx);
6325
6326 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6327 let Some(project) = self.project.as_ref() else {
6328 return breakpoint_display_points;
6329 };
6330
6331 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6332 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6333
6334 for (buffer_snapshot, range, excerpt_id) in
6335 multi_buffer_snapshot.range_to_buffer_ranges(range)
6336 {
6337 let Some(buffer) = project.read_with(cx, |this, cx| {
6338 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6339 }) else {
6340 continue;
6341 };
6342 let breakpoints = breakpoint_store.read(cx).breakpoints(
6343 &buffer,
6344 Some(
6345 buffer_snapshot.anchor_before(range.start)
6346 ..buffer_snapshot.anchor_after(range.end),
6347 ),
6348 buffer_snapshot,
6349 cx,
6350 );
6351 for (anchor, breakpoint) in breakpoints {
6352 let multi_buffer_anchor =
6353 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6354 let position = multi_buffer_anchor
6355 .to_point(&multi_buffer_snapshot)
6356 .to_display_point(&snapshot);
6357
6358 breakpoint_display_points
6359 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6360 }
6361 }
6362
6363 breakpoint_display_points
6364 }
6365
6366 fn breakpoint_context_menu(
6367 &self,
6368 anchor: Anchor,
6369 window: &mut Window,
6370 cx: &mut Context<Self>,
6371 ) -> Entity<ui::ContextMenu> {
6372 let weak_editor = cx.weak_entity();
6373 let focus_handle = self.focus_handle(cx);
6374
6375 let row = self
6376 .buffer
6377 .read(cx)
6378 .snapshot(cx)
6379 .summary_for_anchor::<Point>(&anchor)
6380 .row;
6381
6382 let breakpoint = self
6383 .breakpoint_at_row(row, window, cx)
6384 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6385
6386 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6387 "Edit Log Breakpoint"
6388 } else {
6389 "Set Log Breakpoint"
6390 };
6391
6392 let condition_breakpoint_msg = if breakpoint
6393 .as_ref()
6394 .is_some_and(|bp| bp.1.condition.is_some())
6395 {
6396 "Edit Condition Breakpoint"
6397 } else {
6398 "Set Condition Breakpoint"
6399 };
6400
6401 let hit_condition_breakpoint_msg = if breakpoint
6402 .as_ref()
6403 .is_some_and(|bp| bp.1.hit_condition.is_some())
6404 {
6405 "Edit Hit Condition Breakpoint"
6406 } else {
6407 "Set Hit Condition Breakpoint"
6408 };
6409
6410 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6411 "Unset Breakpoint"
6412 } else {
6413 "Set Breakpoint"
6414 };
6415
6416 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6417 BreakpointState::Enabled => Some("Disable"),
6418 BreakpointState::Disabled => Some("Enable"),
6419 });
6420
6421 let (anchor, breakpoint) =
6422 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6423
6424 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6425 menu.on_blur_subscription(Subscription::new(|| {}))
6426 .context(focus_handle)
6427 .when_some(toggle_state_msg, |this, msg| {
6428 this.entry(msg, None, {
6429 let weak_editor = weak_editor.clone();
6430 let breakpoint = breakpoint.clone();
6431 move |_window, cx| {
6432 weak_editor
6433 .update(cx, |this, cx| {
6434 this.edit_breakpoint_at_anchor(
6435 anchor,
6436 breakpoint.as_ref().clone(),
6437 BreakpointEditAction::InvertState,
6438 cx,
6439 );
6440 })
6441 .log_err();
6442 }
6443 })
6444 })
6445 .entry(set_breakpoint_msg, None, {
6446 let weak_editor = weak_editor.clone();
6447 let breakpoint = breakpoint.clone();
6448 move |_window, cx| {
6449 weak_editor
6450 .update(cx, |this, cx| {
6451 this.edit_breakpoint_at_anchor(
6452 anchor,
6453 breakpoint.as_ref().clone(),
6454 BreakpointEditAction::Toggle,
6455 cx,
6456 );
6457 })
6458 .log_err();
6459 }
6460 })
6461 .entry(log_breakpoint_msg, None, {
6462 let breakpoint = breakpoint.clone();
6463 let weak_editor = weak_editor.clone();
6464 move |window, cx| {
6465 weak_editor
6466 .update(cx, |this, cx| {
6467 this.add_edit_breakpoint_block(
6468 anchor,
6469 breakpoint.as_ref(),
6470 BreakpointPromptEditAction::Log,
6471 window,
6472 cx,
6473 );
6474 })
6475 .log_err();
6476 }
6477 })
6478 .entry(condition_breakpoint_msg, None, {
6479 let breakpoint = breakpoint.clone();
6480 let weak_editor = weak_editor.clone();
6481 move |window, cx| {
6482 weak_editor
6483 .update(cx, |this, cx| {
6484 this.add_edit_breakpoint_block(
6485 anchor,
6486 breakpoint.as_ref(),
6487 BreakpointPromptEditAction::Condition,
6488 window,
6489 cx,
6490 );
6491 })
6492 .log_err();
6493 }
6494 })
6495 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6496 weak_editor
6497 .update(cx, |this, cx| {
6498 this.add_edit_breakpoint_block(
6499 anchor,
6500 breakpoint.as_ref(),
6501 BreakpointPromptEditAction::HitCondition,
6502 window,
6503 cx,
6504 );
6505 })
6506 .log_err();
6507 })
6508 })
6509 }
6510
6511 fn render_breakpoint(
6512 &self,
6513 position: Anchor,
6514 row: DisplayRow,
6515 breakpoint: &Breakpoint,
6516 cx: &mut Context<Self>,
6517 ) -> IconButton {
6518 let (color, icon) = {
6519 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6520 (false, false) => ui::IconName::DebugBreakpoint,
6521 (true, false) => ui::IconName::DebugLogBreakpoint,
6522 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6523 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6524 };
6525
6526 let color = if self
6527 .gutter_breakpoint_indicator
6528 .0
6529 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6530 {
6531 Color::Hint
6532 } else {
6533 Color::Debugger
6534 };
6535
6536 (color, icon)
6537 };
6538
6539 let breakpoint = Arc::from(breakpoint.clone());
6540
6541 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6542 .icon_size(IconSize::XSmall)
6543 .size(ui::ButtonSize::None)
6544 .icon_color(color)
6545 .style(ButtonStyle::Transparent)
6546 .on_click(cx.listener({
6547 let breakpoint = breakpoint.clone();
6548
6549 move |editor, event: &ClickEvent, window, cx| {
6550 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6551 BreakpointEditAction::InvertState
6552 } else {
6553 BreakpointEditAction::Toggle
6554 };
6555
6556 window.focus(&editor.focus_handle(cx));
6557 editor.edit_breakpoint_at_anchor(
6558 position,
6559 breakpoint.as_ref().clone(),
6560 edit_action,
6561 cx,
6562 );
6563 }
6564 }))
6565 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6566 editor.set_breakpoint_context_menu(
6567 row,
6568 Some(position),
6569 event.down.position,
6570 window,
6571 cx,
6572 );
6573 }))
6574 }
6575
6576 fn build_tasks_context(
6577 project: &Entity<Project>,
6578 buffer: &Entity<Buffer>,
6579 buffer_row: u32,
6580 tasks: &Arc<RunnableTasks>,
6581 cx: &mut Context<Self>,
6582 ) -> Task<Option<task::TaskContext>> {
6583 let position = Point::new(buffer_row, tasks.column);
6584 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6585 let location = Location {
6586 buffer: buffer.clone(),
6587 range: range_start..range_start,
6588 };
6589 // Fill in the environmental variables from the tree-sitter captures
6590 let mut captured_task_variables = TaskVariables::default();
6591 for (capture_name, value) in tasks.extra_variables.clone() {
6592 captured_task_variables.insert(
6593 task::VariableName::Custom(capture_name.into()),
6594 value.clone(),
6595 );
6596 }
6597 project.update(cx, |project, cx| {
6598 project.task_store().update(cx, |task_store, cx| {
6599 task_store.task_context_for_location(captured_task_variables, location, cx)
6600 })
6601 })
6602 }
6603
6604 pub fn spawn_nearest_task(
6605 &mut self,
6606 action: &SpawnNearestTask,
6607 window: &mut Window,
6608 cx: &mut Context<Self>,
6609 ) {
6610 let Some((workspace, _)) = self.workspace.clone() else {
6611 return;
6612 };
6613 let Some(project) = self.project.clone() else {
6614 return;
6615 };
6616
6617 // Try to find a closest, enclosing node using tree-sitter that has a
6618 // task
6619 let Some((buffer, buffer_row, tasks)) = self
6620 .find_enclosing_node_task(cx)
6621 // Or find the task that's closest in row-distance.
6622 .or_else(|| self.find_closest_task(cx))
6623 else {
6624 return;
6625 };
6626
6627 let reveal_strategy = action.reveal;
6628 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6629 cx.spawn_in(window, async move |_, cx| {
6630 let context = task_context.await?;
6631 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6632
6633 let resolved = resolved_task.resolved.as_mut()?;
6634 resolved.reveal = reveal_strategy;
6635
6636 workspace
6637 .update(cx, |workspace, cx| {
6638 workspace::tasks::schedule_resolved_task(
6639 workspace,
6640 task_source_kind,
6641 resolved_task,
6642 false,
6643 cx,
6644 );
6645 })
6646 .ok()
6647 })
6648 .detach();
6649 }
6650
6651 fn find_closest_task(
6652 &mut self,
6653 cx: &mut Context<Self>,
6654 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6655 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6656
6657 let ((buffer_id, row), tasks) = self
6658 .tasks
6659 .iter()
6660 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6661
6662 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6663 let tasks = Arc::new(tasks.to_owned());
6664 Some((buffer, *row, tasks))
6665 }
6666
6667 fn find_enclosing_node_task(
6668 &mut self,
6669 cx: &mut Context<Self>,
6670 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6671 let snapshot = self.buffer.read(cx).snapshot(cx);
6672 let offset = self.selections.newest::<usize>(cx).head();
6673 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6674 let buffer_id = excerpt.buffer().remote_id();
6675
6676 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6677 let mut cursor = layer.node().walk();
6678
6679 while cursor.goto_first_child_for_byte(offset).is_some() {
6680 if cursor.node().end_byte() == offset {
6681 cursor.goto_next_sibling();
6682 }
6683 }
6684
6685 // Ascend to the smallest ancestor that contains the range and has a task.
6686 loop {
6687 let node = cursor.node();
6688 let node_range = node.byte_range();
6689 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6690
6691 // Check if this node contains our offset
6692 if node_range.start <= offset && node_range.end >= offset {
6693 // If it contains offset, check for task
6694 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6695 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6696 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6697 }
6698 }
6699
6700 if !cursor.goto_parent() {
6701 break;
6702 }
6703 }
6704 None
6705 }
6706
6707 fn render_run_indicator(
6708 &self,
6709 _style: &EditorStyle,
6710 is_active: bool,
6711 row: DisplayRow,
6712 breakpoint: Option<(Anchor, Breakpoint)>,
6713 cx: &mut Context<Self>,
6714 ) -> IconButton {
6715 let color = Color::Muted;
6716 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6717
6718 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6719 .shape(ui::IconButtonShape::Square)
6720 .icon_size(IconSize::XSmall)
6721 .icon_color(color)
6722 .toggle_state(is_active)
6723 .on_click(cx.listener(move |editor, _e, window, cx| {
6724 window.focus(&editor.focus_handle(cx));
6725 editor.toggle_code_actions(
6726 &ToggleCodeActions {
6727 deployed_from_indicator: Some(row),
6728 },
6729 window,
6730 cx,
6731 );
6732 }))
6733 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6734 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6735 }))
6736 }
6737
6738 pub fn context_menu_visible(&self) -> bool {
6739 !self.edit_prediction_preview_is_active()
6740 && self
6741 .context_menu
6742 .borrow()
6743 .as_ref()
6744 .map_or(false, |menu| menu.visible())
6745 }
6746
6747 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6748 self.context_menu
6749 .borrow()
6750 .as_ref()
6751 .map(|menu| menu.origin())
6752 }
6753
6754 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6755 self.context_menu_options = Some(options);
6756 }
6757
6758 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6759 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6760
6761 fn render_edit_prediction_popover(
6762 &mut self,
6763 text_bounds: &Bounds<Pixels>,
6764 content_origin: gpui::Point<Pixels>,
6765 editor_snapshot: &EditorSnapshot,
6766 visible_row_range: Range<DisplayRow>,
6767 scroll_top: f32,
6768 scroll_bottom: f32,
6769 line_layouts: &[LineWithInvisibles],
6770 line_height: Pixels,
6771 scroll_pixel_position: gpui::Point<Pixels>,
6772 newest_selection_head: Option<DisplayPoint>,
6773 editor_width: Pixels,
6774 style: &EditorStyle,
6775 window: &mut Window,
6776 cx: &mut App,
6777 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6778 let active_inline_completion = self.active_inline_completion.as_ref()?;
6779
6780 if self.edit_prediction_visible_in_cursor_popover(true) {
6781 return None;
6782 }
6783
6784 match &active_inline_completion.completion {
6785 InlineCompletion::Move { target, .. } => {
6786 let target_display_point = target.to_display_point(editor_snapshot);
6787
6788 if self.edit_prediction_requires_modifier() {
6789 if !self.edit_prediction_preview_is_active() {
6790 return None;
6791 }
6792
6793 self.render_edit_prediction_modifier_jump_popover(
6794 text_bounds,
6795 content_origin,
6796 visible_row_range,
6797 line_layouts,
6798 line_height,
6799 scroll_pixel_position,
6800 newest_selection_head,
6801 target_display_point,
6802 window,
6803 cx,
6804 )
6805 } else {
6806 self.render_edit_prediction_eager_jump_popover(
6807 text_bounds,
6808 content_origin,
6809 editor_snapshot,
6810 visible_row_range,
6811 scroll_top,
6812 scroll_bottom,
6813 line_height,
6814 scroll_pixel_position,
6815 target_display_point,
6816 editor_width,
6817 window,
6818 cx,
6819 )
6820 }
6821 }
6822 InlineCompletion::Edit {
6823 display_mode: EditDisplayMode::Inline,
6824 ..
6825 } => None,
6826 InlineCompletion::Edit {
6827 display_mode: EditDisplayMode::TabAccept,
6828 edits,
6829 ..
6830 } => {
6831 let range = &edits.first()?.0;
6832 let target_display_point = range.end.to_display_point(editor_snapshot);
6833
6834 self.render_edit_prediction_end_of_line_popover(
6835 "Accept",
6836 editor_snapshot,
6837 visible_row_range,
6838 target_display_point,
6839 line_height,
6840 scroll_pixel_position,
6841 content_origin,
6842 editor_width,
6843 window,
6844 cx,
6845 )
6846 }
6847 InlineCompletion::Edit {
6848 edits,
6849 edit_preview,
6850 display_mode: EditDisplayMode::DiffPopover,
6851 snapshot,
6852 } => self.render_edit_prediction_diff_popover(
6853 text_bounds,
6854 content_origin,
6855 editor_snapshot,
6856 visible_row_range,
6857 line_layouts,
6858 line_height,
6859 scroll_pixel_position,
6860 newest_selection_head,
6861 editor_width,
6862 style,
6863 edits,
6864 edit_preview,
6865 snapshot,
6866 window,
6867 cx,
6868 ),
6869 }
6870 }
6871
6872 fn render_edit_prediction_modifier_jump_popover(
6873 &mut self,
6874 text_bounds: &Bounds<Pixels>,
6875 content_origin: gpui::Point<Pixels>,
6876 visible_row_range: Range<DisplayRow>,
6877 line_layouts: &[LineWithInvisibles],
6878 line_height: Pixels,
6879 scroll_pixel_position: gpui::Point<Pixels>,
6880 newest_selection_head: Option<DisplayPoint>,
6881 target_display_point: DisplayPoint,
6882 window: &mut Window,
6883 cx: &mut App,
6884 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6885 let scrolled_content_origin =
6886 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6887
6888 const SCROLL_PADDING_Y: Pixels = px(12.);
6889
6890 if target_display_point.row() < visible_row_range.start {
6891 return self.render_edit_prediction_scroll_popover(
6892 |_| SCROLL_PADDING_Y,
6893 IconName::ArrowUp,
6894 visible_row_range,
6895 line_layouts,
6896 newest_selection_head,
6897 scrolled_content_origin,
6898 window,
6899 cx,
6900 );
6901 } else if target_display_point.row() >= visible_row_range.end {
6902 return self.render_edit_prediction_scroll_popover(
6903 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6904 IconName::ArrowDown,
6905 visible_row_range,
6906 line_layouts,
6907 newest_selection_head,
6908 scrolled_content_origin,
6909 window,
6910 cx,
6911 );
6912 }
6913
6914 const POLE_WIDTH: Pixels = px(2.);
6915
6916 let line_layout =
6917 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6918 let target_column = target_display_point.column() as usize;
6919
6920 let target_x = line_layout.x_for_index(target_column);
6921 let target_y =
6922 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6923
6924 let flag_on_right = target_x < text_bounds.size.width / 2.;
6925
6926 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6927 border_color.l += 0.001;
6928
6929 let mut element = v_flex()
6930 .items_end()
6931 .when(flag_on_right, |el| el.items_start())
6932 .child(if flag_on_right {
6933 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6934 .rounded_bl(px(0.))
6935 .rounded_tl(px(0.))
6936 .border_l_2()
6937 .border_color(border_color)
6938 } else {
6939 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6940 .rounded_br(px(0.))
6941 .rounded_tr(px(0.))
6942 .border_r_2()
6943 .border_color(border_color)
6944 })
6945 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6946 .into_any();
6947
6948 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6949
6950 let mut origin = scrolled_content_origin + point(target_x, target_y)
6951 - point(
6952 if flag_on_right {
6953 POLE_WIDTH
6954 } else {
6955 size.width - POLE_WIDTH
6956 },
6957 size.height - line_height,
6958 );
6959
6960 origin.x = origin.x.max(content_origin.x);
6961
6962 element.prepaint_at(origin, window, cx);
6963
6964 Some((element, origin))
6965 }
6966
6967 fn render_edit_prediction_scroll_popover(
6968 &mut self,
6969 to_y: impl Fn(Size<Pixels>) -> Pixels,
6970 scroll_icon: IconName,
6971 visible_row_range: Range<DisplayRow>,
6972 line_layouts: &[LineWithInvisibles],
6973 newest_selection_head: Option<DisplayPoint>,
6974 scrolled_content_origin: gpui::Point<Pixels>,
6975 window: &mut Window,
6976 cx: &mut App,
6977 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6978 let mut element = self
6979 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
6980 .into_any();
6981
6982 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6983
6984 let cursor = newest_selection_head?;
6985 let cursor_row_layout =
6986 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
6987 let cursor_column = cursor.column() as usize;
6988
6989 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
6990
6991 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
6992
6993 element.prepaint_at(origin, window, cx);
6994 Some((element, origin))
6995 }
6996
6997 fn render_edit_prediction_eager_jump_popover(
6998 &mut self,
6999 text_bounds: &Bounds<Pixels>,
7000 content_origin: gpui::Point<Pixels>,
7001 editor_snapshot: &EditorSnapshot,
7002 visible_row_range: Range<DisplayRow>,
7003 scroll_top: f32,
7004 scroll_bottom: f32,
7005 line_height: Pixels,
7006 scroll_pixel_position: gpui::Point<Pixels>,
7007 target_display_point: DisplayPoint,
7008 editor_width: Pixels,
7009 window: &mut Window,
7010 cx: &mut App,
7011 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7012 if target_display_point.row().as_f32() < scroll_top {
7013 let mut element = self
7014 .render_edit_prediction_line_popover(
7015 "Jump to Edit",
7016 Some(IconName::ArrowUp),
7017 window,
7018 cx,
7019 )?
7020 .into_any();
7021
7022 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7023 let offset = point(
7024 (text_bounds.size.width - size.width) / 2.,
7025 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7026 );
7027
7028 let origin = text_bounds.origin + offset;
7029 element.prepaint_at(origin, window, cx);
7030 Some((element, origin))
7031 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7032 let mut element = self
7033 .render_edit_prediction_line_popover(
7034 "Jump to Edit",
7035 Some(IconName::ArrowDown),
7036 window,
7037 cx,
7038 )?
7039 .into_any();
7040
7041 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7042 let offset = point(
7043 (text_bounds.size.width - size.width) / 2.,
7044 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7045 );
7046
7047 let origin = text_bounds.origin + offset;
7048 element.prepaint_at(origin, window, cx);
7049 Some((element, origin))
7050 } else {
7051 self.render_edit_prediction_end_of_line_popover(
7052 "Jump to Edit",
7053 editor_snapshot,
7054 visible_row_range,
7055 target_display_point,
7056 line_height,
7057 scroll_pixel_position,
7058 content_origin,
7059 editor_width,
7060 window,
7061 cx,
7062 )
7063 }
7064 }
7065
7066 fn render_edit_prediction_end_of_line_popover(
7067 self: &mut Editor,
7068 label: &'static str,
7069 editor_snapshot: &EditorSnapshot,
7070 visible_row_range: Range<DisplayRow>,
7071 target_display_point: DisplayPoint,
7072 line_height: Pixels,
7073 scroll_pixel_position: gpui::Point<Pixels>,
7074 content_origin: gpui::Point<Pixels>,
7075 editor_width: Pixels,
7076 window: &mut Window,
7077 cx: &mut App,
7078 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7079 let target_line_end = DisplayPoint::new(
7080 target_display_point.row(),
7081 editor_snapshot.line_len(target_display_point.row()),
7082 );
7083
7084 let mut element = self
7085 .render_edit_prediction_line_popover(label, None, window, cx)?
7086 .into_any();
7087
7088 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7089
7090 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7091
7092 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7093 let mut origin = start_point
7094 + line_origin
7095 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7096 origin.x = origin.x.max(content_origin.x);
7097
7098 let max_x = content_origin.x + editor_width - size.width;
7099
7100 if origin.x > max_x {
7101 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7102
7103 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7104 origin.y += offset;
7105 IconName::ArrowUp
7106 } else {
7107 origin.y -= offset;
7108 IconName::ArrowDown
7109 };
7110
7111 element = self
7112 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7113 .into_any();
7114
7115 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7116
7117 origin.x = content_origin.x + editor_width - size.width - px(2.);
7118 }
7119
7120 element.prepaint_at(origin, window, cx);
7121 Some((element, origin))
7122 }
7123
7124 fn render_edit_prediction_diff_popover(
7125 self: &Editor,
7126 text_bounds: &Bounds<Pixels>,
7127 content_origin: gpui::Point<Pixels>,
7128 editor_snapshot: &EditorSnapshot,
7129 visible_row_range: Range<DisplayRow>,
7130 line_layouts: &[LineWithInvisibles],
7131 line_height: Pixels,
7132 scroll_pixel_position: gpui::Point<Pixels>,
7133 newest_selection_head: Option<DisplayPoint>,
7134 editor_width: Pixels,
7135 style: &EditorStyle,
7136 edits: &Vec<(Range<Anchor>, String)>,
7137 edit_preview: &Option<language::EditPreview>,
7138 snapshot: &language::BufferSnapshot,
7139 window: &mut Window,
7140 cx: &mut App,
7141 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7142 let edit_start = edits
7143 .first()
7144 .unwrap()
7145 .0
7146 .start
7147 .to_display_point(editor_snapshot);
7148 let edit_end = edits
7149 .last()
7150 .unwrap()
7151 .0
7152 .end
7153 .to_display_point(editor_snapshot);
7154
7155 let is_visible = visible_row_range.contains(&edit_start.row())
7156 || visible_row_range.contains(&edit_end.row());
7157 if !is_visible {
7158 return None;
7159 }
7160
7161 let highlighted_edits =
7162 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7163
7164 let styled_text = highlighted_edits.to_styled_text(&style.text);
7165 let line_count = highlighted_edits.text.lines().count();
7166
7167 const BORDER_WIDTH: Pixels = px(1.);
7168
7169 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7170 let has_keybind = keybind.is_some();
7171
7172 let mut element = h_flex()
7173 .items_start()
7174 .child(
7175 h_flex()
7176 .bg(cx.theme().colors().editor_background)
7177 .border(BORDER_WIDTH)
7178 .shadow_sm()
7179 .border_color(cx.theme().colors().border)
7180 .rounded_l_lg()
7181 .when(line_count > 1, |el| el.rounded_br_lg())
7182 .pr_1()
7183 .child(styled_text),
7184 )
7185 .child(
7186 h_flex()
7187 .h(line_height + BORDER_WIDTH * 2.)
7188 .px_1p5()
7189 .gap_1()
7190 // Workaround: For some reason, there's a gap if we don't do this
7191 .ml(-BORDER_WIDTH)
7192 .shadow(smallvec![gpui::BoxShadow {
7193 color: gpui::black().opacity(0.05),
7194 offset: point(px(1.), px(1.)),
7195 blur_radius: px(2.),
7196 spread_radius: px(0.),
7197 }])
7198 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7199 .border(BORDER_WIDTH)
7200 .border_color(cx.theme().colors().border)
7201 .rounded_r_lg()
7202 .id("edit_prediction_diff_popover_keybind")
7203 .when(!has_keybind, |el| {
7204 let status_colors = cx.theme().status();
7205
7206 el.bg(status_colors.error_background)
7207 .border_color(status_colors.error.opacity(0.6))
7208 .child(Icon::new(IconName::Info).color(Color::Error))
7209 .cursor_default()
7210 .hoverable_tooltip(move |_window, cx| {
7211 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7212 })
7213 })
7214 .children(keybind),
7215 )
7216 .into_any();
7217
7218 let longest_row =
7219 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7220 let longest_line_width = if visible_row_range.contains(&longest_row) {
7221 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7222 } else {
7223 layout_line(
7224 longest_row,
7225 editor_snapshot,
7226 style,
7227 editor_width,
7228 |_| false,
7229 window,
7230 cx,
7231 )
7232 .width
7233 };
7234
7235 let viewport_bounds =
7236 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7237 right: -EditorElement::SCROLLBAR_WIDTH,
7238 ..Default::default()
7239 });
7240
7241 let x_after_longest =
7242 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7243 - scroll_pixel_position.x;
7244
7245 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7246
7247 // Fully visible if it can be displayed within the window (allow overlapping other
7248 // panes). However, this is only allowed if the popover starts within text_bounds.
7249 let can_position_to_the_right = x_after_longest < text_bounds.right()
7250 && x_after_longest + element_bounds.width < viewport_bounds.right();
7251
7252 let mut origin = if can_position_to_the_right {
7253 point(
7254 x_after_longest,
7255 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7256 - scroll_pixel_position.y,
7257 )
7258 } else {
7259 let cursor_row = newest_selection_head.map(|head| head.row());
7260 let above_edit = edit_start
7261 .row()
7262 .0
7263 .checked_sub(line_count as u32)
7264 .map(DisplayRow);
7265 let below_edit = Some(edit_end.row() + 1);
7266 let above_cursor =
7267 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7268 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7269
7270 // Place the edit popover adjacent to the edit if there is a location
7271 // available that is onscreen and does not obscure the cursor. Otherwise,
7272 // place it adjacent to the cursor.
7273 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7274 .into_iter()
7275 .flatten()
7276 .find(|&start_row| {
7277 let end_row = start_row + line_count as u32;
7278 visible_row_range.contains(&start_row)
7279 && visible_row_range.contains(&end_row)
7280 && cursor_row.map_or(true, |cursor_row| {
7281 !((start_row..end_row).contains(&cursor_row))
7282 })
7283 })?;
7284
7285 content_origin
7286 + point(
7287 -scroll_pixel_position.x,
7288 row_target.as_f32() * line_height - scroll_pixel_position.y,
7289 )
7290 };
7291
7292 origin.x -= BORDER_WIDTH;
7293
7294 window.defer_draw(element, origin, 1);
7295
7296 // Do not return an element, since it will already be drawn due to defer_draw.
7297 None
7298 }
7299
7300 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7301 px(30.)
7302 }
7303
7304 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7305 if self.read_only(cx) {
7306 cx.theme().players().read_only()
7307 } else {
7308 self.style.as_ref().unwrap().local_player
7309 }
7310 }
7311
7312 fn render_edit_prediction_accept_keybind(
7313 &self,
7314 window: &mut Window,
7315 cx: &App,
7316 ) -> Option<AnyElement> {
7317 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7318 let accept_keystroke = accept_binding.keystroke()?;
7319
7320 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7321
7322 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7323 Color::Accent
7324 } else {
7325 Color::Muted
7326 };
7327
7328 h_flex()
7329 .px_0p5()
7330 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7331 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7332 .text_size(TextSize::XSmall.rems(cx))
7333 .child(h_flex().children(ui::render_modifiers(
7334 &accept_keystroke.modifiers,
7335 PlatformStyle::platform(),
7336 Some(modifiers_color),
7337 Some(IconSize::XSmall.rems().into()),
7338 true,
7339 )))
7340 .when(is_platform_style_mac, |parent| {
7341 parent.child(accept_keystroke.key.clone())
7342 })
7343 .when(!is_platform_style_mac, |parent| {
7344 parent.child(
7345 Key::new(
7346 util::capitalize(&accept_keystroke.key),
7347 Some(Color::Default),
7348 )
7349 .size(Some(IconSize::XSmall.rems().into())),
7350 )
7351 })
7352 .into_any()
7353 .into()
7354 }
7355
7356 fn render_edit_prediction_line_popover(
7357 &self,
7358 label: impl Into<SharedString>,
7359 icon: Option<IconName>,
7360 window: &mut Window,
7361 cx: &App,
7362 ) -> Option<Stateful<Div>> {
7363 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7364
7365 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7366 let has_keybind = keybind.is_some();
7367
7368 let result = h_flex()
7369 .id("ep-line-popover")
7370 .py_0p5()
7371 .pl_1()
7372 .pr(padding_right)
7373 .gap_1()
7374 .rounded_md()
7375 .border_1()
7376 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7377 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7378 .shadow_sm()
7379 .when(!has_keybind, |el| {
7380 let status_colors = cx.theme().status();
7381
7382 el.bg(status_colors.error_background)
7383 .border_color(status_colors.error.opacity(0.6))
7384 .pl_2()
7385 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7386 .cursor_default()
7387 .hoverable_tooltip(move |_window, cx| {
7388 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7389 })
7390 })
7391 .children(keybind)
7392 .child(
7393 Label::new(label)
7394 .size(LabelSize::Small)
7395 .when(!has_keybind, |el| {
7396 el.color(cx.theme().status().error.into()).strikethrough()
7397 }),
7398 )
7399 .when(!has_keybind, |el| {
7400 el.child(
7401 h_flex().ml_1().child(
7402 Icon::new(IconName::Info)
7403 .size(IconSize::Small)
7404 .color(cx.theme().status().error.into()),
7405 ),
7406 )
7407 })
7408 .when_some(icon, |element, icon| {
7409 element.child(
7410 div()
7411 .mt(px(1.5))
7412 .child(Icon::new(icon).size(IconSize::Small)),
7413 )
7414 });
7415
7416 Some(result)
7417 }
7418
7419 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7420 let accent_color = cx.theme().colors().text_accent;
7421 let editor_bg_color = cx.theme().colors().editor_background;
7422 editor_bg_color.blend(accent_color.opacity(0.1))
7423 }
7424
7425 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7426 let accent_color = cx.theme().colors().text_accent;
7427 let editor_bg_color = cx.theme().colors().editor_background;
7428 editor_bg_color.blend(accent_color.opacity(0.6))
7429 }
7430
7431 fn render_edit_prediction_cursor_popover(
7432 &self,
7433 min_width: Pixels,
7434 max_width: Pixels,
7435 cursor_point: Point,
7436 style: &EditorStyle,
7437 accept_keystroke: Option<&gpui::Keystroke>,
7438 _window: &Window,
7439 cx: &mut Context<Editor>,
7440 ) -> Option<AnyElement> {
7441 let provider = self.edit_prediction_provider.as_ref()?;
7442
7443 if provider.provider.needs_terms_acceptance(cx) {
7444 return Some(
7445 h_flex()
7446 .min_w(min_width)
7447 .flex_1()
7448 .px_2()
7449 .py_1()
7450 .gap_3()
7451 .elevation_2(cx)
7452 .hover(|style| style.bg(cx.theme().colors().element_hover))
7453 .id("accept-terms")
7454 .cursor_pointer()
7455 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7456 .on_click(cx.listener(|this, _event, window, cx| {
7457 cx.stop_propagation();
7458 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7459 window.dispatch_action(
7460 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7461 cx,
7462 );
7463 }))
7464 .child(
7465 h_flex()
7466 .flex_1()
7467 .gap_2()
7468 .child(Icon::new(IconName::ZedPredict))
7469 .child(Label::new("Accept Terms of Service"))
7470 .child(div().w_full())
7471 .child(
7472 Icon::new(IconName::ArrowUpRight)
7473 .color(Color::Muted)
7474 .size(IconSize::Small),
7475 )
7476 .into_any_element(),
7477 )
7478 .into_any(),
7479 );
7480 }
7481
7482 let is_refreshing = provider.provider.is_refreshing(cx);
7483
7484 fn pending_completion_container() -> Div {
7485 h_flex()
7486 .h_full()
7487 .flex_1()
7488 .gap_2()
7489 .child(Icon::new(IconName::ZedPredict))
7490 }
7491
7492 let completion = match &self.active_inline_completion {
7493 Some(prediction) => {
7494 if !self.has_visible_completions_menu() {
7495 const RADIUS: Pixels = px(6.);
7496 const BORDER_WIDTH: Pixels = px(1.);
7497
7498 return Some(
7499 h_flex()
7500 .elevation_2(cx)
7501 .border(BORDER_WIDTH)
7502 .border_color(cx.theme().colors().border)
7503 .when(accept_keystroke.is_none(), |el| {
7504 el.border_color(cx.theme().status().error)
7505 })
7506 .rounded(RADIUS)
7507 .rounded_tl(px(0.))
7508 .overflow_hidden()
7509 .child(div().px_1p5().child(match &prediction.completion {
7510 InlineCompletion::Move { target, snapshot } => {
7511 use text::ToPoint as _;
7512 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7513 {
7514 Icon::new(IconName::ZedPredictDown)
7515 } else {
7516 Icon::new(IconName::ZedPredictUp)
7517 }
7518 }
7519 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7520 }))
7521 .child(
7522 h_flex()
7523 .gap_1()
7524 .py_1()
7525 .px_2()
7526 .rounded_r(RADIUS - BORDER_WIDTH)
7527 .border_l_1()
7528 .border_color(cx.theme().colors().border)
7529 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7530 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7531 el.child(
7532 Label::new("Hold")
7533 .size(LabelSize::Small)
7534 .when(accept_keystroke.is_none(), |el| {
7535 el.strikethrough()
7536 })
7537 .line_height_style(LineHeightStyle::UiLabel),
7538 )
7539 })
7540 .id("edit_prediction_cursor_popover_keybind")
7541 .when(accept_keystroke.is_none(), |el| {
7542 let status_colors = cx.theme().status();
7543
7544 el.bg(status_colors.error_background)
7545 .border_color(status_colors.error.opacity(0.6))
7546 .child(Icon::new(IconName::Info).color(Color::Error))
7547 .cursor_default()
7548 .hoverable_tooltip(move |_window, cx| {
7549 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7550 .into()
7551 })
7552 })
7553 .when_some(
7554 accept_keystroke.as_ref(),
7555 |el, accept_keystroke| {
7556 el.child(h_flex().children(ui::render_modifiers(
7557 &accept_keystroke.modifiers,
7558 PlatformStyle::platform(),
7559 Some(Color::Default),
7560 Some(IconSize::XSmall.rems().into()),
7561 false,
7562 )))
7563 },
7564 ),
7565 )
7566 .into_any(),
7567 );
7568 }
7569
7570 self.render_edit_prediction_cursor_popover_preview(
7571 prediction,
7572 cursor_point,
7573 style,
7574 cx,
7575 )?
7576 }
7577
7578 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7579 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7580 stale_completion,
7581 cursor_point,
7582 style,
7583 cx,
7584 )?,
7585
7586 None => {
7587 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7588 }
7589 },
7590
7591 None => pending_completion_container().child(Label::new("No Prediction")),
7592 };
7593
7594 let completion = if is_refreshing {
7595 completion
7596 .with_animation(
7597 "loading-completion",
7598 Animation::new(Duration::from_secs(2))
7599 .repeat()
7600 .with_easing(pulsating_between(0.4, 0.8)),
7601 |label, delta| label.opacity(delta),
7602 )
7603 .into_any_element()
7604 } else {
7605 completion.into_any_element()
7606 };
7607
7608 let has_completion = self.active_inline_completion.is_some();
7609
7610 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7611 Some(
7612 h_flex()
7613 .min_w(min_width)
7614 .max_w(max_width)
7615 .flex_1()
7616 .elevation_2(cx)
7617 .border_color(cx.theme().colors().border)
7618 .child(
7619 div()
7620 .flex_1()
7621 .py_1()
7622 .px_2()
7623 .overflow_hidden()
7624 .child(completion),
7625 )
7626 .when_some(accept_keystroke, |el, accept_keystroke| {
7627 if !accept_keystroke.modifiers.modified() {
7628 return el;
7629 }
7630
7631 el.child(
7632 h_flex()
7633 .h_full()
7634 .border_l_1()
7635 .rounded_r_lg()
7636 .border_color(cx.theme().colors().border)
7637 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7638 .gap_1()
7639 .py_1()
7640 .px_2()
7641 .child(
7642 h_flex()
7643 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7644 .when(is_platform_style_mac, |parent| parent.gap_1())
7645 .child(h_flex().children(ui::render_modifiers(
7646 &accept_keystroke.modifiers,
7647 PlatformStyle::platform(),
7648 Some(if !has_completion {
7649 Color::Muted
7650 } else {
7651 Color::Default
7652 }),
7653 None,
7654 false,
7655 ))),
7656 )
7657 .child(Label::new("Preview").into_any_element())
7658 .opacity(if has_completion { 1.0 } else { 0.4 }),
7659 )
7660 })
7661 .into_any(),
7662 )
7663 }
7664
7665 fn render_edit_prediction_cursor_popover_preview(
7666 &self,
7667 completion: &InlineCompletionState,
7668 cursor_point: Point,
7669 style: &EditorStyle,
7670 cx: &mut Context<Editor>,
7671 ) -> Option<Div> {
7672 use text::ToPoint as _;
7673
7674 fn render_relative_row_jump(
7675 prefix: impl Into<String>,
7676 current_row: u32,
7677 target_row: u32,
7678 ) -> Div {
7679 let (row_diff, arrow) = if target_row < current_row {
7680 (current_row - target_row, IconName::ArrowUp)
7681 } else {
7682 (target_row - current_row, IconName::ArrowDown)
7683 };
7684
7685 h_flex()
7686 .child(
7687 Label::new(format!("{}{}", prefix.into(), row_diff))
7688 .color(Color::Muted)
7689 .size(LabelSize::Small),
7690 )
7691 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7692 }
7693
7694 match &completion.completion {
7695 InlineCompletion::Move {
7696 target, snapshot, ..
7697 } => Some(
7698 h_flex()
7699 .px_2()
7700 .gap_2()
7701 .flex_1()
7702 .child(
7703 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7704 Icon::new(IconName::ZedPredictDown)
7705 } else {
7706 Icon::new(IconName::ZedPredictUp)
7707 },
7708 )
7709 .child(Label::new("Jump to Edit")),
7710 ),
7711
7712 InlineCompletion::Edit {
7713 edits,
7714 edit_preview,
7715 snapshot,
7716 display_mode: _,
7717 } => {
7718 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7719
7720 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7721 &snapshot,
7722 &edits,
7723 edit_preview.as_ref()?,
7724 true,
7725 cx,
7726 )
7727 .first_line_preview();
7728
7729 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7730 .with_default_highlights(&style.text, highlighted_edits.highlights);
7731
7732 let preview = h_flex()
7733 .gap_1()
7734 .min_w_16()
7735 .child(styled_text)
7736 .when(has_more_lines, |parent| parent.child("…"));
7737
7738 let left = if first_edit_row != cursor_point.row {
7739 render_relative_row_jump("", cursor_point.row, first_edit_row)
7740 .into_any_element()
7741 } else {
7742 Icon::new(IconName::ZedPredict).into_any_element()
7743 };
7744
7745 Some(
7746 h_flex()
7747 .h_full()
7748 .flex_1()
7749 .gap_2()
7750 .pr_1()
7751 .overflow_x_hidden()
7752 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7753 .child(left)
7754 .child(preview),
7755 )
7756 }
7757 }
7758 }
7759
7760 fn render_context_menu(
7761 &self,
7762 style: &EditorStyle,
7763 max_height_in_lines: u32,
7764 window: &mut Window,
7765 cx: &mut Context<Editor>,
7766 ) -> Option<AnyElement> {
7767 let menu = self.context_menu.borrow();
7768 let menu = menu.as_ref()?;
7769 if !menu.visible() {
7770 return None;
7771 };
7772 Some(menu.render(style, max_height_in_lines, window, cx))
7773 }
7774
7775 fn render_context_menu_aside(
7776 &mut self,
7777 max_size: Size<Pixels>,
7778 window: &mut Window,
7779 cx: &mut Context<Editor>,
7780 ) -> Option<AnyElement> {
7781 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7782 if menu.visible() {
7783 menu.render_aside(self, max_size, window, cx)
7784 } else {
7785 None
7786 }
7787 })
7788 }
7789
7790 fn hide_context_menu(
7791 &mut self,
7792 window: &mut Window,
7793 cx: &mut Context<Self>,
7794 ) -> Option<CodeContextMenu> {
7795 cx.notify();
7796 self.completion_tasks.clear();
7797 let context_menu = self.context_menu.borrow_mut().take();
7798 self.stale_inline_completion_in_menu.take();
7799 self.update_visible_inline_completion(window, cx);
7800 context_menu
7801 }
7802
7803 fn show_snippet_choices(
7804 &mut self,
7805 choices: &Vec<String>,
7806 selection: Range<Anchor>,
7807 cx: &mut Context<Self>,
7808 ) {
7809 if selection.start.buffer_id.is_none() {
7810 return;
7811 }
7812 let buffer_id = selection.start.buffer_id.unwrap();
7813 let buffer = self.buffer().read(cx).buffer(buffer_id);
7814 let id = post_inc(&mut self.next_completion_id);
7815
7816 if let Some(buffer) = buffer {
7817 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7818 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7819 ));
7820 }
7821 }
7822
7823 pub fn insert_snippet(
7824 &mut self,
7825 insertion_ranges: &[Range<usize>],
7826 snippet: Snippet,
7827 window: &mut Window,
7828 cx: &mut Context<Self>,
7829 ) -> Result<()> {
7830 struct Tabstop<T> {
7831 is_end_tabstop: bool,
7832 ranges: Vec<Range<T>>,
7833 choices: Option<Vec<String>>,
7834 }
7835
7836 let tabstops = self.buffer.update(cx, |buffer, cx| {
7837 let snippet_text: Arc<str> = snippet.text.clone().into();
7838 let edits = insertion_ranges
7839 .iter()
7840 .cloned()
7841 .map(|range| (range, snippet_text.clone()));
7842 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
7843
7844 let snapshot = &*buffer.read(cx);
7845 let snippet = &snippet;
7846 snippet
7847 .tabstops
7848 .iter()
7849 .map(|tabstop| {
7850 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7851 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7852 });
7853 let mut tabstop_ranges = tabstop
7854 .ranges
7855 .iter()
7856 .flat_map(|tabstop_range| {
7857 let mut delta = 0_isize;
7858 insertion_ranges.iter().map(move |insertion_range| {
7859 let insertion_start = insertion_range.start as isize + delta;
7860 delta +=
7861 snippet.text.len() as isize - insertion_range.len() as isize;
7862
7863 let start = ((insertion_start + tabstop_range.start) as usize)
7864 .min(snapshot.len());
7865 let end = ((insertion_start + tabstop_range.end) as usize)
7866 .min(snapshot.len());
7867 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7868 })
7869 })
7870 .collect::<Vec<_>>();
7871 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7872
7873 Tabstop {
7874 is_end_tabstop,
7875 ranges: tabstop_ranges,
7876 choices: tabstop.choices.clone(),
7877 }
7878 })
7879 .collect::<Vec<_>>()
7880 });
7881 if let Some(tabstop) = tabstops.first() {
7882 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7883 s.select_ranges(tabstop.ranges.iter().cloned());
7884 });
7885
7886 if let Some(choices) = &tabstop.choices {
7887 if let Some(selection) = tabstop.ranges.first() {
7888 self.show_snippet_choices(choices, selection.clone(), cx)
7889 }
7890 }
7891
7892 // If we're already at the last tabstop and it's at the end of the snippet,
7893 // we're done, we don't need to keep the state around.
7894 if !tabstop.is_end_tabstop {
7895 let choices = tabstops
7896 .iter()
7897 .map(|tabstop| tabstop.choices.clone())
7898 .collect();
7899
7900 let ranges = tabstops
7901 .into_iter()
7902 .map(|tabstop| tabstop.ranges)
7903 .collect::<Vec<_>>();
7904
7905 self.snippet_stack.push(SnippetState {
7906 active_index: 0,
7907 ranges,
7908 choices,
7909 });
7910 }
7911
7912 // Check whether the just-entered snippet ends with an auto-closable bracket.
7913 if self.autoclose_regions.is_empty() {
7914 let snapshot = self.buffer.read(cx).snapshot(cx);
7915 for selection in &mut self.selections.all::<Point>(cx) {
7916 let selection_head = selection.head();
7917 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7918 continue;
7919 };
7920
7921 let mut bracket_pair = None;
7922 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7923 let prev_chars = snapshot
7924 .reversed_chars_at(selection_head)
7925 .collect::<String>();
7926 for (pair, enabled) in scope.brackets() {
7927 if enabled
7928 && pair.close
7929 && prev_chars.starts_with(pair.start.as_str())
7930 && next_chars.starts_with(pair.end.as_str())
7931 {
7932 bracket_pair = Some(pair.clone());
7933 break;
7934 }
7935 }
7936 if let Some(pair) = bracket_pair {
7937 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
7938 let autoclose_enabled =
7939 self.use_autoclose && snapshot_settings.use_autoclose;
7940 if autoclose_enabled {
7941 let start = snapshot.anchor_after(selection_head);
7942 let end = snapshot.anchor_after(selection_head);
7943 self.autoclose_regions.push(AutocloseRegion {
7944 selection_id: selection.id,
7945 range: start..end,
7946 pair,
7947 });
7948 }
7949 }
7950 }
7951 }
7952 }
7953 Ok(())
7954 }
7955
7956 pub fn move_to_next_snippet_tabstop(
7957 &mut self,
7958 window: &mut Window,
7959 cx: &mut Context<Self>,
7960 ) -> bool {
7961 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7962 }
7963
7964 pub fn move_to_prev_snippet_tabstop(
7965 &mut self,
7966 window: &mut Window,
7967 cx: &mut Context<Self>,
7968 ) -> bool {
7969 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7970 }
7971
7972 pub fn move_to_snippet_tabstop(
7973 &mut self,
7974 bias: Bias,
7975 window: &mut Window,
7976 cx: &mut Context<Self>,
7977 ) -> bool {
7978 if let Some(mut snippet) = self.snippet_stack.pop() {
7979 match bias {
7980 Bias::Left => {
7981 if snippet.active_index > 0 {
7982 snippet.active_index -= 1;
7983 } else {
7984 self.snippet_stack.push(snippet);
7985 return false;
7986 }
7987 }
7988 Bias::Right => {
7989 if snippet.active_index + 1 < snippet.ranges.len() {
7990 snippet.active_index += 1;
7991 } else {
7992 self.snippet_stack.push(snippet);
7993 return false;
7994 }
7995 }
7996 }
7997 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
7998 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7999 s.select_anchor_ranges(current_ranges.iter().cloned())
8000 });
8001
8002 if let Some(choices) = &snippet.choices[snippet.active_index] {
8003 if let Some(selection) = current_ranges.first() {
8004 self.show_snippet_choices(&choices, selection.clone(), cx);
8005 }
8006 }
8007
8008 // If snippet state is not at the last tabstop, push it back on the stack
8009 if snippet.active_index + 1 < snippet.ranges.len() {
8010 self.snippet_stack.push(snippet);
8011 }
8012 return true;
8013 }
8014 }
8015
8016 false
8017 }
8018
8019 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8020 self.transact(window, cx, |this, window, cx| {
8021 this.select_all(&SelectAll, window, cx);
8022 this.insert("", window, cx);
8023 });
8024 }
8025
8026 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8027 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8028 self.transact(window, cx, |this, window, cx| {
8029 this.select_autoclose_pair(window, cx);
8030 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8031 if !this.linked_edit_ranges.is_empty() {
8032 let selections = this.selections.all::<MultiBufferPoint>(cx);
8033 let snapshot = this.buffer.read(cx).snapshot(cx);
8034
8035 for selection in selections.iter() {
8036 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8037 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8038 if selection_start.buffer_id != selection_end.buffer_id {
8039 continue;
8040 }
8041 if let Some(ranges) =
8042 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8043 {
8044 for (buffer, entries) in ranges {
8045 linked_ranges.entry(buffer).or_default().extend(entries);
8046 }
8047 }
8048 }
8049 }
8050
8051 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8052 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8053 for selection in &mut selections {
8054 if selection.is_empty() {
8055 let old_head = selection.head();
8056 let mut new_head =
8057 movement::left(&display_map, old_head.to_display_point(&display_map))
8058 .to_point(&display_map);
8059 if let Some((buffer, line_buffer_range)) = display_map
8060 .buffer_snapshot
8061 .buffer_line_for_row(MultiBufferRow(old_head.row))
8062 {
8063 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8064 let indent_len = match indent_size.kind {
8065 IndentKind::Space => {
8066 buffer.settings_at(line_buffer_range.start, cx).tab_size
8067 }
8068 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8069 };
8070 if old_head.column <= indent_size.len && old_head.column > 0 {
8071 let indent_len = indent_len.get();
8072 new_head = cmp::min(
8073 new_head,
8074 MultiBufferPoint::new(
8075 old_head.row,
8076 ((old_head.column - 1) / indent_len) * indent_len,
8077 ),
8078 );
8079 }
8080 }
8081
8082 selection.set_head(new_head, SelectionGoal::None);
8083 }
8084 }
8085
8086 this.signature_help_state.set_backspace_pressed(true);
8087 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8088 s.select(selections)
8089 });
8090 this.insert("", window, cx);
8091 let empty_str: Arc<str> = Arc::from("");
8092 for (buffer, edits) in linked_ranges {
8093 let snapshot = buffer.read(cx).snapshot();
8094 use text::ToPoint as TP;
8095
8096 let edits = edits
8097 .into_iter()
8098 .map(|range| {
8099 let end_point = TP::to_point(&range.end, &snapshot);
8100 let mut start_point = TP::to_point(&range.start, &snapshot);
8101
8102 if end_point == start_point {
8103 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8104 .saturating_sub(1);
8105 start_point =
8106 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8107 };
8108
8109 (start_point..end_point, empty_str.clone())
8110 })
8111 .sorted_by_key(|(range, _)| range.start)
8112 .collect::<Vec<_>>();
8113 buffer.update(cx, |this, cx| {
8114 this.edit(edits, None, cx);
8115 })
8116 }
8117 this.refresh_inline_completion(true, false, window, cx);
8118 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8119 });
8120 }
8121
8122 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8123 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8124 self.transact(window, cx, |this, window, cx| {
8125 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8126 s.move_with(|map, selection| {
8127 if selection.is_empty() {
8128 let cursor = movement::right(map, selection.head());
8129 selection.end = cursor;
8130 selection.reversed = true;
8131 selection.goal = SelectionGoal::None;
8132 }
8133 })
8134 });
8135 this.insert("", window, cx);
8136 this.refresh_inline_completion(true, false, window, cx);
8137 });
8138 }
8139
8140 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8141 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8142 if self.move_to_prev_snippet_tabstop(window, cx) {
8143 return;
8144 }
8145 self.outdent(&Outdent, window, cx);
8146 }
8147
8148 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8149 if self.move_to_next_snippet_tabstop(window, cx) {
8150 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8151 return;
8152 }
8153 if self.read_only(cx) {
8154 return;
8155 }
8156 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8157 let mut selections = self.selections.all_adjusted(cx);
8158 let buffer = self.buffer.read(cx);
8159 let snapshot = buffer.snapshot(cx);
8160 let rows_iter = selections.iter().map(|s| s.head().row);
8161 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8162
8163 let mut edits = Vec::new();
8164 let mut prev_edited_row = 0;
8165 let mut row_delta = 0;
8166 for selection in &mut selections {
8167 if selection.start.row != prev_edited_row {
8168 row_delta = 0;
8169 }
8170 prev_edited_row = selection.end.row;
8171
8172 // If the selection is non-empty, then increase the indentation of the selected lines.
8173 if !selection.is_empty() {
8174 row_delta =
8175 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8176 continue;
8177 }
8178
8179 // If the selection is empty and the cursor is in the leading whitespace before the
8180 // suggested indentation, then auto-indent the line.
8181 let cursor = selection.head();
8182 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8183 if let Some(suggested_indent) =
8184 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8185 {
8186 if cursor.column < suggested_indent.len
8187 && cursor.column <= current_indent.len
8188 && current_indent.len <= suggested_indent.len
8189 {
8190 selection.start = Point::new(cursor.row, suggested_indent.len);
8191 selection.end = selection.start;
8192 if row_delta == 0 {
8193 edits.extend(Buffer::edit_for_indent_size_adjustment(
8194 cursor.row,
8195 current_indent,
8196 suggested_indent,
8197 ));
8198 row_delta = suggested_indent.len - current_indent.len;
8199 }
8200 continue;
8201 }
8202 }
8203
8204 // Otherwise, insert a hard or soft tab.
8205 let settings = buffer.language_settings_at(cursor, cx);
8206 let tab_size = if settings.hard_tabs {
8207 IndentSize::tab()
8208 } else {
8209 let tab_size = settings.tab_size.get();
8210 let char_column = snapshot
8211 .text_for_range(Point::new(cursor.row, 0)..cursor)
8212 .flat_map(str::chars)
8213 .count()
8214 + row_delta as usize;
8215 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
8216 IndentSize::spaces(chars_to_next_tab_stop)
8217 };
8218 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8219 selection.end = selection.start;
8220 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8221 row_delta += tab_size.len;
8222 }
8223
8224 self.transact(window, cx, |this, window, cx| {
8225 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8226 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8227 s.select(selections)
8228 });
8229 this.refresh_inline_completion(true, false, window, cx);
8230 });
8231 }
8232
8233 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8234 if self.read_only(cx) {
8235 return;
8236 }
8237 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8238 let mut selections = self.selections.all::<Point>(cx);
8239 let mut prev_edited_row = 0;
8240 let mut row_delta = 0;
8241 let mut edits = Vec::new();
8242 let buffer = self.buffer.read(cx);
8243 let snapshot = buffer.snapshot(cx);
8244 for selection in &mut selections {
8245 if selection.start.row != prev_edited_row {
8246 row_delta = 0;
8247 }
8248 prev_edited_row = selection.end.row;
8249
8250 row_delta =
8251 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8252 }
8253
8254 self.transact(window, cx, |this, window, cx| {
8255 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8256 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8257 s.select(selections)
8258 });
8259 });
8260 }
8261
8262 fn indent_selection(
8263 buffer: &MultiBuffer,
8264 snapshot: &MultiBufferSnapshot,
8265 selection: &mut Selection<Point>,
8266 edits: &mut Vec<(Range<Point>, String)>,
8267 delta_for_start_row: u32,
8268 cx: &App,
8269 ) -> u32 {
8270 let settings = buffer.language_settings_at(selection.start, cx);
8271 let tab_size = settings.tab_size.get();
8272 let indent_kind = if settings.hard_tabs {
8273 IndentKind::Tab
8274 } else {
8275 IndentKind::Space
8276 };
8277 let mut start_row = selection.start.row;
8278 let mut end_row = selection.end.row + 1;
8279
8280 // If a selection ends at the beginning of a line, don't indent
8281 // that last line.
8282 if selection.end.column == 0 && selection.end.row > selection.start.row {
8283 end_row -= 1;
8284 }
8285
8286 // Avoid re-indenting a row that has already been indented by a
8287 // previous selection, but still update this selection's column
8288 // to reflect that indentation.
8289 if delta_for_start_row > 0 {
8290 start_row += 1;
8291 selection.start.column += delta_for_start_row;
8292 if selection.end.row == selection.start.row {
8293 selection.end.column += delta_for_start_row;
8294 }
8295 }
8296
8297 let mut delta_for_end_row = 0;
8298 let has_multiple_rows = start_row + 1 != end_row;
8299 for row in start_row..end_row {
8300 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8301 let indent_delta = match (current_indent.kind, indent_kind) {
8302 (IndentKind::Space, IndentKind::Space) => {
8303 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8304 IndentSize::spaces(columns_to_next_tab_stop)
8305 }
8306 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8307 (_, IndentKind::Tab) => IndentSize::tab(),
8308 };
8309
8310 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8311 0
8312 } else {
8313 selection.start.column
8314 };
8315 let row_start = Point::new(row, start);
8316 edits.push((
8317 row_start..row_start,
8318 indent_delta.chars().collect::<String>(),
8319 ));
8320
8321 // Update this selection's endpoints to reflect the indentation.
8322 if row == selection.start.row {
8323 selection.start.column += indent_delta.len;
8324 }
8325 if row == selection.end.row {
8326 selection.end.column += indent_delta.len;
8327 delta_for_end_row = indent_delta.len;
8328 }
8329 }
8330
8331 if selection.start.row == selection.end.row {
8332 delta_for_start_row + delta_for_end_row
8333 } else {
8334 delta_for_end_row
8335 }
8336 }
8337
8338 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8339 if self.read_only(cx) {
8340 return;
8341 }
8342 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8343 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8344 let selections = self.selections.all::<Point>(cx);
8345 let mut deletion_ranges = Vec::new();
8346 let mut last_outdent = None;
8347 {
8348 let buffer = self.buffer.read(cx);
8349 let snapshot = buffer.snapshot(cx);
8350 for selection in &selections {
8351 let settings = buffer.language_settings_at(selection.start, cx);
8352 let tab_size = settings.tab_size.get();
8353 let mut rows = selection.spanned_rows(false, &display_map);
8354
8355 // Avoid re-outdenting a row that has already been outdented by a
8356 // previous selection.
8357 if let Some(last_row) = last_outdent {
8358 if last_row == rows.start {
8359 rows.start = rows.start.next_row();
8360 }
8361 }
8362 let has_multiple_rows = rows.len() > 1;
8363 for row in rows.iter_rows() {
8364 let indent_size = snapshot.indent_size_for_line(row);
8365 if indent_size.len > 0 {
8366 let deletion_len = match indent_size.kind {
8367 IndentKind::Space => {
8368 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8369 if columns_to_prev_tab_stop == 0 {
8370 tab_size
8371 } else {
8372 columns_to_prev_tab_stop
8373 }
8374 }
8375 IndentKind::Tab => 1,
8376 };
8377 let start = if has_multiple_rows
8378 || deletion_len > selection.start.column
8379 || indent_size.len < selection.start.column
8380 {
8381 0
8382 } else {
8383 selection.start.column - deletion_len
8384 };
8385 deletion_ranges.push(
8386 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8387 );
8388 last_outdent = Some(row);
8389 }
8390 }
8391 }
8392 }
8393
8394 self.transact(window, cx, |this, window, cx| {
8395 this.buffer.update(cx, |buffer, cx| {
8396 let empty_str: Arc<str> = Arc::default();
8397 buffer.edit(
8398 deletion_ranges
8399 .into_iter()
8400 .map(|range| (range, empty_str.clone())),
8401 None,
8402 cx,
8403 );
8404 });
8405 let selections = this.selections.all::<usize>(cx);
8406 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8407 s.select(selections)
8408 });
8409 });
8410 }
8411
8412 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8413 if self.read_only(cx) {
8414 return;
8415 }
8416 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8417 let selections = self
8418 .selections
8419 .all::<usize>(cx)
8420 .into_iter()
8421 .map(|s| s.range());
8422
8423 self.transact(window, cx, |this, window, cx| {
8424 this.buffer.update(cx, |buffer, cx| {
8425 buffer.autoindent_ranges(selections, cx);
8426 });
8427 let selections = this.selections.all::<usize>(cx);
8428 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8429 s.select(selections)
8430 });
8431 });
8432 }
8433
8434 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8435 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8436 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8437 let selections = self.selections.all::<Point>(cx);
8438
8439 let mut new_cursors = Vec::new();
8440 let mut edit_ranges = Vec::new();
8441 let mut selections = selections.iter().peekable();
8442 while let Some(selection) = selections.next() {
8443 let mut rows = selection.spanned_rows(false, &display_map);
8444 let goal_display_column = selection.head().to_display_point(&display_map).column();
8445
8446 // Accumulate contiguous regions of rows that we want to delete.
8447 while let Some(next_selection) = selections.peek() {
8448 let next_rows = next_selection.spanned_rows(false, &display_map);
8449 if next_rows.start <= rows.end {
8450 rows.end = next_rows.end;
8451 selections.next().unwrap();
8452 } else {
8453 break;
8454 }
8455 }
8456
8457 let buffer = &display_map.buffer_snapshot;
8458 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8459 let edit_end;
8460 let cursor_buffer_row;
8461 if buffer.max_point().row >= rows.end.0 {
8462 // If there's a line after the range, delete the \n from the end of the row range
8463 // and position the cursor on the next line.
8464 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8465 cursor_buffer_row = rows.end;
8466 } else {
8467 // If there isn't a line after the range, delete the \n from the line before the
8468 // start of the row range and position the cursor there.
8469 edit_start = edit_start.saturating_sub(1);
8470 edit_end = buffer.len();
8471 cursor_buffer_row = rows.start.previous_row();
8472 }
8473
8474 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8475 *cursor.column_mut() =
8476 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8477
8478 new_cursors.push((
8479 selection.id,
8480 buffer.anchor_after(cursor.to_point(&display_map)),
8481 ));
8482 edit_ranges.push(edit_start..edit_end);
8483 }
8484
8485 self.transact(window, cx, |this, window, cx| {
8486 let buffer = this.buffer.update(cx, |buffer, cx| {
8487 let empty_str: Arc<str> = Arc::default();
8488 buffer.edit(
8489 edit_ranges
8490 .into_iter()
8491 .map(|range| (range, empty_str.clone())),
8492 None,
8493 cx,
8494 );
8495 buffer.snapshot(cx)
8496 });
8497 let new_selections = new_cursors
8498 .into_iter()
8499 .map(|(id, cursor)| {
8500 let cursor = cursor.to_point(&buffer);
8501 Selection {
8502 id,
8503 start: cursor,
8504 end: cursor,
8505 reversed: false,
8506 goal: SelectionGoal::None,
8507 }
8508 })
8509 .collect();
8510
8511 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8512 s.select(new_selections);
8513 });
8514 });
8515 }
8516
8517 pub fn join_lines_impl(
8518 &mut self,
8519 insert_whitespace: bool,
8520 window: &mut Window,
8521 cx: &mut Context<Self>,
8522 ) {
8523 if self.read_only(cx) {
8524 return;
8525 }
8526 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8527 for selection in self.selections.all::<Point>(cx) {
8528 let start = MultiBufferRow(selection.start.row);
8529 // Treat single line selections as if they include the next line. Otherwise this action
8530 // would do nothing for single line selections individual cursors.
8531 let end = if selection.start.row == selection.end.row {
8532 MultiBufferRow(selection.start.row + 1)
8533 } else {
8534 MultiBufferRow(selection.end.row)
8535 };
8536
8537 if let Some(last_row_range) = row_ranges.last_mut() {
8538 if start <= last_row_range.end {
8539 last_row_range.end = end;
8540 continue;
8541 }
8542 }
8543 row_ranges.push(start..end);
8544 }
8545
8546 let snapshot = self.buffer.read(cx).snapshot(cx);
8547 let mut cursor_positions = Vec::new();
8548 for row_range in &row_ranges {
8549 let anchor = snapshot.anchor_before(Point::new(
8550 row_range.end.previous_row().0,
8551 snapshot.line_len(row_range.end.previous_row()),
8552 ));
8553 cursor_positions.push(anchor..anchor);
8554 }
8555
8556 self.transact(window, cx, |this, window, cx| {
8557 for row_range in row_ranges.into_iter().rev() {
8558 for row in row_range.iter_rows().rev() {
8559 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8560 let next_line_row = row.next_row();
8561 let indent = snapshot.indent_size_for_line(next_line_row);
8562 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8563
8564 let replace =
8565 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8566 " "
8567 } else {
8568 ""
8569 };
8570
8571 this.buffer.update(cx, |buffer, cx| {
8572 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8573 });
8574 }
8575 }
8576
8577 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8578 s.select_anchor_ranges(cursor_positions)
8579 });
8580 });
8581 }
8582
8583 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8584 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8585 self.join_lines_impl(true, window, cx);
8586 }
8587
8588 pub fn sort_lines_case_sensitive(
8589 &mut self,
8590 _: &SortLinesCaseSensitive,
8591 window: &mut Window,
8592 cx: &mut Context<Self>,
8593 ) {
8594 self.manipulate_lines(window, cx, |lines| lines.sort())
8595 }
8596
8597 pub fn sort_lines_case_insensitive(
8598 &mut self,
8599 _: &SortLinesCaseInsensitive,
8600 window: &mut Window,
8601 cx: &mut Context<Self>,
8602 ) {
8603 self.manipulate_lines(window, cx, |lines| {
8604 lines.sort_by_key(|line| line.to_lowercase())
8605 })
8606 }
8607
8608 pub fn unique_lines_case_insensitive(
8609 &mut self,
8610 _: &UniqueLinesCaseInsensitive,
8611 window: &mut Window,
8612 cx: &mut Context<Self>,
8613 ) {
8614 self.manipulate_lines(window, cx, |lines| {
8615 let mut seen = HashSet::default();
8616 lines.retain(|line| seen.insert(line.to_lowercase()));
8617 })
8618 }
8619
8620 pub fn unique_lines_case_sensitive(
8621 &mut self,
8622 _: &UniqueLinesCaseSensitive,
8623 window: &mut Window,
8624 cx: &mut Context<Self>,
8625 ) {
8626 self.manipulate_lines(window, cx, |lines| {
8627 let mut seen = HashSet::default();
8628 lines.retain(|line| seen.insert(*line));
8629 })
8630 }
8631
8632 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8633 let Some(project) = self.project.clone() else {
8634 return;
8635 };
8636 self.reload(project, window, cx)
8637 .detach_and_notify_err(window, cx);
8638 }
8639
8640 pub fn restore_file(
8641 &mut self,
8642 _: &::git::RestoreFile,
8643 window: &mut Window,
8644 cx: &mut Context<Self>,
8645 ) {
8646 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8647 let mut buffer_ids = HashSet::default();
8648 let snapshot = self.buffer().read(cx).snapshot(cx);
8649 for selection in self.selections.all::<usize>(cx) {
8650 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8651 }
8652
8653 let buffer = self.buffer().read(cx);
8654 let ranges = buffer_ids
8655 .into_iter()
8656 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8657 .collect::<Vec<_>>();
8658
8659 self.restore_hunks_in_ranges(ranges, window, cx);
8660 }
8661
8662 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8663 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8664 let selections = self
8665 .selections
8666 .all(cx)
8667 .into_iter()
8668 .map(|s| s.range())
8669 .collect();
8670 self.restore_hunks_in_ranges(selections, window, cx);
8671 }
8672
8673 pub fn restore_hunks_in_ranges(
8674 &mut self,
8675 ranges: Vec<Range<Point>>,
8676 window: &mut Window,
8677 cx: &mut Context<Editor>,
8678 ) {
8679 let mut revert_changes = HashMap::default();
8680 let chunk_by = self
8681 .snapshot(window, cx)
8682 .hunks_for_ranges(ranges)
8683 .into_iter()
8684 .chunk_by(|hunk| hunk.buffer_id);
8685 for (buffer_id, hunks) in &chunk_by {
8686 let hunks = hunks.collect::<Vec<_>>();
8687 for hunk in &hunks {
8688 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8689 }
8690 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8691 }
8692 drop(chunk_by);
8693 if !revert_changes.is_empty() {
8694 self.transact(window, cx, |editor, window, cx| {
8695 editor.restore(revert_changes, window, cx);
8696 });
8697 }
8698 }
8699
8700 pub fn open_active_item_in_terminal(
8701 &mut self,
8702 _: &OpenInTerminal,
8703 window: &mut Window,
8704 cx: &mut Context<Self>,
8705 ) {
8706 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8707 let project_path = buffer.read(cx).project_path(cx)?;
8708 let project = self.project.as_ref()?.read(cx);
8709 let entry = project.entry_for_path(&project_path, cx)?;
8710 let parent = match &entry.canonical_path {
8711 Some(canonical_path) => canonical_path.to_path_buf(),
8712 None => project.absolute_path(&project_path, cx)?,
8713 }
8714 .parent()?
8715 .to_path_buf();
8716 Some(parent)
8717 }) {
8718 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8719 }
8720 }
8721
8722 fn set_breakpoint_context_menu(
8723 &mut self,
8724 display_row: DisplayRow,
8725 position: Option<Anchor>,
8726 clicked_point: gpui::Point<Pixels>,
8727 window: &mut Window,
8728 cx: &mut Context<Self>,
8729 ) {
8730 if !cx.has_flag::<Debugger>() {
8731 return;
8732 }
8733 let source = self
8734 .buffer
8735 .read(cx)
8736 .snapshot(cx)
8737 .anchor_before(Point::new(display_row.0, 0u32));
8738
8739 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8740
8741 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8742 self,
8743 source,
8744 clicked_point,
8745 context_menu,
8746 window,
8747 cx,
8748 );
8749 }
8750
8751 fn add_edit_breakpoint_block(
8752 &mut self,
8753 anchor: Anchor,
8754 breakpoint: &Breakpoint,
8755 edit_action: BreakpointPromptEditAction,
8756 window: &mut Window,
8757 cx: &mut Context<Self>,
8758 ) {
8759 let weak_editor = cx.weak_entity();
8760 let bp_prompt = cx.new(|cx| {
8761 BreakpointPromptEditor::new(
8762 weak_editor,
8763 anchor,
8764 breakpoint.clone(),
8765 edit_action,
8766 window,
8767 cx,
8768 )
8769 });
8770
8771 let height = bp_prompt.update(cx, |this, cx| {
8772 this.prompt
8773 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8774 });
8775 let cloned_prompt = bp_prompt.clone();
8776 let blocks = vec![BlockProperties {
8777 style: BlockStyle::Sticky,
8778 placement: BlockPlacement::Above(anchor),
8779 height: Some(height),
8780 render: Arc::new(move |cx| {
8781 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8782 cloned_prompt.clone().into_any_element()
8783 }),
8784 priority: 0,
8785 }];
8786
8787 let focus_handle = bp_prompt.focus_handle(cx);
8788 window.focus(&focus_handle);
8789
8790 let block_ids = self.insert_blocks(blocks, None, cx);
8791 bp_prompt.update(cx, |prompt, _| {
8792 prompt.add_block_ids(block_ids);
8793 });
8794 }
8795
8796 fn breakpoint_at_cursor_head(
8797 &self,
8798 window: &mut Window,
8799 cx: &mut Context<Self>,
8800 ) -> Option<(Anchor, Breakpoint)> {
8801 let cursor_position: Point = self.selections.newest(cx).head();
8802 self.breakpoint_at_row(cursor_position.row, window, cx)
8803 }
8804
8805 pub(crate) fn breakpoint_at_row(
8806 &self,
8807 row: u32,
8808 window: &mut Window,
8809 cx: &mut Context<Self>,
8810 ) -> Option<(Anchor, Breakpoint)> {
8811 let snapshot = self.snapshot(window, cx);
8812 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
8813
8814 let project = self.project.clone()?;
8815
8816 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
8817 snapshot
8818 .buffer_snapshot
8819 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
8820 })?;
8821
8822 let enclosing_excerpt = breakpoint_position.excerpt_id;
8823 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8824 let buffer_snapshot = buffer.read(cx).snapshot();
8825
8826 let row = buffer_snapshot
8827 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
8828 .row;
8829
8830 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
8831 let anchor_end = snapshot
8832 .buffer_snapshot
8833 .anchor_after(Point::new(row, line_len));
8834
8835 let bp = self
8836 .breakpoint_store
8837 .as_ref()?
8838 .read_with(cx, |breakpoint_store, cx| {
8839 breakpoint_store
8840 .breakpoints(
8841 &buffer,
8842 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
8843 &buffer_snapshot,
8844 cx,
8845 )
8846 .next()
8847 .and_then(|(anchor, bp)| {
8848 let breakpoint_row = buffer_snapshot
8849 .summary_for_anchor::<text::PointUtf16>(anchor)
8850 .row;
8851
8852 if breakpoint_row == row {
8853 snapshot
8854 .buffer_snapshot
8855 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8856 .map(|anchor| (anchor, bp.clone()))
8857 } else {
8858 None
8859 }
8860 })
8861 });
8862 bp
8863 }
8864
8865 pub fn edit_log_breakpoint(
8866 &mut self,
8867 _: &EditLogBreakpoint,
8868 window: &mut Window,
8869 cx: &mut Context<Self>,
8870 ) {
8871 let (anchor, bp) = self
8872 .breakpoint_at_cursor_head(window, cx)
8873 .unwrap_or_else(|| {
8874 let cursor_position: Point = self.selections.newest(cx).head();
8875
8876 let breakpoint_position = self
8877 .snapshot(window, cx)
8878 .display_snapshot
8879 .buffer_snapshot
8880 .anchor_after(Point::new(cursor_position.row, 0));
8881
8882 (
8883 breakpoint_position,
8884 Breakpoint {
8885 message: None,
8886 state: BreakpointState::Enabled,
8887 condition: None,
8888 hit_condition: None,
8889 },
8890 )
8891 });
8892
8893 self.add_edit_breakpoint_block(anchor, &bp, BreakpointPromptEditAction::Log, window, cx);
8894 }
8895
8896 pub fn enable_breakpoint(
8897 &mut self,
8898 _: &crate::actions::EnableBreakpoint,
8899 window: &mut Window,
8900 cx: &mut Context<Self>,
8901 ) {
8902 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8903 if breakpoint.is_disabled() {
8904 self.edit_breakpoint_at_anchor(
8905 anchor,
8906 breakpoint,
8907 BreakpointEditAction::InvertState,
8908 cx,
8909 );
8910 }
8911 }
8912 }
8913
8914 pub fn disable_breakpoint(
8915 &mut self,
8916 _: &crate::actions::DisableBreakpoint,
8917 window: &mut Window,
8918 cx: &mut Context<Self>,
8919 ) {
8920 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8921 if breakpoint.is_enabled() {
8922 self.edit_breakpoint_at_anchor(
8923 anchor,
8924 breakpoint,
8925 BreakpointEditAction::InvertState,
8926 cx,
8927 );
8928 }
8929 }
8930 }
8931
8932 pub fn toggle_breakpoint(
8933 &mut self,
8934 _: &crate::actions::ToggleBreakpoint,
8935 window: &mut Window,
8936 cx: &mut Context<Self>,
8937 ) {
8938 let edit_action = BreakpointEditAction::Toggle;
8939
8940 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8941 self.edit_breakpoint_at_anchor(anchor, breakpoint, edit_action, cx);
8942 } else {
8943 let cursor_position: Point = self.selections.newest(cx).head();
8944
8945 let breakpoint_position = self
8946 .snapshot(window, cx)
8947 .display_snapshot
8948 .buffer_snapshot
8949 .anchor_after(Point::new(cursor_position.row, 0));
8950
8951 self.edit_breakpoint_at_anchor(
8952 breakpoint_position,
8953 Breakpoint::new_standard(),
8954 edit_action,
8955 cx,
8956 );
8957 }
8958 }
8959
8960 pub fn edit_breakpoint_at_anchor(
8961 &mut self,
8962 breakpoint_position: Anchor,
8963 breakpoint: Breakpoint,
8964 edit_action: BreakpointEditAction,
8965 cx: &mut Context<Self>,
8966 ) {
8967 let Some(breakpoint_store) = &self.breakpoint_store else {
8968 return;
8969 };
8970
8971 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8972 if breakpoint_position == Anchor::min() {
8973 self.buffer()
8974 .read(cx)
8975 .excerpt_buffer_ids()
8976 .into_iter()
8977 .next()
8978 } else {
8979 None
8980 }
8981 }) else {
8982 return;
8983 };
8984
8985 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8986 return;
8987 };
8988
8989 breakpoint_store.update(cx, |breakpoint_store, cx| {
8990 breakpoint_store.toggle_breakpoint(
8991 buffer,
8992 (breakpoint_position.text_anchor, breakpoint),
8993 edit_action,
8994 cx,
8995 );
8996 });
8997
8998 cx.notify();
8999 }
9000
9001 #[cfg(any(test, feature = "test-support"))]
9002 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9003 self.breakpoint_store.clone()
9004 }
9005
9006 pub fn prepare_restore_change(
9007 &self,
9008 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9009 hunk: &MultiBufferDiffHunk,
9010 cx: &mut App,
9011 ) -> Option<()> {
9012 if hunk.is_created_file() {
9013 return None;
9014 }
9015 let buffer = self.buffer.read(cx);
9016 let diff = buffer.diff_for(hunk.buffer_id)?;
9017 let buffer = buffer.buffer(hunk.buffer_id)?;
9018 let buffer = buffer.read(cx);
9019 let original_text = diff
9020 .read(cx)
9021 .base_text()
9022 .as_rope()
9023 .slice(hunk.diff_base_byte_range.clone());
9024 let buffer_snapshot = buffer.snapshot();
9025 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9026 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9027 probe
9028 .0
9029 .start
9030 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9031 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9032 }) {
9033 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9034 Some(())
9035 } else {
9036 None
9037 }
9038 }
9039
9040 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9041 self.manipulate_lines(window, cx, |lines| lines.reverse())
9042 }
9043
9044 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9045 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9046 }
9047
9048 fn manipulate_lines<Fn>(
9049 &mut self,
9050 window: &mut Window,
9051 cx: &mut Context<Self>,
9052 mut callback: Fn,
9053 ) where
9054 Fn: FnMut(&mut Vec<&str>),
9055 {
9056 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9057
9058 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9059 let buffer = self.buffer.read(cx).snapshot(cx);
9060
9061 let mut edits = Vec::new();
9062
9063 let selections = self.selections.all::<Point>(cx);
9064 let mut selections = selections.iter().peekable();
9065 let mut contiguous_row_selections = Vec::new();
9066 let mut new_selections = Vec::new();
9067 let mut added_lines = 0;
9068 let mut removed_lines = 0;
9069
9070 while let Some(selection) = selections.next() {
9071 let (start_row, end_row) = consume_contiguous_rows(
9072 &mut contiguous_row_selections,
9073 selection,
9074 &display_map,
9075 &mut selections,
9076 );
9077
9078 let start_point = Point::new(start_row.0, 0);
9079 let end_point = Point::new(
9080 end_row.previous_row().0,
9081 buffer.line_len(end_row.previous_row()),
9082 );
9083 let text = buffer
9084 .text_for_range(start_point..end_point)
9085 .collect::<String>();
9086
9087 let mut lines = text.split('\n').collect_vec();
9088
9089 let lines_before = lines.len();
9090 callback(&mut lines);
9091 let lines_after = lines.len();
9092
9093 edits.push((start_point..end_point, lines.join("\n")));
9094
9095 // Selections must change based on added and removed line count
9096 let start_row =
9097 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9098 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9099 new_selections.push(Selection {
9100 id: selection.id,
9101 start: start_row,
9102 end: end_row,
9103 goal: SelectionGoal::None,
9104 reversed: selection.reversed,
9105 });
9106
9107 if lines_after > lines_before {
9108 added_lines += lines_after - lines_before;
9109 } else if lines_before > lines_after {
9110 removed_lines += lines_before - lines_after;
9111 }
9112 }
9113
9114 self.transact(window, cx, |this, window, cx| {
9115 let buffer = this.buffer.update(cx, |buffer, cx| {
9116 buffer.edit(edits, None, cx);
9117 buffer.snapshot(cx)
9118 });
9119
9120 // Recalculate offsets on newly edited buffer
9121 let new_selections = new_selections
9122 .iter()
9123 .map(|s| {
9124 let start_point = Point::new(s.start.0, 0);
9125 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9126 Selection {
9127 id: s.id,
9128 start: buffer.point_to_offset(start_point),
9129 end: buffer.point_to_offset(end_point),
9130 goal: s.goal,
9131 reversed: s.reversed,
9132 }
9133 })
9134 .collect();
9135
9136 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9137 s.select(new_selections);
9138 });
9139
9140 this.request_autoscroll(Autoscroll::fit(), cx);
9141 });
9142 }
9143
9144 pub fn convert_to_upper_case(
9145 &mut self,
9146 _: &ConvertToUpperCase,
9147 window: &mut Window,
9148 cx: &mut Context<Self>,
9149 ) {
9150 self.manipulate_text(window, cx, |text| text.to_uppercase())
9151 }
9152
9153 pub fn convert_to_lower_case(
9154 &mut self,
9155 _: &ConvertToLowerCase,
9156 window: &mut Window,
9157 cx: &mut Context<Self>,
9158 ) {
9159 self.manipulate_text(window, cx, |text| text.to_lowercase())
9160 }
9161
9162 pub fn convert_to_title_case(
9163 &mut self,
9164 _: &ConvertToTitleCase,
9165 window: &mut Window,
9166 cx: &mut Context<Self>,
9167 ) {
9168 self.manipulate_text(window, cx, |text| {
9169 text.split('\n')
9170 .map(|line| line.to_case(Case::Title))
9171 .join("\n")
9172 })
9173 }
9174
9175 pub fn convert_to_snake_case(
9176 &mut self,
9177 _: &ConvertToSnakeCase,
9178 window: &mut Window,
9179 cx: &mut Context<Self>,
9180 ) {
9181 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9182 }
9183
9184 pub fn convert_to_kebab_case(
9185 &mut self,
9186 _: &ConvertToKebabCase,
9187 window: &mut Window,
9188 cx: &mut Context<Self>,
9189 ) {
9190 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9191 }
9192
9193 pub fn convert_to_upper_camel_case(
9194 &mut self,
9195 _: &ConvertToUpperCamelCase,
9196 window: &mut Window,
9197 cx: &mut Context<Self>,
9198 ) {
9199 self.manipulate_text(window, cx, |text| {
9200 text.split('\n')
9201 .map(|line| line.to_case(Case::UpperCamel))
9202 .join("\n")
9203 })
9204 }
9205
9206 pub fn convert_to_lower_camel_case(
9207 &mut self,
9208 _: &ConvertToLowerCamelCase,
9209 window: &mut Window,
9210 cx: &mut Context<Self>,
9211 ) {
9212 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9213 }
9214
9215 pub fn convert_to_opposite_case(
9216 &mut self,
9217 _: &ConvertToOppositeCase,
9218 window: &mut Window,
9219 cx: &mut Context<Self>,
9220 ) {
9221 self.manipulate_text(window, cx, |text| {
9222 text.chars()
9223 .fold(String::with_capacity(text.len()), |mut t, c| {
9224 if c.is_uppercase() {
9225 t.extend(c.to_lowercase());
9226 } else {
9227 t.extend(c.to_uppercase());
9228 }
9229 t
9230 })
9231 })
9232 }
9233
9234 pub fn convert_to_rot13(
9235 &mut self,
9236 _: &ConvertToRot13,
9237 window: &mut Window,
9238 cx: &mut Context<Self>,
9239 ) {
9240 self.manipulate_text(window, cx, |text| {
9241 text.chars()
9242 .map(|c| match c {
9243 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9244 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9245 _ => c,
9246 })
9247 .collect()
9248 })
9249 }
9250
9251 pub fn convert_to_rot47(
9252 &mut self,
9253 _: &ConvertToRot47,
9254 window: &mut Window,
9255 cx: &mut Context<Self>,
9256 ) {
9257 self.manipulate_text(window, cx, |text| {
9258 text.chars()
9259 .map(|c| {
9260 let code_point = c as u32;
9261 if code_point >= 33 && code_point <= 126 {
9262 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9263 }
9264 c
9265 })
9266 .collect()
9267 })
9268 }
9269
9270 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9271 where
9272 Fn: FnMut(&str) -> String,
9273 {
9274 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9275 let buffer = self.buffer.read(cx).snapshot(cx);
9276
9277 let mut new_selections = Vec::new();
9278 let mut edits = Vec::new();
9279 let mut selection_adjustment = 0i32;
9280
9281 for selection in self.selections.all::<usize>(cx) {
9282 let selection_is_empty = selection.is_empty();
9283
9284 let (start, end) = if selection_is_empty {
9285 let word_range = movement::surrounding_word(
9286 &display_map,
9287 selection.start.to_display_point(&display_map),
9288 );
9289 let start = word_range.start.to_offset(&display_map, Bias::Left);
9290 let end = word_range.end.to_offset(&display_map, Bias::Left);
9291 (start, end)
9292 } else {
9293 (selection.start, selection.end)
9294 };
9295
9296 let text = buffer.text_for_range(start..end).collect::<String>();
9297 let old_length = text.len() as i32;
9298 let text = callback(&text);
9299
9300 new_selections.push(Selection {
9301 start: (start as i32 - selection_adjustment) as usize,
9302 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9303 goal: SelectionGoal::None,
9304 ..selection
9305 });
9306
9307 selection_adjustment += old_length - text.len() as i32;
9308
9309 edits.push((start..end, text));
9310 }
9311
9312 self.transact(window, cx, |this, window, cx| {
9313 this.buffer.update(cx, |buffer, cx| {
9314 buffer.edit(edits, None, cx);
9315 });
9316
9317 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9318 s.select(new_selections);
9319 });
9320
9321 this.request_autoscroll(Autoscroll::fit(), cx);
9322 });
9323 }
9324
9325 pub fn duplicate(
9326 &mut self,
9327 upwards: bool,
9328 whole_lines: bool,
9329 window: &mut Window,
9330 cx: &mut Context<Self>,
9331 ) {
9332 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9333
9334 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9335 let buffer = &display_map.buffer_snapshot;
9336 let selections = self.selections.all::<Point>(cx);
9337
9338 let mut edits = Vec::new();
9339 let mut selections_iter = selections.iter().peekable();
9340 while let Some(selection) = selections_iter.next() {
9341 let mut rows = selection.spanned_rows(false, &display_map);
9342 // duplicate line-wise
9343 if whole_lines || selection.start == selection.end {
9344 // Avoid duplicating the same lines twice.
9345 while let Some(next_selection) = selections_iter.peek() {
9346 let next_rows = next_selection.spanned_rows(false, &display_map);
9347 if next_rows.start < rows.end {
9348 rows.end = next_rows.end;
9349 selections_iter.next().unwrap();
9350 } else {
9351 break;
9352 }
9353 }
9354
9355 // Copy the text from the selected row region and splice it either at the start
9356 // or end of the region.
9357 let start = Point::new(rows.start.0, 0);
9358 let end = Point::new(
9359 rows.end.previous_row().0,
9360 buffer.line_len(rows.end.previous_row()),
9361 );
9362 let text = buffer
9363 .text_for_range(start..end)
9364 .chain(Some("\n"))
9365 .collect::<String>();
9366 let insert_location = if upwards {
9367 Point::new(rows.end.0, 0)
9368 } else {
9369 start
9370 };
9371 edits.push((insert_location..insert_location, text));
9372 } else {
9373 // duplicate character-wise
9374 let start = selection.start;
9375 let end = selection.end;
9376 let text = buffer.text_for_range(start..end).collect::<String>();
9377 edits.push((selection.end..selection.end, text));
9378 }
9379 }
9380
9381 self.transact(window, cx, |this, _, cx| {
9382 this.buffer.update(cx, |buffer, cx| {
9383 buffer.edit(edits, None, cx);
9384 });
9385
9386 this.request_autoscroll(Autoscroll::fit(), cx);
9387 });
9388 }
9389
9390 pub fn duplicate_line_up(
9391 &mut self,
9392 _: &DuplicateLineUp,
9393 window: &mut Window,
9394 cx: &mut Context<Self>,
9395 ) {
9396 self.duplicate(true, true, window, cx);
9397 }
9398
9399 pub fn duplicate_line_down(
9400 &mut self,
9401 _: &DuplicateLineDown,
9402 window: &mut Window,
9403 cx: &mut Context<Self>,
9404 ) {
9405 self.duplicate(false, true, window, cx);
9406 }
9407
9408 pub fn duplicate_selection(
9409 &mut self,
9410 _: &DuplicateSelection,
9411 window: &mut Window,
9412 cx: &mut Context<Self>,
9413 ) {
9414 self.duplicate(false, false, window, cx);
9415 }
9416
9417 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9418 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9419
9420 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9421 let buffer = self.buffer.read(cx).snapshot(cx);
9422
9423 let mut edits = Vec::new();
9424 let mut unfold_ranges = Vec::new();
9425 let mut refold_creases = Vec::new();
9426
9427 let selections = self.selections.all::<Point>(cx);
9428 let mut selections = selections.iter().peekable();
9429 let mut contiguous_row_selections = Vec::new();
9430 let mut new_selections = Vec::new();
9431
9432 while let Some(selection) = selections.next() {
9433 // Find all the selections that span a contiguous row range
9434 let (start_row, end_row) = consume_contiguous_rows(
9435 &mut contiguous_row_selections,
9436 selection,
9437 &display_map,
9438 &mut selections,
9439 );
9440
9441 // Move the text spanned by the row range to be before the line preceding the row range
9442 if start_row.0 > 0 {
9443 let range_to_move = Point::new(
9444 start_row.previous_row().0,
9445 buffer.line_len(start_row.previous_row()),
9446 )
9447 ..Point::new(
9448 end_row.previous_row().0,
9449 buffer.line_len(end_row.previous_row()),
9450 );
9451 let insertion_point = display_map
9452 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9453 .0;
9454
9455 // Don't move lines across excerpts
9456 if buffer
9457 .excerpt_containing(insertion_point..range_to_move.end)
9458 .is_some()
9459 {
9460 let text = buffer
9461 .text_for_range(range_to_move.clone())
9462 .flat_map(|s| s.chars())
9463 .skip(1)
9464 .chain(['\n'])
9465 .collect::<String>();
9466
9467 edits.push((
9468 buffer.anchor_after(range_to_move.start)
9469 ..buffer.anchor_before(range_to_move.end),
9470 String::new(),
9471 ));
9472 let insertion_anchor = buffer.anchor_after(insertion_point);
9473 edits.push((insertion_anchor..insertion_anchor, text));
9474
9475 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9476
9477 // Move selections up
9478 new_selections.extend(contiguous_row_selections.drain(..).map(
9479 |mut selection| {
9480 selection.start.row -= row_delta;
9481 selection.end.row -= row_delta;
9482 selection
9483 },
9484 ));
9485
9486 // Move folds up
9487 unfold_ranges.push(range_to_move.clone());
9488 for fold in display_map.folds_in_range(
9489 buffer.anchor_before(range_to_move.start)
9490 ..buffer.anchor_after(range_to_move.end),
9491 ) {
9492 let mut start = fold.range.start.to_point(&buffer);
9493 let mut end = fold.range.end.to_point(&buffer);
9494 start.row -= row_delta;
9495 end.row -= row_delta;
9496 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9497 }
9498 }
9499 }
9500
9501 // If we didn't move line(s), preserve the existing selections
9502 new_selections.append(&mut contiguous_row_selections);
9503 }
9504
9505 self.transact(window, cx, |this, window, cx| {
9506 this.unfold_ranges(&unfold_ranges, true, true, cx);
9507 this.buffer.update(cx, |buffer, cx| {
9508 for (range, text) in edits {
9509 buffer.edit([(range, text)], None, cx);
9510 }
9511 });
9512 this.fold_creases(refold_creases, true, window, cx);
9513 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9514 s.select(new_selections);
9515 })
9516 });
9517 }
9518
9519 pub fn move_line_down(
9520 &mut self,
9521 _: &MoveLineDown,
9522 window: &mut Window,
9523 cx: &mut Context<Self>,
9524 ) {
9525 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9526
9527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9528 let buffer = self.buffer.read(cx).snapshot(cx);
9529
9530 let mut edits = Vec::new();
9531 let mut unfold_ranges = Vec::new();
9532 let mut refold_creases = Vec::new();
9533
9534 let selections = self.selections.all::<Point>(cx);
9535 let mut selections = selections.iter().peekable();
9536 let mut contiguous_row_selections = Vec::new();
9537 let mut new_selections = Vec::new();
9538
9539 while let Some(selection) = selections.next() {
9540 // Find all the selections that span a contiguous row range
9541 let (start_row, end_row) = consume_contiguous_rows(
9542 &mut contiguous_row_selections,
9543 selection,
9544 &display_map,
9545 &mut selections,
9546 );
9547
9548 // Move the text spanned by the row range to be after the last line of the row range
9549 if end_row.0 <= buffer.max_point().row {
9550 let range_to_move =
9551 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9552 let insertion_point = display_map
9553 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9554 .0;
9555
9556 // Don't move lines across excerpt boundaries
9557 if buffer
9558 .excerpt_containing(range_to_move.start..insertion_point)
9559 .is_some()
9560 {
9561 let mut text = String::from("\n");
9562 text.extend(buffer.text_for_range(range_to_move.clone()));
9563 text.pop(); // Drop trailing newline
9564 edits.push((
9565 buffer.anchor_after(range_to_move.start)
9566 ..buffer.anchor_before(range_to_move.end),
9567 String::new(),
9568 ));
9569 let insertion_anchor = buffer.anchor_after(insertion_point);
9570 edits.push((insertion_anchor..insertion_anchor, text));
9571
9572 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9573
9574 // Move selections down
9575 new_selections.extend(contiguous_row_selections.drain(..).map(
9576 |mut selection| {
9577 selection.start.row += row_delta;
9578 selection.end.row += row_delta;
9579 selection
9580 },
9581 ));
9582
9583 // Move folds down
9584 unfold_ranges.push(range_to_move.clone());
9585 for fold in display_map.folds_in_range(
9586 buffer.anchor_before(range_to_move.start)
9587 ..buffer.anchor_after(range_to_move.end),
9588 ) {
9589 let mut start = fold.range.start.to_point(&buffer);
9590 let mut end = fold.range.end.to_point(&buffer);
9591 start.row += row_delta;
9592 end.row += row_delta;
9593 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9594 }
9595 }
9596 }
9597
9598 // If we didn't move line(s), preserve the existing selections
9599 new_selections.append(&mut contiguous_row_selections);
9600 }
9601
9602 self.transact(window, cx, |this, window, cx| {
9603 this.unfold_ranges(&unfold_ranges, true, true, cx);
9604 this.buffer.update(cx, |buffer, cx| {
9605 for (range, text) in edits {
9606 buffer.edit([(range, text)], None, cx);
9607 }
9608 });
9609 this.fold_creases(refold_creases, true, window, cx);
9610 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9611 s.select(new_selections)
9612 });
9613 });
9614 }
9615
9616 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9617 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9618 let text_layout_details = &self.text_layout_details(window);
9619 self.transact(window, cx, |this, window, cx| {
9620 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9621 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9622 s.move_with(|display_map, selection| {
9623 if !selection.is_empty() {
9624 return;
9625 }
9626
9627 let mut head = selection.head();
9628 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9629 if head.column() == display_map.line_len(head.row()) {
9630 transpose_offset = display_map
9631 .buffer_snapshot
9632 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9633 }
9634
9635 if transpose_offset == 0 {
9636 return;
9637 }
9638
9639 *head.column_mut() += 1;
9640 head = display_map.clip_point(head, Bias::Right);
9641 let goal = SelectionGoal::HorizontalPosition(
9642 display_map
9643 .x_for_display_point(head, text_layout_details)
9644 .into(),
9645 );
9646 selection.collapse_to(head, goal);
9647
9648 let transpose_start = display_map
9649 .buffer_snapshot
9650 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9651 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9652 let transpose_end = display_map
9653 .buffer_snapshot
9654 .clip_offset(transpose_offset + 1, Bias::Right);
9655 if let Some(ch) =
9656 display_map.buffer_snapshot.chars_at(transpose_start).next()
9657 {
9658 edits.push((transpose_start..transpose_offset, String::new()));
9659 edits.push((transpose_end..transpose_end, ch.to_string()));
9660 }
9661 }
9662 });
9663 edits
9664 });
9665 this.buffer
9666 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9667 let selections = this.selections.all::<usize>(cx);
9668 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9669 s.select(selections);
9670 });
9671 });
9672 }
9673
9674 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9675 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9676 self.rewrap_impl(RewrapOptions::default(), cx)
9677 }
9678
9679 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9680 let buffer = self.buffer.read(cx).snapshot(cx);
9681 let selections = self.selections.all::<Point>(cx);
9682 let mut selections = selections.iter().peekable();
9683
9684 let mut edits = Vec::new();
9685 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9686
9687 while let Some(selection) = selections.next() {
9688 let mut start_row = selection.start.row;
9689 let mut end_row = selection.end.row;
9690
9691 // Skip selections that overlap with a range that has already been rewrapped.
9692 let selection_range = start_row..end_row;
9693 if rewrapped_row_ranges
9694 .iter()
9695 .any(|range| range.overlaps(&selection_range))
9696 {
9697 continue;
9698 }
9699
9700 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9701
9702 // Since not all lines in the selection may be at the same indent
9703 // level, choose the indent size that is the most common between all
9704 // of the lines.
9705 //
9706 // If there is a tie, we use the deepest indent.
9707 let (indent_size, indent_end) = {
9708 let mut indent_size_occurrences = HashMap::default();
9709 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9710
9711 for row in start_row..=end_row {
9712 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9713 rows_by_indent_size.entry(indent).or_default().push(row);
9714 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9715 }
9716
9717 let indent_size = indent_size_occurrences
9718 .into_iter()
9719 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9720 .map(|(indent, _)| indent)
9721 .unwrap_or_default();
9722 let row = rows_by_indent_size[&indent_size][0];
9723 let indent_end = Point::new(row, indent_size.len);
9724
9725 (indent_size, indent_end)
9726 };
9727
9728 let mut line_prefix = indent_size.chars().collect::<String>();
9729
9730 let mut inside_comment = false;
9731 if let Some(comment_prefix) =
9732 buffer
9733 .language_scope_at(selection.head())
9734 .and_then(|language| {
9735 language
9736 .line_comment_prefixes()
9737 .iter()
9738 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9739 .cloned()
9740 })
9741 {
9742 line_prefix.push_str(&comment_prefix);
9743 inside_comment = true;
9744 }
9745
9746 let language_settings = buffer.language_settings_at(selection.head(), cx);
9747 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9748 RewrapBehavior::InComments => inside_comment,
9749 RewrapBehavior::InSelections => !selection.is_empty(),
9750 RewrapBehavior::Anywhere => true,
9751 };
9752
9753 let should_rewrap = options.override_language_settings
9754 || allow_rewrap_based_on_language
9755 || self.hard_wrap.is_some();
9756 if !should_rewrap {
9757 continue;
9758 }
9759
9760 if selection.is_empty() {
9761 'expand_upwards: while start_row > 0 {
9762 let prev_row = start_row - 1;
9763 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9764 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9765 {
9766 start_row = prev_row;
9767 } else {
9768 break 'expand_upwards;
9769 }
9770 }
9771
9772 'expand_downwards: while end_row < buffer.max_point().row {
9773 let next_row = end_row + 1;
9774 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9775 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9776 {
9777 end_row = next_row;
9778 } else {
9779 break 'expand_downwards;
9780 }
9781 }
9782 }
9783
9784 let start = Point::new(start_row, 0);
9785 let start_offset = start.to_offset(&buffer);
9786 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9787 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9788 let Some(lines_without_prefixes) = selection_text
9789 .lines()
9790 .map(|line| {
9791 line.strip_prefix(&line_prefix)
9792 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9793 .ok_or_else(|| {
9794 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9795 })
9796 })
9797 .collect::<Result<Vec<_>, _>>()
9798 .log_err()
9799 else {
9800 continue;
9801 };
9802
9803 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9804 buffer
9805 .language_settings_at(Point::new(start_row, 0), cx)
9806 .preferred_line_length as usize
9807 });
9808 let wrapped_text = wrap_with_prefix(
9809 line_prefix,
9810 lines_without_prefixes.join("\n"),
9811 wrap_column,
9812 tab_size,
9813 options.preserve_existing_whitespace,
9814 );
9815
9816 // TODO: should always use char-based diff while still supporting cursor behavior that
9817 // matches vim.
9818 let mut diff_options = DiffOptions::default();
9819 if options.override_language_settings {
9820 diff_options.max_word_diff_len = 0;
9821 diff_options.max_word_diff_line_count = 0;
9822 } else {
9823 diff_options.max_word_diff_len = usize::MAX;
9824 diff_options.max_word_diff_line_count = usize::MAX;
9825 }
9826
9827 for (old_range, new_text) in
9828 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9829 {
9830 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9831 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9832 edits.push((edit_start..edit_end, new_text));
9833 }
9834
9835 rewrapped_row_ranges.push(start_row..=end_row);
9836 }
9837
9838 self.buffer
9839 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9840 }
9841
9842 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9843 let mut text = String::new();
9844 let buffer = self.buffer.read(cx).snapshot(cx);
9845 let mut selections = self.selections.all::<Point>(cx);
9846 let mut clipboard_selections = Vec::with_capacity(selections.len());
9847 {
9848 let max_point = buffer.max_point();
9849 let mut is_first = true;
9850 for selection in &mut selections {
9851 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9852 if is_entire_line {
9853 selection.start = Point::new(selection.start.row, 0);
9854 if !selection.is_empty() && selection.end.column == 0 {
9855 selection.end = cmp::min(max_point, selection.end);
9856 } else {
9857 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9858 }
9859 selection.goal = SelectionGoal::None;
9860 }
9861 if is_first {
9862 is_first = false;
9863 } else {
9864 text += "\n";
9865 }
9866 let mut len = 0;
9867 for chunk in buffer.text_for_range(selection.start..selection.end) {
9868 text.push_str(chunk);
9869 len += chunk.len();
9870 }
9871 clipboard_selections.push(ClipboardSelection {
9872 len,
9873 is_entire_line,
9874 first_line_indent: buffer
9875 .indent_size_for_line(MultiBufferRow(selection.start.row))
9876 .len,
9877 });
9878 }
9879 }
9880
9881 self.transact(window, cx, |this, window, cx| {
9882 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9883 s.select(selections);
9884 });
9885 this.insert("", window, cx);
9886 });
9887 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9888 }
9889
9890 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9891 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9892 let item = self.cut_common(window, cx);
9893 cx.write_to_clipboard(item);
9894 }
9895
9896 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9897 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9898 self.change_selections(None, window, cx, |s| {
9899 s.move_with(|snapshot, sel| {
9900 if sel.is_empty() {
9901 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9902 }
9903 });
9904 });
9905 let item = self.cut_common(window, cx);
9906 cx.set_global(KillRing(item))
9907 }
9908
9909 pub fn kill_ring_yank(
9910 &mut self,
9911 _: &KillRingYank,
9912 window: &mut Window,
9913 cx: &mut Context<Self>,
9914 ) {
9915 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9916 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9917 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9918 (kill_ring.text().to_string(), kill_ring.metadata_json())
9919 } else {
9920 return;
9921 }
9922 } else {
9923 return;
9924 };
9925 self.do_paste(&text, metadata, false, window, cx);
9926 }
9927
9928 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9929 self.do_copy(true, cx);
9930 }
9931
9932 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9933 self.do_copy(false, cx);
9934 }
9935
9936 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9937 let selections = self.selections.all::<Point>(cx);
9938 let buffer = self.buffer.read(cx).read(cx);
9939 let mut text = String::new();
9940
9941 let mut clipboard_selections = Vec::with_capacity(selections.len());
9942 {
9943 let max_point = buffer.max_point();
9944 let mut is_first = true;
9945 for selection in &selections {
9946 let mut start = selection.start;
9947 let mut end = selection.end;
9948 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9949 if is_entire_line {
9950 start = Point::new(start.row, 0);
9951 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9952 }
9953
9954 let mut trimmed_selections = Vec::new();
9955 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
9956 let row = MultiBufferRow(start.row);
9957 let first_indent = buffer.indent_size_for_line(row);
9958 if first_indent.len == 0 || start.column > first_indent.len {
9959 trimmed_selections.push(start..end);
9960 } else {
9961 trimmed_selections.push(
9962 Point::new(row.0, first_indent.len)
9963 ..Point::new(row.0, buffer.line_len(row)),
9964 );
9965 for row in start.row + 1..=end.row {
9966 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
9967 if row_indent_size.len >= first_indent.len {
9968 trimmed_selections.push(
9969 Point::new(row, first_indent.len)
9970 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
9971 );
9972 } else {
9973 trimmed_selections.clear();
9974 trimmed_selections.push(start..end);
9975 break;
9976 }
9977 }
9978 }
9979 } else {
9980 trimmed_selections.push(start..end);
9981 }
9982
9983 for trimmed_range in trimmed_selections {
9984 if is_first {
9985 is_first = false;
9986 } else {
9987 text += "\n";
9988 }
9989 let mut len = 0;
9990 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
9991 text.push_str(chunk);
9992 len += chunk.len();
9993 }
9994 clipboard_selections.push(ClipboardSelection {
9995 len,
9996 is_entire_line,
9997 first_line_indent: buffer
9998 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
9999 .len,
10000 });
10001 }
10002 }
10003 }
10004
10005 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10006 text,
10007 clipboard_selections,
10008 ));
10009 }
10010
10011 pub fn do_paste(
10012 &mut self,
10013 text: &String,
10014 clipboard_selections: Option<Vec<ClipboardSelection>>,
10015 handle_entire_lines: bool,
10016 window: &mut Window,
10017 cx: &mut Context<Self>,
10018 ) {
10019 if self.read_only(cx) {
10020 return;
10021 }
10022
10023 let clipboard_text = Cow::Borrowed(text);
10024
10025 self.transact(window, cx, |this, window, cx| {
10026 if let Some(mut clipboard_selections) = clipboard_selections {
10027 let old_selections = this.selections.all::<usize>(cx);
10028 let all_selections_were_entire_line =
10029 clipboard_selections.iter().all(|s| s.is_entire_line);
10030 let first_selection_indent_column =
10031 clipboard_selections.first().map(|s| s.first_line_indent);
10032 if clipboard_selections.len() != old_selections.len() {
10033 clipboard_selections.drain(..);
10034 }
10035 let cursor_offset = this.selections.last::<usize>(cx).head();
10036 let mut auto_indent_on_paste = true;
10037
10038 this.buffer.update(cx, |buffer, cx| {
10039 let snapshot = buffer.read(cx);
10040 auto_indent_on_paste = snapshot
10041 .language_settings_at(cursor_offset, cx)
10042 .auto_indent_on_paste;
10043
10044 let mut start_offset = 0;
10045 let mut edits = Vec::new();
10046 let mut original_indent_columns = Vec::new();
10047 for (ix, selection) in old_selections.iter().enumerate() {
10048 let to_insert;
10049 let entire_line;
10050 let original_indent_column;
10051 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10052 let end_offset = start_offset + clipboard_selection.len;
10053 to_insert = &clipboard_text[start_offset..end_offset];
10054 entire_line = clipboard_selection.is_entire_line;
10055 start_offset = end_offset + 1;
10056 original_indent_column = Some(clipboard_selection.first_line_indent);
10057 } else {
10058 to_insert = clipboard_text.as_str();
10059 entire_line = all_selections_were_entire_line;
10060 original_indent_column = first_selection_indent_column
10061 }
10062
10063 // If the corresponding selection was empty when this slice of the
10064 // clipboard text was written, then the entire line containing the
10065 // selection was copied. If this selection is also currently empty,
10066 // then paste the line before the current line of the buffer.
10067 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10068 let column = selection.start.to_point(&snapshot).column as usize;
10069 let line_start = selection.start - column;
10070 line_start..line_start
10071 } else {
10072 selection.range()
10073 };
10074
10075 edits.push((range, to_insert));
10076 original_indent_columns.push(original_indent_column);
10077 }
10078 drop(snapshot);
10079
10080 buffer.edit(
10081 edits,
10082 if auto_indent_on_paste {
10083 Some(AutoindentMode::Block {
10084 original_indent_columns,
10085 })
10086 } else {
10087 None
10088 },
10089 cx,
10090 );
10091 });
10092
10093 let selections = this.selections.all::<usize>(cx);
10094 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10095 s.select(selections)
10096 });
10097 } else {
10098 this.insert(&clipboard_text, window, cx);
10099 }
10100 });
10101 }
10102
10103 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10104 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10105 if let Some(item) = cx.read_from_clipboard() {
10106 let entries = item.entries();
10107
10108 match entries.first() {
10109 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10110 // of all the pasted entries.
10111 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10112 .do_paste(
10113 clipboard_string.text(),
10114 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10115 true,
10116 window,
10117 cx,
10118 ),
10119 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10120 }
10121 }
10122 }
10123
10124 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10125 if self.read_only(cx) {
10126 return;
10127 }
10128
10129 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10130
10131 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10132 if let Some((selections, _)) =
10133 self.selection_history.transaction(transaction_id).cloned()
10134 {
10135 self.change_selections(None, window, cx, |s| {
10136 s.select_anchors(selections.to_vec());
10137 });
10138 } else {
10139 log::error!(
10140 "No entry in selection_history found for undo. \
10141 This may correspond to a bug where undo does not update the selection. \
10142 If this is occurring, please add details to \
10143 https://github.com/zed-industries/zed/issues/22692"
10144 );
10145 }
10146 self.request_autoscroll(Autoscroll::fit(), cx);
10147 self.unmark_text(window, cx);
10148 self.refresh_inline_completion(true, false, window, cx);
10149 cx.emit(EditorEvent::Edited { transaction_id });
10150 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10151 }
10152 }
10153
10154 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10155 if self.read_only(cx) {
10156 return;
10157 }
10158
10159 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10160
10161 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10162 if let Some((_, Some(selections))) =
10163 self.selection_history.transaction(transaction_id).cloned()
10164 {
10165 self.change_selections(None, window, cx, |s| {
10166 s.select_anchors(selections.to_vec());
10167 });
10168 } else {
10169 log::error!(
10170 "No entry in selection_history found for redo. \
10171 This may correspond to a bug where undo does not update the selection. \
10172 If this is occurring, please add details to \
10173 https://github.com/zed-industries/zed/issues/22692"
10174 );
10175 }
10176 self.request_autoscroll(Autoscroll::fit(), cx);
10177 self.unmark_text(window, cx);
10178 self.refresh_inline_completion(true, false, window, cx);
10179 cx.emit(EditorEvent::Edited { transaction_id });
10180 }
10181 }
10182
10183 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10184 self.buffer
10185 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10186 }
10187
10188 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10189 self.buffer
10190 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10191 }
10192
10193 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10194 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10195 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10196 s.move_with(|map, selection| {
10197 let cursor = if selection.is_empty() {
10198 movement::left(map, selection.start)
10199 } else {
10200 selection.start
10201 };
10202 selection.collapse_to(cursor, SelectionGoal::None);
10203 });
10204 })
10205 }
10206
10207 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10208 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10209 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10210 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10211 })
10212 }
10213
10214 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10215 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10216 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10217 s.move_with(|map, selection| {
10218 let cursor = if selection.is_empty() {
10219 movement::right(map, selection.end)
10220 } else {
10221 selection.end
10222 };
10223 selection.collapse_to(cursor, SelectionGoal::None)
10224 });
10225 })
10226 }
10227
10228 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10229 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10230 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10231 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10232 })
10233 }
10234
10235 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10236 if self.take_rename(true, window, cx).is_some() {
10237 return;
10238 }
10239
10240 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10241 cx.propagate();
10242 return;
10243 }
10244
10245 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10246
10247 let text_layout_details = &self.text_layout_details(window);
10248 let selection_count = self.selections.count();
10249 let first_selection = self.selections.first_anchor();
10250
10251 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10252 s.move_with(|map, selection| {
10253 if !selection.is_empty() {
10254 selection.goal = SelectionGoal::None;
10255 }
10256 let (cursor, goal) = movement::up(
10257 map,
10258 selection.start,
10259 selection.goal,
10260 false,
10261 text_layout_details,
10262 );
10263 selection.collapse_to(cursor, goal);
10264 });
10265 });
10266
10267 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10268 {
10269 cx.propagate();
10270 }
10271 }
10272
10273 pub fn move_up_by_lines(
10274 &mut self,
10275 action: &MoveUpByLines,
10276 window: &mut Window,
10277 cx: &mut Context<Self>,
10278 ) {
10279 if self.take_rename(true, window, cx).is_some() {
10280 return;
10281 }
10282
10283 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10284 cx.propagate();
10285 return;
10286 }
10287
10288 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10289
10290 let text_layout_details = &self.text_layout_details(window);
10291
10292 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10293 s.move_with(|map, selection| {
10294 if !selection.is_empty() {
10295 selection.goal = SelectionGoal::None;
10296 }
10297 let (cursor, goal) = movement::up_by_rows(
10298 map,
10299 selection.start,
10300 action.lines,
10301 selection.goal,
10302 false,
10303 text_layout_details,
10304 );
10305 selection.collapse_to(cursor, goal);
10306 });
10307 })
10308 }
10309
10310 pub fn move_down_by_lines(
10311 &mut self,
10312 action: &MoveDownByLines,
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::down_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 select_down_by_lines(
10348 &mut self,
10349 action: &SelectDownByLines,
10350 window: &mut Window,
10351 cx: &mut Context<Self>,
10352 ) {
10353 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10354 let text_layout_details = &self.text_layout_details(window);
10355 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10356 s.move_heads_with(|map, head, goal| {
10357 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10358 })
10359 })
10360 }
10361
10362 pub fn select_up_by_lines(
10363 &mut self,
10364 action: &SelectUpByLines,
10365 window: &mut Window,
10366 cx: &mut Context<Self>,
10367 ) {
10368 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10369 let text_layout_details = &self.text_layout_details(window);
10370 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10371 s.move_heads_with(|map, head, goal| {
10372 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10373 })
10374 })
10375 }
10376
10377 pub fn select_page_up(
10378 &mut self,
10379 _: &SelectPageUp,
10380 window: &mut Window,
10381 cx: &mut Context<Self>,
10382 ) {
10383 let Some(row_count) = self.visible_row_count() else {
10384 return;
10385 };
10386
10387 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10388
10389 let text_layout_details = &self.text_layout_details(window);
10390
10391 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10392 s.move_heads_with(|map, head, goal| {
10393 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10394 })
10395 })
10396 }
10397
10398 pub fn move_page_up(
10399 &mut self,
10400 action: &MovePageUp,
10401 window: &mut Window,
10402 cx: &mut Context<Self>,
10403 ) {
10404 if self.take_rename(true, window, cx).is_some() {
10405 return;
10406 }
10407
10408 if self
10409 .context_menu
10410 .borrow_mut()
10411 .as_mut()
10412 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10413 .unwrap_or(false)
10414 {
10415 return;
10416 }
10417
10418 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10419 cx.propagate();
10420 return;
10421 }
10422
10423 let Some(row_count) = self.visible_row_count() else {
10424 return;
10425 };
10426
10427 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10428
10429 let autoscroll = if action.center_cursor {
10430 Autoscroll::center()
10431 } else {
10432 Autoscroll::fit()
10433 };
10434
10435 let text_layout_details = &self.text_layout_details(window);
10436
10437 self.change_selections(Some(autoscroll), window, cx, |s| {
10438 s.move_with(|map, selection| {
10439 if !selection.is_empty() {
10440 selection.goal = SelectionGoal::None;
10441 }
10442 let (cursor, goal) = movement::up_by_rows(
10443 map,
10444 selection.end,
10445 row_count,
10446 selection.goal,
10447 false,
10448 text_layout_details,
10449 );
10450 selection.collapse_to(cursor, goal);
10451 });
10452 });
10453 }
10454
10455 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10456 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10457 let text_layout_details = &self.text_layout_details(window);
10458 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10459 s.move_heads_with(|map, head, goal| {
10460 movement::up(map, head, goal, false, text_layout_details)
10461 })
10462 })
10463 }
10464
10465 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10466 self.take_rename(true, window, cx);
10467
10468 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10469 cx.propagate();
10470 return;
10471 }
10472
10473 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10474
10475 let text_layout_details = &self.text_layout_details(window);
10476 let selection_count = self.selections.count();
10477 let first_selection = self.selections.first_anchor();
10478
10479 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10480 s.move_with(|map, selection| {
10481 if !selection.is_empty() {
10482 selection.goal = SelectionGoal::None;
10483 }
10484 let (cursor, goal) = movement::down(
10485 map,
10486 selection.end,
10487 selection.goal,
10488 false,
10489 text_layout_details,
10490 );
10491 selection.collapse_to(cursor, goal);
10492 });
10493 });
10494
10495 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10496 {
10497 cx.propagate();
10498 }
10499 }
10500
10501 pub fn select_page_down(
10502 &mut self,
10503 _: &SelectPageDown,
10504 window: &mut Window,
10505 cx: &mut Context<Self>,
10506 ) {
10507 let Some(row_count) = self.visible_row_count() else {
10508 return;
10509 };
10510
10511 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10512
10513 let text_layout_details = &self.text_layout_details(window);
10514
10515 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10516 s.move_heads_with(|map, head, goal| {
10517 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10518 })
10519 })
10520 }
10521
10522 pub fn move_page_down(
10523 &mut self,
10524 action: &MovePageDown,
10525 window: &mut Window,
10526 cx: &mut Context<Self>,
10527 ) {
10528 if self.take_rename(true, window, cx).is_some() {
10529 return;
10530 }
10531
10532 if self
10533 .context_menu
10534 .borrow_mut()
10535 .as_mut()
10536 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10537 .unwrap_or(false)
10538 {
10539 return;
10540 }
10541
10542 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10543 cx.propagate();
10544 return;
10545 }
10546
10547 let Some(row_count) = self.visible_row_count() else {
10548 return;
10549 };
10550
10551 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10552
10553 let autoscroll = if action.center_cursor {
10554 Autoscroll::center()
10555 } else {
10556 Autoscroll::fit()
10557 };
10558
10559 let text_layout_details = &self.text_layout_details(window);
10560 self.change_selections(Some(autoscroll), window, cx, |s| {
10561 s.move_with(|map, selection| {
10562 if !selection.is_empty() {
10563 selection.goal = SelectionGoal::None;
10564 }
10565 let (cursor, goal) = movement::down_by_rows(
10566 map,
10567 selection.end,
10568 row_count,
10569 selection.goal,
10570 false,
10571 text_layout_details,
10572 );
10573 selection.collapse_to(cursor, goal);
10574 });
10575 });
10576 }
10577
10578 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10579 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10580 let text_layout_details = &self.text_layout_details(window);
10581 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10582 s.move_heads_with(|map, head, goal| {
10583 movement::down(map, head, goal, false, text_layout_details)
10584 })
10585 });
10586 }
10587
10588 pub fn context_menu_first(
10589 &mut self,
10590 _: &ContextMenuFirst,
10591 _window: &mut Window,
10592 cx: &mut Context<Self>,
10593 ) {
10594 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10595 context_menu.select_first(self.completion_provider.as_deref(), cx);
10596 }
10597 }
10598
10599 pub fn context_menu_prev(
10600 &mut self,
10601 _: &ContextMenuPrevious,
10602 _window: &mut Window,
10603 cx: &mut Context<Self>,
10604 ) {
10605 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10606 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10607 }
10608 }
10609
10610 pub fn context_menu_next(
10611 &mut self,
10612 _: &ContextMenuNext,
10613 _window: &mut Window,
10614 cx: &mut Context<Self>,
10615 ) {
10616 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10617 context_menu.select_next(self.completion_provider.as_deref(), cx);
10618 }
10619 }
10620
10621 pub fn context_menu_last(
10622 &mut self,
10623 _: &ContextMenuLast,
10624 _window: &mut Window,
10625 cx: &mut Context<Self>,
10626 ) {
10627 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10628 context_menu.select_last(self.completion_provider.as_deref(), cx);
10629 }
10630 }
10631
10632 pub fn move_to_previous_word_start(
10633 &mut self,
10634 _: &MoveToPreviousWordStart,
10635 window: &mut Window,
10636 cx: &mut Context<Self>,
10637 ) {
10638 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10639 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10640 s.move_cursors_with(|map, head, _| {
10641 (
10642 movement::previous_word_start(map, head),
10643 SelectionGoal::None,
10644 )
10645 });
10646 })
10647 }
10648
10649 pub fn move_to_previous_subword_start(
10650 &mut self,
10651 _: &MoveToPreviousSubwordStart,
10652 window: &mut Window,
10653 cx: &mut Context<Self>,
10654 ) {
10655 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10656 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10657 s.move_cursors_with(|map, head, _| {
10658 (
10659 movement::previous_subword_start(map, head),
10660 SelectionGoal::None,
10661 )
10662 });
10663 })
10664 }
10665
10666 pub fn select_to_previous_word_start(
10667 &mut self,
10668 _: &SelectToPreviousWordStart,
10669 window: &mut Window,
10670 cx: &mut Context<Self>,
10671 ) {
10672 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10673 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10674 s.move_heads_with(|map, head, _| {
10675 (
10676 movement::previous_word_start(map, head),
10677 SelectionGoal::None,
10678 )
10679 });
10680 })
10681 }
10682
10683 pub fn select_to_previous_subword_start(
10684 &mut self,
10685 _: &SelectToPreviousSubwordStart,
10686 window: &mut Window,
10687 cx: &mut Context<Self>,
10688 ) {
10689 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10690 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10691 s.move_heads_with(|map, head, _| {
10692 (
10693 movement::previous_subword_start(map, head),
10694 SelectionGoal::None,
10695 )
10696 });
10697 })
10698 }
10699
10700 pub fn delete_to_previous_word_start(
10701 &mut self,
10702 action: &DeleteToPreviousWordStart,
10703 window: &mut Window,
10704 cx: &mut Context<Self>,
10705 ) {
10706 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10707 self.transact(window, cx, |this, window, cx| {
10708 this.select_autoclose_pair(window, cx);
10709 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10710 s.move_with(|map, selection| {
10711 if selection.is_empty() {
10712 let cursor = if action.ignore_newlines {
10713 movement::previous_word_start(map, selection.head())
10714 } else {
10715 movement::previous_word_start_or_newline(map, selection.head())
10716 };
10717 selection.set_head(cursor, SelectionGoal::None);
10718 }
10719 });
10720 });
10721 this.insert("", window, cx);
10722 });
10723 }
10724
10725 pub fn delete_to_previous_subword_start(
10726 &mut self,
10727 _: &DeleteToPreviousSubwordStart,
10728 window: &mut Window,
10729 cx: &mut Context<Self>,
10730 ) {
10731 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10732 self.transact(window, cx, |this, window, cx| {
10733 this.select_autoclose_pair(window, cx);
10734 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10735 s.move_with(|map, selection| {
10736 if selection.is_empty() {
10737 let cursor = movement::previous_subword_start(map, selection.head());
10738 selection.set_head(cursor, SelectionGoal::None);
10739 }
10740 });
10741 });
10742 this.insert("", window, cx);
10743 });
10744 }
10745
10746 pub fn move_to_next_word_end(
10747 &mut self,
10748 _: &MoveToNextWordEnd,
10749 window: &mut Window,
10750 cx: &mut Context<Self>,
10751 ) {
10752 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10753 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10754 s.move_cursors_with(|map, head, _| {
10755 (movement::next_word_end(map, head), SelectionGoal::None)
10756 });
10757 })
10758 }
10759
10760 pub fn move_to_next_subword_end(
10761 &mut self,
10762 _: &MoveToNextSubwordEnd,
10763 window: &mut Window,
10764 cx: &mut Context<Self>,
10765 ) {
10766 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10767 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10768 s.move_cursors_with(|map, head, _| {
10769 (movement::next_subword_end(map, head), SelectionGoal::None)
10770 });
10771 })
10772 }
10773
10774 pub fn select_to_next_word_end(
10775 &mut self,
10776 _: &SelectToNextWordEnd,
10777 window: &mut Window,
10778 cx: &mut Context<Self>,
10779 ) {
10780 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10781 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10782 s.move_heads_with(|map, head, _| {
10783 (movement::next_word_end(map, head), SelectionGoal::None)
10784 });
10785 })
10786 }
10787
10788 pub fn select_to_next_subword_end(
10789 &mut self,
10790 _: &SelectToNextSubwordEnd,
10791 window: &mut Window,
10792 cx: &mut Context<Self>,
10793 ) {
10794 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10795 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10796 s.move_heads_with(|map, head, _| {
10797 (movement::next_subword_end(map, head), SelectionGoal::None)
10798 });
10799 })
10800 }
10801
10802 pub fn delete_to_next_word_end(
10803 &mut self,
10804 action: &DeleteToNextWordEnd,
10805 window: &mut Window,
10806 cx: &mut Context<Self>,
10807 ) {
10808 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10809 self.transact(window, cx, |this, window, cx| {
10810 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10811 s.move_with(|map, selection| {
10812 if selection.is_empty() {
10813 let cursor = if action.ignore_newlines {
10814 movement::next_word_end(map, selection.head())
10815 } else {
10816 movement::next_word_end_or_newline(map, selection.head())
10817 };
10818 selection.set_head(cursor, SelectionGoal::None);
10819 }
10820 });
10821 });
10822 this.insert("", window, cx);
10823 });
10824 }
10825
10826 pub fn delete_to_next_subword_end(
10827 &mut self,
10828 _: &DeleteToNextSubwordEnd,
10829 window: &mut Window,
10830 cx: &mut Context<Self>,
10831 ) {
10832 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10833 self.transact(window, cx, |this, window, cx| {
10834 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10835 s.move_with(|map, selection| {
10836 if selection.is_empty() {
10837 let cursor = movement::next_subword_end(map, selection.head());
10838 selection.set_head(cursor, SelectionGoal::None);
10839 }
10840 });
10841 });
10842 this.insert("", window, cx);
10843 });
10844 }
10845
10846 pub fn move_to_beginning_of_line(
10847 &mut self,
10848 action: &MoveToBeginningOfLine,
10849 window: &mut Window,
10850 cx: &mut Context<Self>,
10851 ) {
10852 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10853 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10854 s.move_cursors_with(|map, head, _| {
10855 (
10856 movement::indented_line_beginning(
10857 map,
10858 head,
10859 action.stop_at_soft_wraps,
10860 action.stop_at_indent,
10861 ),
10862 SelectionGoal::None,
10863 )
10864 });
10865 })
10866 }
10867
10868 pub fn select_to_beginning_of_line(
10869 &mut self,
10870 action: &SelectToBeginningOfLine,
10871 window: &mut Window,
10872 cx: &mut Context<Self>,
10873 ) {
10874 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10875 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10876 s.move_heads_with(|map, head, _| {
10877 (
10878 movement::indented_line_beginning(
10879 map,
10880 head,
10881 action.stop_at_soft_wraps,
10882 action.stop_at_indent,
10883 ),
10884 SelectionGoal::None,
10885 )
10886 });
10887 });
10888 }
10889
10890 pub fn delete_to_beginning_of_line(
10891 &mut self,
10892 action: &DeleteToBeginningOfLine,
10893 window: &mut Window,
10894 cx: &mut Context<Self>,
10895 ) {
10896 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10897 self.transact(window, cx, |this, window, cx| {
10898 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10899 s.move_with(|_, selection| {
10900 selection.reversed = true;
10901 });
10902 });
10903
10904 this.select_to_beginning_of_line(
10905 &SelectToBeginningOfLine {
10906 stop_at_soft_wraps: false,
10907 stop_at_indent: action.stop_at_indent,
10908 },
10909 window,
10910 cx,
10911 );
10912 this.backspace(&Backspace, window, cx);
10913 });
10914 }
10915
10916 pub fn move_to_end_of_line(
10917 &mut self,
10918 action: &MoveToEndOfLine,
10919 window: &mut Window,
10920 cx: &mut Context<Self>,
10921 ) {
10922 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10923 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10924 s.move_cursors_with(|map, head, _| {
10925 (
10926 movement::line_end(map, head, action.stop_at_soft_wraps),
10927 SelectionGoal::None,
10928 )
10929 });
10930 })
10931 }
10932
10933 pub fn select_to_end_of_line(
10934 &mut self,
10935 action: &SelectToEndOfLine,
10936 window: &mut Window,
10937 cx: &mut Context<Self>,
10938 ) {
10939 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10940 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10941 s.move_heads_with(|map, head, _| {
10942 (
10943 movement::line_end(map, head, action.stop_at_soft_wraps),
10944 SelectionGoal::None,
10945 )
10946 });
10947 })
10948 }
10949
10950 pub fn delete_to_end_of_line(
10951 &mut self,
10952 _: &DeleteToEndOfLine,
10953 window: &mut Window,
10954 cx: &mut Context<Self>,
10955 ) {
10956 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10957 self.transact(window, cx, |this, window, cx| {
10958 this.select_to_end_of_line(
10959 &SelectToEndOfLine {
10960 stop_at_soft_wraps: false,
10961 },
10962 window,
10963 cx,
10964 );
10965 this.delete(&Delete, window, cx);
10966 });
10967 }
10968
10969 pub fn cut_to_end_of_line(
10970 &mut self,
10971 _: &CutToEndOfLine,
10972 window: &mut Window,
10973 cx: &mut Context<Self>,
10974 ) {
10975 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10976 self.transact(window, cx, |this, window, cx| {
10977 this.select_to_end_of_line(
10978 &SelectToEndOfLine {
10979 stop_at_soft_wraps: false,
10980 },
10981 window,
10982 cx,
10983 );
10984 this.cut(&Cut, window, cx);
10985 });
10986 }
10987
10988 pub fn move_to_start_of_paragraph(
10989 &mut self,
10990 _: &MoveToStartOfParagraph,
10991 window: &mut Window,
10992 cx: &mut Context<Self>,
10993 ) {
10994 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10995 cx.propagate();
10996 return;
10997 }
10998 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10999 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11000 s.move_with(|map, selection| {
11001 selection.collapse_to(
11002 movement::start_of_paragraph(map, selection.head(), 1),
11003 SelectionGoal::None,
11004 )
11005 });
11006 })
11007 }
11008
11009 pub fn move_to_end_of_paragraph(
11010 &mut self,
11011 _: &MoveToEndOfParagraph,
11012 window: &mut Window,
11013 cx: &mut Context<Self>,
11014 ) {
11015 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11016 cx.propagate();
11017 return;
11018 }
11019 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11020 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11021 s.move_with(|map, selection| {
11022 selection.collapse_to(
11023 movement::end_of_paragraph(map, selection.head(), 1),
11024 SelectionGoal::None,
11025 )
11026 });
11027 })
11028 }
11029
11030 pub fn select_to_start_of_paragraph(
11031 &mut self,
11032 _: &SelectToStartOfParagraph,
11033 window: &mut Window,
11034 cx: &mut Context<Self>,
11035 ) {
11036 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11037 cx.propagate();
11038 return;
11039 }
11040 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11041 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11042 s.move_heads_with(|map, head, _| {
11043 (
11044 movement::start_of_paragraph(map, head, 1),
11045 SelectionGoal::None,
11046 )
11047 });
11048 })
11049 }
11050
11051 pub fn select_to_end_of_paragraph(
11052 &mut self,
11053 _: &SelectToEndOfParagraph,
11054 window: &mut Window,
11055 cx: &mut Context<Self>,
11056 ) {
11057 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11058 cx.propagate();
11059 return;
11060 }
11061 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11062 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11063 s.move_heads_with(|map, head, _| {
11064 (
11065 movement::end_of_paragraph(map, head, 1),
11066 SelectionGoal::None,
11067 )
11068 });
11069 })
11070 }
11071
11072 pub fn move_to_start_of_excerpt(
11073 &mut self,
11074 _: &MoveToStartOfExcerpt,
11075 window: &mut Window,
11076 cx: &mut Context<Self>,
11077 ) {
11078 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11079 cx.propagate();
11080 return;
11081 }
11082 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11083 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11084 s.move_with(|map, selection| {
11085 selection.collapse_to(
11086 movement::start_of_excerpt(
11087 map,
11088 selection.head(),
11089 workspace::searchable::Direction::Prev,
11090 ),
11091 SelectionGoal::None,
11092 )
11093 });
11094 })
11095 }
11096
11097 pub fn move_to_start_of_next_excerpt(
11098 &mut self,
11099 _: &MoveToStartOfNextExcerpt,
11100 window: &mut Window,
11101 cx: &mut Context<Self>,
11102 ) {
11103 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11104 cx.propagate();
11105 return;
11106 }
11107
11108 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11109 s.move_with(|map, selection| {
11110 selection.collapse_to(
11111 movement::start_of_excerpt(
11112 map,
11113 selection.head(),
11114 workspace::searchable::Direction::Next,
11115 ),
11116 SelectionGoal::None,
11117 )
11118 });
11119 })
11120 }
11121
11122 pub fn move_to_end_of_excerpt(
11123 &mut self,
11124 _: &MoveToEndOfExcerpt,
11125 window: &mut Window,
11126 cx: &mut Context<Self>,
11127 ) {
11128 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11129 cx.propagate();
11130 return;
11131 }
11132 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11133 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11134 s.move_with(|map, selection| {
11135 selection.collapse_to(
11136 movement::end_of_excerpt(
11137 map,
11138 selection.head(),
11139 workspace::searchable::Direction::Next,
11140 ),
11141 SelectionGoal::None,
11142 )
11143 });
11144 })
11145 }
11146
11147 pub fn move_to_end_of_previous_excerpt(
11148 &mut self,
11149 _: &MoveToEndOfPreviousExcerpt,
11150 window: &mut Window,
11151 cx: &mut Context<Self>,
11152 ) {
11153 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11154 cx.propagate();
11155 return;
11156 }
11157 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11158 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11159 s.move_with(|map, selection| {
11160 selection.collapse_to(
11161 movement::end_of_excerpt(
11162 map,
11163 selection.head(),
11164 workspace::searchable::Direction::Prev,
11165 ),
11166 SelectionGoal::None,
11167 )
11168 });
11169 })
11170 }
11171
11172 pub fn select_to_start_of_excerpt(
11173 &mut self,
11174 _: &SelectToStartOfExcerpt,
11175 window: &mut Window,
11176 cx: &mut Context<Self>,
11177 ) {
11178 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11179 cx.propagate();
11180 return;
11181 }
11182 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11183 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11184 s.move_heads_with(|map, head, _| {
11185 (
11186 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11187 SelectionGoal::None,
11188 )
11189 });
11190 })
11191 }
11192
11193 pub fn select_to_start_of_next_excerpt(
11194 &mut self,
11195 _: &SelectToStartOfNextExcerpt,
11196 window: &mut Window,
11197 cx: &mut Context<Self>,
11198 ) {
11199 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11200 cx.propagate();
11201 return;
11202 }
11203 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11204 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11205 s.move_heads_with(|map, head, _| {
11206 (
11207 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11208 SelectionGoal::None,
11209 )
11210 });
11211 })
11212 }
11213
11214 pub fn select_to_end_of_excerpt(
11215 &mut self,
11216 _: &SelectToEndOfExcerpt,
11217 window: &mut Window,
11218 cx: &mut Context<Self>,
11219 ) {
11220 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11221 cx.propagate();
11222 return;
11223 }
11224 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11225 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11226 s.move_heads_with(|map, head, _| {
11227 (
11228 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11229 SelectionGoal::None,
11230 )
11231 });
11232 })
11233 }
11234
11235 pub fn select_to_end_of_previous_excerpt(
11236 &mut self,
11237 _: &SelectToEndOfPreviousExcerpt,
11238 window: &mut Window,
11239 cx: &mut Context<Self>,
11240 ) {
11241 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11242 cx.propagate();
11243 return;
11244 }
11245 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11246 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11247 s.move_heads_with(|map, head, _| {
11248 (
11249 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11250 SelectionGoal::None,
11251 )
11252 });
11253 })
11254 }
11255
11256 pub fn move_to_beginning(
11257 &mut self,
11258 _: &MoveToBeginning,
11259 window: &mut Window,
11260 cx: &mut Context<Self>,
11261 ) {
11262 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11263 cx.propagate();
11264 return;
11265 }
11266 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11267 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11268 s.select_ranges(vec![0..0]);
11269 });
11270 }
11271
11272 pub fn select_to_beginning(
11273 &mut self,
11274 _: &SelectToBeginning,
11275 window: &mut Window,
11276 cx: &mut Context<Self>,
11277 ) {
11278 let mut selection = self.selections.last::<Point>(cx);
11279 selection.set_head(Point::zero(), SelectionGoal::None);
11280 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11281 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11282 s.select(vec![selection]);
11283 });
11284 }
11285
11286 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11287 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11288 cx.propagate();
11289 return;
11290 }
11291 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11292 let cursor = self.buffer.read(cx).read(cx).len();
11293 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11294 s.select_ranges(vec![cursor..cursor])
11295 });
11296 }
11297
11298 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11299 self.nav_history = nav_history;
11300 }
11301
11302 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11303 self.nav_history.as_ref()
11304 }
11305
11306 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11307 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11308 }
11309
11310 fn push_to_nav_history(
11311 &mut self,
11312 cursor_anchor: Anchor,
11313 new_position: Option<Point>,
11314 is_deactivate: bool,
11315 cx: &mut Context<Self>,
11316 ) {
11317 if let Some(nav_history) = self.nav_history.as_mut() {
11318 let buffer = self.buffer.read(cx).read(cx);
11319 let cursor_position = cursor_anchor.to_point(&buffer);
11320 let scroll_state = self.scroll_manager.anchor();
11321 let scroll_top_row = scroll_state.top_row(&buffer);
11322 drop(buffer);
11323
11324 if let Some(new_position) = new_position {
11325 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11326 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11327 return;
11328 }
11329 }
11330
11331 nav_history.push(
11332 Some(NavigationData {
11333 cursor_anchor,
11334 cursor_position,
11335 scroll_anchor: scroll_state,
11336 scroll_top_row,
11337 }),
11338 cx,
11339 );
11340 cx.emit(EditorEvent::PushedToNavHistory {
11341 anchor: cursor_anchor,
11342 is_deactivate,
11343 })
11344 }
11345 }
11346
11347 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11348 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11349 let buffer = self.buffer.read(cx).snapshot(cx);
11350 let mut selection = self.selections.first::<usize>(cx);
11351 selection.set_head(buffer.len(), SelectionGoal::None);
11352 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11353 s.select(vec![selection]);
11354 });
11355 }
11356
11357 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11358 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11359 let end = self.buffer.read(cx).read(cx).len();
11360 self.change_selections(None, window, cx, |s| {
11361 s.select_ranges(vec![0..end]);
11362 });
11363 }
11364
11365 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11366 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11367 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11368 let mut selections = self.selections.all::<Point>(cx);
11369 let max_point = display_map.buffer_snapshot.max_point();
11370 for selection in &mut selections {
11371 let rows = selection.spanned_rows(true, &display_map);
11372 selection.start = Point::new(rows.start.0, 0);
11373 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11374 selection.reversed = false;
11375 }
11376 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11377 s.select(selections);
11378 });
11379 }
11380
11381 pub fn split_selection_into_lines(
11382 &mut self,
11383 _: &SplitSelectionIntoLines,
11384 window: &mut Window,
11385 cx: &mut Context<Self>,
11386 ) {
11387 let selections = self
11388 .selections
11389 .all::<Point>(cx)
11390 .into_iter()
11391 .map(|selection| selection.start..selection.end)
11392 .collect::<Vec<_>>();
11393 self.unfold_ranges(&selections, true, true, cx);
11394
11395 let mut new_selection_ranges = Vec::new();
11396 {
11397 let buffer = self.buffer.read(cx).read(cx);
11398 for selection in selections {
11399 for row in selection.start.row..selection.end.row {
11400 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11401 new_selection_ranges.push(cursor..cursor);
11402 }
11403
11404 let is_multiline_selection = selection.start.row != selection.end.row;
11405 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11406 // so this action feels more ergonomic when paired with other selection operations
11407 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11408 if !should_skip_last {
11409 new_selection_ranges.push(selection.end..selection.end);
11410 }
11411 }
11412 }
11413 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11414 s.select_ranges(new_selection_ranges);
11415 });
11416 }
11417
11418 pub fn add_selection_above(
11419 &mut self,
11420 _: &AddSelectionAbove,
11421 window: &mut Window,
11422 cx: &mut Context<Self>,
11423 ) {
11424 self.add_selection(true, window, cx);
11425 }
11426
11427 pub fn add_selection_below(
11428 &mut self,
11429 _: &AddSelectionBelow,
11430 window: &mut Window,
11431 cx: &mut Context<Self>,
11432 ) {
11433 self.add_selection(false, window, cx);
11434 }
11435
11436 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11437 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11438
11439 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11440 let mut selections = self.selections.all::<Point>(cx);
11441 let text_layout_details = self.text_layout_details(window);
11442 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11443 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11444 let range = oldest_selection.display_range(&display_map).sorted();
11445
11446 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11447 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11448 let positions = start_x.min(end_x)..start_x.max(end_x);
11449
11450 selections.clear();
11451 let mut stack = Vec::new();
11452 for row in range.start.row().0..=range.end.row().0 {
11453 if let Some(selection) = self.selections.build_columnar_selection(
11454 &display_map,
11455 DisplayRow(row),
11456 &positions,
11457 oldest_selection.reversed,
11458 &text_layout_details,
11459 ) {
11460 stack.push(selection.id);
11461 selections.push(selection);
11462 }
11463 }
11464
11465 if above {
11466 stack.reverse();
11467 }
11468
11469 AddSelectionsState { above, stack }
11470 });
11471
11472 let last_added_selection = *state.stack.last().unwrap();
11473 let mut new_selections = Vec::new();
11474 if above == state.above {
11475 let end_row = if above {
11476 DisplayRow(0)
11477 } else {
11478 display_map.max_point().row()
11479 };
11480
11481 'outer: for selection in selections {
11482 if selection.id == last_added_selection {
11483 let range = selection.display_range(&display_map).sorted();
11484 debug_assert_eq!(range.start.row(), range.end.row());
11485 let mut row = range.start.row();
11486 let positions =
11487 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11488 px(start)..px(end)
11489 } else {
11490 let start_x =
11491 display_map.x_for_display_point(range.start, &text_layout_details);
11492 let end_x =
11493 display_map.x_for_display_point(range.end, &text_layout_details);
11494 start_x.min(end_x)..start_x.max(end_x)
11495 };
11496
11497 while row != end_row {
11498 if above {
11499 row.0 -= 1;
11500 } else {
11501 row.0 += 1;
11502 }
11503
11504 if let Some(new_selection) = self.selections.build_columnar_selection(
11505 &display_map,
11506 row,
11507 &positions,
11508 selection.reversed,
11509 &text_layout_details,
11510 ) {
11511 state.stack.push(new_selection.id);
11512 if above {
11513 new_selections.push(new_selection);
11514 new_selections.push(selection);
11515 } else {
11516 new_selections.push(selection);
11517 new_selections.push(new_selection);
11518 }
11519
11520 continue 'outer;
11521 }
11522 }
11523 }
11524
11525 new_selections.push(selection);
11526 }
11527 } else {
11528 new_selections = selections;
11529 new_selections.retain(|s| s.id != last_added_selection);
11530 state.stack.pop();
11531 }
11532
11533 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11534 s.select(new_selections);
11535 });
11536 if state.stack.len() > 1 {
11537 self.add_selections_state = Some(state);
11538 }
11539 }
11540
11541 pub fn select_next_match_internal(
11542 &mut self,
11543 display_map: &DisplaySnapshot,
11544 replace_newest: bool,
11545 autoscroll: Option<Autoscroll>,
11546 window: &mut Window,
11547 cx: &mut Context<Self>,
11548 ) -> Result<()> {
11549 fn select_next_match_ranges(
11550 this: &mut Editor,
11551 range: Range<usize>,
11552 replace_newest: bool,
11553 auto_scroll: Option<Autoscroll>,
11554 window: &mut Window,
11555 cx: &mut Context<Editor>,
11556 ) {
11557 this.unfold_ranges(&[range.clone()], false, true, cx);
11558 this.change_selections(auto_scroll, window, cx, |s| {
11559 if replace_newest {
11560 s.delete(s.newest_anchor().id);
11561 }
11562 s.insert_range(range.clone());
11563 });
11564 }
11565
11566 let buffer = &display_map.buffer_snapshot;
11567 let mut selections = self.selections.all::<usize>(cx);
11568 if let Some(mut select_next_state) = self.select_next_state.take() {
11569 let query = &select_next_state.query;
11570 if !select_next_state.done {
11571 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11572 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11573 let mut next_selected_range = None;
11574
11575 let bytes_after_last_selection =
11576 buffer.bytes_in_range(last_selection.end..buffer.len());
11577 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11578 let query_matches = query
11579 .stream_find_iter(bytes_after_last_selection)
11580 .map(|result| (last_selection.end, result))
11581 .chain(
11582 query
11583 .stream_find_iter(bytes_before_first_selection)
11584 .map(|result| (0, result)),
11585 );
11586
11587 for (start_offset, query_match) in query_matches {
11588 let query_match = query_match.unwrap(); // can only fail due to I/O
11589 let offset_range =
11590 start_offset + query_match.start()..start_offset + query_match.end();
11591 let display_range = offset_range.start.to_display_point(display_map)
11592 ..offset_range.end.to_display_point(display_map);
11593
11594 if !select_next_state.wordwise
11595 || (!movement::is_inside_word(display_map, display_range.start)
11596 && !movement::is_inside_word(display_map, display_range.end))
11597 {
11598 // TODO: This is n^2, because we might check all the selections
11599 if !selections
11600 .iter()
11601 .any(|selection| selection.range().overlaps(&offset_range))
11602 {
11603 next_selected_range = Some(offset_range);
11604 break;
11605 }
11606 }
11607 }
11608
11609 if let Some(next_selected_range) = next_selected_range {
11610 select_next_match_ranges(
11611 self,
11612 next_selected_range,
11613 replace_newest,
11614 autoscroll,
11615 window,
11616 cx,
11617 );
11618 } else {
11619 select_next_state.done = true;
11620 }
11621 }
11622
11623 self.select_next_state = Some(select_next_state);
11624 } else {
11625 let mut only_carets = true;
11626 let mut same_text_selected = true;
11627 let mut selected_text = None;
11628
11629 let mut selections_iter = selections.iter().peekable();
11630 while let Some(selection) = selections_iter.next() {
11631 if selection.start != selection.end {
11632 only_carets = false;
11633 }
11634
11635 if same_text_selected {
11636 if selected_text.is_none() {
11637 selected_text =
11638 Some(buffer.text_for_range(selection.range()).collect::<String>());
11639 }
11640
11641 if let Some(next_selection) = selections_iter.peek() {
11642 if next_selection.range().len() == selection.range().len() {
11643 let next_selected_text = buffer
11644 .text_for_range(next_selection.range())
11645 .collect::<String>();
11646 if Some(next_selected_text) != selected_text {
11647 same_text_selected = false;
11648 selected_text = None;
11649 }
11650 } else {
11651 same_text_selected = false;
11652 selected_text = None;
11653 }
11654 }
11655 }
11656 }
11657
11658 if only_carets {
11659 for selection in &mut selections {
11660 let word_range = movement::surrounding_word(
11661 display_map,
11662 selection.start.to_display_point(display_map),
11663 );
11664 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11665 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11666 selection.goal = SelectionGoal::None;
11667 selection.reversed = false;
11668 select_next_match_ranges(
11669 self,
11670 selection.start..selection.end,
11671 replace_newest,
11672 autoscroll,
11673 window,
11674 cx,
11675 );
11676 }
11677
11678 if selections.len() == 1 {
11679 let selection = selections
11680 .last()
11681 .expect("ensured that there's only one selection");
11682 let query = buffer
11683 .text_for_range(selection.start..selection.end)
11684 .collect::<String>();
11685 let is_empty = query.is_empty();
11686 let select_state = SelectNextState {
11687 query: AhoCorasick::new(&[query])?,
11688 wordwise: true,
11689 done: is_empty,
11690 };
11691 self.select_next_state = Some(select_state);
11692 } else {
11693 self.select_next_state = None;
11694 }
11695 } else if let Some(selected_text) = selected_text {
11696 self.select_next_state = Some(SelectNextState {
11697 query: AhoCorasick::new(&[selected_text])?,
11698 wordwise: false,
11699 done: false,
11700 });
11701 self.select_next_match_internal(
11702 display_map,
11703 replace_newest,
11704 autoscroll,
11705 window,
11706 cx,
11707 )?;
11708 }
11709 }
11710 Ok(())
11711 }
11712
11713 pub fn select_all_matches(
11714 &mut self,
11715 _action: &SelectAllMatches,
11716 window: &mut Window,
11717 cx: &mut Context<Self>,
11718 ) -> Result<()> {
11719 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11720
11721 self.push_to_selection_history();
11722 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11723
11724 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11725 let Some(select_next_state) = self.select_next_state.as_mut() else {
11726 return Ok(());
11727 };
11728 if select_next_state.done {
11729 return Ok(());
11730 }
11731
11732 let mut new_selections = self.selections.all::<usize>(cx);
11733
11734 let buffer = &display_map.buffer_snapshot;
11735 let query_matches = select_next_state
11736 .query
11737 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11738
11739 for query_match in query_matches {
11740 let query_match = query_match.unwrap(); // can only fail due to I/O
11741 let offset_range = query_match.start()..query_match.end();
11742 let display_range = offset_range.start.to_display_point(&display_map)
11743 ..offset_range.end.to_display_point(&display_map);
11744
11745 if !select_next_state.wordwise
11746 || (!movement::is_inside_word(&display_map, display_range.start)
11747 && !movement::is_inside_word(&display_map, display_range.end))
11748 {
11749 self.selections.change_with(cx, |selections| {
11750 new_selections.push(Selection {
11751 id: selections.new_selection_id(),
11752 start: offset_range.start,
11753 end: offset_range.end,
11754 reversed: false,
11755 goal: SelectionGoal::None,
11756 });
11757 });
11758 }
11759 }
11760
11761 new_selections.sort_by_key(|selection| selection.start);
11762 let mut ix = 0;
11763 while ix + 1 < new_selections.len() {
11764 let current_selection = &new_selections[ix];
11765 let next_selection = &new_selections[ix + 1];
11766 if current_selection.range().overlaps(&next_selection.range()) {
11767 if current_selection.id < next_selection.id {
11768 new_selections.remove(ix + 1);
11769 } else {
11770 new_selections.remove(ix);
11771 }
11772 } else {
11773 ix += 1;
11774 }
11775 }
11776
11777 let reversed = self.selections.oldest::<usize>(cx).reversed;
11778
11779 for selection in new_selections.iter_mut() {
11780 selection.reversed = reversed;
11781 }
11782
11783 select_next_state.done = true;
11784 self.unfold_ranges(
11785 &new_selections
11786 .iter()
11787 .map(|selection| selection.range())
11788 .collect::<Vec<_>>(),
11789 false,
11790 false,
11791 cx,
11792 );
11793 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11794 selections.select(new_selections)
11795 });
11796
11797 Ok(())
11798 }
11799
11800 pub fn select_next(
11801 &mut self,
11802 action: &SelectNext,
11803 window: &mut Window,
11804 cx: &mut Context<Self>,
11805 ) -> Result<()> {
11806 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11807 self.push_to_selection_history();
11808 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11809 self.select_next_match_internal(
11810 &display_map,
11811 action.replace_newest,
11812 Some(Autoscroll::newest()),
11813 window,
11814 cx,
11815 )?;
11816 Ok(())
11817 }
11818
11819 pub fn select_previous(
11820 &mut self,
11821 action: &SelectPrevious,
11822 window: &mut Window,
11823 cx: &mut Context<Self>,
11824 ) -> Result<()> {
11825 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11826 self.push_to_selection_history();
11827 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11828 let buffer = &display_map.buffer_snapshot;
11829 let mut selections = self.selections.all::<usize>(cx);
11830 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11831 let query = &select_prev_state.query;
11832 if !select_prev_state.done {
11833 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11834 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11835 let mut next_selected_range = None;
11836 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11837 let bytes_before_last_selection =
11838 buffer.reversed_bytes_in_range(0..last_selection.start);
11839 let bytes_after_first_selection =
11840 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11841 let query_matches = query
11842 .stream_find_iter(bytes_before_last_selection)
11843 .map(|result| (last_selection.start, result))
11844 .chain(
11845 query
11846 .stream_find_iter(bytes_after_first_selection)
11847 .map(|result| (buffer.len(), result)),
11848 );
11849 for (end_offset, query_match) in query_matches {
11850 let query_match = query_match.unwrap(); // can only fail due to I/O
11851 let offset_range =
11852 end_offset - query_match.end()..end_offset - query_match.start();
11853 let display_range = offset_range.start.to_display_point(&display_map)
11854 ..offset_range.end.to_display_point(&display_map);
11855
11856 if !select_prev_state.wordwise
11857 || (!movement::is_inside_word(&display_map, display_range.start)
11858 && !movement::is_inside_word(&display_map, display_range.end))
11859 {
11860 next_selected_range = Some(offset_range);
11861 break;
11862 }
11863 }
11864
11865 if let Some(next_selected_range) = next_selected_range {
11866 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11867 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11868 if action.replace_newest {
11869 s.delete(s.newest_anchor().id);
11870 }
11871 s.insert_range(next_selected_range);
11872 });
11873 } else {
11874 select_prev_state.done = true;
11875 }
11876 }
11877
11878 self.select_prev_state = Some(select_prev_state);
11879 } else {
11880 let mut only_carets = true;
11881 let mut same_text_selected = true;
11882 let mut selected_text = None;
11883
11884 let mut selections_iter = selections.iter().peekable();
11885 while let Some(selection) = selections_iter.next() {
11886 if selection.start != selection.end {
11887 only_carets = false;
11888 }
11889
11890 if same_text_selected {
11891 if selected_text.is_none() {
11892 selected_text =
11893 Some(buffer.text_for_range(selection.range()).collect::<String>());
11894 }
11895
11896 if let Some(next_selection) = selections_iter.peek() {
11897 if next_selection.range().len() == selection.range().len() {
11898 let next_selected_text = buffer
11899 .text_for_range(next_selection.range())
11900 .collect::<String>();
11901 if Some(next_selected_text) != selected_text {
11902 same_text_selected = false;
11903 selected_text = None;
11904 }
11905 } else {
11906 same_text_selected = false;
11907 selected_text = None;
11908 }
11909 }
11910 }
11911 }
11912
11913 if only_carets {
11914 for selection in &mut selections {
11915 let word_range = movement::surrounding_word(
11916 &display_map,
11917 selection.start.to_display_point(&display_map),
11918 );
11919 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11920 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11921 selection.goal = SelectionGoal::None;
11922 selection.reversed = false;
11923 }
11924 if selections.len() == 1 {
11925 let selection = selections
11926 .last()
11927 .expect("ensured that there's only one selection");
11928 let query = buffer
11929 .text_for_range(selection.start..selection.end)
11930 .collect::<String>();
11931 let is_empty = query.is_empty();
11932 let select_state = SelectNextState {
11933 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11934 wordwise: true,
11935 done: is_empty,
11936 };
11937 self.select_prev_state = Some(select_state);
11938 } else {
11939 self.select_prev_state = None;
11940 }
11941
11942 self.unfold_ranges(
11943 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11944 false,
11945 true,
11946 cx,
11947 );
11948 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11949 s.select(selections);
11950 });
11951 } else if let Some(selected_text) = selected_text {
11952 self.select_prev_state = Some(SelectNextState {
11953 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11954 wordwise: false,
11955 done: false,
11956 });
11957 self.select_previous(action, window, cx)?;
11958 }
11959 }
11960 Ok(())
11961 }
11962
11963 pub fn toggle_comments(
11964 &mut self,
11965 action: &ToggleComments,
11966 window: &mut Window,
11967 cx: &mut Context<Self>,
11968 ) {
11969 if self.read_only(cx) {
11970 return;
11971 }
11972 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11973 let text_layout_details = &self.text_layout_details(window);
11974 self.transact(window, cx, |this, window, cx| {
11975 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11976 let mut edits = Vec::new();
11977 let mut selection_edit_ranges = Vec::new();
11978 let mut last_toggled_row = None;
11979 let snapshot = this.buffer.read(cx).read(cx);
11980 let empty_str: Arc<str> = Arc::default();
11981 let mut suffixes_inserted = Vec::new();
11982 let ignore_indent = action.ignore_indent;
11983
11984 fn comment_prefix_range(
11985 snapshot: &MultiBufferSnapshot,
11986 row: MultiBufferRow,
11987 comment_prefix: &str,
11988 comment_prefix_whitespace: &str,
11989 ignore_indent: bool,
11990 ) -> Range<Point> {
11991 let indent_size = if ignore_indent {
11992 0
11993 } else {
11994 snapshot.indent_size_for_line(row).len
11995 };
11996
11997 let start = Point::new(row.0, indent_size);
11998
11999 let mut line_bytes = snapshot
12000 .bytes_in_range(start..snapshot.max_point())
12001 .flatten()
12002 .copied();
12003
12004 // If this line currently begins with the line comment prefix, then record
12005 // the range containing the prefix.
12006 if line_bytes
12007 .by_ref()
12008 .take(comment_prefix.len())
12009 .eq(comment_prefix.bytes())
12010 {
12011 // Include any whitespace that matches the comment prefix.
12012 let matching_whitespace_len = line_bytes
12013 .zip(comment_prefix_whitespace.bytes())
12014 .take_while(|(a, b)| a == b)
12015 .count() as u32;
12016 let end = Point::new(
12017 start.row,
12018 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12019 );
12020 start..end
12021 } else {
12022 start..start
12023 }
12024 }
12025
12026 fn comment_suffix_range(
12027 snapshot: &MultiBufferSnapshot,
12028 row: MultiBufferRow,
12029 comment_suffix: &str,
12030 comment_suffix_has_leading_space: bool,
12031 ) -> Range<Point> {
12032 let end = Point::new(row.0, snapshot.line_len(row));
12033 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12034
12035 let mut line_end_bytes = snapshot
12036 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12037 .flatten()
12038 .copied();
12039
12040 let leading_space_len = if suffix_start_column > 0
12041 && line_end_bytes.next() == Some(b' ')
12042 && comment_suffix_has_leading_space
12043 {
12044 1
12045 } else {
12046 0
12047 };
12048
12049 // If this line currently begins with the line comment prefix, then record
12050 // the range containing the prefix.
12051 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12052 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12053 start..end
12054 } else {
12055 end..end
12056 }
12057 }
12058
12059 // TODO: Handle selections that cross excerpts
12060 for selection in &mut selections {
12061 let start_column = snapshot
12062 .indent_size_for_line(MultiBufferRow(selection.start.row))
12063 .len;
12064 let language = if let Some(language) =
12065 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12066 {
12067 language
12068 } else {
12069 continue;
12070 };
12071
12072 selection_edit_ranges.clear();
12073
12074 // If multiple selections contain a given row, avoid processing that
12075 // row more than once.
12076 let mut start_row = MultiBufferRow(selection.start.row);
12077 if last_toggled_row == Some(start_row) {
12078 start_row = start_row.next_row();
12079 }
12080 let end_row =
12081 if selection.end.row > selection.start.row && selection.end.column == 0 {
12082 MultiBufferRow(selection.end.row - 1)
12083 } else {
12084 MultiBufferRow(selection.end.row)
12085 };
12086 last_toggled_row = Some(end_row);
12087
12088 if start_row > end_row {
12089 continue;
12090 }
12091
12092 // If the language has line comments, toggle those.
12093 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12094
12095 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12096 if ignore_indent {
12097 full_comment_prefixes = full_comment_prefixes
12098 .into_iter()
12099 .map(|s| Arc::from(s.trim_end()))
12100 .collect();
12101 }
12102
12103 if !full_comment_prefixes.is_empty() {
12104 let first_prefix = full_comment_prefixes
12105 .first()
12106 .expect("prefixes is non-empty");
12107 let prefix_trimmed_lengths = full_comment_prefixes
12108 .iter()
12109 .map(|p| p.trim_end_matches(' ').len())
12110 .collect::<SmallVec<[usize; 4]>>();
12111
12112 let mut all_selection_lines_are_comments = true;
12113
12114 for row in start_row.0..=end_row.0 {
12115 let row = MultiBufferRow(row);
12116 if start_row < end_row && snapshot.is_line_blank(row) {
12117 continue;
12118 }
12119
12120 let prefix_range = full_comment_prefixes
12121 .iter()
12122 .zip(prefix_trimmed_lengths.iter().copied())
12123 .map(|(prefix, trimmed_prefix_len)| {
12124 comment_prefix_range(
12125 snapshot.deref(),
12126 row,
12127 &prefix[..trimmed_prefix_len],
12128 &prefix[trimmed_prefix_len..],
12129 ignore_indent,
12130 )
12131 })
12132 .max_by_key(|range| range.end.column - range.start.column)
12133 .expect("prefixes is non-empty");
12134
12135 if prefix_range.is_empty() {
12136 all_selection_lines_are_comments = false;
12137 }
12138
12139 selection_edit_ranges.push(prefix_range);
12140 }
12141
12142 if all_selection_lines_are_comments {
12143 edits.extend(
12144 selection_edit_ranges
12145 .iter()
12146 .cloned()
12147 .map(|range| (range, empty_str.clone())),
12148 );
12149 } else {
12150 let min_column = selection_edit_ranges
12151 .iter()
12152 .map(|range| range.start.column)
12153 .min()
12154 .unwrap_or(0);
12155 edits.extend(selection_edit_ranges.iter().map(|range| {
12156 let position = Point::new(range.start.row, min_column);
12157 (position..position, first_prefix.clone())
12158 }));
12159 }
12160 } else if let Some((full_comment_prefix, comment_suffix)) =
12161 language.block_comment_delimiters()
12162 {
12163 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12164 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12165 let prefix_range = comment_prefix_range(
12166 snapshot.deref(),
12167 start_row,
12168 comment_prefix,
12169 comment_prefix_whitespace,
12170 ignore_indent,
12171 );
12172 let suffix_range = comment_suffix_range(
12173 snapshot.deref(),
12174 end_row,
12175 comment_suffix.trim_start_matches(' '),
12176 comment_suffix.starts_with(' '),
12177 );
12178
12179 if prefix_range.is_empty() || suffix_range.is_empty() {
12180 edits.push((
12181 prefix_range.start..prefix_range.start,
12182 full_comment_prefix.clone(),
12183 ));
12184 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12185 suffixes_inserted.push((end_row, comment_suffix.len()));
12186 } else {
12187 edits.push((prefix_range, empty_str.clone()));
12188 edits.push((suffix_range, empty_str.clone()));
12189 }
12190 } else {
12191 continue;
12192 }
12193 }
12194
12195 drop(snapshot);
12196 this.buffer.update(cx, |buffer, cx| {
12197 buffer.edit(edits, None, cx);
12198 });
12199
12200 // Adjust selections so that they end before any comment suffixes that
12201 // were inserted.
12202 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12203 let mut selections = this.selections.all::<Point>(cx);
12204 let snapshot = this.buffer.read(cx).read(cx);
12205 for selection in &mut selections {
12206 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12207 match row.cmp(&MultiBufferRow(selection.end.row)) {
12208 Ordering::Less => {
12209 suffixes_inserted.next();
12210 continue;
12211 }
12212 Ordering::Greater => break,
12213 Ordering::Equal => {
12214 if selection.end.column == snapshot.line_len(row) {
12215 if selection.is_empty() {
12216 selection.start.column -= suffix_len as u32;
12217 }
12218 selection.end.column -= suffix_len as u32;
12219 }
12220 break;
12221 }
12222 }
12223 }
12224 }
12225
12226 drop(snapshot);
12227 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12228 s.select(selections)
12229 });
12230
12231 let selections = this.selections.all::<Point>(cx);
12232 let selections_on_single_row = selections.windows(2).all(|selections| {
12233 selections[0].start.row == selections[1].start.row
12234 && selections[0].end.row == selections[1].end.row
12235 && selections[0].start.row == selections[0].end.row
12236 });
12237 let selections_selecting = selections
12238 .iter()
12239 .any(|selection| selection.start != selection.end);
12240 let advance_downwards = action.advance_downwards
12241 && selections_on_single_row
12242 && !selections_selecting
12243 && !matches!(this.mode, EditorMode::SingleLine { .. });
12244
12245 if advance_downwards {
12246 let snapshot = this.buffer.read(cx).snapshot(cx);
12247
12248 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12249 s.move_cursors_with(|display_snapshot, display_point, _| {
12250 let mut point = display_point.to_point(display_snapshot);
12251 point.row += 1;
12252 point = snapshot.clip_point(point, Bias::Left);
12253 let display_point = point.to_display_point(display_snapshot);
12254 let goal = SelectionGoal::HorizontalPosition(
12255 display_snapshot
12256 .x_for_display_point(display_point, text_layout_details)
12257 .into(),
12258 );
12259 (display_point, goal)
12260 })
12261 });
12262 }
12263 });
12264 }
12265
12266 pub fn select_enclosing_symbol(
12267 &mut self,
12268 _: &SelectEnclosingSymbol,
12269 window: &mut Window,
12270 cx: &mut Context<Self>,
12271 ) {
12272 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12273
12274 let buffer = self.buffer.read(cx).snapshot(cx);
12275 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12276
12277 fn update_selection(
12278 selection: &Selection<usize>,
12279 buffer_snap: &MultiBufferSnapshot,
12280 ) -> Option<Selection<usize>> {
12281 let cursor = selection.head();
12282 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12283 for symbol in symbols.iter().rev() {
12284 let start = symbol.range.start.to_offset(buffer_snap);
12285 let end = symbol.range.end.to_offset(buffer_snap);
12286 let new_range = start..end;
12287 if start < selection.start || end > selection.end {
12288 return Some(Selection {
12289 id: selection.id,
12290 start: new_range.start,
12291 end: new_range.end,
12292 goal: SelectionGoal::None,
12293 reversed: selection.reversed,
12294 });
12295 }
12296 }
12297 None
12298 }
12299
12300 let mut selected_larger_symbol = false;
12301 let new_selections = old_selections
12302 .iter()
12303 .map(|selection| match update_selection(selection, &buffer) {
12304 Some(new_selection) => {
12305 if new_selection.range() != selection.range() {
12306 selected_larger_symbol = true;
12307 }
12308 new_selection
12309 }
12310 None => selection.clone(),
12311 })
12312 .collect::<Vec<_>>();
12313
12314 if selected_larger_symbol {
12315 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12316 s.select(new_selections);
12317 });
12318 }
12319 }
12320
12321 pub fn select_larger_syntax_node(
12322 &mut self,
12323 _: &SelectLargerSyntaxNode,
12324 window: &mut Window,
12325 cx: &mut Context<Self>,
12326 ) {
12327 let Some(visible_row_count) = self.visible_row_count() else {
12328 return;
12329 };
12330 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12331 if old_selections.is_empty() {
12332 return;
12333 }
12334
12335 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12336
12337 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12338 let buffer = self.buffer.read(cx).snapshot(cx);
12339
12340 let mut selected_larger_node = false;
12341 let mut new_selections = old_selections
12342 .iter()
12343 .map(|selection| {
12344 let old_range = selection.start..selection.end;
12345 let mut new_range = old_range.clone();
12346 let mut new_node = None;
12347 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12348 {
12349 new_node = Some(node);
12350 new_range = match containing_range {
12351 MultiOrSingleBufferOffsetRange::Single(_) => break,
12352 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12353 };
12354 if !display_map.intersects_fold(new_range.start)
12355 && !display_map.intersects_fold(new_range.end)
12356 {
12357 break;
12358 }
12359 }
12360
12361 if let Some(node) = new_node {
12362 // Log the ancestor, to support using this action as a way to explore TreeSitter
12363 // nodes. Parent and grandparent are also logged because this operation will not
12364 // visit nodes that have the same range as their parent.
12365 log::info!("Node: {node:?}");
12366 let parent = node.parent();
12367 log::info!("Parent: {parent:?}");
12368 let grandparent = parent.and_then(|x| x.parent());
12369 log::info!("Grandparent: {grandparent:?}");
12370 }
12371
12372 selected_larger_node |= new_range != old_range;
12373 Selection {
12374 id: selection.id,
12375 start: new_range.start,
12376 end: new_range.end,
12377 goal: SelectionGoal::None,
12378 reversed: selection.reversed,
12379 }
12380 })
12381 .collect::<Vec<_>>();
12382
12383 if !selected_larger_node {
12384 return; // don't put this call in the history
12385 }
12386
12387 // scroll based on transformation done to the last selection created by the user
12388 let (last_old, last_new) = old_selections
12389 .last()
12390 .zip(new_selections.last().cloned())
12391 .expect("old_selections isn't empty");
12392
12393 // revert selection
12394 let is_selection_reversed = {
12395 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12396 new_selections.last_mut().expect("checked above").reversed =
12397 should_newest_selection_be_reversed;
12398 should_newest_selection_be_reversed
12399 };
12400
12401 if selected_larger_node {
12402 self.select_syntax_node_history.disable_clearing = true;
12403 self.change_selections(None, window, cx, |s| {
12404 s.select(new_selections.clone());
12405 });
12406 self.select_syntax_node_history.disable_clearing = false;
12407 }
12408
12409 let start_row = last_new.start.to_display_point(&display_map).row().0;
12410 let end_row = last_new.end.to_display_point(&display_map).row().0;
12411 let selection_height = end_row - start_row + 1;
12412 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12413
12414 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
12415 let scroll_behavior = if fits_on_the_screen {
12416 self.request_autoscroll(Autoscroll::fit(), cx);
12417 SelectSyntaxNodeScrollBehavior::FitSelection
12418 } else if is_selection_reversed {
12419 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12420 SelectSyntaxNodeScrollBehavior::CursorTop
12421 } else {
12422 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12423 SelectSyntaxNodeScrollBehavior::CursorBottom
12424 };
12425
12426 self.select_syntax_node_history.push((
12427 old_selections,
12428 scroll_behavior,
12429 is_selection_reversed,
12430 ));
12431 }
12432
12433 pub fn select_smaller_syntax_node(
12434 &mut self,
12435 _: &SelectSmallerSyntaxNode,
12436 window: &mut Window,
12437 cx: &mut Context<Self>,
12438 ) {
12439 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12440
12441 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12442 self.select_syntax_node_history.pop()
12443 {
12444 if let Some(selection) = selections.last_mut() {
12445 selection.reversed = is_selection_reversed;
12446 }
12447
12448 self.select_syntax_node_history.disable_clearing = true;
12449 self.change_selections(None, window, cx, |s| {
12450 s.select(selections.to_vec());
12451 });
12452 self.select_syntax_node_history.disable_clearing = false;
12453
12454 match scroll_behavior {
12455 SelectSyntaxNodeScrollBehavior::CursorTop => {
12456 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12457 }
12458 SelectSyntaxNodeScrollBehavior::FitSelection => {
12459 self.request_autoscroll(Autoscroll::fit(), cx);
12460 }
12461 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12462 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12463 }
12464 }
12465 }
12466 }
12467
12468 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12469 if !EditorSettings::get_global(cx).gutter.runnables {
12470 self.clear_tasks();
12471 return Task::ready(());
12472 }
12473 let project = self.project.as_ref().map(Entity::downgrade);
12474 let task_sources = self.lsp_task_sources(cx);
12475 cx.spawn_in(window, async move |editor, cx| {
12476 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12477 let Some(project) = project.and_then(|p| p.upgrade()) else {
12478 return;
12479 };
12480 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
12481 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12482 }) else {
12483 return;
12484 };
12485
12486 let hide_runnables = project
12487 .update(cx, |project, cx| {
12488 // Do not display any test indicators in non-dev server remote projects.
12489 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12490 })
12491 .unwrap_or(true);
12492 if hide_runnables {
12493 return;
12494 }
12495 let new_rows =
12496 cx.background_spawn({
12497 let snapshot = display_snapshot.clone();
12498 async move {
12499 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12500 }
12501 })
12502 .await;
12503 let Ok(lsp_tasks) =
12504 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
12505 else {
12506 return;
12507 };
12508 let lsp_tasks = lsp_tasks.await;
12509
12510 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
12511 lsp_tasks
12512 .into_iter()
12513 .flat_map(|(kind, tasks)| {
12514 tasks.into_iter().filter_map(move |(location, task)| {
12515 Some((kind.clone(), location?, task))
12516 })
12517 })
12518 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
12519 let buffer = location.target.buffer;
12520 let buffer_snapshot = buffer.read(cx).snapshot();
12521 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
12522 |(excerpt_id, snapshot, _)| {
12523 if snapshot.remote_id() == buffer_snapshot.remote_id() {
12524 display_snapshot
12525 .buffer_snapshot
12526 .anchor_in_excerpt(excerpt_id, location.target.range.start)
12527 } else {
12528 None
12529 }
12530 },
12531 );
12532 if let Some(offset) = offset {
12533 let task_buffer_range =
12534 location.target.range.to_point(&buffer_snapshot);
12535 let context_buffer_range =
12536 task_buffer_range.to_offset(&buffer_snapshot);
12537 let context_range = BufferOffset(context_buffer_range.start)
12538 ..BufferOffset(context_buffer_range.end);
12539
12540 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
12541 .or_insert_with(|| RunnableTasks {
12542 templates: Vec::new(),
12543 offset,
12544 column: task_buffer_range.start.column,
12545 extra_variables: HashMap::default(),
12546 context_range,
12547 })
12548 .templates
12549 .push((kind, task.original_task().clone()));
12550 }
12551
12552 acc
12553 })
12554 }) else {
12555 return;
12556 };
12557
12558 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12559 editor
12560 .update(cx, |editor, _| {
12561 editor.clear_tasks();
12562 for (key, mut value) in rows {
12563 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
12564 value.templates.extend(lsp_tasks.templates);
12565 }
12566
12567 editor.insert_tasks(key, value);
12568 }
12569 for (key, value) in lsp_tasks_by_rows {
12570 editor.insert_tasks(key, value);
12571 }
12572 })
12573 .ok();
12574 })
12575 }
12576 fn fetch_runnable_ranges(
12577 snapshot: &DisplaySnapshot,
12578 range: Range<Anchor>,
12579 ) -> Vec<language::RunnableRange> {
12580 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12581 }
12582
12583 fn runnable_rows(
12584 project: Entity<Project>,
12585 snapshot: DisplaySnapshot,
12586 runnable_ranges: Vec<RunnableRange>,
12587 mut cx: AsyncWindowContext,
12588 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
12589 runnable_ranges
12590 .into_iter()
12591 .filter_map(|mut runnable| {
12592 let tasks = cx
12593 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12594 .ok()?;
12595 if tasks.is_empty() {
12596 return None;
12597 }
12598
12599 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12600
12601 let row = snapshot
12602 .buffer_snapshot
12603 .buffer_line_for_row(MultiBufferRow(point.row))?
12604 .1
12605 .start
12606 .row;
12607
12608 let context_range =
12609 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12610 Some((
12611 (runnable.buffer_id, row),
12612 RunnableTasks {
12613 templates: tasks,
12614 offset: snapshot
12615 .buffer_snapshot
12616 .anchor_before(runnable.run_range.start),
12617 context_range,
12618 column: point.column,
12619 extra_variables: runnable.extra_captures,
12620 },
12621 ))
12622 })
12623 .collect()
12624 }
12625
12626 fn templates_with_tags(
12627 project: &Entity<Project>,
12628 runnable: &mut Runnable,
12629 cx: &mut App,
12630 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12631 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12632 let (worktree_id, file) = project
12633 .buffer_for_id(runnable.buffer, cx)
12634 .and_then(|buffer| buffer.read(cx).file())
12635 .map(|file| (file.worktree_id(cx), file.clone()))
12636 .unzip();
12637
12638 (
12639 project.task_store().read(cx).task_inventory().cloned(),
12640 worktree_id,
12641 file,
12642 )
12643 });
12644
12645 let mut templates_with_tags = mem::take(&mut runnable.tags)
12646 .into_iter()
12647 .flat_map(|RunnableTag(tag)| {
12648 inventory
12649 .as_ref()
12650 .into_iter()
12651 .flat_map(|inventory| {
12652 inventory.read(cx).list_tasks(
12653 file.clone(),
12654 Some(runnable.language.clone()),
12655 worktree_id,
12656 cx,
12657 )
12658 })
12659 .filter(move |(_, template)| {
12660 template.tags.iter().any(|source_tag| source_tag == &tag)
12661 })
12662 })
12663 .sorted_by_key(|(kind, _)| kind.to_owned())
12664 .collect::<Vec<_>>();
12665 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
12666 // Strongest source wins; if we have worktree tag binding, prefer that to
12667 // global and language bindings;
12668 // if we have a global binding, prefer that to language binding.
12669 let first_mismatch = templates_with_tags
12670 .iter()
12671 .position(|(tag_source, _)| tag_source != leading_tag_source);
12672 if let Some(index) = first_mismatch {
12673 templates_with_tags.truncate(index);
12674 }
12675 }
12676
12677 templates_with_tags
12678 }
12679
12680 pub fn move_to_enclosing_bracket(
12681 &mut self,
12682 _: &MoveToEnclosingBracket,
12683 window: &mut Window,
12684 cx: &mut Context<Self>,
12685 ) {
12686 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12687 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12688 s.move_offsets_with(|snapshot, selection| {
12689 let Some(enclosing_bracket_ranges) =
12690 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12691 else {
12692 return;
12693 };
12694
12695 let mut best_length = usize::MAX;
12696 let mut best_inside = false;
12697 let mut best_in_bracket_range = false;
12698 let mut best_destination = None;
12699 for (open, close) in enclosing_bracket_ranges {
12700 let close = close.to_inclusive();
12701 let length = close.end() - open.start;
12702 let inside = selection.start >= open.end && selection.end <= *close.start();
12703 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12704 || close.contains(&selection.head());
12705
12706 // If best is next to a bracket and current isn't, skip
12707 if !in_bracket_range && best_in_bracket_range {
12708 continue;
12709 }
12710
12711 // Prefer smaller lengths unless best is inside and current isn't
12712 if length > best_length && (best_inside || !inside) {
12713 continue;
12714 }
12715
12716 best_length = length;
12717 best_inside = inside;
12718 best_in_bracket_range = in_bracket_range;
12719 best_destination = Some(
12720 if close.contains(&selection.start) && close.contains(&selection.end) {
12721 if inside { open.end } else { open.start }
12722 } else if inside {
12723 *close.start()
12724 } else {
12725 *close.end()
12726 },
12727 );
12728 }
12729
12730 if let Some(destination) = best_destination {
12731 selection.collapse_to(destination, SelectionGoal::None);
12732 }
12733 })
12734 });
12735 }
12736
12737 pub fn undo_selection(
12738 &mut self,
12739 _: &UndoSelection,
12740 window: &mut Window,
12741 cx: &mut Context<Self>,
12742 ) {
12743 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12744 self.end_selection(window, cx);
12745 self.selection_history.mode = SelectionHistoryMode::Undoing;
12746 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12747 self.change_selections(None, window, cx, |s| {
12748 s.select_anchors(entry.selections.to_vec())
12749 });
12750 self.select_next_state = entry.select_next_state;
12751 self.select_prev_state = entry.select_prev_state;
12752 self.add_selections_state = entry.add_selections_state;
12753 self.request_autoscroll(Autoscroll::newest(), cx);
12754 }
12755 self.selection_history.mode = SelectionHistoryMode::Normal;
12756 }
12757
12758 pub fn redo_selection(
12759 &mut self,
12760 _: &RedoSelection,
12761 window: &mut Window,
12762 cx: &mut Context<Self>,
12763 ) {
12764 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12765 self.end_selection(window, cx);
12766 self.selection_history.mode = SelectionHistoryMode::Redoing;
12767 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12768 self.change_selections(None, window, cx, |s| {
12769 s.select_anchors(entry.selections.to_vec())
12770 });
12771 self.select_next_state = entry.select_next_state;
12772 self.select_prev_state = entry.select_prev_state;
12773 self.add_selections_state = entry.add_selections_state;
12774 self.request_autoscroll(Autoscroll::newest(), cx);
12775 }
12776 self.selection_history.mode = SelectionHistoryMode::Normal;
12777 }
12778
12779 pub fn expand_excerpts(
12780 &mut self,
12781 action: &ExpandExcerpts,
12782 _: &mut Window,
12783 cx: &mut Context<Self>,
12784 ) {
12785 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12786 }
12787
12788 pub fn expand_excerpts_down(
12789 &mut self,
12790 action: &ExpandExcerptsDown,
12791 _: &mut Window,
12792 cx: &mut Context<Self>,
12793 ) {
12794 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12795 }
12796
12797 pub fn expand_excerpts_up(
12798 &mut self,
12799 action: &ExpandExcerptsUp,
12800 _: &mut Window,
12801 cx: &mut Context<Self>,
12802 ) {
12803 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12804 }
12805
12806 pub fn expand_excerpts_for_direction(
12807 &mut self,
12808 lines: u32,
12809 direction: ExpandExcerptDirection,
12810
12811 cx: &mut Context<Self>,
12812 ) {
12813 let selections = self.selections.disjoint_anchors();
12814
12815 let lines = if lines == 0 {
12816 EditorSettings::get_global(cx).expand_excerpt_lines
12817 } else {
12818 lines
12819 };
12820
12821 self.buffer.update(cx, |buffer, cx| {
12822 let snapshot = buffer.snapshot(cx);
12823 let mut excerpt_ids = selections
12824 .iter()
12825 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12826 .collect::<Vec<_>>();
12827 excerpt_ids.sort();
12828 excerpt_ids.dedup();
12829 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12830 })
12831 }
12832
12833 pub fn expand_excerpt(
12834 &mut self,
12835 excerpt: ExcerptId,
12836 direction: ExpandExcerptDirection,
12837 window: &mut Window,
12838 cx: &mut Context<Self>,
12839 ) {
12840 let current_scroll_position = self.scroll_position(cx);
12841 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
12842 let mut should_scroll_up = false;
12843
12844 if direction == ExpandExcerptDirection::Down {
12845 let multi_buffer = self.buffer.read(cx);
12846 let snapshot = multi_buffer.snapshot(cx);
12847 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
12848 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
12849 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
12850 let buffer_snapshot = buffer.read(cx).snapshot();
12851 let excerpt_end_row =
12852 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
12853 let last_row = buffer_snapshot.max_point().row;
12854 let lines_below = last_row.saturating_sub(excerpt_end_row);
12855 should_scroll_up = lines_below >= lines_to_expand;
12856 }
12857 }
12858 }
12859 }
12860
12861 self.buffer.update(cx, |buffer, cx| {
12862 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
12863 });
12864
12865 if should_scroll_up {
12866 let new_scroll_position =
12867 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
12868 self.set_scroll_position(new_scroll_position, window, cx);
12869 }
12870 }
12871
12872 pub fn go_to_singleton_buffer_point(
12873 &mut self,
12874 point: Point,
12875 window: &mut Window,
12876 cx: &mut Context<Self>,
12877 ) {
12878 self.go_to_singleton_buffer_range(point..point, window, cx);
12879 }
12880
12881 pub fn go_to_singleton_buffer_range(
12882 &mut self,
12883 range: Range<Point>,
12884 window: &mut Window,
12885 cx: &mut Context<Self>,
12886 ) {
12887 let multibuffer = self.buffer().read(cx);
12888 let Some(buffer) = multibuffer.as_singleton() else {
12889 return;
12890 };
12891 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12892 return;
12893 };
12894 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12895 return;
12896 };
12897 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12898 s.select_anchor_ranges([start..end])
12899 });
12900 }
12901
12902 fn go_to_diagnostic(
12903 &mut self,
12904 _: &GoToDiagnostic,
12905 window: &mut Window,
12906 cx: &mut Context<Self>,
12907 ) {
12908 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12909 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12910 }
12911
12912 fn go_to_prev_diagnostic(
12913 &mut self,
12914 _: &GoToPreviousDiagnostic,
12915 window: &mut Window,
12916 cx: &mut Context<Self>,
12917 ) {
12918 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12919 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12920 }
12921
12922 pub fn go_to_diagnostic_impl(
12923 &mut self,
12924 direction: Direction,
12925 window: &mut Window,
12926 cx: &mut Context<Self>,
12927 ) {
12928 let buffer = self.buffer.read(cx).snapshot(cx);
12929 let selection = self.selections.newest::<usize>(cx);
12930 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12931 if direction == Direction::Next {
12932 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12933 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12934 return;
12935 };
12936 self.activate_diagnostics(
12937 buffer_id,
12938 popover.local_diagnostic.diagnostic.group_id,
12939 window,
12940 cx,
12941 );
12942 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12943 let primary_range_start = active_diagnostics.primary_range.start;
12944 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12945 let mut new_selection = s.newest_anchor().clone();
12946 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12947 s.select_anchors(vec![new_selection.clone()]);
12948 });
12949 self.refresh_inline_completion(false, true, window, cx);
12950 }
12951 return;
12952 }
12953 }
12954
12955 let active_group_id = self
12956 .active_diagnostics
12957 .as_ref()
12958 .map(|active_group| active_group.group_id);
12959 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12960 active_diagnostics
12961 .primary_range
12962 .to_offset(&buffer)
12963 .to_inclusive()
12964 });
12965 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12966 if active_primary_range.contains(&selection.head()) {
12967 *active_primary_range.start()
12968 } else {
12969 selection.head()
12970 }
12971 } else {
12972 selection.head()
12973 };
12974
12975 let snapshot = self.snapshot(window, cx);
12976 let primary_diagnostics_before = buffer
12977 .diagnostics_in_range::<usize>(0..search_start)
12978 .filter(|entry| entry.diagnostic.is_primary)
12979 .filter(|entry| entry.range.start != entry.range.end)
12980 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12981 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12982 .collect::<Vec<_>>();
12983 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12984 primary_diagnostics_before
12985 .iter()
12986 .position(|entry| entry.diagnostic.group_id == active_group_id)
12987 });
12988
12989 let primary_diagnostics_after = buffer
12990 .diagnostics_in_range::<usize>(search_start..buffer.len())
12991 .filter(|entry| entry.diagnostic.is_primary)
12992 .filter(|entry| entry.range.start != entry.range.end)
12993 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12994 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12995 .collect::<Vec<_>>();
12996 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12997 primary_diagnostics_after
12998 .iter()
12999 .enumerate()
13000 .rev()
13001 .find_map(|(i, entry)| {
13002 if entry.diagnostic.group_id == active_group_id {
13003 Some(i)
13004 } else {
13005 None
13006 }
13007 })
13008 });
13009
13010 let next_primary_diagnostic = match direction {
13011 Direction::Prev => primary_diagnostics_before
13012 .iter()
13013 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
13014 .rev()
13015 .next(),
13016 Direction::Next => primary_diagnostics_after
13017 .iter()
13018 .skip(
13019 last_same_group_diagnostic_after
13020 .map(|index| index + 1)
13021 .unwrap_or(0),
13022 )
13023 .next(),
13024 };
13025
13026 // Cycle around to the start of the buffer, potentially moving back to the start of
13027 // the currently active diagnostic.
13028 let cycle_around = || match direction {
13029 Direction::Prev => primary_diagnostics_after
13030 .iter()
13031 .rev()
13032 .chain(primary_diagnostics_before.iter().rev())
13033 .next(),
13034 Direction::Next => primary_diagnostics_before
13035 .iter()
13036 .chain(primary_diagnostics_after.iter())
13037 .next(),
13038 };
13039
13040 if let Some((primary_range, group_id)) = next_primary_diagnostic
13041 .or_else(cycle_around)
13042 .map(|entry| (&entry.range, entry.diagnostic.group_id))
13043 {
13044 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
13045 return;
13046 };
13047 self.activate_diagnostics(buffer_id, group_id, window, cx);
13048 if self.active_diagnostics.is_some() {
13049 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13050 s.select(vec![Selection {
13051 id: selection.id,
13052 start: primary_range.start,
13053 end: primary_range.start,
13054 reversed: false,
13055 goal: SelectionGoal::None,
13056 }]);
13057 });
13058 self.refresh_inline_completion(false, true, window, cx);
13059 }
13060 }
13061 }
13062
13063 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13064 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13065 let snapshot = self.snapshot(window, cx);
13066 let selection = self.selections.newest::<Point>(cx);
13067 self.go_to_hunk_before_or_after_position(
13068 &snapshot,
13069 selection.head(),
13070 Direction::Next,
13071 window,
13072 cx,
13073 );
13074 }
13075
13076 pub fn go_to_hunk_before_or_after_position(
13077 &mut self,
13078 snapshot: &EditorSnapshot,
13079 position: Point,
13080 direction: Direction,
13081 window: &mut Window,
13082 cx: &mut Context<Editor>,
13083 ) {
13084 let row = if direction == Direction::Next {
13085 self.hunk_after_position(snapshot, position)
13086 .map(|hunk| hunk.row_range.start)
13087 } else {
13088 self.hunk_before_position(snapshot, position)
13089 };
13090
13091 if let Some(row) = row {
13092 let destination = Point::new(row.0, 0);
13093 let autoscroll = Autoscroll::center();
13094
13095 self.unfold_ranges(&[destination..destination], false, false, cx);
13096 self.change_selections(Some(autoscroll), window, cx, |s| {
13097 s.select_ranges([destination..destination]);
13098 });
13099 }
13100 }
13101
13102 fn hunk_after_position(
13103 &mut self,
13104 snapshot: &EditorSnapshot,
13105 position: Point,
13106 ) -> Option<MultiBufferDiffHunk> {
13107 snapshot
13108 .buffer_snapshot
13109 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13110 .find(|hunk| hunk.row_range.start.0 > position.row)
13111 .or_else(|| {
13112 snapshot
13113 .buffer_snapshot
13114 .diff_hunks_in_range(Point::zero()..position)
13115 .find(|hunk| hunk.row_range.end.0 < position.row)
13116 })
13117 }
13118
13119 fn go_to_prev_hunk(
13120 &mut self,
13121 _: &GoToPreviousHunk,
13122 window: &mut Window,
13123 cx: &mut Context<Self>,
13124 ) {
13125 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13126 let snapshot = self.snapshot(window, cx);
13127 let selection = self.selections.newest::<Point>(cx);
13128 self.go_to_hunk_before_or_after_position(
13129 &snapshot,
13130 selection.head(),
13131 Direction::Prev,
13132 window,
13133 cx,
13134 );
13135 }
13136
13137 fn hunk_before_position(
13138 &mut self,
13139 snapshot: &EditorSnapshot,
13140 position: Point,
13141 ) -> Option<MultiBufferRow> {
13142 snapshot
13143 .buffer_snapshot
13144 .diff_hunk_before(position)
13145 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13146 }
13147
13148 fn go_to_line<T: 'static>(
13149 &mut self,
13150 position: Anchor,
13151 highlight_color: Option<Hsla>,
13152 window: &mut Window,
13153 cx: &mut Context<Self>,
13154 ) {
13155 let snapshot = self.snapshot(window, cx).display_snapshot;
13156 let position = position.to_point(&snapshot.buffer_snapshot);
13157 let start = snapshot
13158 .buffer_snapshot
13159 .clip_point(Point::new(position.row, 0), Bias::Left);
13160 let end = start + Point::new(1, 0);
13161 let start = snapshot.buffer_snapshot.anchor_before(start);
13162 let end = snapshot.buffer_snapshot.anchor_before(end);
13163
13164 self.highlight_rows::<T>(
13165 start..end,
13166 highlight_color
13167 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13168 false,
13169 cx,
13170 );
13171 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13172 }
13173
13174 pub fn go_to_definition(
13175 &mut self,
13176 _: &GoToDefinition,
13177 window: &mut Window,
13178 cx: &mut Context<Self>,
13179 ) -> Task<Result<Navigated>> {
13180 let definition =
13181 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13182 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13183 cx.spawn_in(window, async move |editor, cx| {
13184 if definition.await? == Navigated::Yes {
13185 return Ok(Navigated::Yes);
13186 }
13187 match fallback_strategy {
13188 GoToDefinitionFallback::None => Ok(Navigated::No),
13189 GoToDefinitionFallback::FindAllReferences => {
13190 match editor.update_in(cx, |editor, window, cx| {
13191 editor.find_all_references(&FindAllReferences, window, cx)
13192 })? {
13193 Some(references) => references.await,
13194 None => Ok(Navigated::No),
13195 }
13196 }
13197 }
13198 })
13199 }
13200
13201 pub fn go_to_declaration(
13202 &mut self,
13203 _: &GoToDeclaration,
13204 window: &mut Window,
13205 cx: &mut Context<Self>,
13206 ) -> Task<Result<Navigated>> {
13207 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13208 }
13209
13210 pub fn go_to_declaration_split(
13211 &mut self,
13212 _: &GoToDeclaration,
13213 window: &mut Window,
13214 cx: &mut Context<Self>,
13215 ) -> Task<Result<Navigated>> {
13216 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13217 }
13218
13219 pub fn go_to_implementation(
13220 &mut self,
13221 _: &GoToImplementation,
13222 window: &mut Window,
13223 cx: &mut Context<Self>,
13224 ) -> Task<Result<Navigated>> {
13225 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13226 }
13227
13228 pub fn go_to_implementation_split(
13229 &mut self,
13230 _: &GoToImplementationSplit,
13231 window: &mut Window,
13232 cx: &mut Context<Self>,
13233 ) -> Task<Result<Navigated>> {
13234 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13235 }
13236
13237 pub fn go_to_type_definition(
13238 &mut self,
13239 _: &GoToTypeDefinition,
13240 window: &mut Window,
13241 cx: &mut Context<Self>,
13242 ) -> Task<Result<Navigated>> {
13243 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13244 }
13245
13246 pub fn go_to_definition_split(
13247 &mut self,
13248 _: &GoToDefinitionSplit,
13249 window: &mut Window,
13250 cx: &mut Context<Self>,
13251 ) -> Task<Result<Navigated>> {
13252 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13253 }
13254
13255 pub fn go_to_type_definition_split(
13256 &mut self,
13257 _: &GoToTypeDefinitionSplit,
13258 window: &mut Window,
13259 cx: &mut Context<Self>,
13260 ) -> Task<Result<Navigated>> {
13261 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13262 }
13263
13264 fn go_to_definition_of_kind(
13265 &mut self,
13266 kind: GotoDefinitionKind,
13267 split: bool,
13268 window: &mut Window,
13269 cx: &mut Context<Self>,
13270 ) -> Task<Result<Navigated>> {
13271 let Some(provider) = self.semantics_provider.clone() else {
13272 return Task::ready(Ok(Navigated::No));
13273 };
13274 let head = self.selections.newest::<usize>(cx).head();
13275 let buffer = self.buffer.read(cx);
13276 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13277 text_anchor
13278 } else {
13279 return Task::ready(Ok(Navigated::No));
13280 };
13281
13282 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13283 return Task::ready(Ok(Navigated::No));
13284 };
13285
13286 cx.spawn_in(window, async move |editor, cx| {
13287 let definitions = definitions.await?;
13288 let navigated = editor
13289 .update_in(cx, |editor, window, cx| {
13290 editor.navigate_to_hover_links(
13291 Some(kind),
13292 definitions
13293 .into_iter()
13294 .filter(|location| {
13295 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13296 })
13297 .map(HoverLink::Text)
13298 .collect::<Vec<_>>(),
13299 split,
13300 window,
13301 cx,
13302 )
13303 })?
13304 .await?;
13305 anyhow::Ok(navigated)
13306 })
13307 }
13308
13309 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13310 let selection = self.selections.newest_anchor();
13311 let head = selection.head();
13312 let tail = selection.tail();
13313
13314 let Some((buffer, start_position)) =
13315 self.buffer.read(cx).text_anchor_for_position(head, cx)
13316 else {
13317 return;
13318 };
13319
13320 let end_position = if head != tail {
13321 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13322 return;
13323 };
13324 Some(pos)
13325 } else {
13326 None
13327 };
13328
13329 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13330 let url = if let Some(end_pos) = end_position {
13331 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13332 } else {
13333 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13334 };
13335
13336 if let Some(url) = url {
13337 editor.update(cx, |_, cx| {
13338 cx.open_url(&url);
13339 })
13340 } else {
13341 Ok(())
13342 }
13343 });
13344
13345 url_finder.detach();
13346 }
13347
13348 pub fn open_selected_filename(
13349 &mut self,
13350 _: &OpenSelectedFilename,
13351 window: &mut Window,
13352 cx: &mut Context<Self>,
13353 ) {
13354 let Some(workspace) = self.workspace() else {
13355 return;
13356 };
13357
13358 let position = self.selections.newest_anchor().head();
13359
13360 let Some((buffer, buffer_position)) =
13361 self.buffer.read(cx).text_anchor_for_position(position, cx)
13362 else {
13363 return;
13364 };
13365
13366 let project = self.project.clone();
13367
13368 cx.spawn_in(window, async move |_, cx| {
13369 let result = find_file(&buffer, project, buffer_position, cx).await;
13370
13371 if let Some((_, path)) = result {
13372 workspace
13373 .update_in(cx, |workspace, window, cx| {
13374 workspace.open_resolved_path(path, window, cx)
13375 })?
13376 .await?;
13377 }
13378 anyhow::Ok(())
13379 })
13380 .detach();
13381 }
13382
13383 pub(crate) fn navigate_to_hover_links(
13384 &mut self,
13385 kind: Option<GotoDefinitionKind>,
13386 mut definitions: Vec<HoverLink>,
13387 split: bool,
13388 window: &mut Window,
13389 cx: &mut Context<Editor>,
13390 ) -> Task<Result<Navigated>> {
13391 // If there is one definition, just open it directly
13392 if definitions.len() == 1 {
13393 let definition = definitions.pop().unwrap();
13394
13395 enum TargetTaskResult {
13396 Location(Option<Location>),
13397 AlreadyNavigated,
13398 }
13399
13400 let target_task = match definition {
13401 HoverLink::Text(link) => {
13402 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13403 }
13404 HoverLink::InlayHint(lsp_location, server_id) => {
13405 let computation =
13406 self.compute_target_location(lsp_location, server_id, window, cx);
13407 cx.background_spawn(async move {
13408 let location = computation.await?;
13409 Ok(TargetTaskResult::Location(location))
13410 })
13411 }
13412 HoverLink::Url(url) => {
13413 cx.open_url(&url);
13414 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13415 }
13416 HoverLink::File(path) => {
13417 if let Some(workspace) = self.workspace() {
13418 cx.spawn_in(window, async move |_, cx| {
13419 workspace
13420 .update_in(cx, |workspace, window, cx| {
13421 workspace.open_resolved_path(path, window, cx)
13422 })?
13423 .await
13424 .map(|_| TargetTaskResult::AlreadyNavigated)
13425 })
13426 } else {
13427 Task::ready(Ok(TargetTaskResult::Location(None)))
13428 }
13429 }
13430 };
13431 cx.spawn_in(window, async move |editor, cx| {
13432 let target = match target_task.await.context("target resolution task")? {
13433 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13434 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13435 TargetTaskResult::Location(Some(target)) => target,
13436 };
13437
13438 editor.update_in(cx, |editor, window, cx| {
13439 let Some(workspace) = editor.workspace() else {
13440 return Navigated::No;
13441 };
13442 let pane = workspace.read(cx).active_pane().clone();
13443
13444 let range = target.range.to_point(target.buffer.read(cx));
13445 let range = editor.range_for_match(&range);
13446 let range = collapse_multiline_range(range);
13447
13448 if !split
13449 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13450 {
13451 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13452 } else {
13453 window.defer(cx, move |window, cx| {
13454 let target_editor: Entity<Self> =
13455 workspace.update(cx, |workspace, cx| {
13456 let pane = if split {
13457 workspace.adjacent_pane(window, cx)
13458 } else {
13459 workspace.active_pane().clone()
13460 };
13461
13462 workspace.open_project_item(
13463 pane,
13464 target.buffer.clone(),
13465 true,
13466 true,
13467 window,
13468 cx,
13469 )
13470 });
13471 target_editor.update(cx, |target_editor, cx| {
13472 // When selecting a definition in a different buffer, disable the nav history
13473 // to avoid creating a history entry at the previous cursor location.
13474 pane.update(cx, |pane, _| pane.disable_history());
13475 target_editor.go_to_singleton_buffer_range(range, window, cx);
13476 pane.update(cx, |pane, _| pane.enable_history());
13477 });
13478 });
13479 }
13480 Navigated::Yes
13481 })
13482 })
13483 } else if !definitions.is_empty() {
13484 cx.spawn_in(window, async move |editor, cx| {
13485 let (title, location_tasks, workspace) = editor
13486 .update_in(cx, |editor, window, cx| {
13487 let tab_kind = match kind {
13488 Some(GotoDefinitionKind::Implementation) => "Implementations",
13489 _ => "Definitions",
13490 };
13491 let title = definitions
13492 .iter()
13493 .find_map(|definition| match definition {
13494 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13495 let buffer = origin.buffer.read(cx);
13496 format!(
13497 "{} for {}",
13498 tab_kind,
13499 buffer
13500 .text_for_range(origin.range.clone())
13501 .collect::<String>()
13502 )
13503 }),
13504 HoverLink::InlayHint(_, _) => None,
13505 HoverLink::Url(_) => None,
13506 HoverLink::File(_) => None,
13507 })
13508 .unwrap_or(tab_kind.to_string());
13509 let location_tasks = definitions
13510 .into_iter()
13511 .map(|definition| match definition {
13512 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13513 HoverLink::InlayHint(lsp_location, server_id) => editor
13514 .compute_target_location(lsp_location, server_id, window, cx),
13515 HoverLink::Url(_) => Task::ready(Ok(None)),
13516 HoverLink::File(_) => Task::ready(Ok(None)),
13517 })
13518 .collect::<Vec<_>>();
13519 (title, location_tasks, editor.workspace().clone())
13520 })
13521 .context("location tasks preparation")?;
13522
13523 let locations = future::join_all(location_tasks)
13524 .await
13525 .into_iter()
13526 .filter_map(|location| location.transpose())
13527 .collect::<Result<_>>()
13528 .context("location tasks")?;
13529
13530 let Some(workspace) = workspace else {
13531 return Ok(Navigated::No);
13532 };
13533 let opened = workspace
13534 .update_in(cx, |workspace, window, cx| {
13535 Self::open_locations_in_multibuffer(
13536 workspace,
13537 locations,
13538 title,
13539 split,
13540 MultibufferSelectionMode::First,
13541 window,
13542 cx,
13543 )
13544 })
13545 .ok();
13546
13547 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13548 })
13549 } else {
13550 Task::ready(Ok(Navigated::No))
13551 }
13552 }
13553
13554 fn compute_target_location(
13555 &self,
13556 lsp_location: lsp::Location,
13557 server_id: LanguageServerId,
13558 window: &mut Window,
13559 cx: &mut Context<Self>,
13560 ) -> Task<anyhow::Result<Option<Location>>> {
13561 let Some(project) = self.project.clone() else {
13562 return Task::ready(Ok(None));
13563 };
13564
13565 cx.spawn_in(window, async move |editor, cx| {
13566 let location_task = editor.update(cx, |_, cx| {
13567 project.update(cx, |project, cx| {
13568 let language_server_name = project
13569 .language_server_statuses(cx)
13570 .find(|(id, _)| server_id == *id)
13571 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13572 language_server_name.map(|language_server_name| {
13573 project.open_local_buffer_via_lsp(
13574 lsp_location.uri.clone(),
13575 server_id,
13576 language_server_name,
13577 cx,
13578 )
13579 })
13580 })
13581 })?;
13582 let location = match location_task {
13583 Some(task) => Some({
13584 let target_buffer_handle = task.await.context("open local buffer")?;
13585 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13586 let target_start = target_buffer
13587 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13588 let target_end = target_buffer
13589 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13590 target_buffer.anchor_after(target_start)
13591 ..target_buffer.anchor_before(target_end)
13592 })?;
13593 Location {
13594 buffer: target_buffer_handle,
13595 range,
13596 }
13597 }),
13598 None => None,
13599 };
13600 Ok(location)
13601 })
13602 }
13603
13604 pub fn find_all_references(
13605 &mut self,
13606 _: &FindAllReferences,
13607 window: &mut Window,
13608 cx: &mut Context<Self>,
13609 ) -> Option<Task<Result<Navigated>>> {
13610 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13611
13612 let selection = self.selections.newest::<usize>(cx);
13613 let multi_buffer = self.buffer.read(cx);
13614 let head = selection.head();
13615
13616 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13617 let head_anchor = multi_buffer_snapshot.anchor_at(
13618 head,
13619 if head < selection.tail() {
13620 Bias::Right
13621 } else {
13622 Bias::Left
13623 },
13624 );
13625
13626 match self
13627 .find_all_references_task_sources
13628 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13629 {
13630 Ok(_) => {
13631 log::info!(
13632 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13633 );
13634 return None;
13635 }
13636 Err(i) => {
13637 self.find_all_references_task_sources.insert(i, head_anchor);
13638 }
13639 }
13640
13641 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13642 let workspace = self.workspace()?;
13643 let project = workspace.read(cx).project().clone();
13644 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13645 Some(cx.spawn_in(window, async move |editor, cx| {
13646 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13647 if let Ok(i) = editor
13648 .find_all_references_task_sources
13649 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13650 {
13651 editor.find_all_references_task_sources.remove(i);
13652 }
13653 });
13654
13655 let locations = references.await?;
13656 if locations.is_empty() {
13657 return anyhow::Ok(Navigated::No);
13658 }
13659
13660 workspace.update_in(cx, |workspace, window, cx| {
13661 let title = locations
13662 .first()
13663 .as_ref()
13664 .map(|location| {
13665 let buffer = location.buffer.read(cx);
13666 format!(
13667 "References to `{}`",
13668 buffer
13669 .text_for_range(location.range.clone())
13670 .collect::<String>()
13671 )
13672 })
13673 .unwrap();
13674 Self::open_locations_in_multibuffer(
13675 workspace,
13676 locations,
13677 title,
13678 false,
13679 MultibufferSelectionMode::First,
13680 window,
13681 cx,
13682 );
13683 Navigated::Yes
13684 })
13685 }))
13686 }
13687
13688 /// Opens a multibuffer with the given project locations in it
13689 pub fn open_locations_in_multibuffer(
13690 workspace: &mut Workspace,
13691 mut locations: Vec<Location>,
13692 title: String,
13693 split: bool,
13694 multibuffer_selection_mode: MultibufferSelectionMode,
13695 window: &mut Window,
13696 cx: &mut Context<Workspace>,
13697 ) {
13698 // If there are multiple definitions, open them in a multibuffer
13699 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13700 let mut locations = locations.into_iter().peekable();
13701 let mut ranges: Vec<Range<Anchor>> = Vec::new();
13702 let capability = workspace.project().read(cx).capability();
13703
13704 let excerpt_buffer = cx.new(|cx| {
13705 let mut multibuffer = MultiBuffer::new(capability);
13706 while let Some(location) = locations.next() {
13707 let buffer = location.buffer.read(cx);
13708 let mut ranges_for_buffer = Vec::new();
13709 let range = location.range.to_point(buffer);
13710 ranges_for_buffer.push(range.clone());
13711
13712 while let Some(next_location) = locations.peek() {
13713 if next_location.buffer == location.buffer {
13714 ranges_for_buffer.push(next_location.range.to_point(buffer));
13715 locations.next();
13716 } else {
13717 break;
13718 }
13719 }
13720
13721 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13722 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
13723 PathKey::for_buffer(&location.buffer, cx),
13724 location.buffer.clone(),
13725 ranges_for_buffer,
13726 DEFAULT_MULTIBUFFER_CONTEXT,
13727 cx,
13728 );
13729 ranges.extend(new_ranges)
13730 }
13731
13732 multibuffer.with_title(title)
13733 });
13734
13735 let editor = cx.new(|cx| {
13736 Editor::for_multibuffer(
13737 excerpt_buffer,
13738 Some(workspace.project().clone()),
13739 window,
13740 cx,
13741 )
13742 });
13743 editor.update(cx, |editor, cx| {
13744 match multibuffer_selection_mode {
13745 MultibufferSelectionMode::First => {
13746 if let Some(first_range) = ranges.first() {
13747 editor.change_selections(None, window, cx, |selections| {
13748 selections.clear_disjoint();
13749 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13750 });
13751 }
13752 editor.highlight_background::<Self>(
13753 &ranges,
13754 |theme| theme.editor_highlighted_line_background,
13755 cx,
13756 );
13757 }
13758 MultibufferSelectionMode::All => {
13759 editor.change_selections(None, window, cx, |selections| {
13760 selections.clear_disjoint();
13761 selections.select_anchor_ranges(ranges);
13762 });
13763 }
13764 }
13765 editor.register_buffers_with_language_servers(cx);
13766 });
13767
13768 let item = Box::new(editor);
13769 let item_id = item.item_id();
13770
13771 if split {
13772 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13773 } else {
13774 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13775 let (preview_item_id, preview_item_idx) =
13776 workspace.active_pane().update(cx, |pane, _| {
13777 (pane.preview_item_id(), pane.preview_item_idx())
13778 });
13779
13780 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13781
13782 if let Some(preview_item_id) = preview_item_id {
13783 workspace.active_pane().update(cx, |pane, cx| {
13784 pane.remove_item(preview_item_id, false, false, window, cx);
13785 });
13786 }
13787 } else {
13788 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13789 }
13790 }
13791 workspace.active_pane().update(cx, |pane, cx| {
13792 pane.set_preview_item_id(Some(item_id), cx);
13793 });
13794 }
13795
13796 pub fn rename(
13797 &mut self,
13798 _: &Rename,
13799 window: &mut Window,
13800 cx: &mut Context<Self>,
13801 ) -> Option<Task<Result<()>>> {
13802 use language::ToOffset as _;
13803
13804 let provider = self.semantics_provider.clone()?;
13805 let selection = self.selections.newest_anchor().clone();
13806 let (cursor_buffer, cursor_buffer_position) = self
13807 .buffer
13808 .read(cx)
13809 .text_anchor_for_position(selection.head(), cx)?;
13810 let (tail_buffer, cursor_buffer_position_end) = self
13811 .buffer
13812 .read(cx)
13813 .text_anchor_for_position(selection.tail(), cx)?;
13814 if tail_buffer != cursor_buffer {
13815 return None;
13816 }
13817
13818 let snapshot = cursor_buffer.read(cx).snapshot();
13819 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13820 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13821 let prepare_rename = provider
13822 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13823 .unwrap_or_else(|| Task::ready(Ok(None)));
13824 drop(snapshot);
13825
13826 Some(cx.spawn_in(window, async move |this, cx| {
13827 let rename_range = if let Some(range) = prepare_rename.await? {
13828 Some(range)
13829 } else {
13830 this.update(cx, |this, cx| {
13831 let buffer = this.buffer.read(cx).snapshot(cx);
13832 let mut buffer_highlights = this
13833 .document_highlights_for_position(selection.head(), &buffer)
13834 .filter(|highlight| {
13835 highlight.start.excerpt_id == selection.head().excerpt_id
13836 && highlight.end.excerpt_id == selection.head().excerpt_id
13837 });
13838 buffer_highlights
13839 .next()
13840 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13841 })?
13842 };
13843 if let Some(rename_range) = rename_range {
13844 this.update_in(cx, |this, window, cx| {
13845 let snapshot = cursor_buffer.read(cx).snapshot();
13846 let rename_buffer_range = rename_range.to_offset(&snapshot);
13847 let cursor_offset_in_rename_range =
13848 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13849 let cursor_offset_in_rename_range_end =
13850 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13851
13852 this.take_rename(false, window, cx);
13853 let buffer = this.buffer.read(cx).read(cx);
13854 let cursor_offset = selection.head().to_offset(&buffer);
13855 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13856 let rename_end = rename_start + rename_buffer_range.len();
13857 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13858 let mut old_highlight_id = None;
13859 let old_name: Arc<str> = buffer
13860 .chunks(rename_start..rename_end, true)
13861 .map(|chunk| {
13862 if old_highlight_id.is_none() {
13863 old_highlight_id = chunk.syntax_highlight_id;
13864 }
13865 chunk.text
13866 })
13867 .collect::<String>()
13868 .into();
13869
13870 drop(buffer);
13871
13872 // Position the selection in the rename editor so that it matches the current selection.
13873 this.show_local_selections = false;
13874 let rename_editor = cx.new(|cx| {
13875 let mut editor = Editor::single_line(window, cx);
13876 editor.buffer.update(cx, |buffer, cx| {
13877 buffer.edit([(0..0, old_name.clone())], None, cx)
13878 });
13879 let rename_selection_range = match cursor_offset_in_rename_range
13880 .cmp(&cursor_offset_in_rename_range_end)
13881 {
13882 Ordering::Equal => {
13883 editor.select_all(&SelectAll, window, cx);
13884 return editor;
13885 }
13886 Ordering::Less => {
13887 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13888 }
13889 Ordering::Greater => {
13890 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13891 }
13892 };
13893 if rename_selection_range.end > old_name.len() {
13894 editor.select_all(&SelectAll, window, cx);
13895 } else {
13896 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13897 s.select_ranges([rename_selection_range]);
13898 });
13899 }
13900 editor
13901 });
13902 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13903 if e == &EditorEvent::Focused {
13904 cx.emit(EditorEvent::FocusedIn)
13905 }
13906 })
13907 .detach();
13908
13909 let write_highlights =
13910 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13911 let read_highlights =
13912 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13913 let ranges = write_highlights
13914 .iter()
13915 .flat_map(|(_, ranges)| ranges.iter())
13916 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13917 .cloned()
13918 .collect();
13919
13920 this.highlight_text::<Rename>(
13921 ranges,
13922 HighlightStyle {
13923 fade_out: Some(0.6),
13924 ..Default::default()
13925 },
13926 cx,
13927 );
13928 let rename_focus_handle = rename_editor.focus_handle(cx);
13929 window.focus(&rename_focus_handle);
13930 let block_id = this.insert_blocks(
13931 [BlockProperties {
13932 style: BlockStyle::Flex,
13933 placement: BlockPlacement::Below(range.start),
13934 height: Some(1),
13935 render: Arc::new({
13936 let rename_editor = rename_editor.clone();
13937 move |cx: &mut BlockContext| {
13938 let mut text_style = cx.editor_style.text.clone();
13939 if let Some(highlight_style) = old_highlight_id
13940 .and_then(|h| h.style(&cx.editor_style.syntax))
13941 {
13942 text_style = text_style.highlight(highlight_style);
13943 }
13944 div()
13945 .block_mouse_down()
13946 .pl(cx.anchor_x)
13947 .child(EditorElement::new(
13948 &rename_editor,
13949 EditorStyle {
13950 background: cx.theme().system().transparent,
13951 local_player: cx.editor_style.local_player,
13952 text: text_style,
13953 scrollbar_width: cx.editor_style.scrollbar_width,
13954 syntax: cx.editor_style.syntax.clone(),
13955 status: cx.editor_style.status.clone(),
13956 inlay_hints_style: HighlightStyle {
13957 font_weight: Some(FontWeight::BOLD),
13958 ..make_inlay_hints_style(cx.app)
13959 },
13960 inline_completion_styles: make_suggestion_styles(
13961 cx.app,
13962 ),
13963 ..EditorStyle::default()
13964 },
13965 ))
13966 .into_any_element()
13967 }
13968 }),
13969 priority: 0,
13970 }],
13971 Some(Autoscroll::fit()),
13972 cx,
13973 )[0];
13974 this.pending_rename = Some(RenameState {
13975 range,
13976 old_name,
13977 editor: rename_editor,
13978 block_id,
13979 });
13980 })?;
13981 }
13982
13983 Ok(())
13984 }))
13985 }
13986
13987 pub fn confirm_rename(
13988 &mut self,
13989 _: &ConfirmRename,
13990 window: &mut Window,
13991 cx: &mut Context<Self>,
13992 ) -> Option<Task<Result<()>>> {
13993 let rename = self.take_rename(false, window, cx)?;
13994 let workspace = self.workspace()?.downgrade();
13995 let (buffer, start) = self
13996 .buffer
13997 .read(cx)
13998 .text_anchor_for_position(rename.range.start, cx)?;
13999 let (end_buffer, _) = self
14000 .buffer
14001 .read(cx)
14002 .text_anchor_for_position(rename.range.end, cx)?;
14003 if buffer != end_buffer {
14004 return None;
14005 }
14006
14007 let old_name = rename.old_name;
14008 let new_name = rename.editor.read(cx).text(cx);
14009
14010 let rename = self.semantics_provider.as_ref()?.perform_rename(
14011 &buffer,
14012 start,
14013 new_name.clone(),
14014 cx,
14015 )?;
14016
14017 Some(cx.spawn_in(window, async move |editor, cx| {
14018 let project_transaction = rename.await?;
14019 Self::open_project_transaction(
14020 &editor,
14021 workspace,
14022 project_transaction,
14023 format!("Rename: {} → {}", old_name, new_name),
14024 cx,
14025 )
14026 .await?;
14027
14028 editor.update(cx, |editor, cx| {
14029 editor.refresh_document_highlights(cx);
14030 })?;
14031 Ok(())
14032 }))
14033 }
14034
14035 fn take_rename(
14036 &mut self,
14037 moving_cursor: bool,
14038 window: &mut Window,
14039 cx: &mut Context<Self>,
14040 ) -> Option<RenameState> {
14041 let rename = self.pending_rename.take()?;
14042 if rename.editor.focus_handle(cx).is_focused(window) {
14043 window.focus(&self.focus_handle);
14044 }
14045
14046 self.remove_blocks(
14047 [rename.block_id].into_iter().collect(),
14048 Some(Autoscroll::fit()),
14049 cx,
14050 );
14051 self.clear_highlights::<Rename>(cx);
14052 self.show_local_selections = true;
14053
14054 if moving_cursor {
14055 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14056 editor.selections.newest::<usize>(cx).head()
14057 });
14058
14059 // Update the selection to match the position of the selection inside
14060 // the rename editor.
14061 let snapshot = self.buffer.read(cx).read(cx);
14062 let rename_range = rename.range.to_offset(&snapshot);
14063 let cursor_in_editor = snapshot
14064 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14065 .min(rename_range.end);
14066 drop(snapshot);
14067
14068 self.change_selections(None, window, cx, |s| {
14069 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14070 });
14071 } else {
14072 self.refresh_document_highlights(cx);
14073 }
14074
14075 Some(rename)
14076 }
14077
14078 pub fn pending_rename(&self) -> Option<&RenameState> {
14079 self.pending_rename.as_ref()
14080 }
14081
14082 fn format(
14083 &mut self,
14084 _: &Format,
14085 window: &mut Window,
14086 cx: &mut Context<Self>,
14087 ) -> Option<Task<Result<()>>> {
14088 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14089
14090 let project = match &self.project {
14091 Some(project) => project.clone(),
14092 None => return None,
14093 };
14094
14095 Some(self.perform_format(
14096 project,
14097 FormatTrigger::Manual,
14098 FormatTarget::Buffers,
14099 window,
14100 cx,
14101 ))
14102 }
14103
14104 fn format_selections(
14105 &mut self,
14106 _: &FormatSelections,
14107 window: &mut Window,
14108 cx: &mut Context<Self>,
14109 ) -> Option<Task<Result<()>>> {
14110 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14111
14112 let project = match &self.project {
14113 Some(project) => project.clone(),
14114 None => return None,
14115 };
14116
14117 let ranges = self
14118 .selections
14119 .all_adjusted(cx)
14120 .into_iter()
14121 .map(|selection| selection.range())
14122 .collect_vec();
14123
14124 Some(self.perform_format(
14125 project,
14126 FormatTrigger::Manual,
14127 FormatTarget::Ranges(ranges),
14128 window,
14129 cx,
14130 ))
14131 }
14132
14133 fn perform_format(
14134 &mut self,
14135 project: Entity<Project>,
14136 trigger: FormatTrigger,
14137 target: FormatTarget,
14138 window: &mut Window,
14139 cx: &mut Context<Self>,
14140 ) -> Task<Result<()>> {
14141 let buffer = self.buffer.clone();
14142 let (buffers, target) = match target {
14143 FormatTarget::Buffers => {
14144 let mut buffers = buffer.read(cx).all_buffers();
14145 if trigger == FormatTrigger::Save {
14146 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14147 }
14148 (buffers, LspFormatTarget::Buffers)
14149 }
14150 FormatTarget::Ranges(selection_ranges) => {
14151 let multi_buffer = buffer.read(cx);
14152 let snapshot = multi_buffer.read(cx);
14153 let mut buffers = HashSet::default();
14154 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14155 BTreeMap::new();
14156 for selection_range in selection_ranges {
14157 for (buffer, buffer_range, _) in
14158 snapshot.range_to_buffer_ranges(selection_range)
14159 {
14160 let buffer_id = buffer.remote_id();
14161 let start = buffer.anchor_before(buffer_range.start);
14162 let end = buffer.anchor_after(buffer_range.end);
14163 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14164 buffer_id_to_ranges
14165 .entry(buffer_id)
14166 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14167 .or_insert_with(|| vec![start..end]);
14168 }
14169 }
14170 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14171 }
14172 };
14173
14174 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14175 let format = project.update(cx, |project, cx| {
14176 project.format(buffers, target, true, trigger, cx)
14177 });
14178
14179 cx.spawn_in(window, async move |_, cx| {
14180 let transaction = futures::select_biased! {
14181 transaction = format.log_err().fuse() => transaction,
14182 () = timeout => {
14183 log::warn!("timed out waiting for formatting");
14184 None
14185 }
14186 };
14187
14188 buffer
14189 .update(cx, |buffer, cx| {
14190 if let Some(transaction) = transaction {
14191 if !buffer.is_singleton() {
14192 buffer.push_transaction(&transaction.0, cx);
14193 }
14194 }
14195 cx.notify();
14196 })
14197 .ok();
14198
14199 Ok(())
14200 })
14201 }
14202
14203 fn organize_imports(
14204 &mut self,
14205 _: &OrganizeImports,
14206 window: &mut Window,
14207 cx: &mut Context<Self>,
14208 ) -> Option<Task<Result<()>>> {
14209 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14210 let project = match &self.project {
14211 Some(project) => project.clone(),
14212 None => return None,
14213 };
14214 Some(self.perform_code_action_kind(
14215 project,
14216 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14217 window,
14218 cx,
14219 ))
14220 }
14221
14222 fn perform_code_action_kind(
14223 &mut self,
14224 project: Entity<Project>,
14225 kind: CodeActionKind,
14226 window: &mut Window,
14227 cx: &mut Context<Self>,
14228 ) -> Task<Result<()>> {
14229 let buffer = self.buffer.clone();
14230 let buffers = buffer.read(cx).all_buffers();
14231 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14232 let apply_action = project.update(cx, |project, cx| {
14233 project.apply_code_action_kind(buffers, kind, true, cx)
14234 });
14235 cx.spawn_in(window, async move |_, cx| {
14236 let transaction = futures::select_biased! {
14237 () = timeout => {
14238 log::warn!("timed out waiting for executing code action");
14239 None
14240 }
14241 transaction = apply_action.log_err().fuse() => transaction,
14242 };
14243 buffer
14244 .update(cx, |buffer, cx| {
14245 // check if we need this
14246 if let Some(transaction) = transaction {
14247 if !buffer.is_singleton() {
14248 buffer.push_transaction(&transaction.0, cx);
14249 }
14250 }
14251 cx.notify();
14252 })
14253 .ok();
14254 Ok(())
14255 })
14256 }
14257
14258 fn restart_language_server(
14259 &mut self,
14260 _: &RestartLanguageServer,
14261 _: &mut Window,
14262 cx: &mut Context<Self>,
14263 ) {
14264 if let Some(project) = self.project.clone() {
14265 self.buffer.update(cx, |multi_buffer, cx| {
14266 project.update(cx, |project, cx| {
14267 project.restart_language_servers_for_buffers(
14268 multi_buffer.all_buffers().into_iter().collect(),
14269 cx,
14270 );
14271 });
14272 })
14273 }
14274 }
14275
14276 fn stop_language_server(
14277 &mut self,
14278 _: &StopLanguageServer,
14279 _: &mut Window,
14280 cx: &mut Context<Self>,
14281 ) {
14282 if let Some(project) = self.project.clone() {
14283 self.buffer.update(cx, |multi_buffer, cx| {
14284 project.update(cx, |project, cx| {
14285 project.stop_language_servers_for_buffers(
14286 multi_buffer.all_buffers().into_iter().collect(),
14287 cx,
14288 );
14289 cx.emit(project::Event::RefreshInlayHints);
14290 });
14291 });
14292 }
14293 }
14294
14295 fn cancel_language_server_work(
14296 workspace: &mut Workspace,
14297 _: &actions::CancelLanguageServerWork,
14298 _: &mut Window,
14299 cx: &mut Context<Workspace>,
14300 ) {
14301 let project = workspace.project();
14302 let buffers = workspace
14303 .active_item(cx)
14304 .and_then(|item| item.act_as::<Editor>(cx))
14305 .map_or(HashSet::default(), |editor| {
14306 editor.read(cx).buffer.read(cx).all_buffers()
14307 });
14308 project.update(cx, |project, cx| {
14309 project.cancel_language_server_work_for_buffers(buffers, cx);
14310 });
14311 }
14312
14313 fn show_character_palette(
14314 &mut self,
14315 _: &ShowCharacterPalette,
14316 window: &mut Window,
14317 _: &mut Context<Self>,
14318 ) {
14319 window.show_character_palette();
14320 }
14321
14322 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14323 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
14324 let buffer = self.buffer.read(cx).snapshot(cx);
14325 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
14326 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
14327 let is_valid = buffer
14328 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14329 .any(|entry| {
14330 entry.diagnostic.is_primary
14331 && !entry.range.is_empty()
14332 && entry.range.start == primary_range_start
14333 && entry.diagnostic.message == active_diagnostics.primary_message
14334 });
14335
14336 if is_valid != active_diagnostics.is_valid {
14337 active_diagnostics.is_valid = is_valid;
14338 if is_valid {
14339 let mut new_styles = HashMap::default();
14340 for (block_id, diagnostic) in &active_diagnostics.blocks {
14341 new_styles.insert(
14342 *block_id,
14343 diagnostic_block_renderer(diagnostic.clone(), None, true),
14344 );
14345 }
14346 self.display_map.update(cx, |display_map, _cx| {
14347 display_map.replace_blocks(new_styles);
14348 });
14349 } else {
14350 self.dismiss_diagnostics(cx);
14351 }
14352 }
14353 }
14354 }
14355
14356 fn activate_diagnostics(
14357 &mut self,
14358 buffer_id: BufferId,
14359 group_id: usize,
14360 window: &mut Window,
14361 cx: &mut Context<Self>,
14362 ) {
14363 self.dismiss_diagnostics(cx);
14364 let snapshot = self.snapshot(window, cx);
14365 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
14366 let buffer = self.buffer.read(cx).snapshot(cx);
14367
14368 let mut primary_range = None;
14369 let mut primary_message = None;
14370 let diagnostic_group = buffer
14371 .diagnostic_group(buffer_id, group_id)
14372 .filter_map(|entry| {
14373 let start = entry.range.start;
14374 let end = entry.range.end;
14375 if snapshot.is_line_folded(MultiBufferRow(start.row))
14376 && (start.row == end.row
14377 || snapshot.is_line_folded(MultiBufferRow(end.row)))
14378 {
14379 return None;
14380 }
14381 if entry.diagnostic.is_primary {
14382 primary_range = Some(entry.range.clone());
14383 primary_message = Some(entry.diagnostic.message.clone());
14384 }
14385 Some(entry)
14386 })
14387 .collect::<Vec<_>>();
14388 let primary_range = primary_range?;
14389 let primary_message = primary_message?;
14390
14391 let blocks = display_map
14392 .insert_blocks(
14393 diagnostic_group.iter().map(|entry| {
14394 let diagnostic = entry.diagnostic.clone();
14395 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
14396 BlockProperties {
14397 style: BlockStyle::Fixed,
14398 placement: BlockPlacement::Below(
14399 buffer.anchor_after(entry.range.start),
14400 ),
14401 height: Some(message_height),
14402 render: diagnostic_block_renderer(diagnostic, None, true),
14403 priority: 0,
14404 }
14405 }),
14406 cx,
14407 )
14408 .into_iter()
14409 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
14410 .collect();
14411
14412 Some(ActiveDiagnosticGroup {
14413 primary_range: buffer.anchor_before(primary_range.start)
14414 ..buffer.anchor_after(primary_range.end),
14415 primary_message,
14416 group_id,
14417 blocks,
14418 is_valid: true,
14419 })
14420 });
14421 }
14422
14423 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14424 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
14425 self.display_map.update(cx, |display_map, cx| {
14426 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
14427 });
14428 cx.notify();
14429 }
14430 }
14431
14432 /// Disable inline diagnostics rendering for this editor.
14433 pub fn disable_inline_diagnostics(&mut self) {
14434 self.inline_diagnostics_enabled = false;
14435 self.inline_diagnostics_update = Task::ready(());
14436 self.inline_diagnostics.clear();
14437 }
14438
14439 pub fn inline_diagnostics_enabled(&self) -> bool {
14440 self.inline_diagnostics_enabled
14441 }
14442
14443 pub fn show_inline_diagnostics(&self) -> bool {
14444 self.show_inline_diagnostics
14445 }
14446
14447 pub fn toggle_inline_diagnostics(
14448 &mut self,
14449 _: &ToggleInlineDiagnostics,
14450 window: &mut Window,
14451 cx: &mut Context<Editor>,
14452 ) {
14453 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14454 self.refresh_inline_diagnostics(false, window, cx);
14455 }
14456
14457 fn refresh_inline_diagnostics(
14458 &mut self,
14459 debounce: bool,
14460 window: &mut Window,
14461 cx: &mut Context<Self>,
14462 ) {
14463 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14464 self.inline_diagnostics_update = Task::ready(());
14465 self.inline_diagnostics.clear();
14466 return;
14467 }
14468
14469 let debounce_ms = ProjectSettings::get_global(cx)
14470 .diagnostics
14471 .inline
14472 .update_debounce_ms;
14473 let debounce = if debounce && debounce_ms > 0 {
14474 Some(Duration::from_millis(debounce_ms))
14475 } else {
14476 None
14477 };
14478 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14479 if let Some(debounce) = debounce {
14480 cx.background_executor().timer(debounce).await;
14481 }
14482 let Some(snapshot) = editor
14483 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14484 .ok()
14485 else {
14486 return;
14487 };
14488
14489 let new_inline_diagnostics = cx
14490 .background_spawn(async move {
14491 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14492 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14493 let message = diagnostic_entry
14494 .diagnostic
14495 .message
14496 .split_once('\n')
14497 .map(|(line, _)| line)
14498 .map(SharedString::new)
14499 .unwrap_or_else(|| {
14500 SharedString::from(diagnostic_entry.diagnostic.message)
14501 });
14502 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14503 let (Ok(i) | Err(i)) = inline_diagnostics
14504 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14505 inline_diagnostics.insert(
14506 i,
14507 (
14508 start_anchor,
14509 InlineDiagnostic {
14510 message,
14511 group_id: diagnostic_entry.diagnostic.group_id,
14512 start: diagnostic_entry.range.start.to_point(&snapshot),
14513 is_primary: diagnostic_entry.diagnostic.is_primary,
14514 severity: diagnostic_entry.diagnostic.severity,
14515 },
14516 ),
14517 );
14518 }
14519 inline_diagnostics
14520 })
14521 .await;
14522
14523 editor
14524 .update(cx, |editor, cx| {
14525 editor.inline_diagnostics = new_inline_diagnostics;
14526 cx.notify();
14527 })
14528 .ok();
14529 });
14530 }
14531
14532 pub fn set_selections_from_remote(
14533 &mut self,
14534 selections: Vec<Selection<Anchor>>,
14535 pending_selection: Option<Selection<Anchor>>,
14536 window: &mut Window,
14537 cx: &mut Context<Self>,
14538 ) {
14539 let old_cursor_position = self.selections.newest_anchor().head();
14540 self.selections.change_with(cx, |s| {
14541 s.select_anchors(selections);
14542 if let Some(pending_selection) = pending_selection {
14543 s.set_pending(pending_selection, SelectMode::Character);
14544 } else {
14545 s.clear_pending();
14546 }
14547 });
14548 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14549 }
14550
14551 fn push_to_selection_history(&mut self) {
14552 self.selection_history.push(SelectionHistoryEntry {
14553 selections: self.selections.disjoint_anchors(),
14554 select_next_state: self.select_next_state.clone(),
14555 select_prev_state: self.select_prev_state.clone(),
14556 add_selections_state: self.add_selections_state.clone(),
14557 });
14558 }
14559
14560 pub fn transact(
14561 &mut self,
14562 window: &mut Window,
14563 cx: &mut Context<Self>,
14564 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14565 ) -> Option<TransactionId> {
14566 self.start_transaction_at(Instant::now(), window, cx);
14567 update(self, window, cx);
14568 self.end_transaction_at(Instant::now(), cx)
14569 }
14570
14571 pub fn start_transaction_at(
14572 &mut self,
14573 now: Instant,
14574 window: &mut Window,
14575 cx: &mut Context<Self>,
14576 ) {
14577 self.end_selection(window, cx);
14578 if let Some(tx_id) = self
14579 .buffer
14580 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14581 {
14582 self.selection_history
14583 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14584 cx.emit(EditorEvent::TransactionBegun {
14585 transaction_id: tx_id,
14586 })
14587 }
14588 }
14589
14590 pub fn end_transaction_at(
14591 &mut self,
14592 now: Instant,
14593 cx: &mut Context<Self>,
14594 ) -> Option<TransactionId> {
14595 if let Some(transaction_id) = self
14596 .buffer
14597 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14598 {
14599 if let Some((_, end_selections)) =
14600 self.selection_history.transaction_mut(transaction_id)
14601 {
14602 *end_selections = Some(self.selections.disjoint_anchors());
14603 } else {
14604 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14605 }
14606
14607 cx.emit(EditorEvent::Edited { transaction_id });
14608 Some(transaction_id)
14609 } else {
14610 None
14611 }
14612 }
14613
14614 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14615 if self.selection_mark_mode {
14616 self.change_selections(None, window, cx, |s| {
14617 s.move_with(|_, sel| {
14618 sel.collapse_to(sel.head(), SelectionGoal::None);
14619 });
14620 })
14621 }
14622 self.selection_mark_mode = true;
14623 cx.notify();
14624 }
14625
14626 pub fn swap_selection_ends(
14627 &mut self,
14628 _: &actions::SwapSelectionEnds,
14629 window: &mut Window,
14630 cx: &mut Context<Self>,
14631 ) {
14632 self.change_selections(None, window, cx, |s| {
14633 s.move_with(|_, sel| {
14634 if sel.start != sel.end {
14635 sel.reversed = !sel.reversed
14636 }
14637 });
14638 });
14639 self.request_autoscroll(Autoscroll::newest(), cx);
14640 cx.notify();
14641 }
14642
14643 pub fn toggle_fold(
14644 &mut self,
14645 _: &actions::ToggleFold,
14646 window: &mut Window,
14647 cx: &mut Context<Self>,
14648 ) {
14649 if self.is_singleton(cx) {
14650 let selection = self.selections.newest::<Point>(cx);
14651
14652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14653 let range = if selection.is_empty() {
14654 let point = selection.head().to_display_point(&display_map);
14655 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14656 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14657 .to_point(&display_map);
14658 start..end
14659 } else {
14660 selection.range()
14661 };
14662 if display_map.folds_in_range(range).next().is_some() {
14663 self.unfold_lines(&Default::default(), window, cx)
14664 } else {
14665 self.fold(&Default::default(), window, cx)
14666 }
14667 } else {
14668 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14669 let buffer_ids: HashSet<_> = self
14670 .selections
14671 .disjoint_anchor_ranges()
14672 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14673 .collect();
14674
14675 let should_unfold = buffer_ids
14676 .iter()
14677 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14678
14679 for buffer_id in buffer_ids {
14680 if should_unfold {
14681 self.unfold_buffer(buffer_id, cx);
14682 } else {
14683 self.fold_buffer(buffer_id, cx);
14684 }
14685 }
14686 }
14687 }
14688
14689 pub fn toggle_fold_recursive(
14690 &mut self,
14691 _: &actions::ToggleFoldRecursive,
14692 window: &mut Window,
14693 cx: &mut Context<Self>,
14694 ) {
14695 let selection = self.selections.newest::<Point>(cx);
14696
14697 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14698 let range = if selection.is_empty() {
14699 let point = selection.head().to_display_point(&display_map);
14700 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14701 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14702 .to_point(&display_map);
14703 start..end
14704 } else {
14705 selection.range()
14706 };
14707 if display_map.folds_in_range(range).next().is_some() {
14708 self.unfold_recursive(&Default::default(), window, cx)
14709 } else {
14710 self.fold_recursive(&Default::default(), window, cx)
14711 }
14712 }
14713
14714 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14715 if self.is_singleton(cx) {
14716 let mut to_fold = Vec::new();
14717 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14718 let selections = self.selections.all_adjusted(cx);
14719
14720 for selection in selections {
14721 let range = selection.range().sorted();
14722 let buffer_start_row = range.start.row;
14723
14724 if range.start.row != range.end.row {
14725 let mut found = false;
14726 let mut row = range.start.row;
14727 while row <= range.end.row {
14728 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14729 {
14730 found = true;
14731 row = crease.range().end.row + 1;
14732 to_fold.push(crease);
14733 } else {
14734 row += 1
14735 }
14736 }
14737 if found {
14738 continue;
14739 }
14740 }
14741
14742 for row in (0..=range.start.row).rev() {
14743 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14744 if crease.range().end.row >= buffer_start_row {
14745 to_fold.push(crease);
14746 if row <= range.start.row {
14747 break;
14748 }
14749 }
14750 }
14751 }
14752 }
14753
14754 self.fold_creases(to_fold, true, window, cx);
14755 } else {
14756 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14757 let buffer_ids = self
14758 .selections
14759 .disjoint_anchor_ranges()
14760 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14761 .collect::<HashSet<_>>();
14762 for buffer_id in buffer_ids {
14763 self.fold_buffer(buffer_id, cx);
14764 }
14765 }
14766 }
14767
14768 fn fold_at_level(
14769 &mut self,
14770 fold_at: &FoldAtLevel,
14771 window: &mut Window,
14772 cx: &mut Context<Self>,
14773 ) {
14774 if !self.buffer.read(cx).is_singleton() {
14775 return;
14776 }
14777
14778 let fold_at_level = fold_at.0;
14779 let snapshot = self.buffer.read(cx).snapshot(cx);
14780 let mut to_fold = Vec::new();
14781 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14782
14783 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14784 while start_row < end_row {
14785 match self
14786 .snapshot(window, cx)
14787 .crease_for_buffer_row(MultiBufferRow(start_row))
14788 {
14789 Some(crease) => {
14790 let nested_start_row = crease.range().start.row + 1;
14791 let nested_end_row = crease.range().end.row;
14792
14793 if current_level < fold_at_level {
14794 stack.push((nested_start_row, nested_end_row, current_level + 1));
14795 } else if current_level == fold_at_level {
14796 to_fold.push(crease);
14797 }
14798
14799 start_row = nested_end_row + 1;
14800 }
14801 None => start_row += 1,
14802 }
14803 }
14804 }
14805
14806 self.fold_creases(to_fold, true, window, cx);
14807 }
14808
14809 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14810 if self.buffer.read(cx).is_singleton() {
14811 let mut fold_ranges = Vec::new();
14812 let snapshot = self.buffer.read(cx).snapshot(cx);
14813
14814 for row in 0..snapshot.max_row().0 {
14815 if let Some(foldable_range) = self
14816 .snapshot(window, cx)
14817 .crease_for_buffer_row(MultiBufferRow(row))
14818 {
14819 fold_ranges.push(foldable_range);
14820 }
14821 }
14822
14823 self.fold_creases(fold_ranges, true, window, cx);
14824 } else {
14825 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14826 editor
14827 .update_in(cx, |editor, _, cx| {
14828 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14829 editor.fold_buffer(buffer_id, cx);
14830 }
14831 })
14832 .ok();
14833 });
14834 }
14835 }
14836
14837 pub fn fold_function_bodies(
14838 &mut self,
14839 _: &actions::FoldFunctionBodies,
14840 window: &mut Window,
14841 cx: &mut Context<Self>,
14842 ) {
14843 let snapshot = self.buffer.read(cx).snapshot(cx);
14844
14845 let ranges = snapshot
14846 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14847 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14848 .collect::<Vec<_>>();
14849
14850 let creases = ranges
14851 .into_iter()
14852 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14853 .collect();
14854
14855 self.fold_creases(creases, true, window, cx);
14856 }
14857
14858 pub fn fold_recursive(
14859 &mut self,
14860 _: &actions::FoldRecursive,
14861 window: &mut Window,
14862 cx: &mut Context<Self>,
14863 ) {
14864 let mut to_fold = Vec::new();
14865 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14866 let selections = self.selections.all_adjusted(cx);
14867
14868 for selection in selections {
14869 let range = selection.range().sorted();
14870 let buffer_start_row = range.start.row;
14871
14872 if range.start.row != range.end.row {
14873 let mut found = false;
14874 for row in range.start.row..=range.end.row {
14875 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14876 found = true;
14877 to_fold.push(crease);
14878 }
14879 }
14880 if found {
14881 continue;
14882 }
14883 }
14884
14885 for row in (0..=range.start.row).rev() {
14886 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14887 if crease.range().end.row >= buffer_start_row {
14888 to_fold.push(crease);
14889 } else {
14890 break;
14891 }
14892 }
14893 }
14894 }
14895
14896 self.fold_creases(to_fold, true, window, cx);
14897 }
14898
14899 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14900 let buffer_row = fold_at.buffer_row;
14901 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14902
14903 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14904 let autoscroll = self
14905 .selections
14906 .all::<Point>(cx)
14907 .iter()
14908 .any(|selection| crease.range().overlaps(&selection.range()));
14909
14910 self.fold_creases(vec![crease], autoscroll, window, cx);
14911 }
14912 }
14913
14914 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14915 if self.is_singleton(cx) {
14916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14917 let buffer = &display_map.buffer_snapshot;
14918 let selections = self.selections.all::<Point>(cx);
14919 let ranges = selections
14920 .iter()
14921 .map(|s| {
14922 let range = s.display_range(&display_map).sorted();
14923 let mut start = range.start.to_point(&display_map);
14924 let mut end = range.end.to_point(&display_map);
14925 start.column = 0;
14926 end.column = buffer.line_len(MultiBufferRow(end.row));
14927 start..end
14928 })
14929 .collect::<Vec<_>>();
14930
14931 self.unfold_ranges(&ranges, true, true, cx);
14932 } else {
14933 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14934 let buffer_ids = self
14935 .selections
14936 .disjoint_anchor_ranges()
14937 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14938 .collect::<HashSet<_>>();
14939 for buffer_id in buffer_ids {
14940 self.unfold_buffer(buffer_id, cx);
14941 }
14942 }
14943 }
14944
14945 pub fn unfold_recursive(
14946 &mut self,
14947 _: &UnfoldRecursive,
14948 _window: &mut Window,
14949 cx: &mut Context<Self>,
14950 ) {
14951 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14952 let selections = self.selections.all::<Point>(cx);
14953 let ranges = selections
14954 .iter()
14955 .map(|s| {
14956 let mut range = s.display_range(&display_map).sorted();
14957 *range.start.column_mut() = 0;
14958 *range.end.column_mut() = display_map.line_len(range.end.row());
14959 let start = range.start.to_point(&display_map);
14960 let end = range.end.to_point(&display_map);
14961 start..end
14962 })
14963 .collect::<Vec<_>>();
14964
14965 self.unfold_ranges(&ranges, true, true, cx);
14966 }
14967
14968 pub fn unfold_at(
14969 &mut self,
14970 unfold_at: &UnfoldAt,
14971 _window: &mut Window,
14972 cx: &mut Context<Self>,
14973 ) {
14974 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14975
14976 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14977 ..Point::new(
14978 unfold_at.buffer_row.0,
14979 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14980 );
14981
14982 let autoscroll = self
14983 .selections
14984 .all::<Point>(cx)
14985 .iter()
14986 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14987
14988 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14989 }
14990
14991 pub fn unfold_all(
14992 &mut self,
14993 _: &actions::UnfoldAll,
14994 _window: &mut Window,
14995 cx: &mut Context<Self>,
14996 ) {
14997 if self.buffer.read(cx).is_singleton() {
14998 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14999 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15000 } else {
15001 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15002 editor
15003 .update(cx, |editor, cx| {
15004 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15005 editor.unfold_buffer(buffer_id, cx);
15006 }
15007 })
15008 .ok();
15009 });
15010 }
15011 }
15012
15013 pub fn fold_selected_ranges(
15014 &mut self,
15015 _: &FoldSelectedRanges,
15016 window: &mut Window,
15017 cx: &mut Context<Self>,
15018 ) {
15019 let selections = self.selections.all_adjusted(cx);
15020 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15021 let ranges = selections
15022 .into_iter()
15023 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15024 .collect::<Vec<_>>();
15025 self.fold_creases(ranges, true, window, cx);
15026 }
15027
15028 pub fn fold_ranges<T: ToOffset + Clone>(
15029 &mut self,
15030 ranges: Vec<Range<T>>,
15031 auto_scroll: bool,
15032 window: &mut Window,
15033 cx: &mut Context<Self>,
15034 ) {
15035 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15036 let ranges = ranges
15037 .into_iter()
15038 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15039 .collect::<Vec<_>>();
15040 self.fold_creases(ranges, auto_scroll, window, cx);
15041 }
15042
15043 pub fn fold_creases<T: ToOffset + Clone>(
15044 &mut self,
15045 creases: Vec<Crease<T>>,
15046 auto_scroll: bool,
15047 window: &mut Window,
15048 cx: &mut Context<Self>,
15049 ) {
15050 if creases.is_empty() {
15051 return;
15052 }
15053
15054 let mut buffers_affected = HashSet::default();
15055 let multi_buffer = self.buffer().read(cx);
15056 for crease in &creases {
15057 if let Some((_, buffer, _)) =
15058 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15059 {
15060 buffers_affected.insert(buffer.read(cx).remote_id());
15061 };
15062 }
15063
15064 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15065
15066 if auto_scroll {
15067 self.request_autoscroll(Autoscroll::fit(), cx);
15068 }
15069
15070 cx.notify();
15071
15072 if let Some(active_diagnostics) = self.active_diagnostics.take() {
15073 // Clear diagnostics block when folding a range that contains it.
15074 let snapshot = self.snapshot(window, cx);
15075 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
15076 drop(snapshot);
15077 self.active_diagnostics = Some(active_diagnostics);
15078 self.dismiss_diagnostics(cx);
15079 } else {
15080 self.active_diagnostics = Some(active_diagnostics);
15081 }
15082 }
15083
15084 self.scrollbar_marker_state.dirty = true;
15085 self.folds_did_change(cx);
15086 }
15087
15088 /// Removes any folds whose ranges intersect any of the given ranges.
15089 pub fn unfold_ranges<T: ToOffset + Clone>(
15090 &mut self,
15091 ranges: &[Range<T>],
15092 inclusive: bool,
15093 auto_scroll: bool,
15094 cx: &mut Context<Self>,
15095 ) {
15096 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15097 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15098 });
15099 self.folds_did_change(cx);
15100 }
15101
15102 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15103 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15104 return;
15105 }
15106 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15107 self.display_map.update(cx, |display_map, cx| {
15108 display_map.fold_buffers([buffer_id], cx)
15109 });
15110 cx.emit(EditorEvent::BufferFoldToggled {
15111 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15112 folded: true,
15113 });
15114 cx.notify();
15115 }
15116
15117 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15118 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15119 return;
15120 }
15121 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15122 self.display_map.update(cx, |display_map, cx| {
15123 display_map.unfold_buffers([buffer_id], cx);
15124 });
15125 cx.emit(EditorEvent::BufferFoldToggled {
15126 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15127 folded: false,
15128 });
15129 cx.notify();
15130 }
15131
15132 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15133 self.display_map.read(cx).is_buffer_folded(buffer)
15134 }
15135
15136 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15137 self.display_map.read(cx).folded_buffers()
15138 }
15139
15140 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15141 self.display_map.update(cx, |display_map, cx| {
15142 display_map.disable_header_for_buffer(buffer_id, cx);
15143 });
15144 cx.notify();
15145 }
15146
15147 /// Removes any folds with the given ranges.
15148 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15149 &mut self,
15150 ranges: &[Range<T>],
15151 type_id: TypeId,
15152 auto_scroll: bool,
15153 cx: &mut Context<Self>,
15154 ) {
15155 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15156 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15157 });
15158 self.folds_did_change(cx);
15159 }
15160
15161 fn remove_folds_with<T: ToOffset + Clone>(
15162 &mut self,
15163 ranges: &[Range<T>],
15164 auto_scroll: bool,
15165 cx: &mut Context<Self>,
15166 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15167 ) {
15168 if ranges.is_empty() {
15169 return;
15170 }
15171
15172 let mut buffers_affected = HashSet::default();
15173 let multi_buffer = self.buffer().read(cx);
15174 for range in ranges {
15175 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15176 buffers_affected.insert(buffer.read(cx).remote_id());
15177 };
15178 }
15179
15180 self.display_map.update(cx, update);
15181
15182 if auto_scroll {
15183 self.request_autoscroll(Autoscroll::fit(), cx);
15184 }
15185
15186 cx.notify();
15187 self.scrollbar_marker_state.dirty = true;
15188 self.active_indent_guides_state.dirty = true;
15189 }
15190
15191 pub fn update_fold_widths(
15192 &mut self,
15193 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15194 cx: &mut Context<Self>,
15195 ) -> bool {
15196 self.display_map
15197 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15198 }
15199
15200 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15201 self.display_map.read(cx).fold_placeholder.clone()
15202 }
15203
15204 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15205 self.buffer.update(cx, |buffer, cx| {
15206 buffer.set_all_diff_hunks_expanded(cx);
15207 });
15208 }
15209
15210 pub fn expand_all_diff_hunks(
15211 &mut self,
15212 _: &ExpandAllDiffHunks,
15213 _window: &mut Window,
15214 cx: &mut Context<Self>,
15215 ) {
15216 self.buffer.update(cx, |buffer, cx| {
15217 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15218 });
15219 }
15220
15221 pub fn toggle_selected_diff_hunks(
15222 &mut self,
15223 _: &ToggleSelectedDiffHunks,
15224 _window: &mut Window,
15225 cx: &mut Context<Self>,
15226 ) {
15227 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15228 self.toggle_diff_hunks_in_ranges(ranges, cx);
15229 }
15230
15231 pub fn diff_hunks_in_ranges<'a>(
15232 &'a self,
15233 ranges: &'a [Range<Anchor>],
15234 buffer: &'a MultiBufferSnapshot,
15235 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15236 ranges.iter().flat_map(move |range| {
15237 let end_excerpt_id = range.end.excerpt_id;
15238 let range = range.to_point(buffer);
15239 let mut peek_end = range.end;
15240 if range.end.row < buffer.max_row().0 {
15241 peek_end = Point::new(range.end.row + 1, 0);
15242 }
15243 buffer
15244 .diff_hunks_in_range(range.start..peek_end)
15245 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15246 })
15247 }
15248
15249 pub fn has_stageable_diff_hunks_in_ranges(
15250 &self,
15251 ranges: &[Range<Anchor>],
15252 snapshot: &MultiBufferSnapshot,
15253 ) -> bool {
15254 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15255 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15256 }
15257
15258 pub fn toggle_staged_selected_diff_hunks(
15259 &mut self,
15260 _: &::git::ToggleStaged,
15261 _: &mut Window,
15262 cx: &mut Context<Self>,
15263 ) {
15264 let snapshot = self.buffer.read(cx).snapshot(cx);
15265 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15266 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15267 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15268 }
15269
15270 pub fn set_render_diff_hunk_controls(
15271 &mut self,
15272 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15273 cx: &mut Context<Self>,
15274 ) {
15275 self.render_diff_hunk_controls = render_diff_hunk_controls;
15276 cx.notify();
15277 }
15278
15279 pub fn stage_and_next(
15280 &mut self,
15281 _: &::git::StageAndNext,
15282 window: &mut Window,
15283 cx: &mut Context<Self>,
15284 ) {
15285 self.do_stage_or_unstage_and_next(true, window, cx);
15286 }
15287
15288 pub fn unstage_and_next(
15289 &mut self,
15290 _: &::git::UnstageAndNext,
15291 window: &mut Window,
15292 cx: &mut Context<Self>,
15293 ) {
15294 self.do_stage_or_unstage_and_next(false, window, cx);
15295 }
15296
15297 pub fn stage_or_unstage_diff_hunks(
15298 &mut self,
15299 stage: bool,
15300 ranges: Vec<Range<Anchor>>,
15301 cx: &mut Context<Self>,
15302 ) {
15303 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15304 cx.spawn(async move |this, cx| {
15305 task.await?;
15306 this.update(cx, |this, cx| {
15307 let snapshot = this.buffer.read(cx).snapshot(cx);
15308 let chunk_by = this
15309 .diff_hunks_in_ranges(&ranges, &snapshot)
15310 .chunk_by(|hunk| hunk.buffer_id);
15311 for (buffer_id, hunks) in &chunk_by {
15312 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15313 }
15314 })
15315 })
15316 .detach_and_log_err(cx);
15317 }
15318
15319 fn save_buffers_for_ranges_if_needed(
15320 &mut self,
15321 ranges: &[Range<Anchor>],
15322 cx: &mut Context<Editor>,
15323 ) -> Task<Result<()>> {
15324 let multibuffer = self.buffer.read(cx);
15325 let snapshot = multibuffer.read(cx);
15326 let buffer_ids: HashSet<_> = ranges
15327 .iter()
15328 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15329 .collect();
15330 drop(snapshot);
15331
15332 let mut buffers = HashSet::default();
15333 for buffer_id in buffer_ids {
15334 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15335 let buffer = buffer_entity.read(cx);
15336 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15337 {
15338 buffers.insert(buffer_entity);
15339 }
15340 }
15341 }
15342
15343 if let Some(project) = &self.project {
15344 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15345 } else {
15346 Task::ready(Ok(()))
15347 }
15348 }
15349
15350 fn do_stage_or_unstage_and_next(
15351 &mut self,
15352 stage: bool,
15353 window: &mut Window,
15354 cx: &mut Context<Self>,
15355 ) {
15356 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15357
15358 if ranges.iter().any(|range| range.start != range.end) {
15359 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15360 return;
15361 }
15362
15363 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15364 let snapshot = self.snapshot(window, cx);
15365 let position = self.selections.newest::<Point>(cx).head();
15366 let mut row = snapshot
15367 .buffer_snapshot
15368 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15369 .find(|hunk| hunk.row_range.start.0 > position.row)
15370 .map(|hunk| hunk.row_range.start);
15371
15372 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15373 // Outside of the project diff editor, wrap around to the beginning.
15374 if !all_diff_hunks_expanded {
15375 row = row.or_else(|| {
15376 snapshot
15377 .buffer_snapshot
15378 .diff_hunks_in_range(Point::zero()..position)
15379 .find(|hunk| hunk.row_range.end.0 < position.row)
15380 .map(|hunk| hunk.row_range.start)
15381 });
15382 }
15383
15384 if let Some(row) = row {
15385 let destination = Point::new(row.0, 0);
15386 let autoscroll = Autoscroll::center();
15387
15388 self.unfold_ranges(&[destination..destination], false, false, cx);
15389 self.change_selections(Some(autoscroll), window, cx, |s| {
15390 s.select_ranges([destination..destination]);
15391 });
15392 }
15393 }
15394
15395 fn do_stage_or_unstage(
15396 &self,
15397 stage: bool,
15398 buffer_id: BufferId,
15399 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15400 cx: &mut App,
15401 ) -> Option<()> {
15402 let project = self.project.as_ref()?;
15403 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15404 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15405 let buffer_snapshot = buffer.read(cx).snapshot();
15406 let file_exists = buffer_snapshot
15407 .file()
15408 .is_some_and(|file| file.disk_state().exists());
15409 diff.update(cx, |diff, cx| {
15410 diff.stage_or_unstage_hunks(
15411 stage,
15412 &hunks
15413 .map(|hunk| buffer_diff::DiffHunk {
15414 buffer_range: hunk.buffer_range,
15415 diff_base_byte_range: hunk.diff_base_byte_range,
15416 secondary_status: hunk.secondary_status,
15417 range: Point::zero()..Point::zero(), // unused
15418 })
15419 .collect::<Vec<_>>(),
15420 &buffer_snapshot,
15421 file_exists,
15422 cx,
15423 )
15424 });
15425 None
15426 }
15427
15428 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15429 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15430 self.buffer
15431 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15432 }
15433
15434 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15435 self.buffer.update(cx, |buffer, cx| {
15436 let ranges = vec![Anchor::min()..Anchor::max()];
15437 if !buffer.all_diff_hunks_expanded()
15438 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15439 {
15440 buffer.collapse_diff_hunks(ranges, cx);
15441 true
15442 } else {
15443 false
15444 }
15445 })
15446 }
15447
15448 fn toggle_diff_hunks_in_ranges(
15449 &mut self,
15450 ranges: Vec<Range<Anchor>>,
15451 cx: &mut Context<Editor>,
15452 ) {
15453 self.buffer.update(cx, |buffer, cx| {
15454 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15455 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15456 })
15457 }
15458
15459 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15460 self.buffer.update(cx, |buffer, cx| {
15461 let snapshot = buffer.snapshot(cx);
15462 let excerpt_id = range.end.excerpt_id;
15463 let point_range = range.to_point(&snapshot);
15464 let expand = !buffer.single_hunk_is_expanded(range, cx);
15465 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15466 })
15467 }
15468
15469 pub(crate) fn apply_all_diff_hunks(
15470 &mut self,
15471 _: &ApplyAllDiffHunks,
15472 window: &mut Window,
15473 cx: &mut Context<Self>,
15474 ) {
15475 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15476
15477 let buffers = self.buffer.read(cx).all_buffers();
15478 for branch_buffer in buffers {
15479 branch_buffer.update(cx, |branch_buffer, cx| {
15480 branch_buffer.merge_into_base(Vec::new(), cx);
15481 });
15482 }
15483
15484 if let Some(project) = self.project.clone() {
15485 self.save(true, project, window, cx).detach_and_log_err(cx);
15486 }
15487 }
15488
15489 pub(crate) fn apply_selected_diff_hunks(
15490 &mut self,
15491 _: &ApplyDiffHunk,
15492 window: &mut Window,
15493 cx: &mut Context<Self>,
15494 ) {
15495 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15496 let snapshot = self.snapshot(window, cx);
15497 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15498 let mut ranges_by_buffer = HashMap::default();
15499 self.transact(window, cx, |editor, _window, cx| {
15500 for hunk in hunks {
15501 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15502 ranges_by_buffer
15503 .entry(buffer.clone())
15504 .or_insert_with(Vec::new)
15505 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15506 }
15507 }
15508
15509 for (buffer, ranges) in ranges_by_buffer {
15510 buffer.update(cx, |buffer, cx| {
15511 buffer.merge_into_base(ranges, cx);
15512 });
15513 }
15514 });
15515
15516 if let Some(project) = self.project.clone() {
15517 self.save(true, project, window, cx).detach_and_log_err(cx);
15518 }
15519 }
15520
15521 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15522 if hovered != self.gutter_hovered {
15523 self.gutter_hovered = hovered;
15524 cx.notify();
15525 }
15526 }
15527
15528 pub fn insert_blocks(
15529 &mut self,
15530 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15531 autoscroll: Option<Autoscroll>,
15532 cx: &mut Context<Self>,
15533 ) -> Vec<CustomBlockId> {
15534 let blocks = self
15535 .display_map
15536 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15537 if let Some(autoscroll) = autoscroll {
15538 self.request_autoscroll(autoscroll, cx);
15539 }
15540 cx.notify();
15541 blocks
15542 }
15543
15544 pub fn resize_blocks(
15545 &mut self,
15546 heights: HashMap<CustomBlockId, u32>,
15547 autoscroll: Option<Autoscroll>,
15548 cx: &mut Context<Self>,
15549 ) {
15550 self.display_map
15551 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15552 if let Some(autoscroll) = autoscroll {
15553 self.request_autoscroll(autoscroll, cx);
15554 }
15555 cx.notify();
15556 }
15557
15558 pub fn replace_blocks(
15559 &mut self,
15560 renderers: HashMap<CustomBlockId, RenderBlock>,
15561 autoscroll: Option<Autoscroll>,
15562 cx: &mut Context<Self>,
15563 ) {
15564 self.display_map
15565 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15566 if let Some(autoscroll) = autoscroll {
15567 self.request_autoscroll(autoscroll, cx);
15568 }
15569 cx.notify();
15570 }
15571
15572 pub fn remove_blocks(
15573 &mut self,
15574 block_ids: HashSet<CustomBlockId>,
15575 autoscroll: Option<Autoscroll>,
15576 cx: &mut Context<Self>,
15577 ) {
15578 self.display_map.update(cx, |display_map, cx| {
15579 display_map.remove_blocks(block_ids, cx)
15580 });
15581 if let Some(autoscroll) = autoscroll {
15582 self.request_autoscroll(autoscroll, cx);
15583 }
15584 cx.notify();
15585 }
15586
15587 pub fn row_for_block(
15588 &self,
15589 block_id: CustomBlockId,
15590 cx: &mut Context<Self>,
15591 ) -> Option<DisplayRow> {
15592 self.display_map
15593 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15594 }
15595
15596 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15597 self.focused_block = Some(focused_block);
15598 }
15599
15600 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15601 self.focused_block.take()
15602 }
15603
15604 pub fn insert_creases(
15605 &mut self,
15606 creases: impl IntoIterator<Item = Crease<Anchor>>,
15607 cx: &mut Context<Self>,
15608 ) -> Vec<CreaseId> {
15609 self.display_map
15610 .update(cx, |map, cx| map.insert_creases(creases, cx))
15611 }
15612
15613 pub fn remove_creases(
15614 &mut self,
15615 ids: impl IntoIterator<Item = CreaseId>,
15616 cx: &mut Context<Self>,
15617 ) {
15618 self.display_map
15619 .update(cx, |map, cx| map.remove_creases(ids, cx));
15620 }
15621
15622 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15623 self.display_map
15624 .update(cx, |map, cx| map.snapshot(cx))
15625 .longest_row()
15626 }
15627
15628 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15629 self.display_map
15630 .update(cx, |map, cx| map.snapshot(cx))
15631 .max_point()
15632 }
15633
15634 pub fn text(&self, cx: &App) -> String {
15635 self.buffer.read(cx).read(cx).text()
15636 }
15637
15638 pub fn is_empty(&self, cx: &App) -> bool {
15639 self.buffer.read(cx).read(cx).is_empty()
15640 }
15641
15642 pub fn text_option(&self, cx: &App) -> Option<String> {
15643 let text = self.text(cx);
15644 let text = text.trim();
15645
15646 if text.is_empty() {
15647 return None;
15648 }
15649
15650 Some(text.to_string())
15651 }
15652
15653 pub fn set_text(
15654 &mut self,
15655 text: impl Into<Arc<str>>,
15656 window: &mut Window,
15657 cx: &mut Context<Self>,
15658 ) {
15659 self.transact(window, cx, |this, _, cx| {
15660 this.buffer
15661 .read(cx)
15662 .as_singleton()
15663 .expect("you can only call set_text on editors for singleton buffers")
15664 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15665 });
15666 }
15667
15668 pub fn display_text(&self, cx: &mut App) -> String {
15669 self.display_map
15670 .update(cx, |map, cx| map.snapshot(cx))
15671 .text()
15672 }
15673
15674 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15675 let mut wrap_guides = smallvec::smallvec![];
15676
15677 if self.show_wrap_guides == Some(false) {
15678 return wrap_guides;
15679 }
15680
15681 let settings = self.buffer.read(cx).language_settings(cx);
15682 if settings.show_wrap_guides {
15683 match self.soft_wrap_mode(cx) {
15684 SoftWrap::Column(soft_wrap) => {
15685 wrap_guides.push((soft_wrap as usize, true));
15686 }
15687 SoftWrap::Bounded(soft_wrap) => {
15688 wrap_guides.push((soft_wrap as usize, true));
15689 }
15690 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15691 }
15692 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15693 }
15694
15695 wrap_guides
15696 }
15697
15698 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15699 let settings = self.buffer.read(cx).language_settings(cx);
15700 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15701 match mode {
15702 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15703 SoftWrap::None
15704 }
15705 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15706 language_settings::SoftWrap::PreferredLineLength => {
15707 SoftWrap::Column(settings.preferred_line_length)
15708 }
15709 language_settings::SoftWrap::Bounded => {
15710 SoftWrap::Bounded(settings.preferred_line_length)
15711 }
15712 }
15713 }
15714
15715 pub fn set_soft_wrap_mode(
15716 &mut self,
15717 mode: language_settings::SoftWrap,
15718
15719 cx: &mut Context<Self>,
15720 ) {
15721 self.soft_wrap_mode_override = Some(mode);
15722 cx.notify();
15723 }
15724
15725 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15726 self.hard_wrap = hard_wrap;
15727 cx.notify();
15728 }
15729
15730 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15731 self.text_style_refinement = Some(style);
15732 }
15733
15734 /// called by the Element so we know what style we were most recently rendered with.
15735 pub(crate) fn set_style(
15736 &mut self,
15737 style: EditorStyle,
15738 window: &mut Window,
15739 cx: &mut Context<Self>,
15740 ) {
15741 let rem_size = window.rem_size();
15742 self.display_map.update(cx, |map, cx| {
15743 map.set_font(
15744 style.text.font(),
15745 style.text.font_size.to_pixels(rem_size),
15746 cx,
15747 )
15748 });
15749 self.style = Some(style);
15750 }
15751
15752 pub fn style(&self) -> Option<&EditorStyle> {
15753 self.style.as_ref()
15754 }
15755
15756 // Called by the element. This method is not designed to be called outside of the editor
15757 // element's layout code because it does not notify when rewrapping is computed synchronously.
15758 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15759 self.display_map
15760 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15761 }
15762
15763 pub fn set_soft_wrap(&mut self) {
15764 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15765 }
15766
15767 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15768 if self.soft_wrap_mode_override.is_some() {
15769 self.soft_wrap_mode_override.take();
15770 } else {
15771 let soft_wrap = match self.soft_wrap_mode(cx) {
15772 SoftWrap::GitDiff => return,
15773 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15774 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15775 language_settings::SoftWrap::None
15776 }
15777 };
15778 self.soft_wrap_mode_override = Some(soft_wrap);
15779 }
15780 cx.notify();
15781 }
15782
15783 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15784 let Some(workspace) = self.workspace() else {
15785 return;
15786 };
15787 let fs = workspace.read(cx).app_state().fs.clone();
15788 let current_show = TabBarSettings::get_global(cx).show;
15789 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15790 setting.show = Some(!current_show);
15791 });
15792 }
15793
15794 pub fn toggle_indent_guides(
15795 &mut self,
15796 _: &ToggleIndentGuides,
15797 _: &mut Window,
15798 cx: &mut Context<Self>,
15799 ) {
15800 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15801 self.buffer
15802 .read(cx)
15803 .language_settings(cx)
15804 .indent_guides
15805 .enabled
15806 });
15807 self.show_indent_guides = Some(!currently_enabled);
15808 cx.notify();
15809 }
15810
15811 fn should_show_indent_guides(&self) -> Option<bool> {
15812 self.show_indent_guides
15813 }
15814
15815 pub fn toggle_line_numbers(
15816 &mut self,
15817 _: &ToggleLineNumbers,
15818 _: &mut Window,
15819 cx: &mut Context<Self>,
15820 ) {
15821 let mut editor_settings = EditorSettings::get_global(cx).clone();
15822 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15823 EditorSettings::override_global(editor_settings, cx);
15824 }
15825
15826 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15827 if let Some(show_line_numbers) = self.show_line_numbers {
15828 return show_line_numbers;
15829 }
15830 EditorSettings::get_global(cx).gutter.line_numbers
15831 }
15832
15833 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15834 self.use_relative_line_numbers
15835 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15836 }
15837
15838 pub fn toggle_relative_line_numbers(
15839 &mut self,
15840 _: &ToggleRelativeLineNumbers,
15841 _: &mut Window,
15842 cx: &mut Context<Self>,
15843 ) {
15844 let is_relative = self.should_use_relative_line_numbers(cx);
15845 self.set_relative_line_number(Some(!is_relative), cx)
15846 }
15847
15848 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15849 self.use_relative_line_numbers = is_relative;
15850 cx.notify();
15851 }
15852
15853 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15854 self.show_gutter = show_gutter;
15855 cx.notify();
15856 }
15857
15858 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15859 self.show_scrollbars = show_scrollbars;
15860 cx.notify();
15861 }
15862
15863 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15864 self.show_line_numbers = Some(show_line_numbers);
15865 cx.notify();
15866 }
15867
15868 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15869 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15870 cx.notify();
15871 }
15872
15873 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15874 self.show_code_actions = Some(show_code_actions);
15875 cx.notify();
15876 }
15877
15878 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15879 self.show_runnables = Some(show_runnables);
15880 cx.notify();
15881 }
15882
15883 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15884 self.show_breakpoints = Some(show_breakpoints);
15885 cx.notify();
15886 }
15887
15888 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15889 if self.display_map.read(cx).masked != masked {
15890 self.display_map.update(cx, |map, _| map.masked = masked);
15891 }
15892 cx.notify()
15893 }
15894
15895 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15896 self.show_wrap_guides = Some(show_wrap_guides);
15897 cx.notify();
15898 }
15899
15900 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15901 self.show_indent_guides = Some(show_indent_guides);
15902 cx.notify();
15903 }
15904
15905 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15906 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15907 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15908 if let Some(dir) = file.abs_path(cx).parent() {
15909 return Some(dir.to_owned());
15910 }
15911 }
15912
15913 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15914 return Some(project_path.path.to_path_buf());
15915 }
15916 }
15917
15918 None
15919 }
15920
15921 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15922 self.active_excerpt(cx)?
15923 .1
15924 .read(cx)
15925 .file()
15926 .and_then(|f| f.as_local())
15927 }
15928
15929 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15930 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15931 let buffer = buffer.read(cx);
15932 if let Some(project_path) = buffer.project_path(cx) {
15933 let project = self.project.as_ref()?.read(cx);
15934 project.absolute_path(&project_path, cx)
15935 } else {
15936 buffer
15937 .file()
15938 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15939 }
15940 })
15941 }
15942
15943 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15944 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15945 let project_path = buffer.read(cx).project_path(cx)?;
15946 let project = self.project.as_ref()?.read(cx);
15947 let entry = project.entry_for_path(&project_path, cx)?;
15948 let path = entry.path.to_path_buf();
15949 Some(path)
15950 })
15951 }
15952
15953 pub fn reveal_in_finder(
15954 &mut self,
15955 _: &RevealInFileManager,
15956 _window: &mut Window,
15957 cx: &mut Context<Self>,
15958 ) {
15959 if let Some(target) = self.target_file(cx) {
15960 cx.reveal_path(&target.abs_path(cx));
15961 }
15962 }
15963
15964 pub fn copy_path(
15965 &mut self,
15966 _: &zed_actions::workspace::CopyPath,
15967 _window: &mut Window,
15968 cx: &mut Context<Self>,
15969 ) {
15970 if let Some(path) = self.target_file_abs_path(cx) {
15971 if let Some(path) = path.to_str() {
15972 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15973 }
15974 }
15975 }
15976
15977 pub fn copy_relative_path(
15978 &mut self,
15979 _: &zed_actions::workspace::CopyRelativePath,
15980 _window: &mut Window,
15981 cx: &mut Context<Self>,
15982 ) {
15983 if let Some(path) = self.target_file_path(cx) {
15984 if let Some(path) = path.to_str() {
15985 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15986 }
15987 }
15988 }
15989
15990 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
15991 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15992 buffer.read(cx).project_path(cx)
15993 } else {
15994 None
15995 }
15996 }
15997
15998 // Returns true if the editor handled a go-to-line request
15999 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16000 maybe!({
16001 let breakpoint_store = self.breakpoint_store.as_ref()?;
16002
16003 let Some((_, _, active_position)) =
16004 breakpoint_store.read(cx).active_position().cloned()
16005 else {
16006 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16007 return None;
16008 };
16009
16010 let snapshot = self
16011 .project
16012 .as_ref()?
16013 .read(cx)
16014 .buffer_for_id(active_position.buffer_id?, cx)?
16015 .read(cx)
16016 .snapshot();
16017
16018 let mut handled = false;
16019 for (id, ExcerptRange { context, .. }) in self
16020 .buffer
16021 .read(cx)
16022 .excerpts_for_buffer(active_position.buffer_id?, cx)
16023 {
16024 if context.start.cmp(&active_position, &snapshot).is_ge()
16025 || context.end.cmp(&active_position, &snapshot).is_lt()
16026 {
16027 continue;
16028 }
16029 let snapshot = self.buffer.read(cx).snapshot(cx);
16030 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
16031
16032 handled = true;
16033 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16034 self.go_to_line::<DebugCurrentRowHighlight>(
16035 multibuffer_anchor,
16036 Some(cx.theme().colors().editor_debugger_active_line_background),
16037 window,
16038 cx,
16039 );
16040
16041 cx.notify();
16042 }
16043 handled.then_some(())
16044 })
16045 .is_some()
16046 }
16047
16048 pub fn copy_file_name_without_extension(
16049 &mut self,
16050 _: &CopyFileNameWithoutExtension,
16051 _: &mut Window,
16052 cx: &mut Context<Self>,
16053 ) {
16054 if let Some(file) = self.target_file(cx) {
16055 if let Some(file_stem) = file.path().file_stem() {
16056 if let Some(name) = file_stem.to_str() {
16057 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16058 }
16059 }
16060 }
16061 }
16062
16063 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16064 if let Some(file) = self.target_file(cx) {
16065 if let Some(file_name) = file.path().file_name() {
16066 if let Some(name) = file_name.to_str() {
16067 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16068 }
16069 }
16070 }
16071 }
16072
16073 pub fn toggle_git_blame(
16074 &mut self,
16075 _: &::git::Blame,
16076 window: &mut Window,
16077 cx: &mut Context<Self>,
16078 ) {
16079 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16080
16081 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16082 self.start_git_blame(true, window, cx);
16083 }
16084
16085 cx.notify();
16086 }
16087
16088 pub fn toggle_git_blame_inline(
16089 &mut self,
16090 _: &ToggleGitBlameInline,
16091 window: &mut Window,
16092 cx: &mut Context<Self>,
16093 ) {
16094 self.toggle_git_blame_inline_internal(true, window, cx);
16095 cx.notify();
16096 }
16097
16098 pub fn open_git_blame_commit(
16099 &mut self,
16100 _: &OpenGitBlameCommit,
16101 window: &mut Window,
16102 cx: &mut Context<Self>,
16103 ) {
16104 self.open_git_blame_commit_internal(window, cx);
16105 }
16106
16107 fn open_git_blame_commit_internal(
16108 &mut self,
16109 window: &mut Window,
16110 cx: &mut Context<Self>,
16111 ) -> Option<()> {
16112 let blame = self.blame.as_ref()?;
16113 let snapshot = self.snapshot(window, cx);
16114 let cursor = self.selections.newest::<Point>(cx).head();
16115 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16116 let blame_entry = blame
16117 .update(cx, |blame, cx| {
16118 blame
16119 .blame_for_rows(
16120 &[RowInfo {
16121 buffer_id: Some(buffer.remote_id()),
16122 buffer_row: Some(point.row),
16123 ..Default::default()
16124 }],
16125 cx,
16126 )
16127 .next()
16128 })
16129 .flatten()?;
16130 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16131 let repo = blame.read(cx).repository(cx)?;
16132 let workspace = self.workspace()?.downgrade();
16133 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16134 None
16135 }
16136
16137 pub fn git_blame_inline_enabled(&self) -> bool {
16138 self.git_blame_inline_enabled
16139 }
16140
16141 pub fn toggle_selection_menu(
16142 &mut self,
16143 _: &ToggleSelectionMenu,
16144 _: &mut Window,
16145 cx: &mut Context<Self>,
16146 ) {
16147 self.show_selection_menu = self
16148 .show_selection_menu
16149 .map(|show_selections_menu| !show_selections_menu)
16150 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16151
16152 cx.notify();
16153 }
16154
16155 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16156 self.show_selection_menu
16157 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16158 }
16159
16160 fn start_git_blame(
16161 &mut self,
16162 user_triggered: bool,
16163 window: &mut Window,
16164 cx: &mut Context<Self>,
16165 ) {
16166 if let Some(project) = self.project.as_ref() {
16167 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16168 return;
16169 };
16170
16171 if buffer.read(cx).file().is_none() {
16172 return;
16173 }
16174
16175 let focused = self.focus_handle(cx).contains_focused(window, cx);
16176
16177 let project = project.clone();
16178 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16179 self.blame_subscription =
16180 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16181 self.blame = Some(blame);
16182 }
16183 }
16184
16185 fn toggle_git_blame_inline_internal(
16186 &mut self,
16187 user_triggered: bool,
16188 window: &mut Window,
16189 cx: &mut Context<Self>,
16190 ) {
16191 if self.git_blame_inline_enabled {
16192 self.git_blame_inline_enabled = false;
16193 self.show_git_blame_inline = false;
16194 self.show_git_blame_inline_delay_task.take();
16195 } else {
16196 self.git_blame_inline_enabled = true;
16197 self.start_git_blame_inline(user_triggered, window, cx);
16198 }
16199
16200 cx.notify();
16201 }
16202
16203 fn start_git_blame_inline(
16204 &mut self,
16205 user_triggered: bool,
16206 window: &mut Window,
16207 cx: &mut Context<Self>,
16208 ) {
16209 self.start_git_blame(user_triggered, window, cx);
16210
16211 if ProjectSettings::get_global(cx)
16212 .git
16213 .inline_blame_delay()
16214 .is_some()
16215 {
16216 self.start_inline_blame_timer(window, cx);
16217 } else {
16218 self.show_git_blame_inline = true
16219 }
16220 }
16221
16222 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16223 self.blame.as_ref()
16224 }
16225
16226 pub fn show_git_blame_gutter(&self) -> bool {
16227 self.show_git_blame_gutter
16228 }
16229
16230 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16231 self.show_git_blame_gutter && self.has_blame_entries(cx)
16232 }
16233
16234 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16235 self.show_git_blame_inline
16236 && (self.focus_handle.is_focused(window)
16237 || self
16238 .git_blame_inline_tooltip
16239 .as_ref()
16240 .and_then(|t| t.upgrade())
16241 .is_some())
16242 && !self.newest_selection_head_on_empty_line(cx)
16243 && self.has_blame_entries(cx)
16244 }
16245
16246 fn has_blame_entries(&self, cx: &App) -> bool {
16247 self.blame()
16248 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16249 }
16250
16251 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16252 let cursor_anchor = self.selections.newest_anchor().head();
16253
16254 let snapshot = self.buffer.read(cx).snapshot(cx);
16255 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16256
16257 snapshot.line_len(buffer_row) == 0
16258 }
16259
16260 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16261 let buffer_and_selection = maybe!({
16262 let selection = self.selections.newest::<Point>(cx);
16263 let selection_range = selection.range();
16264
16265 let multi_buffer = self.buffer().read(cx);
16266 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16267 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16268
16269 let (buffer, range, _) = if selection.reversed {
16270 buffer_ranges.first()
16271 } else {
16272 buffer_ranges.last()
16273 }?;
16274
16275 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16276 ..text::ToPoint::to_point(&range.end, &buffer).row;
16277 Some((
16278 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16279 selection,
16280 ))
16281 });
16282
16283 let Some((buffer, selection)) = buffer_and_selection else {
16284 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16285 };
16286
16287 let Some(project) = self.project.as_ref() else {
16288 return Task::ready(Err(anyhow!("editor does not have project")));
16289 };
16290
16291 project.update(cx, |project, cx| {
16292 project.get_permalink_to_line(&buffer, selection, cx)
16293 })
16294 }
16295
16296 pub fn copy_permalink_to_line(
16297 &mut self,
16298 _: &CopyPermalinkToLine,
16299 window: &mut Window,
16300 cx: &mut Context<Self>,
16301 ) {
16302 let permalink_task = self.get_permalink_to_line(cx);
16303 let workspace = self.workspace();
16304
16305 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16306 Ok(permalink) => {
16307 cx.update(|_, cx| {
16308 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16309 })
16310 .ok();
16311 }
16312 Err(err) => {
16313 let message = format!("Failed to copy permalink: {err}");
16314
16315 Err::<(), anyhow::Error>(err).log_err();
16316
16317 if let Some(workspace) = workspace {
16318 workspace
16319 .update_in(cx, |workspace, _, cx| {
16320 struct CopyPermalinkToLine;
16321
16322 workspace.show_toast(
16323 Toast::new(
16324 NotificationId::unique::<CopyPermalinkToLine>(),
16325 message,
16326 ),
16327 cx,
16328 )
16329 })
16330 .ok();
16331 }
16332 }
16333 })
16334 .detach();
16335 }
16336
16337 pub fn copy_file_location(
16338 &mut self,
16339 _: &CopyFileLocation,
16340 _: &mut Window,
16341 cx: &mut Context<Self>,
16342 ) {
16343 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16344 if let Some(file) = self.target_file(cx) {
16345 if let Some(path) = file.path().to_str() {
16346 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16347 }
16348 }
16349 }
16350
16351 pub fn open_permalink_to_line(
16352 &mut self,
16353 _: &OpenPermalinkToLine,
16354 window: &mut Window,
16355 cx: &mut Context<Self>,
16356 ) {
16357 let permalink_task = self.get_permalink_to_line(cx);
16358 let workspace = self.workspace();
16359
16360 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16361 Ok(permalink) => {
16362 cx.update(|_, cx| {
16363 cx.open_url(permalink.as_ref());
16364 })
16365 .ok();
16366 }
16367 Err(err) => {
16368 let message = format!("Failed to open permalink: {err}");
16369
16370 Err::<(), anyhow::Error>(err).log_err();
16371
16372 if let Some(workspace) = workspace {
16373 workspace
16374 .update(cx, |workspace, cx| {
16375 struct OpenPermalinkToLine;
16376
16377 workspace.show_toast(
16378 Toast::new(
16379 NotificationId::unique::<OpenPermalinkToLine>(),
16380 message,
16381 ),
16382 cx,
16383 )
16384 })
16385 .ok();
16386 }
16387 }
16388 })
16389 .detach();
16390 }
16391
16392 pub fn insert_uuid_v4(
16393 &mut self,
16394 _: &InsertUuidV4,
16395 window: &mut Window,
16396 cx: &mut Context<Self>,
16397 ) {
16398 self.insert_uuid(UuidVersion::V4, window, cx);
16399 }
16400
16401 pub fn insert_uuid_v7(
16402 &mut self,
16403 _: &InsertUuidV7,
16404 window: &mut Window,
16405 cx: &mut Context<Self>,
16406 ) {
16407 self.insert_uuid(UuidVersion::V7, window, cx);
16408 }
16409
16410 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16411 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16412 self.transact(window, cx, |this, window, cx| {
16413 let edits = this
16414 .selections
16415 .all::<Point>(cx)
16416 .into_iter()
16417 .map(|selection| {
16418 let uuid = match version {
16419 UuidVersion::V4 => uuid::Uuid::new_v4(),
16420 UuidVersion::V7 => uuid::Uuid::now_v7(),
16421 };
16422
16423 (selection.range(), uuid.to_string())
16424 });
16425 this.edit(edits, cx);
16426 this.refresh_inline_completion(true, false, window, cx);
16427 });
16428 }
16429
16430 pub fn open_selections_in_multibuffer(
16431 &mut self,
16432 _: &OpenSelectionsInMultibuffer,
16433 window: &mut Window,
16434 cx: &mut Context<Self>,
16435 ) {
16436 let multibuffer = self.buffer.read(cx);
16437
16438 let Some(buffer) = multibuffer.as_singleton() else {
16439 return;
16440 };
16441
16442 let Some(workspace) = self.workspace() else {
16443 return;
16444 };
16445
16446 let locations = self
16447 .selections
16448 .disjoint_anchors()
16449 .iter()
16450 .map(|range| Location {
16451 buffer: buffer.clone(),
16452 range: range.start.text_anchor..range.end.text_anchor,
16453 })
16454 .collect::<Vec<_>>();
16455
16456 let title = multibuffer.title(cx).to_string();
16457
16458 cx.spawn_in(window, async move |_, cx| {
16459 workspace.update_in(cx, |workspace, window, cx| {
16460 Self::open_locations_in_multibuffer(
16461 workspace,
16462 locations,
16463 format!("Selections for '{title}'"),
16464 false,
16465 MultibufferSelectionMode::All,
16466 window,
16467 cx,
16468 );
16469 })
16470 })
16471 .detach();
16472 }
16473
16474 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16475 /// last highlight added will be used.
16476 ///
16477 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16478 pub fn highlight_rows<T: 'static>(
16479 &mut self,
16480 range: Range<Anchor>,
16481 color: Hsla,
16482 should_autoscroll: bool,
16483 cx: &mut Context<Self>,
16484 ) {
16485 let snapshot = self.buffer().read(cx).snapshot(cx);
16486 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16487 let ix = row_highlights.binary_search_by(|highlight| {
16488 Ordering::Equal
16489 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16490 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16491 });
16492
16493 if let Err(mut ix) = ix {
16494 let index = post_inc(&mut self.highlight_order);
16495
16496 // If this range intersects with the preceding highlight, then merge it with
16497 // the preceding highlight. Otherwise insert a new highlight.
16498 let mut merged = false;
16499 if ix > 0 {
16500 let prev_highlight = &mut row_highlights[ix - 1];
16501 if prev_highlight
16502 .range
16503 .end
16504 .cmp(&range.start, &snapshot)
16505 .is_ge()
16506 {
16507 ix -= 1;
16508 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16509 prev_highlight.range.end = range.end;
16510 }
16511 merged = true;
16512 prev_highlight.index = index;
16513 prev_highlight.color = color;
16514 prev_highlight.should_autoscroll = should_autoscroll;
16515 }
16516 }
16517
16518 if !merged {
16519 row_highlights.insert(
16520 ix,
16521 RowHighlight {
16522 range: range.clone(),
16523 index,
16524 color,
16525 should_autoscroll,
16526 },
16527 );
16528 }
16529
16530 // If any of the following highlights intersect with this one, merge them.
16531 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16532 let highlight = &row_highlights[ix];
16533 if next_highlight
16534 .range
16535 .start
16536 .cmp(&highlight.range.end, &snapshot)
16537 .is_le()
16538 {
16539 if next_highlight
16540 .range
16541 .end
16542 .cmp(&highlight.range.end, &snapshot)
16543 .is_gt()
16544 {
16545 row_highlights[ix].range.end = next_highlight.range.end;
16546 }
16547 row_highlights.remove(ix + 1);
16548 } else {
16549 break;
16550 }
16551 }
16552 }
16553 }
16554
16555 /// Remove any highlighted row ranges of the given type that intersect the
16556 /// given ranges.
16557 pub fn remove_highlighted_rows<T: 'static>(
16558 &mut self,
16559 ranges_to_remove: Vec<Range<Anchor>>,
16560 cx: &mut Context<Self>,
16561 ) {
16562 let snapshot = self.buffer().read(cx).snapshot(cx);
16563 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16564 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16565 row_highlights.retain(|highlight| {
16566 while let Some(range_to_remove) = ranges_to_remove.peek() {
16567 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16568 Ordering::Less | Ordering::Equal => {
16569 ranges_to_remove.next();
16570 }
16571 Ordering::Greater => {
16572 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16573 Ordering::Less | Ordering::Equal => {
16574 return false;
16575 }
16576 Ordering::Greater => break,
16577 }
16578 }
16579 }
16580 }
16581
16582 true
16583 })
16584 }
16585
16586 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16587 pub fn clear_row_highlights<T: 'static>(&mut self) {
16588 self.highlighted_rows.remove(&TypeId::of::<T>());
16589 }
16590
16591 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16592 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16593 self.highlighted_rows
16594 .get(&TypeId::of::<T>())
16595 .map_or(&[] as &[_], |vec| vec.as_slice())
16596 .iter()
16597 .map(|highlight| (highlight.range.clone(), highlight.color))
16598 }
16599
16600 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16601 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16602 /// Allows to ignore certain kinds of highlights.
16603 pub fn highlighted_display_rows(
16604 &self,
16605 window: &mut Window,
16606 cx: &mut App,
16607 ) -> BTreeMap<DisplayRow, LineHighlight> {
16608 let snapshot = self.snapshot(window, cx);
16609 let mut used_highlight_orders = HashMap::default();
16610 self.highlighted_rows
16611 .iter()
16612 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16613 .fold(
16614 BTreeMap::<DisplayRow, LineHighlight>::new(),
16615 |mut unique_rows, highlight| {
16616 let start = highlight.range.start.to_display_point(&snapshot);
16617 let end = highlight.range.end.to_display_point(&snapshot);
16618 let start_row = start.row().0;
16619 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16620 && end.column() == 0
16621 {
16622 end.row().0.saturating_sub(1)
16623 } else {
16624 end.row().0
16625 };
16626 for row in start_row..=end_row {
16627 let used_index =
16628 used_highlight_orders.entry(row).or_insert(highlight.index);
16629 if highlight.index >= *used_index {
16630 *used_index = highlight.index;
16631 unique_rows.insert(DisplayRow(row), highlight.color.into());
16632 }
16633 }
16634 unique_rows
16635 },
16636 )
16637 }
16638
16639 pub fn highlighted_display_row_for_autoscroll(
16640 &self,
16641 snapshot: &DisplaySnapshot,
16642 ) -> Option<DisplayRow> {
16643 self.highlighted_rows
16644 .values()
16645 .flat_map(|highlighted_rows| highlighted_rows.iter())
16646 .filter_map(|highlight| {
16647 if highlight.should_autoscroll {
16648 Some(highlight.range.start.to_display_point(snapshot).row())
16649 } else {
16650 None
16651 }
16652 })
16653 .min()
16654 }
16655
16656 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16657 self.highlight_background::<SearchWithinRange>(
16658 ranges,
16659 |colors| colors.editor_document_highlight_read_background,
16660 cx,
16661 )
16662 }
16663
16664 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16665 self.breadcrumb_header = Some(new_header);
16666 }
16667
16668 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16669 self.clear_background_highlights::<SearchWithinRange>(cx);
16670 }
16671
16672 pub fn highlight_background<T: 'static>(
16673 &mut self,
16674 ranges: &[Range<Anchor>],
16675 color_fetcher: fn(&ThemeColors) -> Hsla,
16676 cx: &mut Context<Self>,
16677 ) {
16678 self.background_highlights
16679 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16680 self.scrollbar_marker_state.dirty = true;
16681 cx.notify();
16682 }
16683
16684 pub fn clear_background_highlights<T: 'static>(
16685 &mut self,
16686 cx: &mut Context<Self>,
16687 ) -> Option<BackgroundHighlight> {
16688 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16689 if !text_highlights.1.is_empty() {
16690 self.scrollbar_marker_state.dirty = true;
16691 cx.notify();
16692 }
16693 Some(text_highlights)
16694 }
16695
16696 pub fn highlight_gutter<T: 'static>(
16697 &mut self,
16698 ranges: &[Range<Anchor>],
16699 color_fetcher: fn(&App) -> Hsla,
16700 cx: &mut Context<Self>,
16701 ) {
16702 self.gutter_highlights
16703 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16704 cx.notify();
16705 }
16706
16707 pub fn clear_gutter_highlights<T: 'static>(
16708 &mut self,
16709 cx: &mut Context<Self>,
16710 ) -> Option<GutterHighlight> {
16711 cx.notify();
16712 self.gutter_highlights.remove(&TypeId::of::<T>())
16713 }
16714
16715 #[cfg(feature = "test-support")]
16716 pub fn all_text_background_highlights(
16717 &self,
16718 window: &mut Window,
16719 cx: &mut Context<Self>,
16720 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16721 let snapshot = self.snapshot(window, cx);
16722 let buffer = &snapshot.buffer_snapshot;
16723 let start = buffer.anchor_before(0);
16724 let end = buffer.anchor_after(buffer.len());
16725 let theme = cx.theme().colors();
16726 self.background_highlights_in_range(start..end, &snapshot, theme)
16727 }
16728
16729 #[cfg(feature = "test-support")]
16730 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16731 let snapshot = self.buffer().read(cx).snapshot(cx);
16732
16733 let highlights = self
16734 .background_highlights
16735 .get(&TypeId::of::<items::BufferSearchHighlights>());
16736
16737 if let Some((_color, ranges)) = highlights {
16738 ranges
16739 .iter()
16740 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16741 .collect_vec()
16742 } else {
16743 vec![]
16744 }
16745 }
16746
16747 fn document_highlights_for_position<'a>(
16748 &'a self,
16749 position: Anchor,
16750 buffer: &'a MultiBufferSnapshot,
16751 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16752 let read_highlights = self
16753 .background_highlights
16754 .get(&TypeId::of::<DocumentHighlightRead>())
16755 .map(|h| &h.1);
16756 let write_highlights = self
16757 .background_highlights
16758 .get(&TypeId::of::<DocumentHighlightWrite>())
16759 .map(|h| &h.1);
16760 let left_position = position.bias_left(buffer);
16761 let right_position = position.bias_right(buffer);
16762 read_highlights
16763 .into_iter()
16764 .chain(write_highlights)
16765 .flat_map(move |ranges| {
16766 let start_ix = match ranges.binary_search_by(|probe| {
16767 let cmp = probe.end.cmp(&left_position, buffer);
16768 if cmp.is_ge() {
16769 Ordering::Greater
16770 } else {
16771 Ordering::Less
16772 }
16773 }) {
16774 Ok(i) | Err(i) => i,
16775 };
16776
16777 ranges[start_ix..]
16778 .iter()
16779 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16780 })
16781 }
16782
16783 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16784 self.background_highlights
16785 .get(&TypeId::of::<T>())
16786 .map_or(false, |(_, highlights)| !highlights.is_empty())
16787 }
16788
16789 pub fn background_highlights_in_range(
16790 &self,
16791 search_range: Range<Anchor>,
16792 display_snapshot: &DisplaySnapshot,
16793 theme: &ThemeColors,
16794 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16795 let mut results = Vec::new();
16796 for (color_fetcher, ranges) in self.background_highlights.values() {
16797 let color = color_fetcher(theme);
16798 let start_ix = match ranges.binary_search_by(|probe| {
16799 let cmp = probe
16800 .end
16801 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16802 if cmp.is_gt() {
16803 Ordering::Greater
16804 } else {
16805 Ordering::Less
16806 }
16807 }) {
16808 Ok(i) | Err(i) => i,
16809 };
16810 for range in &ranges[start_ix..] {
16811 if range
16812 .start
16813 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16814 .is_ge()
16815 {
16816 break;
16817 }
16818
16819 let start = range.start.to_display_point(display_snapshot);
16820 let end = range.end.to_display_point(display_snapshot);
16821 results.push((start..end, color))
16822 }
16823 }
16824 results
16825 }
16826
16827 pub fn background_highlight_row_ranges<T: 'static>(
16828 &self,
16829 search_range: Range<Anchor>,
16830 display_snapshot: &DisplaySnapshot,
16831 count: usize,
16832 ) -> Vec<RangeInclusive<DisplayPoint>> {
16833 let mut results = Vec::new();
16834 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16835 return vec![];
16836 };
16837
16838 let start_ix = match ranges.binary_search_by(|probe| {
16839 let cmp = probe
16840 .end
16841 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16842 if cmp.is_gt() {
16843 Ordering::Greater
16844 } else {
16845 Ordering::Less
16846 }
16847 }) {
16848 Ok(i) | Err(i) => i,
16849 };
16850 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16851 if let (Some(start_display), Some(end_display)) = (start, end) {
16852 results.push(
16853 start_display.to_display_point(display_snapshot)
16854 ..=end_display.to_display_point(display_snapshot),
16855 );
16856 }
16857 };
16858 let mut start_row: Option<Point> = None;
16859 let mut end_row: Option<Point> = None;
16860 if ranges.len() > count {
16861 return Vec::new();
16862 }
16863 for range in &ranges[start_ix..] {
16864 if range
16865 .start
16866 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16867 .is_ge()
16868 {
16869 break;
16870 }
16871 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16872 if let Some(current_row) = &end_row {
16873 if end.row == current_row.row {
16874 continue;
16875 }
16876 }
16877 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16878 if start_row.is_none() {
16879 assert_eq!(end_row, None);
16880 start_row = Some(start);
16881 end_row = Some(end);
16882 continue;
16883 }
16884 if let Some(current_end) = end_row.as_mut() {
16885 if start.row > current_end.row + 1 {
16886 push_region(start_row, end_row);
16887 start_row = Some(start);
16888 end_row = Some(end);
16889 } else {
16890 // Merge two hunks.
16891 *current_end = end;
16892 }
16893 } else {
16894 unreachable!();
16895 }
16896 }
16897 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16898 push_region(start_row, end_row);
16899 results
16900 }
16901
16902 pub fn gutter_highlights_in_range(
16903 &self,
16904 search_range: Range<Anchor>,
16905 display_snapshot: &DisplaySnapshot,
16906 cx: &App,
16907 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16908 let mut results = Vec::new();
16909 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16910 let color = color_fetcher(cx);
16911 let start_ix = match ranges.binary_search_by(|probe| {
16912 let cmp = probe
16913 .end
16914 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16915 if cmp.is_gt() {
16916 Ordering::Greater
16917 } else {
16918 Ordering::Less
16919 }
16920 }) {
16921 Ok(i) | Err(i) => i,
16922 };
16923 for range in &ranges[start_ix..] {
16924 if range
16925 .start
16926 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16927 .is_ge()
16928 {
16929 break;
16930 }
16931
16932 let start = range.start.to_display_point(display_snapshot);
16933 let end = range.end.to_display_point(display_snapshot);
16934 results.push((start..end, color))
16935 }
16936 }
16937 results
16938 }
16939
16940 /// Get the text ranges corresponding to the redaction query
16941 pub fn redacted_ranges(
16942 &self,
16943 search_range: Range<Anchor>,
16944 display_snapshot: &DisplaySnapshot,
16945 cx: &App,
16946 ) -> Vec<Range<DisplayPoint>> {
16947 display_snapshot
16948 .buffer_snapshot
16949 .redacted_ranges(search_range, |file| {
16950 if let Some(file) = file {
16951 file.is_private()
16952 && EditorSettings::get(
16953 Some(SettingsLocation {
16954 worktree_id: file.worktree_id(cx),
16955 path: file.path().as_ref(),
16956 }),
16957 cx,
16958 )
16959 .redact_private_values
16960 } else {
16961 false
16962 }
16963 })
16964 .map(|range| {
16965 range.start.to_display_point(display_snapshot)
16966 ..range.end.to_display_point(display_snapshot)
16967 })
16968 .collect()
16969 }
16970
16971 pub fn highlight_text<T: 'static>(
16972 &mut self,
16973 ranges: Vec<Range<Anchor>>,
16974 style: HighlightStyle,
16975 cx: &mut Context<Self>,
16976 ) {
16977 self.display_map.update(cx, |map, _| {
16978 map.highlight_text(TypeId::of::<T>(), ranges, style)
16979 });
16980 cx.notify();
16981 }
16982
16983 pub(crate) fn highlight_inlays<T: 'static>(
16984 &mut self,
16985 highlights: Vec<InlayHighlight>,
16986 style: HighlightStyle,
16987 cx: &mut Context<Self>,
16988 ) {
16989 self.display_map.update(cx, |map, _| {
16990 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16991 });
16992 cx.notify();
16993 }
16994
16995 pub fn text_highlights<'a, T: 'static>(
16996 &'a self,
16997 cx: &'a App,
16998 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
16999 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17000 }
17001
17002 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17003 let cleared = self
17004 .display_map
17005 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17006 if cleared {
17007 cx.notify();
17008 }
17009 }
17010
17011 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17012 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17013 && self.focus_handle.is_focused(window)
17014 }
17015
17016 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17017 self.show_cursor_when_unfocused = is_enabled;
17018 cx.notify();
17019 }
17020
17021 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17022 cx.notify();
17023 }
17024
17025 fn on_buffer_event(
17026 &mut self,
17027 multibuffer: &Entity<MultiBuffer>,
17028 event: &multi_buffer::Event,
17029 window: &mut Window,
17030 cx: &mut Context<Self>,
17031 ) {
17032 match event {
17033 multi_buffer::Event::Edited {
17034 singleton_buffer_edited,
17035 edited_buffer: buffer_edited,
17036 } => {
17037 self.scrollbar_marker_state.dirty = true;
17038 self.active_indent_guides_state.dirty = true;
17039 self.refresh_active_diagnostics(cx);
17040 self.refresh_code_actions(window, cx);
17041 if self.has_active_inline_completion() {
17042 self.update_visible_inline_completion(window, cx);
17043 }
17044 if let Some(buffer) = buffer_edited {
17045 let buffer_id = buffer.read(cx).remote_id();
17046 if !self.registered_buffers.contains_key(&buffer_id) {
17047 if let Some(project) = self.project.as_ref() {
17048 project.update(cx, |project, cx| {
17049 self.registered_buffers.insert(
17050 buffer_id,
17051 project.register_buffer_with_language_servers(&buffer, cx),
17052 );
17053 })
17054 }
17055 }
17056 }
17057 cx.emit(EditorEvent::BufferEdited);
17058 cx.emit(SearchEvent::MatchesInvalidated);
17059 if *singleton_buffer_edited {
17060 if let Some(project) = &self.project {
17061 #[allow(clippy::mutable_key_type)]
17062 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17063 multibuffer
17064 .all_buffers()
17065 .into_iter()
17066 .filter_map(|buffer| {
17067 buffer.update(cx, |buffer, cx| {
17068 let language = buffer.language()?;
17069 let should_discard = project.update(cx, |project, cx| {
17070 project.is_local()
17071 && !project.has_language_servers_for(buffer, cx)
17072 });
17073 should_discard.not().then_some(language.clone())
17074 })
17075 })
17076 .collect::<HashSet<_>>()
17077 });
17078 if !languages_affected.is_empty() {
17079 self.refresh_inlay_hints(
17080 InlayHintRefreshReason::BufferEdited(languages_affected),
17081 cx,
17082 );
17083 }
17084 }
17085 }
17086
17087 let Some(project) = &self.project else { return };
17088 let (telemetry, is_via_ssh) = {
17089 let project = project.read(cx);
17090 let telemetry = project.client().telemetry().clone();
17091 let is_via_ssh = project.is_via_ssh();
17092 (telemetry, is_via_ssh)
17093 };
17094 refresh_linked_ranges(self, window, cx);
17095 telemetry.log_edit_event("editor", is_via_ssh);
17096 }
17097 multi_buffer::Event::ExcerptsAdded {
17098 buffer,
17099 predecessor,
17100 excerpts,
17101 } => {
17102 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17103 let buffer_id = buffer.read(cx).remote_id();
17104 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17105 if let Some(project) = &self.project {
17106 get_uncommitted_diff_for_buffer(
17107 project,
17108 [buffer.clone()],
17109 self.buffer.clone(),
17110 cx,
17111 )
17112 .detach();
17113 }
17114 }
17115 cx.emit(EditorEvent::ExcerptsAdded {
17116 buffer: buffer.clone(),
17117 predecessor: *predecessor,
17118 excerpts: excerpts.clone(),
17119 });
17120 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17121 }
17122 multi_buffer::Event::ExcerptsRemoved { ids } => {
17123 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17124 let buffer = self.buffer.read(cx);
17125 self.registered_buffers
17126 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17127 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17128 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
17129 }
17130 multi_buffer::Event::ExcerptsEdited {
17131 excerpt_ids,
17132 buffer_ids,
17133 } => {
17134 self.display_map.update(cx, |map, cx| {
17135 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17136 });
17137 cx.emit(EditorEvent::ExcerptsEdited {
17138 ids: excerpt_ids.clone(),
17139 })
17140 }
17141 multi_buffer::Event::ExcerptsExpanded { ids } => {
17142 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17143 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17144 }
17145 multi_buffer::Event::Reparsed(buffer_id) => {
17146 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17147 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17148
17149 cx.emit(EditorEvent::Reparsed(*buffer_id));
17150 }
17151 multi_buffer::Event::DiffHunksToggled => {
17152 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17153 }
17154 multi_buffer::Event::LanguageChanged(buffer_id) => {
17155 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17156 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17157 cx.emit(EditorEvent::Reparsed(*buffer_id));
17158 cx.notify();
17159 }
17160 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17161 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17162 multi_buffer::Event::FileHandleChanged
17163 | multi_buffer::Event::Reloaded
17164 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17165 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17166 multi_buffer::Event::DiagnosticsUpdated => {
17167 self.refresh_active_diagnostics(cx);
17168 self.refresh_inline_diagnostics(true, window, cx);
17169 self.scrollbar_marker_state.dirty = true;
17170 cx.notify();
17171 }
17172 _ => {}
17173 };
17174 }
17175
17176 fn on_display_map_changed(
17177 &mut self,
17178 _: Entity<DisplayMap>,
17179 _: &mut Window,
17180 cx: &mut Context<Self>,
17181 ) {
17182 cx.notify();
17183 }
17184
17185 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17186 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17187 self.update_edit_prediction_settings(cx);
17188 self.refresh_inline_completion(true, false, window, cx);
17189 self.refresh_inlay_hints(
17190 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17191 self.selections.newest_anchor().head(),
17192 &self.buffer.read(cx).snapshot(cx),
17193 cx,
17194 )),
17195 cx,
17196 );
17197
17198 let old_cursor_shape = self.cursor_shape;
17199
17200 {
17201 let editor_settings = EditorSettings::get_global(cx);
17202 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17203 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17204 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17205 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17206 }
17207
17208 if old_cursor_shape != self.cursor_shape {
17209 cx.emit(EditorEvent::CursorShapeChanged);
17210 }
17211
17212 let project_settings = ProjectSettings::get_global(cx);
17213 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17214
17215 if self.mode == EditorMode::Full {
17216 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17217 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17218 if self.show_inline_diagnostics != show_inline_diagnostics {
17219 self.show_inline_diagnostics = show_inline_diagnostics;
17220 self.refresh_inline_diagnostics(false, window, cx);
17221 }
17222
17223 if self.git_blame_inline_enabled != inline_blame_enabled {
17224 self.toggle_git_blame_inline_internal(false, window, cx);
17225 }
17226 }
17227
17228 cx.notify();
17229 }
17230
17231 pub fn set_searchable(&mut self, searchable: bool) {
17232 self.searchable = searchable;
17233 }
17234
17235 pub fn searchable(&self) -> bool {
17236 self.searchable
17237 }
17238
17239 fn open_proposed_changes_editor(
17240 &mut self,
17241 _: &OpenProposedChangesEditor,
17242 window: &mut Window,
17243 cx: &mut Context<Self>,
17244 ) {
17245 let Some(workspace) = self.workspace() else {
17246 cx.propagate();
17247 return;
17248 };
17249
17250 let selections = self.selections.all::<usize>(cx);
17251 let multi_buffer = self.buffer.read(cx);
17252 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17253 let mut new_selections_by_buffer = HashMap::default();
17254 for selection in selections {
17255 for (buffer, range, _) in
17256 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17257 {
17258 let mut range = range.to_point(buffer);
17259 range.start.column = 0;
17260 range.end.column = buffer.line_len(range.end.row);
17261 new_selections_by_buffer
17262 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17263 .or_insert(Vec::new())
17264 .push(range)
17265 }
17266 }
17267
17268 let proposed_changes_buffers = new_selections_by_buffer
17269 .into_iter()
17270 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17271 .collect::<Vec<_>>();
17272 let proposed_changes_editor = cx.new(|cx| {
17273 ProposedChangesEditor::new(
17274 "Proposed changes",
17275 proposed_changes_buffers,
17276 self.project.clone(),
17277 window,
17278 cx,
17279 )
17280 });
17281
17282 window.defer(cx, move |window, cx| {
17283 workspace.update(cx, |workspace, cx| {
17284 workspace.active_pane().update(cx, |pane, cx| {
17285 pane.add_item(
17286 Box::new(proposed_changes_editor),
17287 true,
17288 true,
17289 None,
17290 window,
17291 cx,
17292 );
17293 });
17294 });
17295 });
17296 }
17297
17298 pub fn open_excerpts_in_split(
17299 &mut self,
17300 _: &OpenExcerptsSplit,
17301 window: &mut Window,
17302 cx: &mut Context<Self>,
17303 ) {
17304 self.open_excerpts_common(None, true, window, cx)
17305 }
17306
17307 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17308 self.open_excerpts_common(None, false, window, cx)
17309 }
17310
17311 fn open_excerpts_common(
17312 &mut self,
17313 jump_data: Option<JumpData>,
17314 split: bool,
17315 window: &mut Window,
17316 cx: &mut Context<Self>,
17317 ) {
17318 let Some(workspace) = self.workspace() else {
17319 cx.propagate();
17320 return;
17321 };
17322
17323 if self.buffer.read(cx).is_singleton() {
17324 cx.propagate();
17325 return;
17326 }
17327
17328 let mut new_selections_by_buffer = HashMap::default();
17329 match &jump_data {
17330 Some(JumpData::MultiBufferPoint {
17331 excerpt_id,
17332 position,
17333 anchor,
17334 line_offset_from_top,
17335 }) => {
17336 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17337 if let Some(buffer) = multi_buffer_snapshot
17338 .buffer_id_for_excerpt(*excerpt_id)
17339 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17340 {
17341 let buffer_snapshot = buffer.read(cx).snapshot();
17342 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17343 language::ToPoint::to_point(anchor, &buffer_snapshot)
17344 } else {
17345 buffer_snapshot.clip_point(*position, Bias::Left)
17346 };
17347 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17348 new_selections_by_buffer.insert(
17349 buffer,
17350 (
17351 vec![jump_to_offset..jump_to_offset],
17352 Some(*line_offset_from_top),
17353 ),
17354 );
17355 }
17356 }
17357 Some(JumpData::MultiBufferRow {
17358 row,
17359 line_offset_from_top,
17360 }) => {
17361 let point = MultiBufferPoint::new(row.0, 0);
17362 if let Some((buffer, buffer_point, _)) =
17363 self.buffer.read(cx).point_to_buffer_point(point, cx)
17364 {
17365 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17366 new_selections_by_buffer
17367 .entry(buffer)
17368 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17369 .0
17370 .push(buffer_offset..buffer_offset)
17371 }
17372 }
17373 None => {
17374 let selections = self.selections.all::<usize>(cx);
17375 let multi_buffer = self.buffer.read(cx);
17376 for selection in selections {
17377 for (snapshot, range, _, anchor) in multi_buffer
17378 .snapshot(cx)
17379 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17380 {
17381 if let Some(anchor) = anchor {
17382 // selection is in a deleted hunk
17383 let Some(buffer_id) = anchor.buffer_id else {
17384 continue;
17385 };
17386 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17387 continue;
17388 };
17389 let offset = text::ToOffset::to_offset(
17390 &anchor.text_anchor,
17391 &buffer_handle.read(cx).snapshot(),
17392 );
17393 let range = offset..offset;
17394 new_selections_by_buffer
17395 .entry(buffer_handle)
17396 .or_insert((Vec::new(), None))
17397 .0
17398 .push(range)
17399 } else {
17400 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17401 else {
17402 continue;
17403 };
17404 new_selections_by_buffer
17405 .entry(buffer_handle)
17406 .or_insert((Vec::new(), None))
17407 .0
17408 .push(range)
17409 }
17410 }
17411 }
17412 }
17413 }
17414
17415 new_selections_by_buffer
17416 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
17417
17418 if new_selections_by_buffer.is_empty() {
17419 return;
17420 }
17421
17422 // We defer the pane interaction because we ourselves are a workspace item
17423 // and activating a new item causes the pane to call a method on us reentrantly,
17424 // which panics if we're on the stack.
17425 window.defer(cx, move |window, cx| {
17426 workspace.update(cx, |workspace, cx| {
17427 let pane = if split {
17428 workspace.adjacent_pane(window, cx)
17429 } else {
17430 workspace.active_pane().clone()
17431 };
17432
17433 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17434 let editor = buffer
17435 .read(cx)
17436 .file()
17437 .is_none()
17438 .then(|| {
17439 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17440 // so `workspace.open_project_item` will never find them, always opening a new editor.
17441 // Instead, we try to activate the existing editor in the pane first.
17442 let (editor, pane_item_index) =
17443 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17444 let editor = item.downcast::<Editor>()?;
17445 let singleton_buffer =
17446 editor.read(cx).buffer().read(cx).as_singleton()?;
17447 if singleton_buffer == buffer {
17448 Some((editor, i))
17449 } else {
17450 None
17451 }
17452 })?;
17453 pane.update(cx, |pane, cx| {
17454 pane.activate_item(pane_item_index, true, true, window, cx)
17455 });
17456 Some(editor)
17457 })
17458 .flatten()
17459 .unwrap_or_else(|| {
17460 workspace.open_project_item::<Self>(
17461 pane.clone(),
17462 buffer,
17463 true,
17464 true,
17465 window,
17466 cx,
17467 )
17468 });
17469
17470 editor.update(cx, |editor, cx| {
17471 let autoscroll = match scroll_offset {
17472 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17473 None => Autoscroll::newest(),
17474 };
17475 let nav_history = editor.nav_history.take();
17476 editor.change_selections(Some(autoscroll), window, cx, |s| {
17477 s.select_ranges(ranges);
17478 });
17479 editor.nav_history = nav_history;
17480 });
17481 }
17482 })
17483 });
17484 }
17485
17486 // For now, don't allow opening excerpts in buffers that aren't backed by
17487 // regular project files.
17488 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
17489 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
17490 }
17491
17492 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17493 let snapshot = self.buffer.read(cx).read(cx);
17494 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17495 Some(
17496 ranges
17497 .iter()
17498 .map(move |range| {
17499 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17500 })
17501 .collect(),
17502 )
17503 }
17504
17505 fn selection_replacement_ranges(
17506 &self,
17507 range: Range<OffsetUtf16>,
17508 cx: &mut App,
17509 ) -> Vec<Range<OffsetUtf16>> {
17510 let selections = self.selections.all::<OffsetUtf16>(cx);
17511 let newest_selection = selections
17512 .iter()
17513 .max_by_key(|selection| selection.id)
17514 .unwrap();
17515 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17516 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17517 let snapshot = self.buffer.read(cx).read(cx);
17518 selections
17519 .into_iter()
17520 .map(|mut selection| {
17521 selection.start.0 =
17522 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17523 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17524 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17525 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17526 })
17527 .collect()
17528 }
17529
17530 fn report_editor_event(
17531 &self,
17532 event_type: &'static str,
17533 file_extension: Option<String>,
17534 cx: &App,
17535 ) {
17536 if cfg!(any(test, feature = "test-support")) {
17537 return;
17538 }
17539
17540 let Some(project) = &self.project else { return };
17541
17542 // If None, we are in a file without an extension
17543 let file = self
17544 .buffer
17545 .read(cx)
17546 .as_singleton()
17547 .and_then(|b| b.read(cx).file());
17548 let file_extension = file_extension.or(file
17549 .as_ref()
17550 .and_then(|file| Path::new(file.file_name(cx)).extension())
17551 .and_then(|e| e.to_str())
17552 .map(|a| a.to_string()));
17553
17554 let vim_mode = cx
17555 .global::<SettingsStore>()
17556 .raw_user_settings()
17557 .get("vim_mode")
17558 == Some(&serde_json::Value::Bool(true));
17559
17560 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17561 let copilot_enabled = edit_predictions_provider
17562 == language::language_settings::EditPredictionProvider::Copilot;
17563 let copilot_enabled_for_language = self
17564 .buffer
17565 .read(cx)
17566 .language_settings(cx)
17567 .show_edit_predictions;
17568
17569 let project = project.read(cx);
17570 telemetry::event!(
17571 event_type,
17572 file_extension,
17573 vim_mode,
17574 copilot_enabled,
17575 copilot_enabled_for_language,
17576 edit_predictions_provider,
17577 is_via_ssh = project.is_via_ssh(),
17578 );
17579 }
17580
17581 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17582 /// with each line being an array of {text, highlight} objects.
17583 fn copy_highlight_json(
17584 &mut self,
17585 _: &CopyHighlightJson,
17586 window: &mut Window,
17587 cx: &mut Context<Self>,
17588 ) {
17589 #[derive(Serialize)]
17590 struct Chunk<'a> {
17591 text: String,
17592 highlight: Option<&'a str>,
17593 }
17594
17595 let snapshot = self.buffer.read(cx).snapshot(cx);
17596 let range = self
17597 .selected_text_range(false, window, cx)
17598 .and_then(|selection| {
17599 if selection.range.is_empty() {
17600 None
17601 } else {
17602 Some(selection.range)
17603 }
17604 })
17605 .unwrap_or_else(|| 0..snapshot.len());
17606
17607 let chunks = snapshot.chunks(range, true);
17608 let mut lines = Vec::new();
17609 let mut line: VecDeque<Chunk> = VecDeque::new();
17610
17611 let Some(style) = self.style.as_ref() else {
17612 return;
17613 };
17614
17615 for chunk in chunks {
17616 let highlight = chunk
17617 .syntax_highlight_id
17618 .and_then(|id| id.name(&style.syntax));
17619 let mut chunk_lines = chunk.text.split('\n').peekable();
17620 while let Some(text) = chunk_lines.next() {
17621 let mut merged_with_last_token = false;
17622 if let Some(last_token) = line.back_mut() {
17623 if last_token.highlight == highlight {
17624 last_token.text.push_str(text);
17625 merged_with_last_token = true;
17626 }
17627 }
17628
17629 if !merged_with_last_token {
17630 line.push_back(Chunk {
17631 text: text.into(),
17632 highlight,
17633 });
17634 }
17635
17636 if chunk_lines.peek().is_some() {
17637 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17638 line.pop_front();
17639 }
17640 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17641 line.pop_back();
17642 }
17643
17644 lines.push(mem::take(&mut line));
17645 }
17646 }
17647 }
17648
17649 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17650 return;
17651 };
17652 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17653 }
17654
17655 pub fn open_context_menu(
17656 &mut self,
17657 _: &OpenContextMenu,
17658 window: &mut Window,
17659 cx: &mut Context<Self>,
17660 ) {
17661 self.request_autoscroll(Autoscroll::newest(), cx);
17662 let position = self.selections.newest_display(cx).start;
17663 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17664 }
17665
17666 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17667 &self.inlay_hint_cache
17668 }
17669
17670 pub fn replay_insert_event(
17671 &mut self,
17672 text: &str,
17673 relative_utf16_range: Option<Range<isize>>,
17674 window: &mut Window,
17675 cx: &mut Context<Self>,
17676 ) {
17677 if !self.input_enabled {
17678 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17679 return;
17680 }
17681 if let Some(relative_utf16_range) = relative_utf16_range {
17682 let selections = self.selections.all::<OffsetUtf16>(cx);
17683 self.change_selections(None, window, cx, |s| {
17684 let new_ranges = selections.into_iter().map(|range| {
17685 let start = OffsetUtf16(
17686 range
17687 .head()
17688 .0
17689 .saturating_add_signed(relative_utf16_range.start),
17690 );
17691 let end = OffsetUtf16(
17692 range
17693 .head()
17694 .0
17695 .saturating_add_signed(relative_utf16_range.end),
17696 );
17697 start..end
17698 });
17699 s.select_ranges(new_ranges);
17700 });
17701 }
17702
17703 self.handle_input(text, window, cx);
17704 }
17705
17706 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17707 let Some(provider) = self.semantics_provider.as_ref() else {
17708 return false;
17709 };
17710
17711 let mut supports = false;
17712 self.buffer().update(cx, |this, cx| {
17713 this.for_each_buffer(|buffer| {
17714 supports |= provider.supports_inlay_hints(buffer, cx);
17715 });
17716 });
17717
17718 supports
17719 }
17720
17721 pub fn is_focused(&self, window: &Window) -> bool {
17722 self.focus_handle.is_focused(window)
17723 }
17724
17725 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17726 cx.emit(EditorEvent::Focused);
17727
17728 if let Some(descendant) = self
17729 .last_focused_descendant
17730 .take()
17731 .and_then(|descendant| descendant.upgrade())
17732 {
17733 window.focus(&descendant);
17734 } else {
17735 if let Some(blame) = self.blame.as_ref() {
17736 blame.update(cx, GitBlame::focus)
17737 }
17738
17739 self.blink_manager.update(cx, BlinkManager::enable);
17740 self.show_cursor_names(window, cx);
17741 self.buffer.update(cx, |buffer, cx| {
17742 buffer.finalize_last_transaction(cx);
17743 if self.leader_peer_id.is_none() {
17744 buffer.set_active_selections(
17745 &self.selections.disjoint_anchors(),
17746 self.selections.line_mode,
17747 self.cursor_shape,
17748 cx,
17749 );
17750 }
17751 });
17752 }
17753 }
17754
17755 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17756 cx.emit(EditorEvent::FocusedIn)
17757 }
17758
17759 fn handle_focus_out(
17760 &mut self,
17761 event: FocusOutEvent,
17762 _window: &mut Window,
17763 cx: &mut Context<Self>,
17764 ) {
17765 if event.blurred != self.focus_handle {
17766 self.last_focused_descendant = Some(event.blurred);
17767 }
17768 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17769 }
17770
17771 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17772 self.blink_manager.update(cx, BlinkManager::disable);
17773 self.buffer
17774 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17775
17776 if let Some(blame) = self.blame.as_ref() {
17777 blame.update(cx, GitBlame::blur)
17778 }
17779 if !self.hover_state.focused(window, cx) {
17780 hide_hover(self, cx);
17781 }
17782 if !self
17783 .context_menu
17784 .borrow()
17785 .as_ref()
17786 .is_some_and(|context_menu| context_menu.focused(window, cx))
17787 {
17788 self.hide_context_menu(window, cx);
17789 }
17790 self.discard_inline_completion(false, cx);
17791 cx.emit(EditorEvent::Blurred);
17792 cx.notify();
17793 }
17794
17795 pub fn register_action<A: Action>(
17796 &mut self,
17797 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17798 ) -> Subscription {
17799 let id = self.next_editor_action_id.post_inc();
17800 let listener = Arc::new(listener);
17801 self.editor_actions.borrow_mut().insert(
17802 id,
17803 Box::new(move |window, _| {
17804 let listener = listener.clone();
17805 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17806 let action = action.downcast_ref().unwrap();
17807 if phase == DispatchPhase::Bubble {
17808 listener(action, window, cx)
17809 }
17810 })
17811 }),
17812 );
17813
17814 let editor_actions = self.editor_actions.clone();
17815 Subscription::new(move || {
17816 editor_actions.borrow_mut().remove(&id);
17817 })
17818 }
17819
17820 pub fn file_header_size(&self) -> u32 {
17821 FILE_HEADER_HEIGHT
17822 }
17823
17824 pub fn restore(
17825 &mut self,
17826 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17827 window: &mut Window,
17828 cx: &mut Context<Self>,
17829 ) {
17830 let workspace = self.workspace();
17831 let project = self.project.as_ref();
17832 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17833 let mut tasks = Vec::new();
17834 for (buffer_id, changes) in revert_changes {
17835 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17836 buffer.update(cx, |buffer, cx| {
17837 buffer.edit(
17838 changes
17839 .into_iter()
17840 .map(|(range, text)| (range, text.to_string())),
17841 None,
17842 cx,
17843 );
17844 });
17845
17846 if let Some(project) =
17847 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17848 {
17849 project.update(cx, |project, cx| {
17850 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17851 })
17852 }
17853 }
17854 }
17855 tasks
17856 });
17857 cx.spawn_in(window, async move |_, cx| {
17858 for (buffer, task) in save_tasks {
17859 let result = task.await;
17860 if result.is_err() {
17861 let Some(path) = buffer
17862 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17863 .ok()
17864 else {
17865 continue;
17866 };
17867 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17868 let Some(task) = cx
17869 .update_window_entity(&workspace, |workspace, window, cx| {
17870 workspace
17871 .open_path_preview(path, None, false, false, false, window, cx)
17872 })
17873 .ok()
17874 else {
17875 continue;
17876 };
17877 task.await.log_err();
17878 }
17879 }
17880 }
17881 })
17882 .detach();
17883 self.change_selections(None, window, cx, |selections| selections.refresh());
17884 }
17885
17886 pub fn to_pixel_point(
17887 &self,
17888 source: multi_buffer::Anchor,
17889 editor_snapshot: &EditorSnapshot,
17890 window: &mut Window,
17891 ) -> Option<gpui::Point<Pixels>> {
17892 let source_point = source.to_display_point(editor_snapshot);
17893 self.display_to_pixel_point(source_point, editor_snapshot, window)
17894 }
17895
17896 pub fn display_to_pixel_point(
17897 &self,
17898 source: DisplayPoint,
17899 editor_snapshot: &EditorSnapshot,
17900 window: &mut Window,
17901 ) -> Option<gpui::Point<Pixels>> {
17902 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17903 let text_layout_details = self.text_layout_details(window);
17904 let scroll_top = text_layout_details
17905 .scroll_anchor
17906 .scroll_position(editor_snapshot)
17907 .y;
17908
17909 if source.row().as_f32() < scroll_top.floor() {
17910 return None;
17911 }
17912 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17913 let source_y = line_height * (source.row().as_f32() - scroll_top);
17914 Some(gpui::Point::new(source_x, source_y))
17915 }
17916
17917 pub fn has_visible_completions_menu(&self) -> bool {
17918 !self.edit_prediction_preview_is_active()
17919 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17920 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17921 })
17922 }
17923
17924 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17925 self.addons
17926 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17927 }
17928
17929 pub fn unregister_addon<T: Addon>(&mut self) {
17930 self.addons.remove(&std::any::TypeId::of::<T>());
17931 }
17932
17933 pub fn addon<T: Addon>(&self) -> Option<&T> {
17934 let type_id = std::any::TypeId::of::<T>();
17935 self.addons
17936 .get(&type_id)
17937 .and_then(|item| item.to_any().downcast_ref::<T>())
17938 }
17939
17940 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17941 let text_layout_details = self.text_layout_details(window);
17942 let style = &text_layout_details.editor_style;
17943 let font_id = window.text_system().resolve_font(&style.text.font());
17944 let font_size = style.text.font_size.to_pixels(window.rem_size());
17945 let line_height = style.text.line_height_in_pixels(window.rem_size());
17946 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17947
17948 gpui::Size::new(em_width, line_height)
17949 }
17950
17951 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17952 self.load_diff_task.clone()
17953 }
17954
17955 fn read_metadata_from_db(
17956 &mut self,
17957 item_id: u64,
17958 workspace_id: WorkspaceId,
17959 window: &mut Window,
17960 cx: &mut Context<Editor>,
17961 ) {
17962 if self.is_singleton(cx)
17963 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
17964 {
17965 let buffer_snapshot = OnceCell::new();
17966
17967 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
17968 if !folds.is_empty() {
17969 let snapshot =
17970 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17971 self.fold_ranges(
17972 folds
17973 .into_iter()
17974 .map(|(start, end)| {
17975 snapshot.clip_offset(start, Bias::Left)
17976 ..snapshot.clip_offset(end, Bias::Right)
17977 })
17978 .collect(),
17979 false,
17980 window,
17981 cx,
17982 );
17983 }
17984 }
17985
17986 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
17987 if !selections.is_empty() {
17988 let snapshot =
17989 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17990 self.change_selections(None, window, cx, |s| {
17991 s.select_ranges(selections.into_iter().map(|(start, end)| {
17992 snapshot.clip_offset(start, Bias::Left)
17993 ..snapshot.clip_offset(end, Bias::Right)
17994 }));
17995 });
17996 }
17997 };
17998 }
17999
18000 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18001 }
18002}
18003
18004// Consider user intent and default settings
18005fn choose_completion_range(
18006 completion: &Completion,
18007 intent: CompletionIntent,
18008 buffer: &Entity<Buffer>,
18009 cx: &mut Context<Editor>,
18010) -> Range<usize> {
18011 fn should_replace(
18012 completion: &Completion,
18013 insert_range: &Range<text::Anchor>,
18014 intent: CompletionIntent,
18015 completion_mode_setting: LspInsertMode,
18016 buffer: &Buffer,
18017 ) -> bool {
18018 // specific actions take precedence over settings
18019 match intent {
18020 CompletionIntent::CompleteWithInsert => return false,
18021 CompletionIntent::CompleteWithReplace => return true,
18022 CompletionIntent::Complete | CompletionIntent::Compose => {}
18023 }
18024
18025 match completion_mode_setting {
18026 LspInsertMode::Insert => false,
18027 LspInsertMode::Replace => true,
18028 LspInsertMode::ReplaceSubsequence => {
18029 let mut text_to_replace = buffer.chars_for_range(
18030 buffer.anchor_before(completion.replace_range.start)
18031 ..buffer.anchor_after(completion.replace_range.end),
18032 );
18033 let mut completion_text = completion.new_text.chars();
18034
18035 // is `text_to_replace` a subsequence of `completion_text`
18036 text_to_replace
18037 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18038 }
18039 LspInsertMode::ReplaceSuffix => {
18040 let range_after_cursor = insert_range.end..completion.replace_range.end;
18041
18042 let text_after_cursor = buffer
18043 .text_for_range(
18044 buffer.anchor_before(range_after_cursor.start)
18045 ..buffer.anchor_after(range_after_cursor.end),
18046 )
18047 .collect::<String>();
18048 completion.new_text.ends_with(&text_after_cursor)
18049 }
18050 }
18051 }
18052
18053 let buffer = buffer.read(cx);
18054
18055 if let CompletionSource::Lsp {
18056 insert_range: Some(insert_range),
18057 ..
18058 } = &completion.source
18059 {
18060 let completion_mode_setting =
18061 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18062 .completions
18063 .lsp_insert_mode;
18064
18065 if !should_replace(
18066 completion,
18067 &insert_range,
18068 intent,
18069 completion_mode_setting,
18070 buffer,
18071 ) {
18072 return insert_range.to_offset(buffer);
18073 }
18074 }
18075
18076 completion.replace_range.to_offset(buffer)
18077}
18078
18079fn insert_extra_newline_brackets(
18080 buffer: &MultiBufferSnapshot,
18081 range: Range<usize>,
18082 language: &language::LanguageScope,
18083) -> bool {
18084 let leading_whitespace_len = buffer
18085 .reversed_chars_at(range.start)
18086 .take_while(|c| c.is_whitespace() && *c != '\n')
18087 .map(|c| c.len_utf8())
18088 .sum::<usize>();
18089 let trailing_whitespace_len = buffer
18090 .chars_at(range.end)
18091 .take_while(|c| c.is_whitespace() && *c != '\n')
18092 .map(|c| c.len_utf8())
18093 .sum::<usize>();
18094 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18095
18096 language.brackets().any(|(pair, enabled)| {
18097 let pair_start = pair.start.trim_end();
18098 let pair_end = pair.end.trim_start();
18099
18100 enabled
18101 && pair.newline
18102 && buffer.contains_str_at(range.end, pair_end)
18103 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18104 })
18105}
18106
18107fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18108 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18109 [(buffer, range, _)] => (*buffer, range.clone()),
18110 _ => return false,
18111 };
18112 let pair = {
18113 let mut result: Option<BracketMatch> = None;
18114
18115 for pair in buffer
18116 .all_bracket_ranges(range.clone())
18117 .filter(move |pair| {
18118 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18119 })
18120 {
18121 let len = pair.close_range.end - pair.open_range.start;
18122
18123 if let Some(existing) = &result {
18124 let existing_len = existing.close_range.end - existing.open_range.start;
18125 if len > existing_len {
18126 continue;
18127 }
18128 }
18129
18130 result = Some(pair);
18131 }
18132
18133 result
18134 };
18135 let Some(pair) = pair else {
18136 return false;
18137 };
18138 pair.newline_only
18139 && buffer
18140 .chars_for_range(pair.open_range.end..range.start)
18141 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18142 .all(|c| c.is_whitespace() && c != '\n')
18143}
18144
18145fn get_uncommitted_diff_for_buffer(
18146 project: &Entity<Project>,
18147 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18148 buffer: Entity<MultiBuffer>,
18149 cx: &mut App,
18150) -> Task<()> {
18151 let mut tasks = Vec::new();
18152 project.update(cx, |project, cx| {
18153 for buffer in buffers {
18154 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18155 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18156 }
18157 }
18158 });
18159 cx.spawn(async move |cx| {
18160 let diffs = future::join_all(tasks).await;
18161 buffer
18162 .update(cx, |buffer, cx| {
18163 for diff in diffs.into_iter().flatten() {
18164 buffer.add_diff(diff, cx);
18165 }
18166 })
18167 .ok();
18168 })
18169}
18170
18171fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18172 let tab_size = tab_size.get() as usize;
18173 let mut width = offset;
18174
18175 for ch in text.chars() {
18176 width += if ch == '\t' {
18177 tab_size - (width % tab_size)
18178 } else {
18179 1
18180 };
18181 }
18182
18183 width - offset
18184}
18185
18186#[cfg(test)]
18187mod tests {
18188 use super::*;
18189
18190 #[test]
18191 fn test_string_size_with_expanded_tabs() {
18192 let nz = |val| NonZeroU32::new(val).unwrap();
18193 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18194 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18195 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18196 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18197 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18198 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18199 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18200 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18201 }
18202}
18203
18204/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18205struct WordBreakingTokenizer<'a> {
18206 input: &'a str,
18207}
18208
18209impl<'a> WordBreakingTokenizer<'a> {
18210 fn new(input: &'a str) -> Self {
18211 Self { input }
18212 }
18213}
18214
18215fn is_char_ideographic(ch: char) -> bool {
18216 use unicode_script::Script::*;
18217 use unicode_script::UnicodeScript;
18218 matches!(ch.script(), Han | Tangut | Yi)
18219}
18220
18221fn is_grapheme_ideographic(text: &str) -> bool {
18222 text.chars().any(is_char_ideographic)
18223}
18224
18225fn is_grapheme_whitespace(text: &str) -> bool {
18226 text.chars().any(|x| x.is_whitespace())
18227}
18228
18229fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18230 text.chars().next().map_or(false, |ch| {
18231 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18232 })
18233}
18234
18235#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18236enum WordBreakToken<'a> {
18237 Word { token: &'a str, grapheme_len: usize },
18238 InlineWhitespace { token: &'a str, grapheme_len: usize },
18239 Newline,
18240}
18241
18242impl<'a> Iterator for WordBreakingTokenizer<'a> {
18243 /// Yields a span, the count of graphemes in the token, and whether it was
18244 /// whitespace. Note that it also breaks at word boundaries.
18245 type Item = WordBreakToken<'a>;
18246
18247 fn next(&mut self) -> Option<Self::Item> {
18248 use unicode_segmentation::UnicodeSegmentation;
18249 if self.input.is_empty() {
18250 return None;
18251 }
18252
18253 let mut iter = self.input.graphemes(true).peekable();
18254 let mut offset = 0;
18255 let mut grapheme_len = 0;
18256 if let Some(first_grapheme) = iter.next() {
18257 let is_newline = first_grapheme == "\n";
18258 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18259 offset += first_grapheme.len();
18260 grapheme_len += 1;
18261 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18262 if let Some(grapheme) = iter.peek().copied() {
18263 if should_stay_with_preceding_ideograph(grapheme) {
18264 offset += grapheme.len();
18265 grapheme_len += 1;
18266 }
18267 }
18268 } else {
18269 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18270 let mut next_word_bound = words.peek().copied();
18271 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18272 next_word_bound = words.next();
18273 }
18274 while let Some(grapheme) = iter.peek().copied() {
18275 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18276 break;
18277 };
18278 if is_grapheme_whitespace(grapheme) != is_whitespace
18279 || (grapheme == "\n") != is_newline
18280 {
18281 break;
18282 };
18283 offset += grapheme.len();
18284 grapheme_len += 1;
18285 iter.next();
18286 }
18287 }
18288 let token = &self.input[..offset];
18289 self.input = &self.input[offset..];
18290 if token == "\n" {
18291 Some(WordBreakToken::Newline)
18292 } else if is_whitespace {
18293 Some(WordBreakToken::InlineWhitespace {
18294 token,
18295 grapheme_len,
18296 })
18297 } else {
18298 Some(WordBreakToken::Word {
18299 token,
18300 grapheme_len,
18301 })
18302 }
18303 } else {
18304 None
18305 }
18306 }
18307}
18308
18309#[test]
18310fn test_word_breaking_tokenizer() {
18311 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18312 ("", &[]),
18313 (" ", &[whitespace(" ", 2)]),
18314 ("Ʒ", &[word("Ʒ", 1)]),
18315 ("Ǽ", &[word("Ǽ", 1)]),
18316 ("⋑", &[word("⋑", 1)]),
18317 ("⋑⋑", &[word("⋑⋑", 2)]),
18318 (
18319 "原理,进而",
18320 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18321 ),
18322 (
18323 "hello world",
18324 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18325 ),
18326 (
18327 "hello, world",
18328 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18329 ),
18330 (
18331 " hello world",
18332 &[
18333 whitespace(" ", 2),
18334 word("hello", 5),
18335 whitespace(" ", 1),
18336 word("world", 5),
18337 ],
18338 ),
18339 (
18340 "这是什么 \n 钢笔",
18341 &[
18342 word("这", 1),
18343 word("是", 1),
18344 word("什", 1),
18345 word("么", 1),
18346 whitespace(" ", 1),
18347 newline(),
18348 whitespace(" ", 1),
18349 word("钢", 1),
18350 word("笔", 1),
18351 ],
18352 ),
18353 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
18354 ];
18355
18356 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18357 WordBreakToken::Word {
18358 token,
18359 grapheme_len,
18360 }
18361 }
18362
18363 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18364 WordBreakToken::InlineWhitespace {
18365 token,
18366 grapheme_len,
18367 }
18368 }
18369
18370 fn newline() -> WordBreakToken<'static> {
18371 WordBreakToken::Newline
18372 }
18373
18374 for (input, result) in tests {
18375 assert_eq!(
18376 WordBreakingTokenizer::new(input)
18377 .collect::<Vec<_>>()
18378 .as_slice(),
18379 *result,
18380 );
18381 }
18382}
18383
18384fn wrap_with_prefix(
18385 line_prefix: String,
18386 unwrapped_text: String,
18387 wrap_column: usize,
18388 tab_size: NonZeroU32,
18389 preserve_existing_whitespace: bool,
18390) -> String {
18391 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18392 let mut wrapped_text = String::new();
18393 let mut current_line = line_prefix.clone();
18394
18395 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18396 let mut current_line_len = line_prefix_len;
18397 let mut in_whitespace = false;
18398 for token in tokenizer {
18399 let have_preceding_whitespace = in_whitespace;
18400 match token {
18401 WordBreakToken::Word {
18402 token,
18403 grapheme_len,
18404 } => {
18405 in_whitespace = false;
18406 if current_line_len + grapheme_len > wrap_column
18407 && current_line_len != line_prefix_len
18408 {
18409 wrapped_text.push_str(current_line.trim_end());
18410 wrapped_text.push('\n');
18411 current_line.truncate(line_prefix.len());
18412 current_line_len = line_prefix_len;
18413 }
18414 current_line.push_str(token);
18415 current_line_len += grapheme_len;
18416 }
18417 WordBreakToken::InlineWhitespace {
18418 mut token,
18419 mut grapheme_len,
18420 } => {
18421 in_whitespace = true;
18422 if have_preceding_whitespace && !preserve_existing_whitespace {
18423 continue;
18424 }
18425 if !preserve_existing_whitespace {
18426 token = " ";
18427 grapheme_len = 1;
18428 }
18429 if current_line_len + grapheme_len > wrap_column {
18430 wrapped_text.push_str(current_line.trim_end());
18431 wrapped_text.push('\n');
18432 current_line.truncate(line_prefix.len());
18433 current_line_len = line_prefix_len;
18434 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18435 current_line.push_str(token);
18436 current_line_len += grapheme_len;
18437 }
18438 }
18439 WordBreakToken::Newline => {
18440 in_whitespace = true;
18441 if preserve_existing_whitespace {
18442 wrapped_text.push_str(current_line.trim_end());
18443 wrapped_text.push('\n');
18444 current_line.truncate(line_prefix.len());
18445 current_line_len = line_prefix_len;
18446 } else if have_preceding_whitespace {
18447 continue;
18448 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18449 {
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 current_line_len != line_prefix_len {
18455 current_line.push(' ');
18456 current_line_len += 1;
18457 }
18458 }
18459 }
18460 }
18461
18462 if !current_line.is_empty() {
18463 wrapped_text.push_str(¤t_line);
18464 }
18465 wrapped_text
18466}
18467
18468#[test]
18469fn test_wrap_with_prefix() {
18470 assert_eq!(
18471 wrap_with_prefix(
18472 "# ".to_string(),
18473 "abcdefg".to_string(),
18474 4,
18475 NonZeroU32::new(4).unwrap(),
18476 false,
18477 ),
18478 "# abcdefg"
18479 );
18480 assert_eq!(
18481 wrap_with_prefix(
18482 "".to_string(),
18483 "\thello world".to_string(),
18484 8,
18485 NonZeroU32::new(4).unwrap(),
18486 false,
18487 ),
18488 "hello\nworld"
18489 );
18490 assert_eq!(
18491 wrap_with_prefix(
18492 "// ".to_string(),
18493 "xx \nyy zz aa bb cc".to_string(),
18494 12,
18495 NonZeroU32::new(4).unwrap(),
18496 false,
18497 ),
18498 "// xx yy zz\n// aa bb cc"
18499 );
18500 assert_eq!(
18501 wrap_with_prefix(
18502 String::new(),
18503 "这是什么 \n 钢笔".to_string(),
18504 3,
18505 NonZeroU32::new(4).unwrap(),
18506 false,
18507 ),
18508 "这是什\n么 钢\n笔"
18509 );
18510}
18511
18512pub trait CollaborationHub {
18513 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18514 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18515 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18516}
18517
18518impl CollaborationHub for Entity<Project> {
18519 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18520 self.read(cx).collaborators()
18521 }
18522
18523 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18524 self.read(cx).user_store().read(cx).participant_indices()
18525 }
18526
18527 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18528 let this = self.read(cx);
18529 let user_ids = this.collaborators().values().map(|c| c.user_id);
18530 this.user_store().read_with(cx, |user_store, cx| {
18531 user_store.participant_names(user_ids, cx)
18532 })
18533 }
18534}
18535
18536pub trait SemanticsProvider {
18537 fn hover(
18538 &self,
18539 buffer: &Entity<Buffer>,
18540 position: text::Anchor,
18541 cx: &mut App,
18542 ) -> Option<Task<Vec<project::Hover>>>;
18543
18544 fn inlay_hints(
18545 &self,
18546 buffer_handle: Entity<Buffer>,
18547 range: Range<text::Anchor>,
18548 cx: &mut App,
18549 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18550
18551 fn resolve_inlay_hint(
18552 &self,
18553 hint: InlayHint,
18554 buffer_handle: Entity<Buffer>,
18555 server_id: LanguageServerId,
18556 cx: &mut App,
18557 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18558
18559 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18560
18561 fn document_highlights(
18562 &self,
18563 buffer: &Entity<Buffer>,
18564 position: text::Anchor,
18565 cx: &mut App,
18566 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18567
18568 fn definitions(
18569 &self,
18570 buffer: &Entity<Buffer>,
18571 position: text::Anchor,
18572 kind: GotoDefinitionKind,
18573 cx: &mut App,
18574 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18575
18576 fn range_for_rename(
18577 &self,
18578 buffer: &Entity<Buffer>,
18579 position: text::Anchor,
18580 cx: &mut App,
18581 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18582
18583 fn perform_rename(
18584 &self,
18585 buffer: &Entity<Buffer>,
18586 position: text::Anchor,
18587 new_name: String,
18588 cx: &mut App,
18589 ) -> Option<Task<Result<ProjectTransaction>>>;
18590}
18591
18592pub trait CompletionProvider {
18593 fn completions(
18594 &self,
18595 excerpt_id: ExcerptId,
18596 buffer: &Entity<Buffer>,
18597 buffer_position: text::Anchor,
18598 trigger: CompletionContext,
18599 window: &mut Window,
18600 cx: &mut Context<Editor>,
18601 ) -> Task<Result<Option<Vec<Completion>>>>;
18602
18603 fn resolve_completions(
18604 &self,
18605 buffer: Entity<Buffer>,
18606 completion_indices: Vec<usize>,
18607 completions: Rc<RefCell<Box<[Completion]>>>,
18608 cx: &mut Context<Editor>,
18609 ) -> Task<Result<bool>>;
18610
18611 fn apply_additional_edits_for_completion(
18612 &self,
18613 _buffer: Entity<Buffer>,
18614 _completions: Rc<RefCell<Box<[Completion]>>>,
18615 _completion_index: usize,
18616 _push_to_history: bool,
18617 _cx: &mut Context<Editor>,
18618 ) -> Task<Result<Option<language::Transaction>>> {
18619 Task::ready(Ok(None))
18620 }
18621
18622 fn is_completion_trigger(
18623 &self,
18624 buffer: &Entity<Buffer>,
18625 position: language::Anchor,
18626 text: &str,
18627 trigger_in_words: bool,
18628 cx: &mut Context<Editor>,
18629 ) -> bool;
18630
18631 fn sort_completions(&self) -> bool {
18632 true
18633 }
18634
18635 fn filter_completions(&self) -> bool {
18636 true
18637 }
18638}
18639
18640pub trait CodeActionProvider {
18641 fn id(&self) -> Arc<str>;
18642
18643 fn code_actions(
18644 &self,
18645 buffer: &Entity<Buffer>,
18646 range: Range<text::Anchor>,
18647 window: &mut Window,
18648 cx: &mut App,
18649 ) -> Task<Result<Vec<CodeAction>>>;
18650
18651 fn apply_code_action(
18652 &self,
18653 buffer_handle: Entity<Buffer>,
18654 action: CodeAction,
18655 excerpt_id: ExcerptId,
18656 push_to_history: bool,
18657 window: &mut Window,
18658 cx: &mut App,
18659 ) -> Task<Result<ProjectTransaction>>;
18660}
18661
18662impl CodeActionProvider for Entity<Project> {
18663 fn id(&self) -> Arc<str> {
18664 "project".into()
18665 }
18666
18667 fn code_actions(
18668 &self,
18669 buffer: &Entity<Buffer>,
18670 range: Range<text::Anchor>,
18671 _window: &mut Window,
18672 cx: &mut App,
18673 ) -> Task<Result<Vec<CodeAction>>> {
18674 self.update(cx, |project, cx| {
18675 let code_lens = project.code_lens(buffer, range.clone(), cx);
18676 let code_actions = project.code_actions(buffer, range, None, cx);
18677 cx.background_spawn(async move {
18678 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18679 Ok(code_lens
18680 .context("code lens fetch")?
18681 .into_iter()
18682 .chain(code_actions.context("code action fetch")?)
18683 .collect())
18684 })
18685 })
18686 }
18687
18688 fn apply_code_action(
18689 &self,
18690 buffer_handle: Entity<Buffer>,
18691 action: CodeAction,
18692 _excerpt_id: ExcerptId,
18693 push_to_history: bool,
18694 _window: &mut Window,
18695 cx: &mut App,
18696 ) -> Task<Result<ProjectTransaction>> {
18697 self.update(cx, |project, cx| {
18698 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18699 })
18700 }
18701}
18702
18703fn snippet_completions(
18704 project: &Project,
18705 buffer: &Entity<Buffer>,
18706 buffer_position: text::Anchor,
18707 cx: &mut App,
18708) -> Task<Result<Vec<Completion>>> {
18709 let language = buffer.read(cx).language_at(buffer_position);
18710 let language_name = language.as_ref().map(|language| language.lsp_id());
18711 let snippet_store = project.snippets().read(cx);
18712 let snippets = snippet_store.snippets_for(language_name, cx);
18713
18714 if snippets.is_empty() {
18715 return Task::ready(Ok(vec![]));
18716 }
18717 let snapshot = buffer.read(cx).text_snapshot();
18718 let chars: String = snapshot
18719 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18720 .collect();
18721
18722 let scope = language.map(|language| language.default_scope());
18723 let executor = cx.background_executor().clone();
18724
18725 cx.background_spawn(async move {
18726 let classifier = CharClassifier::new(scope).for_completion(true);
18727 let mut last_word = chars
18728 .chars()
18729 .take_while(|c| classifier.is_word(*c))
18730 .collect::<String>();
18731 last_word = last_word.chars().rev().collect();
18732
18733 if last_word.is_empty() {
18734 return Ok(vec![]);
18735 }
18736
18737 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18738 let to_lsp = |point: &text::Anchor| {
18739 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18740 point_to_lsp(end)
18741 };
18742 let lsp_end = to_lsp(&buffer_position);
18743
18744 let candidates = snippets
18745 .iter()
18746 .enumerate()
18747 .flat_map(|(ix, snippet)| {
18748 snippet
18749 .prefix
18750 .iter()
18751 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18752 })
18753 .collect::<Vec<StringMatchCandidate>>();
18754
18755 let mut matches = fuzzy::match_strings(
18756 &candidates,
18757 &last_word,
18758 last_word.chars().any(|c| c.is_uppercase()),
18759 100,
18760 &Default::default(),
18761 executor,
18762 )
18763 .await;
18764
18765 // Remove all candidates where the query's start does not match the start of any word in the candidate
18766 if let Some(query_start) = last_word.chars().next() {
18767 matches.retain(|string_match| {
18768 split_words(&string_match.string).any(|word| {
18769 // Check that the first codepoint of the word as lowercase matches the first
18770 // codepoint of the query as lowercase
18771 word.chars()
18772 .flat_map(|codepoint| codepoint.to_lowercase())
18773 .zip(query_start.to_lowercase())
18774 .all(|(word_cp, query_cp)| word_cp == query_cp)
18775 })
18776 });
18777 }
18778
18779 let matched_strings = matches
18780 .into_iter()
18781 .map(|m| m.string)
18782 .collect::<HashSet<_>>();
18783
18784 let result: Vec<Completion> = snippets
18785 .into_iter()
18786 .filter_map(|snippet| {
18787 let matching_prefix = snippet
18788 .prefix
18789 .iter()
18790 .find(|prefix| matched_strings.contains(*prefix))?;
18791 let start = as_offset - last_word.len();
18792 let start = snapshot.anchor_before(start);
18793 let range = start..buffer_position;
18794 let lsp_start = to_lsp(&start);
18795 let lsp_range = lsp::Range {
18796 start: lsp_start,
18797 end: lsp_end,
18798 };
18799 Some(Completion {
18800 replace_range: range,
18801 new_text: snippet.body.clone(),
18802 source: CompletionSource::Lsp {
18803 insert_range: None,
18804 server_id: LanguageServerId(usize::MAX),
18805 resolved: true,
18806 lsp_completion: Box::new(lsp::CompletionItem {
18807 label: snippet.prefix.first().unwrap().clone(),
18808 kind: Some(CompletionItemKind::SNIPPET),
18809 label_details: snippet.description.as_ref().map(|description| {
18810 lsp::CompletionItemLabelDetails {
18811 detail: Some(description.clone()),
18812 description: None,
18813 }
18814 }),
18815 insert_text_format: Some(InsertTextFormat::SNIPPET),
18816 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18817 lsp::InsertReplaceEdit {
18818 new_text: snippet.body.clone(),
18819 insert: lsp_range,
18820 replace: lsp_range,
18821 },
18822 )),
18823 filter_text: Some(snippet.body.clone()),
18824 sort_text: Some(char::MAX.to_string()),
18825 ..lsp::CompletionItem::default()
18826 }),
18827 lsp_defaults: None,
18828 },
18829 label: CodeLabel {
18830 text: matching_prefix.clone(),
18831 runs: Vec::new(),
18832 filter_range: 0..matching_prefix.len(),
18833 },
18834 icon_path: None,
18835 documentation: snippet
18836 .description
18837 .clone()
18838 .map(|description| CompletionDocumentation::SingleLine(description.into())),
18839 insert_text_mode: None,
18840 confirm: None,
18841 })
18842 })
18843 .collect();
18844
18845 Ok(result)
18846 })
18847}
18848
18849impl CompletionProvider for Entity<Project> {
18850 fn completions(
18851 &self,
18852 _excerpt_id: ExcerptId,
18853 buffer: &Entity<Buffer>,
18854 buffer_position: text::Anchor,
18855 options: CompletionContext,
18856 _window: &mut Window,
18857 cx: &mut Context<Editor>,
18858 ) -> Task<Result<Option<Vec<Completion>>>> {
18859 self.update(cx, |project, cx| {
18860 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18861 let project_completions = project.completions(buffer, buffer_position, options, cx);
18862 cx.background_spawn(async move {
18863 let snippets_completions = snippets.await?;
18864 match project_completions.await? {
18865 Some(mut completions) => {
18866 completions.extend(snippets_completions);
18867 Ok(Some(completions))
18868 }
18869 None => {
18870 if snippets_completions.is_empty() {
18871 Ok(None)
18872 } else {
18873 Ok(Some(snippets_completions))
18874 }
18875 }
18876 }
18877 })
18878 })
18879 }
18880
18881 fn resolve_completions(
18882 &self,
18883 buffer: Entity<Buffer>,
18884 completion_indices: Vec<usize>,
18885 completions: Rc<RefCell<Box<[Completion]>>>,
18886 cx: &mut Context<Editor>,
18887 ) -> Task<Result<bool>> {
18888 self.update(cx, |project, cx| {
18889 project.lsp_store().update(cx, |lsp_store, cx| {
18890 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18891 })
18892 })
18893 }
18894
18895 fn apply_additional_edits_for_completion(
18896 &self,
18897 buffer: Entity<Buffer>,
18898 completions: Rc<RefCell<Box<[Completion]>>>,
18899 completion_index: usize,
18900 push_to_history: bool,
18901 cx: &mut Context<Editor>,
18902 ) -> Task<Result<Option<language::Transaction>>> {
18903 self.update(cx, |project, cx| {
18904 project.lsp_store().update(cx, |lsp_store, cx| {
18905 lsp_store.apply_additional_edits_for_completion(
18906 buffer,
18907 completions,
18908 completion_index,
18909 push_to_history,
18910 cx,
18911 )
18912 })
18913 })
18914 }
18915
18916 fn is_completion_trigger(
18917 &self,
18918 buffer: &Entity<Buffer>,
18919 position: language::Anchor,
18920 text: &str,
18921 trigger_in_words: bool,
18922 cx: &mut Context<Editor>,
18923 ) -> bool {
18924 let mut chars = text.chars();
18925 let char = if let Some(char) = chars.next() {
18926 char
18927 } else {
18928 return false;
18929 };
18930 if chars.next().is_some() {
18931 return false;
18932 }
18933
18934 let buffer = buffer.read(cx);
18935 let snapshot = buffer.snapshot();
18936 if !snapshot.settings_at(position, cx).show_completions_on_input {
18937 return false;
18938 }
18939 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18940 if trigger_in_words && classifier.is_word(char) {
18941 return true;
18942 }
18943
18944 buffer.completion_triggers().contains(text)
18945 }
18946}
18947
18948impl SemanticsProvider for Entity<Project> {
18949 fn hover(
18950 &self,
18951 buffer: &Entity<Buffer>,
18952 position: text::Anchor,
18953 cx: &mut App,
18954 ) -> Option<Task<Vec<project::Hover>>> {
18955 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18956 }
18957
18958 fn document_highlights(
18959 &self,
18960 buffer: &Entity<Buffer>,
18961 position: text::Anchor,
18962 cx: &mut App,
18963 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18964 Some(self.update(cx, |project, cx| {
18965 project.document_highlights(buffer, position, cx)
18966 }))
18967 }
18968
18969 fn definitions(
18970 &self,
18971 buffer: &Entity<Buffer>,
18972 position: text::Anchor,
18973 kind: GotoDefinitionKind,
18974 cx: &mut App,
18975 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18976 Some(self.update(cx, |project, cx| match kind {
18977 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18978 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18979 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18980 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18981 }))
18982 }
18983
18984 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18985 // TODO: make this work for remote projects
18986 self.update(cx, |this, cx| {
18987 buffer.update(cx, |buffer, cx| {
18988 this.any_language_server_supports_inlay_hints(buffer, cx)
18989 })
18990 })
18991 }
18992
18993 fn inlay_hints(
18994 &self,
18995 buffer_handle: Entity<Buffer>,
18996 range: Range<text::Anchor>,
18997 cx: &mut App,
18998 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
18999 Some(self.update(cx, |project, cx| {
19000 project.inlay_hints(buffer_handle, range, cx)
19001 }))
19002 }
19003
19004 fn resolve_inlay_hint(
19005 &self,
19006 hint: InlayHint,
19007 buffer_handle: Entity<Buffer>,
19008 server_id: LanguageServerId,
19009 cx: &mut App,
19010 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19011 Some(self.update(cx, |project, cx| {
19012 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19013 }))
19014 }
19015
19016 fn range_for_rename(
19017 &self,
19018 buffer: &Entity<Buffer>,
19019 position: text::Anchor,
19020 cx: &mut App,
19021 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19022 Some(self.update(cx, |project, cx| {
19023 let buffer = buffer.clone();
19024 let task = project.prepare_rename(buffer.clone(), position, cx);
19025 cx.spawn(async move |_, cx| {
19026 Ok(match task.await? {
19027 PrepareRenameResponse::Success(range) => Some(range),
19028 PrepareRenameResponse::InvalidPosition => None,
19029 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19030 // Fallback on using TreeSitter info to determine identifier range
19031 buffer.update(cx, |buffer, _| {
19032 let snapshot = buffer.snapshot();
19033 let (range, kind) = snapshot.surrounding_word(position);
19034 if kind != Some(CharKind::Word) {
19035 return None;
19036 }
19037 Some(
19038 snapshot.anchor_before(range.start)
19039 ..snapshot.anchor_after(range.end),
19040 )
19041 })?
19042 }
19043 })
19044 })
19045 }))
19046 }
19047
19048 fn perform_rename(
19049 &self,
19050 buffer: &Entity<Buffer>,
19051 position: text::Anchor,
19052 new_name: String,
19053 cx: &mut App,
19054 ) -> Option<Task<Result<ProjectTransaction>>> {
19055 Some(self.update(cx, |project, cx| {
19056 project.perform_rename(buffer.clone(), position, new_name, cx)
19057 }))
19058 }
19059}
19060
19061fn inlay_hint_settings(
19062 location: Anchor,
19063 snapshot: &MultiBufferSnapshot,
19064 cx: &mut Context<Editor>,
19065) -> InlayHintSettings {
19066 let file = snapshot.file_at(location);
19067 let language = snapshot.language_at(location).map(|l| l.name());
19068 language_settings(language, file, cx).inlay_hints
19069}
19070
19071fn consume_contiguous_rows(
19072 contiguous_row_selections: &mut Vec<Selection<Point>>,
19073 selection: &Selection<Point>,
19074 display_map: &DisplaySnapshot,
19075 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19076) -> (MultiBufferRow, MultiBufferRow) {
19077 contiguous_row_selections.push(selection.clone());
19078 let start_row = MultiBufferRow(selection.start.row);
19079 let mut end_row = ending_row(selection, display_map);
19080
19081 while let Some(next_selection) = selections.peek() {
19082 if next_selection.start.row <= end_row.0 {
19083 end_row = ending_row(next_selection, display_map);
19084 contiguous_row_selections.push(selections.next().unwrap().clone());
19085 } else {
19086 break;
19087 }
19088 }
19089 (start_row, end_row)
19090}
19091
19092fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19093 if next_selection.end.column > 0 || next_selection.is_empty() {
19094 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19095 } else {
19096 MultiBufferRow(next_selection.end.row)
19097 }
19098}
19099
19100impl EditorSnapshot {
19101 pub fn remote_selections_in_range<'a>(
19102 &'a self,
19103 range: &'a Range<Anchor>,
19104 collaboration_hub: &dyn CollaborationHub,
19105 cx: &'a App,
19106 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19107 let participant_names = collaboration_hub.user_names(cx);
19108 let participant_indices = collaboration_hub.user_participant_indices(cx);
19109 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19110 let collaborators_by_replica_id = collaborators_by_peer_id
19111 .iter()
19112 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19113 .collect::<HashMap<_, _>>();
19114 self.buffer_snapshot
19115 .selections_in_range(range, false)
19116 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19117 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19118 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19119 let user_name = participant_names.get(&collaborator.user_id).cloned();
19120 Some(RemoteSelection {
19121 replica_id,
19122 selection,
19123 cursor_shape,
19124 line_mode,
19125 participant_index,
19126 peer_id: collaborator.peer_id,
19127 user_name,
19128 })
19129 })
19130 }
19131
19132 pub fn hunks_for_ranges(
19133 &self,
19134 ranges: impl IntoIterator<Item = Range<Point>>,
19135 ) -> Vec<MultiBufferDiffHunk> {
19136 let mut hunks = Vec::new();
19137 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19138 HashMap::default();
19139 for query_range in ranges {
19140 let query_rows =
19141 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19142 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19143 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19144 ) {
19145 // Include deleted hunks that are adjacent to the query range, because
19146 // otherwise they would be missed.
19147 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19148 if hunk.status().is_deleted() {
19149 intersects_range |= hunk.row_range.start == query_rows.end;
19150 intersects_range |= hunk.row_range.end == query_rows.start;
19151 }
19152 if intersects_range {
19153 if !processed_buffer_rows
19154 .entry(hunk.buffer_id)
19155 .or_default()
19156 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19157 {
19158 continue;
19159 }
19160 hunks.push(hunk);
19161 }
19162 }
19163 }
19164
19165 hunks
19166 }
19167
19168 fn display_diff_hunks_for_rows<'a>(
19169 &'a self,
19170 display_rows: Range<DisplayRow>,
19171 folded_buffers: &'a HashSet<BufferId>,
19172 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19173 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19174 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19175
19176 self.buffer_snapshot
19177 .diff_hunks_in_range(buffer_start..buffer_end)
19178 .filter_map(|hunk| {
19179 if folded_buffers.contains(&hunk.buffer_id) {
19180 return None;
19181 }
19182
19183 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19184 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19185
19186 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19187 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19188
19189 let display_hunk = if hunk_display_start.column() != 0 {
19190 DisplayDiffHunk::Folded {
19191 display_row: hunk_display_start.row(),
19192 }
19193 } else {
19194 let mut end_row = hunk_display_end.row();
19195 if hunk_display_end.column() > 0 {
19196 end_row.0 += 1;
19197 }
19198 let is_created_file = hunk.is_created_file();
19199 DisplayDiffHunk::Unfolded {
19200 status: hunk.status(),
19201 diff_base_byte_range: hunk.diff_base_byte_range,
19202 display_row_range: hunk_display_start.row()..end_row,
19203 multi_buffer_range: Anchor::range_in_buffer(
19204 hunk.excerpt_id,
19205 hunk.buffer_id,
19206 hunk.buffer_range,
19207 ),
19208 is_created_file,
19209 }
19210 };
19211
19212 Some(display_hunk)
19213 })
19214 }
19215
19216 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19217 self.display_snapshot.buffer_snapshot.language_at(position)
19218 }
19219
19220 pub fn is_focused(&self) -> bool {
19221 self.is_focused
19222 }
19223
19224 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19225 self.placeholder_text.as_ref()
19226 }
19227
19228 pub fn scroll_position(&self) -> gpui::Point<f32> {
19229 self.scroll_anchor.scroll_position(&self.display_snapshot)
19230 }
19231
19232 fn gutter_dimensions(
19233 &self,
19234 font_id: FontId,
19235 font_size: Pixels,
19236 max_line_number_width: Pixels,
19237 cx: &App,
19238 ) -> Option<GutterDimensions> {
19239 if !self.show_gutter {
19240 return None;
19241 }
19242
19243 let descent = cx.text_system().descent(font_id, font_size);
19244 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19245 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19246
19247 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19248 matches!(
19249 ProjectSettings::get_global(cx).git.git_gutter,
19250 Some(GitGutterSetting::TrackedFiles)
19251 )
19252 });
19253 let gutter_settings = EditorSettings::get_global(cx).gutter;
19254 let show_line_numbers = self
19255 .show_line_numbers
19256 .unwrap_or(gutter_settings.line_numbers);
19257 let line_gutter_width = if show_line_numbers {
19258 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19259 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19260 max_line_number_width.max(min_width_for_number_on_gutter)
19261 } else {
19262 0.0.into()
19263 };
19264
19265 let show_code_actions = self
19266 .show_code_actions
19267 .unwrap_or(gutter_settings.code_actions);
19268
19269 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19270 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19271
19272 let git_blame_entries_width =
19273 self.git_blame_gutter_max_author_length
19274 .map(|max_author_length| {
19275 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19276 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19277
19278 /// The number of characters to dedicate to gaps and margins.
19279 const SPACING_WIDTH: usize = 4;
19280
19281 let max_char_count = max_author_length.min(renderer.max_author_length())
19282 + ::git::SHORT_SHA_LENGTH
19283 + MAX_RELATIVE_TIMESTAMP.len()
19284 + SPACING_WIDTH;
19285
19286 em_advance * max_char_count
19287 });
19288
19289 let is_singleton = self.buffer_snapshot.is_singleton();
19290
19291 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19292 left_padding += if !is_singleton {
19293 em_width * 4.0
19294 } else if show_code_actions || show_runnables || show_breakpoints {
19295 em_width * 3.0
19296 } else if show_git_gutter && show_line_numbers {
19297 em_width * 2.0
19298 } else if show_git_gutter || show_line_numbers {
19299 em_width
19300 } else {
19301 px(0.)
19302 };
19303
19304 let shows_folds = is_singleton && gutter_settings.folds;
19305
19306 let right_padding = if shows_folds && show_line_numbers {
19307 em_width * 4.0
19308 } else if shows_folds || (!is_singleton && show_line_numbers) {
19309 em_width * 3.0
19310 } else if show_line_numbers {
19311 em_width
19312 } else {
19313 px(0.)
19314 };
19315
19316 Some(GutterDimensions {
19317 left_padding,
19318 right_padding,
19319 width: line_gutter_width + left_padding + right_padding,
19320 margin: -descent,
19321 git_blame_entries_width,
19322 })
19323 }
19324
19325 pub fn render_crease_toggle(
19326 &self,
19327 buffer_row: MultiBufferRow,
19328 row_contains_cursor: bool,
19329 editor: Entity<Editor>,
19330 window: &mut Window,
19331 cx: &mut App,
19332 ) -> Option<AnyElement> {
19333 let folded = self.is_line_folded(buffer_row);
19334 let mut is_foldable = false;
19335
19336 if let Some(crease) = self
19337 .crease_snapshot
19338 .query_row(buffer_row, &self.buffer_snapshot)
19339 {
19340 is_foldable = true;
19341 match crease {
19342 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
19343 if let Some(render_toggle) = render_toggle {
19344 let toggle_callback =
19345 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
19346 if folded {
19347 editor.update(cx, |editor, cx| {
19348 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
19349 });
19350 } else {
19351 editor.update(cx, |editor, cx| {
19352 editor.unfold_at(
19353 &crate::UnfoldAt { buffer_row },
19354 window,
19355 cx,
19356 )
19357 });
19358 }
19359 });
19360 return Some((render_toggle)(
19361 buffer_row,
19362 folded,
19363 toggle_callback,
19364 window,
19365 cx,
19366 ));
19367 }
19368 }
19369 }
19370 }
19371
19372 is_foldable |= self.starts_indent(buffer_row);
19373
19374 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19375 Some(
19376 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19377 .toggle_state(folded)
19378 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19379 if folded {
19380 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
19381 } else {
19382 this.fold_at(&FoldAt { buffer_row }, window, cx);
19383 }
19384 }))
19385 .into_any_element(),
19386 )
19387 } else {
19388 None
19389 }
19390 }
19391
19392 pub fn render_crease_trailer(
19393 &self,
19394 buffer_row: MultiBufferRow,
19395 window: &mut Window,
19396 cx: &mut App,
19397 ) -> Option<AnyElement> {
19398 let folded = self.is_line_folded(buffer_row);
19399 if let Crease::Inline { render_trailer, .. } = self
19400 .crease_snapshot
19401 .query_row(buffer_row, &self.buffer_snapshot)?
19402 {
19403 let render_trailer = render_trailer.as_ref()?;
19404 Some(render_trailer(buffer_row, folded, window, cx))
19405 } else {
19406 None
19407 }
19408 }
19409}
19410
19411impl Deref for EditorSnapshot {
19412 type Target = DisplaySnapshot;
19413
19414 fn deref(&self) -> &Self::Target {
19415 &self.display_snapshot
19416 }
19417}
19418
19419#[derive(Clone, Debug, PartialEq, Eq)]
19420pub enum EditorEvent {
19421 InputIgnored {
19422 text: Arc<str>,
19423 },
19424 InputHandled {
19425 utf16_range_to_replace: Option<Range<isize>>,
19426 text: Arc<str>,
19427 },
19428 ExcerptsAdded {
19429 buffer: Entity<Buffer>,
19430 predecessor: ExcerptId,
19431 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19432 },
19433 ExcerptsRemoved {
19434 ids: Vec<ExcerptId>,
19435 },
19436 BufferFoldToggled {
19437 ids: Vec<ExcerptId>,
19438 folded: bool,
19439 },
19440 ExcerptsEdited {
19441 ids: Vec<ExcerptId>,
19442 },
19443 ExcerptsExpanded {
19444 ids: Vec<ExcerptId>,
19445 },
19446 BufferEdited,
19447 Edited {
19448 transaction_id: clock::Lamport,
19449 },
19450 Reparsed(BufferId),
19451 Focused,
19452 FocusedIn,
19453 Blurred,
19454 DirtyChanged,
19455 Saved,
19456 TitleChanged,
19457 DiffBaseChanged,
19458 SelectionsChanged {
19459 local: bool,
19460 },
19461 ScrollPositionChanged {
19462 local: bool,
19463 autoscroll: bool,
19464 },
19465 Closed,
19466 TransactionUndone {
19467 transaction_id: clock::Lamport,
19468 },
19469 TransactionBegun {
19470 transaction_id: clock::Lamport,
19471 },
19472 Reloaded,
19473 CursorShapeChanged,
19474 PushedToNavHistory {
19475 anchor: Anchor,
19476 is_deactivate: bool,
19477 },
19478}
19479
19480impl EventEmitter<EditorEvent> for Editor {}
19481
19482impl Focusable for Editor {
19483 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19484 self.focus_handle.clone()
19485 }
19486}
19487
19488impl Render for Editor {
19489 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19490 let settings = ThemeSettings::get_global(cx);
19491
19492 let mut text_style = match self.mode {
19493 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19494 color: cx.theme().colors().editor_foreground,
19495 font_family: settings.ui_font.family.clone(),
19496 font_features: settings.ui_font.features.clone(),
19497 font_fallbacks: settings.ui_font.fallbacks.clone(),
19498 font_size: rems(0.875).into(),
19499 font_weight: settings.ui_font.weight,
19500 line_height: relative(settings.buffer_line_height.value()),
19501 ..Default::default()
19502 },
19503 EditorMode::Full => TextStyle {
19504 color: cx.theme().colors().editor_foreground,
19505 font_family: settings.buffer_font.family.clone(),
19506 font_features: settings.buffer_font.features.clone(),
19507 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19508 font_size: settings.buffer_font_size(cx).into(),
19509 font_weight: settings.buffer_font.weight,
19510 line_height: relative(settings.buffer_line_height.value()),
19511 ..Default::default()
19512 },
19513 };
19514 if let Some(text_style_refinement) = &self.text_style_refinement {
19515 text_style.refine(text_style_refinement)
19516 }
19517
19518 let background = match self.mode {
19519 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19520 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19521 EditorMode::Full => cx.theme().colors().editor_background,
19522 };
19523
19524 EditorElement::new(
19525 &cx.entity(),
19526 EditorStyle {
19527 background,
19528 local_player: cx.theme().players().local(),
19529 text: text_style,
19530 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19531 syntax: cx.theme().syntax().clone(),
19532 status: cx.theme().status().clone(),
19533 inlay_hints_style: make_inlay_hints_style(cx),
19534 inline_completion_styles: make_suggestion_styles(cx),
19535 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19536 },
19537 )
19538 }
19539}
19540
19541impl EntityInputHandler for Editor {
19542 fn text_for_range(
19543 &mut self,
19544 range_utf16: Range<usize>,
19545 adjusted_range: &mut Option<Range<usize>>,
19546 _: &mut Window,
19547 cx: &mut Context<Self>,
19548 ) -> Option<String> {
19549 let snapshot = self.buffer.read(cx).read(cx);
19550 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19551 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19552 if (start.0..end.0) != range_utf16 {
19553 adjusted_range.replace(start.0..end.0);
19554 }
19555 Some(snapshot.text_for_range(start..end).collect())
19556 }
19557
19558 fn selected_text_range(
19559 &mut self,
19560 ignore_disabled_input: bool,
19561 _: &mut Window,
19562 cx: &mut Context<Self>,
19563 ) -> Option<UTF16Selection> {
19564 // Prevent the IME menu from appearing when holding down an alphabetic key
19565 // while input is disabled.
19566 if !ignore_disabled_input && !self.input_enabled {
19567 return None;
19568 }
19569
19570 let selection = self.selections.newest::<OffsetUtf16>(cx);
19571 let range = selection.range();
19572
19573 Some(UTF16Selection {
19574 range: range.start.0..range.end.0,
19575 reversed: selection.reversed,
19576 })
19577 }
19578
19579 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19580 let snapshot = self.buffer.read(cx).read(cx);
19581 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19582 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19583 }
19584
19585 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19586 self.clear_highlights::<InputComposition>(cx);
19587 self.ime_transaction.take();
19588 }
19589
19590 fn replace_text_in_range(
19591 &mut self,
19592 range_utf16: Option<Range<usize>>,
19593 text: &str,
19594 window: &mut Window,
19595 cx: &mut Context<Self>,
19596 ) {
19597 if !self.input_enabled {
19598 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19599 return;
19600 }
19601
19602 self.transact(window, cx, |this, window, cx| {
19603 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19604 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19605 Some(this.selection_replacement_ranges(range_utf16, cx))
19606 } else {
19607 this.marked_text_ranges(cx)
19608 };
19609
19610 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19611 let newest_selection_id = this.selections.newest_anchor().id;
19612 this.selections
19613 .all::<OffsetUtf16>(cx)
19614 .iter()
19615 .zip(ranges_to_replace.iter())
19616 .find_map(|(selection, range)| {
19617 if selection.id == newest_selection_id {
19618 Some(
19619 (range.start.0 as isize - selection.head().0 as isize)
19620 ..(range.end.0 as isize - selection.head().0 as isize),
19621 )
19622 } else {
19623 None
19624 }
19625 })
19626 });
19627
19628 cx.emit(EditorEvent::InputHandled {
19629 utf16_range_to_replace: range_to_replace,
19630 text: text.into(),
19631 });
19632
19633 if let Some(new_selected_ranges) = new_selected_ranges {
19634 this.change_selections(None, window, cx, |selections| {
19635 selections.select_ranges(new_selected_ranges)
19636 });
19637 this.backspace(&Default::default(), window, cx);
19638 }
19639
19640 this.handle_input(text, window, cx);
19641 });
19642
19643 if let Some(transaction) = self.ime_transaction {
19644 self.buffer.update(cx, |buffer, cx| {
19645 buffer.group_until_transaction(transaction, cx);
19646 });
19647 }
19648
19649 self.unmark_text(window, cx);
19650 }
19651
19652 fn replace_and_mark_text_in_range(
19653 &mut self,
19654 range_utf16: Option<Range<usize>>,
19655 text: &str,
19656 new_selected_range_utf16: Option<Range<usize>>,
19657 window: &mut Window,
19658 cx: &mut Context<Self>,
19659 ) {
19660 if !self.input_enabled {
19661 return;
19662 }
19663
19664 let transaction = self.transact(window, cx, |this, window, cx| {
19665 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19666 let snapshot = this.buffer.read(cx).read(cx);
19667 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19668 for marked_range in &mut marked_ranges {
19669 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19670 marked_range.start.0 += relative_range_utf16.start;
19671 marked_range.start =
19672 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19673 marked_range.end =
19674 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19675 }
19676 }
19677 Some(marked_ranges)
19678 } else if let Some(range_utf16) = range_utf16 {
19679 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19680 Some(this.selection_replacement_ranges(range_utf16, cx))
19681 } else {
19682 None
19683 };
19684
19685 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19686 let newest_selection_id = this.selections.newest_anchor().id;
19687 this.selections
19688 .all::<OffsetUtf16>(cx)
19689 .iter()
19690 .zip(ranges_to_replace.iter())
19691 .find_map(|(selection, range)| {
19692 if selection.id == newest_selection_id {
19693 Some(
19694 (range.start.0 as isize - selection.head().0 as isize)
19695 ..(range.end.0 as isize - selection.head().0 as isize),
19696 )
19697 } else {
19698 None
19699 }
19700 })
19701 });
19702
19703 cx.emit(EditorEvent::InputHandled {
19704 utf16_range_to_replace: range_to_replace,
19705 text: text.into(),
19706 });
19707
19708 if let Some(ranges) = ranges_to_replace {
19709 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19710 }
19711
19712 let marked_ranges = {
19713 let snapshot = this.buffer.read(cx).read(cx);
19714 this.selections
19715 .disjoint_anchors()
19716 .iter()
19717 .map(|selection| {
19718 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19719 })
19720 .collect::<Vec<_>>()
19721 };
19722
19723 if text.is_empty() {
19724 this.unmark_text(window, cx);
19725 } else {
19726 this.highlight_text::<InputComposition>(
19727 marked_ranges.clone(),
19728 HighlightStyle {
19729 underline: Some(UnderlineStyle {
19730 thickness: px(1.),
19731 color: None,
19732 wavy: false,
19733 }),
19734 ..Default::default()
19735 },
19736 cx,
19737 );
19738 }
19739
19740 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19741 let use_autoclose = this.use_autoclose;
19742 let use_auto_surround = this.use_auto_surround;
19743 this.set_use_autoclose(false);
19744 this.set_use_auto_surround(false);
19745 this.handle_input(text, window, cx);
19746 this.set_use_autoclose(use_autoclose);
19747 this.set_use_auto_surround(use_auto_surround);
19748
19749 if let Some(new_selected_range) = new_selected_range_utf16 {
19750 let snapshot = this.buffer.read(cx).read(cx);
19751 let new_selected_ranges = marked_ranges
19752 .into_iter()
19753 .map(|marked_range| {
19754 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19755 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19756 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19757 snapshot.clip_offset_utf16(new_start, Bias::Left)
19758 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19759 })
19760 .collect::<Vec<_>>();
19761
19762 drop(snapshot);
19763 this.change_selections(None, window, cx, |selections| {
19764 selections.select_ranges(new_selected_ranges)
19765 });
19766 }
19767 });
19768
19769 self.ime_transaction = self.ime_transaction.or(transaction);
19770 if let Some(transaction) = self.ime_transaction {
19771 self.buffer.update(cx, |buffer, cx| {
19772 buffer.group_until_transaction(transaction, cx);
19773 });
19774 }
19775
19776 if self.text_highlights::<InputComposition>(cx).is_none() {
19777 self.ime_transaction.take();
19778 }
19779 }
19780
19781 fn bounds_for_range(
19782 &mut self,
19783 range_utf16: Range<usize>,
19784 element_bounds: gpui::Bounds<Pixels>,
19785 window: &mut Window,
19786 cx: &mut Context<Self>,
19787 ) -> Option<gpui::Bounds<Pixels>> {
19788 let text_layout_details = self.text_layout_details(window);
19789 let gpui::Size {
19790 width: em_width,
19791 height: line_height,
19792 } = self.character_size(window);
19793
19794 let snapshot = self.snapshot(window, cx);
19795 let scroll_position = snapshot.scroll_position();
19796 let scroll_left = scroll_position.x * em_width;
19797
19798 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19799 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19800 + self.gutter_dimensions.width
19801 + self.gutter_dimensions.margin;
19802 let y = line_height * (start.row().as_f32() - scroll_position.y);
19803
19804 Some(Bounds {
19805 origin: element_bounds.origin + point(x, y),
19806 size: size(em_width, line_height),
19807 })
19808 }
19809
19810 fn character_index_for_point(
19811 &mut self,
19812 point: gpui::Point<Pixels>,
19813 _window: &mut Window,
19814 _cx: &mut Context<Self>,
19815 ) -> Option<usize> {
19816 let position_map = self.last_position_map.as_ref()?;
19817 if !position_map.text_hitbox.contains(&point) {
19818 return None;
19819 }
19820 let display_point = position_map.point_for_position(point).previous_valid;
19821 let anchor = position_map
19822 .snapshot
19823 .display_point_to_anchor(display_point, Bias::Left);
19824 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19825 Some(utf16_offset.0)
19826 }
19827}
19828
19829trait SelectionExt {
19830 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19831 fn spanned_rows(
19832 &self,
19833 include_end_if_at_line_start: bool,
19834 map: &DisplaySnapshot,
19835 ) -> Range<MultiBufferRow>;
19836}
19837
19838impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19839 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19840 let start = self
19841 .start
19842 .to_point(&map.buffer_snapshot)
19843 .to_display_point(map);
19844 let end = self
19845 .end
19846 .to_point(&map.buffer_snapshot)
19847 .to_display_point(map);
19848 if self.reversed {
19849 end..start
19850 } else {
19851 start..end
19852 }
19853 }
19854
19855 fn spanned_rows(
19856 &self,
19857 include_end_if_at_line_start: bool,
19858 map: &DisplaySnapshot,
19859 ) -> Range<MultiBufferRow> {
19860 let start = self.start.to_point(&map.buffer_snapshot);
19861 let mut end = self.end.to_point(&map.buffer_snapshot);
19862 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19863 end.row -= 1;
19864 }
19865
19866 let buffer_start = map.prev_line_boundary(start).0;
19867 let buffer_end = map.next_line_boundary(end).0;
19868 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19869 }
19870}
19871
19872impl<T: InvalidationRegion> InvalidationStack<T> {
19873 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19874 where
19875 S: Clone + ToOffset,
19876 {
19877 while let Some(region) = self.last() {
19878 let all_selections_inside_invalidation_ranges =
19879 if selections.len() == region.ranges().len() {
19880 selections
19881 .iter()
19882 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19883 .all(|(selection, invalidation_range)| {
19884 let head = selection.head().to_offset(buffer);
19885 invalidation_range.start <= head && invalidation_range.end >= head
19886 })
19887 } else {
19888 false
19889 };
19890
19891 if all_selections_inside_invalidation_ranges {
19892 break;
19893 } else {
19894 self.pop();
19895 }
19896 }
19897 }
19898}
19899
19900impl<T> Default for InvalidationStack<T> {
19901 fn default() -> Self {
19902 Self(Default::default())
19903 }
19904}
19905
19906impl<T> Deref for InvalidationStack<T> {
19907 type Target = Vec<T>;
19908
19909 fn deref(&self) -> &Self::Target {
19910 &self.0
19911 }
19912}
19913
19914impl<T> DerefMut for InvalidationStack<T> {
19915 fn deref_mut(&mut self) -> &mut Self::Target {
19916 &mut self.0
19917 }
19918}
19919
19920impl InvalidationRegion for SnippetState {
19921 fn ranges(&self) -> &[Range<Anchor>] {
19922 &self.ranges[self.active_index]
19923 }
19924}
19925
19926pub fn diagnostic_block_renderer(
19927 diagnostic: Diagnostic,
19928 max_message_rows: Option<u8>,
19929 allow_closing: bool,
19930) -> RenderBlock {
19931 let (text_without_backticks, code_ranges) =
19932 highlight_diagnostic_message(&diagnostic, max_message_rows);
19933
19934 Arc::new(move |cx: &mut BlockContext| {
19935 let group_id: SharedString = cx.block_id.to_string().into();
19936
19937 let mut text_style = cx.window.text_style().clone();
19938 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19939 let theme_settings = ThemeSettings::get_global(cx);
19940 text_style.font_family = theme_settings.buffer_font.family.clone();
19941 text_style.font_style = theme_settings.buffer_font.style;
19942 text_style.font_features = theme_settings.buffer_font.features.clone();
19943 text_style.font_weight = theme_settings.buffer_font.weight;
19944
19945 let multi_line_diagnostic = diagnostic.message.contains('\n');
19946
19947 let buttons = |diagnostic: &Diagnostic| {
19948 if multi_line_diagnostic {
19949 v_flex()
19950 } else {
19951 h_flex()
19952 }
19953 .when(allow_closing, |div| {
19954 div.children(diagnostic.is_primary.then(|| {
19955 IconButton::new("close-block", IconName::XCircle)
19956 .icon_color(Color::Muted)
19957 .size(ButtonSize::Compact)
19958 .style(ButtonStyle::Transparent)
19959 .visible_on_hover(group_id.clone())
19960 .on_click(move |_click, window, cx| {
19961 window.dispatch_action(Box::new(Cancel), cx)
19962 })
19963 .tooltip(|window, cx| {
19964 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19965 })
19966 }))
19967 })
19968 .child(
19969 IconButton::new("copy-block", IconName::Copy)
19970 .icon_color(Color::Muted)
19971 .size(ButtonSize::Compact)
19972 .style(ButtonStyle::Transparent)
19973 .visible_on_hover(group_id.clone())
19974 .on_click({
19975 let message = diagnostic.message.clone();
19976 move |_click, _, cx| {
19977 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19978 }
19979 })
19980 .tooltip(Tooltip::text("Copy diagnostic message")),
19981 )
19982 };
19983
19984 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19985 AvailableSpace::min_size(),
19986 cx.window,
19987 cx.app,
19988 );
19989
19990 h_flex()
19991 .id(cx.block_id)
19992 .group(group_id.clone())
19993 .relative()
19994 .size_full()
19995 .block_mouse_down()
19996 .pl(cx.gutter_dimensions.width)
19997 .w(cx.max_width - cx.gutter_dimensions.full_width())
19998 .child(
19999 div()
20000 .flex()
20001 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
20002 .flex_shrink(),
20003 )
20004 .child(buttons(&diagnostic))
20005 .child(div().flex().flex_shrink_0().child(
20006 StyledText::new(text_without_backticks.clone()).with_default_highlights(
20007 &text_style,
20008 code_ranges.iter().map(|range| {
20009 (
20010 range.clone(),
20011 HighlightStyle {
20012 font_weight: Some(FontWeight::BOLD),
20013 ..Default::default()
20014 },
20015 )
20016 }),
20017 ),
20018 ))
20019 .into_any_element()
20020 })
20021}
20022
20023fn inline_completion_edit_text(
20024 current_snapshot: &BufferSnapshot,
20025 edits: &[(Range<Anchor>, String)],
20026 edit_preview: &EditPreview,
20027 include_deletions: bool,
20028 cx: &App,
20029) -> HighlightedText {
20030 let edits = edits
20031 .iter()
20032 .map(|(anchor, text)| {
20033 (
20034 anchor.start.text_anchor..anchor.end.text_anchor,
20035 text.clone(),
20036 )
20037 })
20038 .collect::<Vec<_>>();
20039
20040 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20041}
20042
20043pub fn highlight_diagnostic_message(
20044 diagnostic: &Diagnostic,
20045 mut max_message_rows: Option<u8>,
20046) -> (SharedString, Vec<Range<usize>>) {
20047 let mut text_without_backticks = String::new();
20048 let mut code_ranges = Vec::new();
20049
20050 if let Some(source) = &diagnostic.source {
20051 text_without_backticks.push_str(source);
20052 code_ranges.push(0..source.len());
20053 text_without_backticks.push_str(": ");
20054 }
20055
20056 let mut prev_offset = 0;
20057 let mut in_code_block = false;
20058 let has_row_limit = max_message_rows.is_some();
20059 let mut newline_indices = diagnostic
20060 .message
20061 .match_indices('\n')
20062 .filter(|_| has_row_limit)
20063 .map(|(ix, _)| ix)
20064 .fuse()
20065 .peekable();
20066
20067 for (quote_ix, _) in diagnostic
20068 .message
20069 .match_indices('`')
20070 .chain([(diagnostic.message.len(), "")])
20071 {
20072 let mut first_newline_ix = None;
20073 let mut last_newline_ix = None;
20074 while let Some(newline_ix) = newline_indices.peek() {
20075 if *newline_ix < quote_ix {
20076 if first_newline_ix.is_none() {
20077 first_newline_ix = Some(*newline_ix);
20078 }
20079 last_newline_ix = Some(*newline_ix);
20080
20081 if let Some(rows_left) = &mut max_message_rows {
20082 if *rows_left == 0 {
20083 break;
20084 } else {
20085 *rows_left -= 1;
20086 }
20087 }
20088 let _ = newline_indices.next();
20089 } else {
20090 break;
20091 }
20092 }
20093 let prev_len = text_without_backticks.len();
20094 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
20095 text_without_backticks.push_str(new_text);
20096 if in_code_block {
20097 code_ranges.push(prev_len..text_without_backticks.len());
20098 }
20099 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
20100 in_code_block = !in_code_block;
20101 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
20102 text_without_backticks.push_str("...");
20103 break;
20104 }
20105 }
20106
20107 (text_without_backticks.into(), code_ranges)
20108}
20109
20110fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20111 match severity {
20112 DiagnosticSeverity::ERROR => colors.error,
20113 DiagnosticSeverity::WARNING => colors.warning,
20114 DiagnosticSeverity::INFORMATION => colors.info,
20115 DiagnosticSeverity::HINT => colors.info,
20116 _ => colors.ignored,
20117 }
20118}
20119
20120pub fn styled_runs_for_code_label<'a>(
20121 label: &'a CodeLabel,
20122 syntax_theme: &'a theme::SyntaxTheme,
20123) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20124 let fade_out = HighlightStyle {
20125 fade_out: Some(0.35),
20126 ..Default::default()
20127 };
20128
20129 let mut prev_end = label.filter_range.end;
20130 label
20131 .runs
20132 .iter()
20133 .enumerate()
20134 .flat_map(move |(ix, (range, highlight_id))| {
20135 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20136 style
20137 } else {
20138 return Default::default();
20139 };
20140 let mut muted_style = style;
20141 muted_style.highlight(fade_out);
20142
20143 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20144 if range.start >= label.filter_range.end {
20145 if range.start > prev_end {
20146 runs.push((prev_end..range.start, fade_out));
20147 }
20148 runs.push((range.clone(), muted_style));
20149 } else if range.end <= label.filter_range.end {
20150 runs.push((range.clone(), style));
20151 } else {
20152 runs.push((range.start..label.filter_range.end, style));
20153 runs.push((label.filter_range.end..range.end, muted_style));
20154 }
20155 prev_end = cmp::max(prev_end, range.end);
20156
20157 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20158 runs.push((prev_end..label.text.len(), fade_out));
20159 }
20160
20161 runs
20162 })
20163}
20164
20165pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20166 let mut prev_index = 0;
20167 let mut prev_codepoint: Option<char> = None;
20168 text.char_indices()
20169 .chain([(text.len(), '\0')])
20170 .filter_map(move |(index, codepoint)| {
20171 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20172 let is_boundary = index == text.len()
20173 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20174 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20175 if is_boundary {
20176 let chunk = &text[prev_index..index];
20177 prev_index = index;
20178 Some(chunk)
20179 } else {
20180 None
20181 }
20182 })
20183}
20184
20185pub trait RangeToAnchorExt: Sized {
20186 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20187
20188 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20189 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20190 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20191 }
20192}
20193
20194impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20195 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20196 let start_offset = self.start.to_offset(snapshot);
20197 let end_offset = self.end.to_offset(snapshot);
20198 if start_offset == end_offset {
20199 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20200 } else {
20201 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20202 }
20203 }
20204}
20205
20206pub trait RowExt {
20207 fn as_f32(&self) -> f32;
20208
20209 fn next_row(&self) -> Self;
20210
20211 fn previous_row(&self) -> Self;
20212
20213 fn minus(&self, other: Self) -> u32;
20214}
20215
20216impl RowExt for DisplayRow {
20217 fn as_f32(&self) -> f32 {
20218 self.0 as f32
20219 }
20220
20221 fn next_row(&self) -> Self {
20222 Self(self.0 + 1)
20223 }
20224
20225 fn previous_row(&self) -> Self {
20226 Self(self.0.saturating_sub(1))
20227 }
20228
20229 fn minus(&self, other: Self) -> u32 {
20230 self.0 - other.0
20231 }
20232}
20233
20234impl RowExt for MultiBufferRow {
20235 fn as_f32(&self) -> f32 {
20236 self.0 as f32
20237 }
20238
20239 fn next_row(&self) -> Self {
20240 Self(self.0 + 1)
20241 }
20242
20243 fn previous_row(&self) -> Self {
20244 Self(self.0.saturating_sub(1))
20245 }
20246
20247 fn minus(&self, other: Self) -> u32 {
20248 self.0 - other.0
20249 }
20250}
20251
20252trait RowRangeExt {
20253 type Row;
20254
20255 fn len(&self) -> usize;
20256
20257 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20258}
20259
20260impl RowRangeExt for Range<MultiBufferRow> {
20261 type Row = MultiBufferRow;
20262
20263 fn len(&self) -> usize {
20264 (self.end.0 - self.start.0) as usize
20265 }
20266
20267 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20268 (self.start.0..self.end.0).map(MultiBufferRow)
20269 }
20270}
20271
20272impl RowRangeExt for Range<DisplayRow> {
20273 type Row = DisplayRow;
20274
20275 fn len(&self) -> usize {
20276 (self.end.0 - self.start.0) as usize
20277 }
20278
20279 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20280 (self.start.0..self.end.0).map(DisplayRow)
20281 }
20282}
20283
20284/// If select range has more than one line, we
20285/// just point the cursor to range.start.
20286fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20287 if range.start.row == range.end.row {
20288 range
20289 } else {
20290 range.start..range.start
20291 }
20292}
20293pub struct KillRing(ClipboardItem);
20294impl Global for KillRing {}
20295
20296const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20297
20298enum BreakpointPromptEditAction {
20299 Log,
20300 Condition,
20301 HitCondition,
20302}
20303
20304struct BreakpointPromptEditor {
20305 pub(crate) prompt: Entity<Editor>,
20306 editor: WeakEntity<Editor>,
20307 breakpoint_anchor: Anchor,
20308 breakpoint: Breakpoint,
20309 edit_action: BreakpointPromptEditAction,
20310 block_ids: HashSet<CustomBlockId>,
20311 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20312 _subscriptions: Vec<Subscription>,
20313}
20314
20315impl BreakpointPromptEditor {
20316 const MAX_LINES: u8 = 4;
20317
20318 fn new(
20319 editor: WeakEntity<Editor>,
20320 breakpoint_anchor: Anchor,
20321 breakpoint: Breakpoint,
20322 edit_action: BreakpointPromptEditAction,
20323 window: &mut Window,
20324 cx: &mut Context<Self>,
20325 ) -> Self {
20326 let base_text = match edit_action {
20327 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20328 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20329 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20330 }
20331 .map(|msg| msg.to_string())
20332 .unwrap_or_default();
20333
20334 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20335 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20336
20337 let prompt = cx.new(|cx| {
20338 let mut prompt = Editor::new(
20339 EditorMode::AutoHeight {
20340 max_lines: Self::MAX_LINES as usize,
20341 },
20342 buffer,
20343 None,
20344 window,
20345 cx,
20346 );
20347 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20348 prompt.set_show_cursor_when_unfocused(false, cx);
20349 prompt.set_placeholder_text(
20350 match edit_action {
20351 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20352 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20353 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20354 },
20355 cx,
20356 );
20357
20358 prompt
20359 });
20360
20361 Self {
20362 prompt,
20363 editor,
20364 breakpoint_anchor,
20365 breakpoint,
20366 edit_action,
20367 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20368 block_ids: Default::default(),
20369 _subscriptions: vec![],
20370 }
20371 }
20372
20373 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20374 self.block_ids.extend(block_ids)
20375 }
20376
20377 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20378 if let Some(editor) = self.editor.upgrade() {
20379 let message = self
20380 .prompt
20381 .read(cx)
20382 .buffer
20383 .read(cx)
20384 .as_singleton()
20385 .expect("A multi buffer in breakpoint prompt isn't possible")
20386 .read(cx)
20387 .as_rope()
20388 .to_string();
20389
20390 editor.update(cx, |editor, cx| {
20391 editor.edit_breakpoint_at_anchor(
20392 self.breakpoint_anchor,
20393 self.breakpoint.clone(),
20394 match self.edit_action {
20395 BreakpointPromptEditAction::Log => {
20396 BreakpointEditAction::EditLogMessage(message.into())
20397 }
20398 BreakpointPromptEditAction::Condition => {
20399 BreakpointEditAction::EditCondition(message.into())
20400 }
20401 BreakpointPromptEditAction::HitCondition => {
20402 BreakpointEditAction::EditHitCondition(message.into())
20403 }
20404 },
20405 cx,
20406 );
20407
20408 editor.remove_blocks(self.block_ids.clone(), None, cx);
20409 cx.focus_self(window);
20410 });
20411 }
20412 }
20413
20414 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20415 self.editor
20416 .update(cx, |editor, cx| {
20417 editor.remove_blocks(self.block_ids.clone(), None, cx);
20418 window.focus(&editor.focus_handle);
20419 })
20420 .log_err();
20421 }
20422
20423 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20424 let settings = ThemeSettings::get_global(cx);
20425 let text_style = TextStyle {
20426 color: if self.prompt.read(cx).read_only(cx) {
20427 cx.theme().colors().text_disabled
20428 } else {
20429 cx.theme().colors().text
20430 },
20431 font_family: settings.buffer_font.family.clone(),
20432 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20433 font_size: settings.buffer_font_size(cx).into(),
20434 font_weight: settings.buffer_font.weight,
20435 line_height: relative(settings.buffer_line_height.value()),
20436 ..Default::default()
20437 };
20438 EditorElement::new(
20439 &self.prompt,
20440 EditorStyle {
20441 background: cx.theme().colors().editor_background,
20442 local_player: cx.theme().players().local(),
20443 text: text_style,
20444 ..Default::default()
20445 },
20446 )
20447 }
20448}
20449
20450impl Render for BreakpointPromptEditor {
20451 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20452 let gutter_dimensions = *self.gutter_dimensions.lock();
20453 h_flex()
20454 .key_context("Editor")
20455 .bg(cx.theme().colors().editor_background)
20456 .border_y_1()
20457 .border_color(cx.theme().status().info_border)
20458 .size_full()
20459 .py(window.line_height() / 2.5)
20460 .on_action(cx.listener(Self::confirm))
20461 .on_action(cx.listener(Self::cancel))
20462 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20463 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20464 }
20465}
20466
20467impl Focusable for BreakpointPromptEditor {
20468 fn focus_handle(&self, cx: &App) -> FocusHandle {
20469 self.prompt.focus_handle(cx)
20470 }
20471}
20472
20473fn all_edits_insertions_or_deletions(
20474 edits: &Vec<(Range<Anchor>, String)>,
20475 snapshot: &MultiBufferSnapshot,
20476) -> bool {
20477 let mut all_insertions = true;
20478 let mut all_deletions = true;
20479
20480 for (range, new_text) in edits.iter() {
20481 let range_is_empty = range.to_offset(&snapshot).is_empty();
20482 let text_is_empty = new_text.is_empty();
20483
20484 if range_is_empty != text_is_empty {
20485 if range_is_empty {
20486 all_deletions = false;
20487 } else {
20488 all_insertions = false;
20489 }
20490 } else {
20491 return false;
20492 }
20493
20494 if !all_insertions && !all_deletions {
20495 return false;
20496 }
20497 }
20498 all_insertions || all_deletions
20499}
20500
20501struct MissingEditPredictionKeybindingTooltip;
20502
20503impl Render for MissingEditPredictionKeybindingTooltip {
20504 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20505 ui::tooltip_container(window, cx, |container, _, cx| {
20506 container
20507 .flex_shrink_0()
20508 .max_w_80()
20509 .min_h(rems_from_px(124.))
20510 .justify_between()
20511 .child(
20512 v_flex()
20513 .flex_1()
20514 .text_ui_sm(cx)
20515 .child(Label::new("Conflict with Accept Keybinding"))
20516 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20517 )
20518 .child(
20519 h_flex()
20520 .pb_1()
20521 .gap_1()
20522 .items_end()
20523 .w_full()
20524 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20525 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20526 }))
20527 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20528 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20529 })),
20530 )
20531 })
20532 }
20533}
20534
20535#[derive(Debug, Clone, Copy, PartialEq)]
20536pub struct LineHighlight {
20537 pub background: Background,
20538 pub border: Option<gpui::Hsla>,
20539}
20540
20541impl From<Hsla> for LineHighlight {
20542 fn from(hsla: Hsla) -> Self {
20543 Self {
20544 background: hsla.into(),
20545 border: None,
20546 }
20547 }
20548}
20549
20550impl From<Background> for LineHighlight {
20551 fn from(background: Background) -> Self {
20552 Self {
20553 background,
20554 border: None,
20555 }
20556 }
20557}
20558
20559fn render_diff_hunk_controls(
20560 row: u32,
20561 status: &DiffHunkStatus,
20562 hunk_range: Range<Anchor>,
20563 is_created_file: bool,
20564 line_height: Pixels,
20565 editor: &Entity<Editor>,
20566 _window: &mut Window,
20567 cx: &mut App,
20568) -> AnyElement {
20569 h_flex()
20570 .h(line_height)
20571 .mr_1()
20572 .gap_1()
20573 .px_0p5()
20574 .pb_1()
20575 .border_x_1()
20576 .border_b_1()
20577 .border_color(cx.theme().colors().border_variant)
20578 .rounded_b_lg()
20579 .bg(cx.theme().colors().editor_background)
20580 .gap_1()
20581 .occlude()
20582 .shadow_md()
20583 .child(if status.has_secondary_hunk() {
20584 Button::new(("stage", row as u64), "Stage")
20585 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20586 .tooltip({
20587 let focus_handle = editor.focus_handle(cx);
20588 move |window, cx| {
20589 Tooltip::for_action_in(
20590 "Stage Hunk",
20591 &::git::ToggleStaged,
20592 &focus_handle,
20593 window,
20594 cx,
20595 )
20596 }
20597 })
20598 .on_click({
20599 let editor = editor.clone();
20600 move |_event, _window, cx| {
20601 editor.update(cx, |editor, cx| {
20602 editor.stage_or_unstage_diff_hunks(
20603 true,
20604 vec![hunk_range.start..hunk_range.start],
20605 cx,
20606 );
20607 });
20608 }
20609 })
20610 } else {
20611 Button::new(("unstage", row as u64), "Unstage")
20612 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20613 .tooltip({
20614 let focus_handle = editor.focus_handle(cx);
20615 move |window, cx| {
20616 Tooltip::for_action_in(
20617 "Unstage Hunk",
20618 &::git::ToggleStaged,
20619 &focus_handle,
20620 window,
20621 cx,
20622 )
20623 }
20624 })
20625 .on_click({
20626 let editor = editor.clone();
20627 move |_event, _window, cx| {
20628 editor.update(cx, |editor, cx| {
20629 editor.stage_or_unstage_diff_hunks(
20630 false,
20631 vec![hunk_range.start..hunk_range.start],
20632 cx,
20633 );
20634 });
20635 }
20636 })
20637 })
20638 .child(
20639 Button::new(("restore", row as u64), "Restore")
20640 .tooltip({
20641 let focus_handle = editor.focus_handle(cx);
20642 move |window, cx| {
20643 Tooltip::for_action_in(
20644 "Restore Hunk",
20645 &::git::Restore,
20646 &focus_handle,
20647 window,
20648 cx,
20649 )
20650 }
20651 })
20652 .on_click({
20653 let editor = editor.clone();
20654 move |_event, window, cx| {
20655 editor.update(cx, |editor, cx| {
20656 let snapshot = editor.snapshot(window, cx);
20657 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20658 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20659 });
20660 }
20661 })
20662 .disabled(is_created_file),
20663 )
20664 .when(
20665 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20666 |el| {
20667 el.child(
20668 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20669 .shape(IconButtonShape::Square)
20670 .icon_size(IconSize::Small)
20671 // .disabled(!has_multiple_hunks)
20672 .tooltip({
20673 let focus_handle = editor.focus_handle(cx);
20674 move |window, cx| {
20675 Tooltip::for_action_in(
20676 "Next Hunk",
20677 &GoToHunk,
20678 &focus_handle,
20679 window,
20680 cx,
20681 )
20682 }
20683 })
20684 .on_click({
20685 let editor = editor.clone();
20686 move |_event, window, cx| {
20687 editor.update(cx, |editor, cx| {
20688 let snapshot = editor.snapshot(window, cx);
20689 let position =
20690 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20691 editor.go_to_hunk_before_or_after_position(
20692 &snapshot,
20693 position,
20694 Direction::Next,
20695 window,
20696 cx,
20697 );
20698 editor.expand_selected_diff_hunks(cx);
20699 });
20700 }
20701 }),
20702 )
20703 .child(
20704 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20705 .shape(IconButtonShape::Square)
20706 .icon_size(IconSize::Small)
20707 // .disabled(!has_multiple_hunks)
20708 .tooltip({
20709 let focus_handle = editor.focus_handle(cx);
20710 move |window, cx| {
20711 Tooltip::for_action_in(
20712 "Previous Hunk",
20713 &GoToPreviousHunk,
20714 &focus_handle,
20715 window,
20716 cx,
20717 )
20718 }
20719 })
20720 .on_click({
20721 let editor = editor.clone();
20722 move |_event, window, cx| {
20723 editor.update(cx, |editor, cx| {
20724 let snapshot = editor.snapshot(window, cx);
20725 let point =
20726 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20727 editor.go_to_hunk_before_or_after_position(
20728 &snapshot,
20729 point,
20730 Direction::Prev,
20731 window,
20732 cx,
20733 );
20734 editor.expand_selected_diff_hunks(cx);
20735 });
20736 }
20737 }),
20738 )
20739 },
20740 )
20741 .into_any_element()
20742}