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;
26pub mod 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, Subscription, Task, TextStyle, TextStyleRefinement,
92 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
93 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
94};
95use highlight_matching_bracket::refresh_matching_bracket_highlights;
96use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
97pub use hover_popover::hover_markdown_style;
98use hover_popover::{HoverState, hide_hover};
99use indent_guides::ActiveIndentGuidesState;
100use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
101pub use inline_completion::Direction;
102use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
103pub use items::MAX_TAB_TITLE_LEN;
104use itertools::Itertools;
105use language::{
106 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
107 CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
108 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
109 TransactionId, TreeSitterOptions, WordsQuery,
110 language_settings::{
111 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
112 all_language_settings, language_settings,
113 },
114 point_from_lsp, text_diff_with_options,
115};
116use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
117use linked_editing_ranges::refresh_linked_ranges;
118use mouse_context_menu::MouseContextMenu;
119use persistence::DB;
120use project::{
121 ProjectPath,
122 debugger::breakpoint_store::{
123 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
124 },
125};
126
127pub use git::blame::BlameRenderer;
128pub use proposed_changes_editor::{
129 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
130};
131use smallvec::smallvec;
132use std::{cell::OnceCell, iter::Peekable};
133use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
134
135pub use lsp::CompletionContext;
136use lsp::{
137 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
138 InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
139};
140
141use language::BufferSnapshot;
142pub use lsp_ext::lsp_tasks;
143use movement::TextLayoutDetails;
144pub use multi_buffer::{
145 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
146 RowInfo, ToOffset, ToPoint,
147};
148use multi_buffer::{
149 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
150 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
151};
152use parking_lot::Mutex;
153use project::{
154 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
155 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
156 TaskSourceKind,
157 debugger::breakpoint_store::Breakpoint,
158 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
159 project_settings::{GitGutterSetting, ProjectSettings},
160};
161use rand::prelude::*;
162use rpc::{ErrorExt, proto::*};
163use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
164use selections_collection::{
165 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
166};
167use serde::{Deserialize, Serialize};
168use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
169use smallvec::SmallVec;
170use snippet::Snippet;
171use std::sync::Arc;
172use std::{
173 any::TypeId,
174 borrow::Cow,
175 cell::RefCell,
176 cmp::{self, Ordering, Reverse},
177 mem,
178 num::NonZeroU32,
179 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
180 path::{Path, PathBuf},
181 rc::Rc,
182 time::{Duration, Instant},
183};
184pub use sum_tree::Bias;
185use sum_tree::TreeMap;
186use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
187use theme::{
188 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
189 observe_buffer_font_size_adjustment,
190};
191use ui::{
192 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
193 IconSize, Key, Tooltip, h_flex, prelude::*,
194};
195use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
196use workspace::{
197 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
198 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
199 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
200 item::{ItemHandle, PreviewTabsSettings},
201 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
202 searchable::SearchEvent,
203};
204
205use crate::hover_links::{find_url, find_url_from_range};
206use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
207
208pub const FILE_HEADER_HEIGHT: u32 = 2;
209pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
210pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
211const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
212const MAX_LINE_LEN: usize = 1024;
213const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
214const MAX_SELECTION_HISTORY_LEN: usize = 1024;
215pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
216#[doc(hidden)]
217pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
218
219pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
220pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
221pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
222
223pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
224pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
225pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
226
227pub type RenderDiffHunkControlsFn = Arc<
228 dyn Fn(
229 u32,
230 &DiffHunkStatus,
231 Range<Anchor>,
232 bool,
233 Pixels,
234 &Entity<Editor>,
235 &mut Window,
236 &mut App,
237 ) -> AnyElement,
238>;
239
240const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
241 alt: true,
242 shift: true,
243 control: false,
244 platform: false,
245 function: false,
246};
247
248#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
249pub enum InlayId {
250 InlineCompletion(usize),
251 Hint(usize),
252}
253
254impl InlayId {
255 fn id(&self) -> usize {
256 match self {
257 Self::InlineCompletion(id) => *id,
258 Self::Hint(id) => *id,
259 }
260 }
261}
262
263pub enum DebugCurrentRowHighlight {}
264enum DocumentHighlightRead {}
265enum DocumentHighlightWrite {}
266enum InputComposition {}
267enum SelectedTextHighlight {}
268
269#[derive(Debug, Copy, Clone, PartialEq, Eq)]
270pub enum Navigated {
271 Yes,
272 No,
273}
274
275impl Navigated {
276 pub fn from_bool(yes: bool) -> Navigated {
277 if yes { Navigated::Yes } else { Navigated::No }
278 }
279}
280
281#[derive(Debug, Clone, PartialEq, Eq)]
282enum DisplayDiffHunk {
283 Folded {
284 display_row: DisplayRow,
285 },
286 Unfolded {
287 is_created_file: bool,
288 diff_base_byte_range: Range<usize>,
289 display_row_range: Range<DisplayRow>,
290 multi_buffer_range: Range<Anchor>,
291 status: DiffHunkStatus,
292 },
293}
294
295pub enum HideMouseCursorOrigin {
296 TypingAction,
297 MovementAction,
298}
299
300pub fn init_settings(cx: &mut App) {
301 EditorSettings::register(cx);
302}
303
304pub fn init(cx: &mut App) {
305 init_settings(cx);
306
307 cx.set_global(GlobalBlameRenderer(Arc::new(())));
308
309 workspace::register_project_item::<Editor>(cx);
310 workspace::FollowableViewRegistry::register::<Editor>(cx);
311 workspace::register_serializable_item::<Editor>(cx);
312
313 cx.observe_new(
314 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
315 workspace.register_action(Editor::new_file);
316 workspace.register_action(Editor::new_file_vertical);
317 workspace.register_action(Editor::new_file_horizontal);
318 workspace.register_action(Editor::cancel_language_server_work);
319 },
320 )
321 .detach();
322
323 cx.on_action(move |_: &workspace::NewFile, cx| {
324 let app_state = workspace::AppState::global(cx);
325 if let Some(app_state) = app_state.upgrade() {
326 workspace::open_new(
327 Default::default(),
328 app_state,
329 cx,
330 |workspace, window, cx| {
331 Editor::new_file(workspace, &Default::default(), window, cx)
332 },
333 )
334 .detach();
335 }
336 });
337 cx.on_action(move |_: &workspace::NewWindow, cx| {
338 let app_state = workspace::AppState::global(cx);
339 if let Some(app_state) = app_state.upgrade() {
340 workspace::open_new(
341 Default::default(),
342 app_state,
343 cx,
344 |workspace, window, cx| {
345 cx.activate(true);
346 Editor::new_file(workspace, &Default::default(), window, cx)
347 },
348 )
349 .detach();
350 }
351 });
352}
353
354pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
355 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
356}
357
358pub trait DiagnosticRenderer {
359 fn render_group(
360 &self,
361 diagnostic_group: Vec<DiagnosticEntry<Point>>,
362 buffer_id: BufferId,
363 snapshot: EditorSnapshot,
364 editor: WeakEntity<Editor>,
365 cx: &mut App,
366 ) -> Vec<BlockProperties<Anchor>>;
367}
368
369pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
370
371impl gpui::Global for GlobalDiagnosticRenderer {}
372pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
373 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
374}
375
376pub struct SearchWithinRange;
377
378trait InvalidationRegion {
379 fn ranges(&self) -> &[Range<Anchor>];
380}
381
382#[derive(Clone, Debug, PartialEq)]
383pub enum SelectPhase {
384 Begin {
385 position: DisplayPoint,
386 add: bool,
387 click_count: usize,
388 },
389 BeginColumnar {
390 position: DisplayPoint,
391 reset: bool,
392 goal_column: u32,
393 },
394 Extend {
395 position: DisplayPoint,
396 click_count: usize,
397 },
398 Update {
399 position: DisplayPoint,
400 goal_column: u32,
401 scroll_delta: gpui::Point<f32>,
402 },
403 End,
404}
405
406#[derive(Clone, Debug)]
407pub enum SelectMode {
408 Character,
409 Word(Range<Anchor>),
410 Line(Range<Anchor>),
411 All,
412}
413
414#[derive(Copy, Clone, PartialEq, Eq, Debug)]
415pub enum EditorMode {
416 SingleLine {
417 auto_width: bool,
418 },
419 AutoHeight {
420 max_lines: usize,
421 },
422 Full {
423 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
424 scale_ui_elements_with_buffer_font_size: bool,
425 /// When set to `true`, the editor will render a background for the active line.
426 show_active_line_background: bool,
427 },
428}
429
430impl EditorMode {
431 pub fn full() -> Self {
432 Self::Full {
433 scale_ui_elements_with_buffer_font_size: true,
434 show_active_line_background: true,
435 }
436 }
437
438 pub fn is_full(&self) -> bool {
439 matches!(self, Self::Full { .. })
440 }
441}
442
443#[derive(Copy, Clone, Debug)]
444pub enum SoftWrap {
445 /// Prefer not to wrap at all.
446 ///
447 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
448 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
449 GitDiff,
450 /// Prefer a single line generally, unless an overly long line is encountered.
451 None,
452 /// Soft wrap lines that exceed the editor width.
453 EditorWidth,
454 /// Soft wrap lines at the preferred line length.
455 Column(u32),
456 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
457 Bounded(u32),
458}
459
460#[derive(Clone)]
461pub struct EditorStyle {
462 pub background: Hsla,
463 pub local_player: PlayerColor,
464 pub text: TextStyle,
465 pub scrollbar_width: Pixels,
466 pub syntax: Arc<SyntaxTheme>,
467 pub status: StatusColors,
468 pub inlay_hints_style: HighlightStyle,
469 pub inline_completion_styles: InlineCompletionStyles,
470 pub unnecessary_code_fade: f32,
471}
472
473impl Default for EditorStyle {
474 fn default() -> Self {
475 Self {
476 background: Hsla::default(),
477 local_player: PlayerColor::default(),
478 text: TextStyle::default(),
479 scrollbar_width: Pixels::default(),
480 syntax: Default::default(),
481 // HACK: Status colors don't have a real default.
482 // We should look into removing the status colors from the editor
483 // style and retrieve them directly from the theme.
484 status: StatusColors::dark(),
485 inlay_hints_style: HighlightStyle::default(),
486 inline_completion_styles: InlineCompletionStyles {
487 insertion: HighlightStyle::default(),
488 whitespace: HighlightStyle::default(),
489 },
490 unnecessary_code_fade: Default::default(),
491 }
492 }
493}
494
495pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
496 let show_background = language_settings::language_settings(None, None, cx)
497 .inlay_hints
498 .show_background;
499
500 HighlightStyle {
501 color: Some(cx.theme().status().hint),
502 background_color: show_background.then(|| cx.theme().status().hint_background),
503 ..HighlightStyle::default()
504 }
505}
506
507pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
508 InlineCompletionStyles {
509 insertion: HighlightStyle {
510 color: Some(cx.theme().status().predictive),
511 ..HighlightStyle::default()
512 },
513 whitespace: HighlightStyle {
514 background_color: Some(cx.theme().status().created_background),
515 ..HighlightStyle::default()
516 },
517 }
518}
519
520type CompletionId = usize;
521
522pub(crate) enum EditDisplayMode {
523 TabAccept,
524 DiffPopover,
525 Inline,
526}
527
528enum InlineCompletion {
529 Edit {
530 edits: Vec<(Range<Anchor>, String)>,
531 edit_preview: Option<EditPreview>,
532 display_mode: EditDisplayMode,
533 snapshot: BufferSnapshot,
534 },
535 Move {
536 target: Anchor,
537 snapshot: BufferSnapshot,
538 },
539}
540
541struct InlineCompletionState {
542 inlay_ids: Vec<InlayId>,
543 completion: InlineCompletion,
544 completion_id: Option<SharedString>,
545 invalidation_range: Range<Anchor>,
546}
547
548enum EditPredictionSettings {
549 Disabled,
550 Enabled {
551 show_in_menu: bool,
552 preview_requires_modifier: bool,
553 },
554}
555
556enum InlineCompletionHighlight {}
557
558#[derive(Debug, Clone)]
559struct InlineDiagnostic {
560 message: SharedString,
561 group_id: usize,
562 is_primary: bool,
563 start: Point,
564 severity: DiagnosticSeverity,
565}
566
567pub enum MenuInlineCompletionsPolicy {
568 Never,
569 ByProvider,
570}
571
572pub enum EditPredictionPreview {
573 /// Modifier is not pressed
574 Inactive { released_too_fast: bool },
575 /// Modifier pressed
576 Active {
577 since: Instant,
578 previous_scroll_position: Option<ScrollAnchor>,
579 },
580}
581
582impl EditPredictionPreview {
583 pub fn released_too_fast(&self) -> bool {
584 match self {
585 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
586 EditPredictionPreview::Active { .. } => false,
587 }
588 }
589
590 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
591 if let EditPredictionPreview::Active {
592 previous_scroll_position,
593 ..
594 } = self
595 {
596 *previous_scroll_position = scroll_position;
597 }
598 }
599}
600
601pub struct ContextMenuOptions {
602 pub min_entries_visible: usize,
603 pub max_entries_visible: usize,
604 pub placement: Option<ContextMenuPlacement>,
605}
606
607#[derive(Debug, Clone, PartialEq, Eq)]
608pub enum ContextMenuPlacement {
609 Above,
610 Below,
611}
612
613#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
614struct EditorActionId(usize);
615
616impl EditorActionId {
617 pub fn post_inc(&mut self) -> Self {
618 let answer = self.0;
619
620 *self = Self(answer + 1);
621
622 Self(answer)
623 }
624}
625
626// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
627// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
628
629type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
630type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
631
632#[derive(Default)]
633struct ScrollbarMarkerState {
634 scrollbar_size: Size<Pixels>,
635 dirty: bool,
636 markers: Arc<[PaintQuad]>,
637 pending_refresh: Option<Task<Result<()>>>,
638}
639
640impl ScrollbarMarkerState {
641 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
642 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
643 }
644}
645
646#[derive(Clone, Debug)]
647struct RunnableTasks {
648 templates: Vec<(TaskSourceKind, TaskTemplate)>,
649 offset: multi_buffer::Anchor,
650 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
651 column: u32,
652 // Values of all named captures, including those starting with '_'
653 extra_variables: HashMap<String, String>,
654 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
655 context_range: Range<BufferOffset>,
656}
657
658impl RunnableTasks {
659 fn resolve<'a>(
660 &'a self,
661 cx: &'a task::TaskContext,
662 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
663 self.templates.iter().filter_map(|(kind, template)| {
664 template
665 .resolve_task(&kind.to_id_base(), cx)
666 .map(|task| (kind.clone(), task))
667 })
668 }
669}
670
671#[derive(Clone)]
672struct ResolvedTasks {
673 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
674 position: Anchor,
675}
676
677#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
678struct BufferOffset(usize);
679
680// Addons allow storing per-editor state in other crates (e.g. Vim)
681pub trait Addon: 'static {
682 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
683
684 fn render_buffer_header_controls(
685 &self,
686 _: &ExcerptInfo,
687 _: &Window,
688 _: &App,
689 ) -> Option<AnyElement> {
690 None
691 }
692
693 fn to_any(&self) -> &dyn std::any::Any;
694}
695
696/// A set of caret positions, registered when the editor was edited.
697pub struct ChangeList {
698 changes: Vec<Vec<Anchor>>,
699 /// Currently "selected" change.
700 position: Option<usize>,
701}
702
703impl ChangeList {
704 pub fn new() -> Self {
705 Self {
706 changes: Vec::new(),
707 position: None,
708 }
709 }
710
711 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
712 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
713 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
714 if self.changes.is_empty() {
715 return None;
716 }
717
718 let prev = self.position.unwrap_or(self.changes.len());
719 let next = if direction == Direction::Prev {
720 prev.saturating_sub(count)
721 } else {
722 (prev + count).min(self.changes.len() - 1)
723 };
724 self.position = Some(next);
725 self.changes.get(next).map(|anchors| anchors.as_slice())
726 }
727
728 /// Adds a new change to the list, resetting the change list position.
729 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
730 self.position.take();
731 if pop_state {
732 self.changes.pop();
733 }
734 self.changes.push(new_positions.clone());
735 }
736
737 pub fn last(&self) -> Option<&[Anchor]> {
738 self.changes.last().map(|anchors| anchors.as_slice())
739 }
740}
741
742/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
743///
744/// See the [module level documentation](self) for more information.
745pub struct Editor {
746 focus_handle: FocusHandle,
747 last_focused_descendant: Option<WeakFocusHandle>,
748 /// The text buffer being edited
749 buffer: Entity<MultiBuffer>,
750 /// Map of how text in the buffer should be displayed.
751 /// Handles soft wraps, folds, fake inlay text insertions, etc.
752 pub display_map: Entity<DisplayMap>,
753 pub selections: SelectionsCollection,
754 pub scroll_manager: ScrollManager,
755 /// When inline assist editors are linked, they all render cursors because
756 /// typing enters text into each of them, even the ones that aren't focused.
757 pub(crate) show_cursor_when_unfocused: bool,
758 columnar_selection_tail: Option<Anchor>,
759 add_selections_state: Option<AddSelectionsState>,
760 select_next_state: Option<SelectNextState>,
761 select_prev_state: Option<SelectNextState>,
762 selection_history: SelectionHistory,
763 autoclose_regions: Vec<AutocloseRegion>,
764 snippet_stack: InvalidationStack<SnippetState>,
765 select_syntax_node_history: SelectSyntaxNodeHistory,
766 ime_transaction: Option<TransactionId>,
767 active_diagnostics: ActiveDiagnostic,
768 show_inline_diagnostics: bool,
769 inline_diagnostics_update: Task<()>,
770 inline_diagnostics_enabled: bool,
771 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
772 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
773 hard_wrap: Option<usize>,
774
775 // TODO: make this a access method
776 pub project: Option<Entity<Project>>,
777 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
778 completion_provider: Option<Box<dyn CompletionProvider>>,
779 collaboration_hub: Option<Box<dyn CollaborationHub>>,
780 blink_manager: Entity<BlinkManager>,
781 show_cursor_names: bool,
782 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
783 pub show_local_selections: bool,
784 mode: EditorMode,
785 show_breadcrumbs: bool,
786 show_gutter: bool,
787 show_scrollbars: bool,
788 show_line_numbers: Option<bool>,
789 use_relative_line_numbers: Option<bool>,
790 show_git_diff_gutter: Option<bool>,
791 show_code_actions: Option<bool>,
792 show_runnables: Option<bool>,
793 show_breakpoints: Option<bool>,
794 show_wrap_guides: Option<bool>,
795 show_indent_guides: Option<bool>,
796 placeholder_text: Option<Arc<str>>,
797 highlight_order: usize,
798 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
799 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
800 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
801 scrollbar_marker_state: ScrollbarMarkerState,
802 active_indent_guides_state: ActiveIndentGuidesState,
803 nav_history: Option<ItemNavHistory>,
804 context_menu: RefCell<Option<CodeContextMenu>>,
805 context_menu_options: Option<ContextMenuOptions>,
806 mouse_context_menu: Option<MouseContextMenu>,
807 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
808 signature_help_state: SignatureHelpState,
809 auto_signature_help: Option<bool>,
810 find_all_references_task_sources: Vec<Anchor>,
811 next_completion_id: CompletionId,
812 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
813 code_actions_task: Option<Task<Result<()>>>,
814 selection_highlight_task: Option<Task<()>>,
815 document_highlights_task: Option<Task<()>>,
816 linked_editing_range_task: Option<Task<Option<()>>>,
817 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
818 pending_rename: Option<RenameState>,
819 searchable: bool,
820 cursor_shape: CursorShape,
821 current_line_highlight: Option<CurrentLineHighlight>,
822 collapse_matches: bool,
823 autoindent_mode: Option<AutoindentMode>,
824 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
825 input_enabled: bool,
826 use_modal_editing: bool,
827 read_only: bool,
828 leader_peer_id: Option<PeerId>,
829 remote_id: Option<ViewId>,
830 hover_state: HoverState,
831 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
832 gutter_hovered: bool,
833 hovered_link_state: Option<HoveredLinkState>,
834 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
835 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
836 active_inline_completion: Option<InlineCompletionState>,
837 /// Used to prevent flickering as the user types while the menu is open
838 stale_inline_completion_in_menu: Option<InlineCompletionState>,
839 edit_prediction_settings: EditPredictionSettings,
840 inline_completions_hidden_for_vim_mode: bool,
841 show_inline_completions_override: Option<bool>,
842 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
843 edit_prediction_preview: EditPredictionPreview,
844 edit_prediction_indent_conflict: bool,
845 edit_prediction_requires_modifier_in_indent_conflict: bool,
846 inlay_hint_cache: InlayHintCache,
847 next_inlay_id: usize,
848 _subscriptions: Vec<Subscription>,
849 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
850 gutter_dimensions: GutterDimensions,
851 style: Option<EditorStyle>,
852 text_style_refinement: Option<TextStyleRefinement>,
853 next_editor_action_id: EditorActionId,
854 editor_actions:
855 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
856 use_autoclose: bool,
857 use_auto_surround: bool,
858 auto_replace_emoji_shortcode: bool,
859 jsx_tag_auto_close_enabled_in_any_buffer: bool,
860 show_git_blame_gutter: bool,
861 show_git_blame_inline: bool,
862 show_git_blame_inline_delay_task: Option<Task<()>>,
863 pub git_blame_inline_tooltip: Option<AnyWeakEntity>,
864 git_blame_inline_enabled: bool,
865 render_diff_hunk_controls: RenderDiffHunkControlsFn,
866 serialize_dirty_buffers: bool,
867 show_selection_menu: Option<bool>,
868 blame: Option<Entity<GitBlame>>,
869 blame_subscription: Option<Subscription>,
870 custom_context_menu: Option<
871 Box<
872 dyn 'static
873 + Fn(
874 &mut Self,
875 DisplayPoint,
876 &mut Window,
877 &mut Context<Self>,
878 ) -> Option<Entity<ui::ContextMenu>>,
879 >,
880 >,
881 last_bounds: Option<Bounds<Pixels>>,
882 last_position_map: Option<Rc<PositionMap>>,
883 expect_bounds_change: Option<Bounds<Pixels>>,
884 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
885 tasks_update_task: Option<Task<()>>,
886 breakpoint_store: Option<Entity<BreakpointStore>>,
887 /// Allow's a user to create a breakpoint by selecting this indicator
888 /// It should be None while a user is not hovering over the gutter
889 /// Otherwise it represents the point that the breakpoint will be shown
890 gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
891 in_project_search: bool,
892 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
893 breadcrumb_header: Option<String>,
894 focused_block: Option<FocusedBlock>,
895 next_scroll_position: NextScrollCursorCenterTopBottom,
896 addons: HashMap<TypeId, Box<dyn Addon>>,
897 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
898 load_diff_task: Option<Shared<Task<()>>>,
899 selection_mark_mode: bool,
900 toggle_fold_multiple_buffers: Task<()>,
901 _scroll_cursor_center_top_bottom_task: Task<()>,
902 serialize_selections: Task<()>,
903 serialize_folds: Task<()>,
904 mouse_cursor_hidden: bool,
905 hide_mouse_mode: HideMouseMode,
906 pub change_list: ChangeList,
907}
908
909#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
910enum NextScrollCursorCenterTopBottom {
911 #[default]
912 Center,
913 Top,
914 Bottom,
915}
916
917impl NextScrollCursorCenterTopBottom {
918 fn next(&self) -> Self {
919 match self {
920 Self::Center => Self::Top,
921 Self::Top => Self::Bottom,
922 Self::Bottom => Self::Center,
923 }
924 }
925}
926
927#[derive(Clone)]
928pub struct EditorSnapshot {
929 pub mode: EditorMode,
930 show_gutter: bool,
931 show_line_numbers: Option<bool>,
932 show_git_diff_gutter: Option<bool>,
933 show_code_actions: Option<bool>,
934 show_runnables: Option<bool>,
935 show_breakpoints: Option<bool>,
936 git_blame_gutter_max_author_length: Option<usize>,
937 pub display_snapshot: DisplaySnapshot,
938 pub placeholder_text: Option<Arc<str>>,
939 is_focused: bool,
940 scroll_anchor: ScrollAnchor,
941 ongoing_scroll: OngoingScroll,
942 current_line_highlight: CurrentLineHighlight,
943 gutter_hovered: bool,
944}
945
946#[derive(Default, Debug, Clone, Copy)]
947pub struct GutterDimensions {
948 pub left_padding: Pixels,
949 pub right_padding: Pixels,
950 pub width: Pixels,
951 pub margin: Pixels,
952 pub git_blame_entries_width: Option<Pixels>,
953}
954
955impl GutterDimensions {
956 /// The full width of the space taken up by the gutter.
957 pub fn full_width(&self) -> Pixels {
958 self.margin + self.width
959 }
960
961 /// The width of the space reserved for the fold indicators,
962 /// use alongside 'justify_end' and `gutter_width` to
963 /// right align content with the line numbers
964 pub fn fold_area_width(&self) -> Pixels {
965 self.margin + self.right_padding
966 }
967}
968
969#[derive(Debug)]
970pub struct RemoteSelection {
971 pub replica_id: ReplicaId,
972 pub selection: Selection<Anchor>,
973 pub cursor_shape: CursorShape,
974 pub peer_id: PeerId,
975 pub line_mode: bool,
976 pub participant_index: Option<ParticipantIndex>,
977 pub user_name: Option<SharedString>,
978}
979
980#[derive(Clone, Debug)]
981struct SelectionHistoryEntry {
982 selections: Arc<[Selection<Anchor>]>,
983 select_next_state: Option<SelectNextState>,
984 select_prev_state: Option<SelectNextState>,
985 add_selections_state: Option<AddSelectionsState>,
986}
987
988enum SelectionHistoryMode {
989 Normal,
990 Undoing,
991 Redoing,
992}
993
994#[derive(Clone, PartialEq, Eq, Hash)]
995struct HoveredCursor {
996 replica_id: u16,
997 selection_id: usize,
998}
999
1000impl Default for SelectionHistoryMode {
1001 fn default() -> Self {
1002 Self::Normal
1003 }
1004}
1005
1006#[derive(Default)]
1007struct SelectionHistory {
1008 #[allow(clippy::type_complexity)]
1009 selections_by_transaction:
1010 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1011 mode: SelectionHistoryMode,
1012 undo_stack: VecDeque<SelectionHistoryEntry>,
1013 redo_stack: VecDeque<SelectionHistoryEntry>,
1014}
1015
1016impl SelectionHistory {
1017 fn insert_transaction(
1018 &mut self,
1019 transaction_id: TransactionId,
1020 selections: Arc<[Selection<Anchor>]>,
1021 ) {
1022 self.selections_by_transaction
1023 .insert(transaction_id, (selections, None));
1024 }
1025
1026 #[allow(clippy::type_complexity)]
1027 fn transaction(
1028 &self,
1029 transaction_id: TransactionId,
1030 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1031 self.selections_by_transaction.get(&transaction_id)
1032 }
1033
1034 #[allow(clippy::type_complexity)]
1035 fn transaction_mut(
1036 &mut self,
1037 transaction_id: TransactionId,
1038 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1039 self.selections_by_transaction.get_mut(&transaction_id)
1040 }
1041
1042 fn push(&mut self, entry: SelectionHistoryEntry) {
1043 if !entry.selections.is_empty() {
1044 match self.mode {
1045 SelectionHistoryMode::Normal => {
1046 self.push_undo(entry);
1047 self.redo_stack.clear();
1048 }
1049 SelectionHistoryMode::Undoing => self.push_redo(entry),
1050 SelectionHistoryMode::Redoing => self.push_undo(entry),
1051 }
1052 }
1053 }
1054
1055 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1056 if self
1057 .undo_stack
1058 .back()
1059 .map_or(true, |e| e.selections != entry.selections)
1060 {
1061 self.undo_stack.push_back(entry);
1062 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1063 self.undo_stack.pop_front();
1064 }
1065 }
1066 }
1067
1068 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1069 if self
1070 .redo_stack
1071 .back()
1072 .map_or(true, |e| e.selections != entry.selections)
1073 {
1074 self.redo_stack.push_back(entry);
1075 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1076 self.redo_stack.pop_front();
1077 }
1078 }
1079 }
1080}
1081
1082struct RowHighlight {
1083 index: usize,
1084 range: Range<Anchor>,
1085 color: Hsla,
1086 should_autoscroll: bool,
1087}
1088
1089#[derive(Clone, Debug)]
1090struct AddSelectionsState {
1091 above: bool,
1092 stack: Vec<usize>,
1093}
1094
1095#[derive(Clone)]
1096struct SelectNextState {
1097 query: AhoCorasick,
1098 wordwise: bool,
1099 done: bool,
1100}
1101
1102impl std::fmt::Debug for SelectNextState {
1103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1104 f.debug_struct(std::any::type_name::<Self>())
1105 .field("wordwise", &self.wordwise)
1106 .field("done", &self.done)
1107 .finish()
1108 }
1109}
1110
1111#[derive(Debug)]
1112struct AutocloseRegion {
1113 selection_id: usize,
1114 range: Range<Anchor>,
1115 pair: BracketPair,
1116}
1117
1118#[derive(Debug)]
1119struct SnippetState {
1120 ranges: Vec<Vec<Range<Anchor>>>,
1121 active_index: usize,
1122 choices: Vec<Option<Vec<String>>>,
1123}
1124
1125#[doc(hidden)]
1126pub struct RenameState {
1127 pub range: Range<Anchor>,
1128 pub old_name: Arc<str>,
1129 pub editor: Entity<Editor>,
1130 block_id: CustomBlockId,
1131}
1132
1133struct InvalidationStack<T>(Vec<T>);
1134
1135struct RegisteredInlineCompletionProvider {
1136 provider: Arc<dyn InlineCompletionProviderHandle>,
1137 _subscription: Subscription,
1138}
1139
1140#[derive(Debug, PartialEq, Eq)]
1141pub struct ActiveDiagnosticGroup {
1142 pub active_range: Range<Anchor>,
1143 pub active_message: String,
1144 pub group_id: usize,
1145 pub blocks: HashSet<CustomBlockId>,
1146}
1147
1148#[derive(Debug, PartialEq, Eq)]
1149#[allow(clippy::large_enum_variant)]
1150pub(crate) enum ActiveDiagnostic {
1151 None,
1152 All,
1153 Group(ActiveDiagnosticGroup),
1154}
1155
1156#[derive(Serialize, Deserialize, Clone, Debug)]
1157pub struct ClipboardSelection {
1158 /// The number of bytes in this selection.
1159 pub len: usize,
1160 /// Whether this was a full-line selection.
1161 pub is_entire_line: bool,
1162 /// The indentation of the first line when this content was originally copied.
1163 pub first_line_indent: u32,
1164}
1165
1166// selections, scroll behavior, was newest selection reversed
1167type SelectSyntaxNodeHistoryState = (
1168 Box<[Selection<usize>]>,
1169 SelectSyntaxNodeScrollBehavior,
1170 bool,
1171);
1172
1173#[derive(Default)]
1174struct SelectSyntaxNodeHistory {
1175 stack: Vec<SelectSyntaxNodeHistoryState>,
1176 // disable temporarily to allow changing selections without losing the stack
1177 pub disable_clearing: bool,
1178}
1179
1180impl SelectSyntaxNodeHistory {
1181 pub fn try_clear(&mut self) {
1182 if !self.disable_clearing {
1183 self.stack.clear();
1184 }
1185 }
1186
1187 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1188 self.stack.push(selection);
1189 }
1190
1191 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1192 self.stack.pop()
1193 }
1194}
1195
1196enum SelectSyntaxNodeScrollBehavior {
1197 CursorTop,
1198 FitSelection,
1199 CursorBottom,
1200}
1201
1202#[derive(Debug)]
1203pub(crate) struct NavigationData {
1204 cursor_anchor: Anchor,
1205 cursor_position: Point,
1206 scroll_anchor: ScrollAnchor,
1207 scroll_top_row: u32,
1208}
1209
1210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1211pub enum GotoDefinitionKind {
1212 Symbol,
1213 Declaration,
1214 Type,
1215 Implementation,
1216}
1217
1218#[derive(Debug, Clone)]
1219enum InlayHintRefreshReason {
1220 ModifiersChanged(bool),
1221 Toggle(bool),
1222 SettingsChange(InlayHintSettings),
1223 NewLinesShown,
1224 BufferEdited(HashSet<Arc<Language>>),
1225 RefreshRequested,
1226 ExcerptsRemoved(Vec<ExcerptId>),
1227}
1228
1229impl InlayHintRefreshReason {
1230 fn description(&self) -> &'static str {
1231 match self {
1232 Self::ModifiersChanged(_) => "modifiers changed",
1233 Self::Toggle(_) => "toggle",
1234 Self::SettingsChange(_) => "settings change",
1235 Self::NewLinesShown => "new lines shown",
1236 Self::BufferEdited(_) => "buffer edited",
1237 Self::RefreshRequested => "refresh requested",
1238 Self::ExcerptsRemoved(_) => "excerpts removed",
1239 }
1240 }
1241}
1242
1243pub enum FormatTarget {
1244 Buffers,
1245 Ranges(Vec<Range<MultiBufferPoint>>),
1246}
1247
1248pub(crate) struct FocusedBlock {
1249 id: BlockId,
1250 focus_handle: WeakFocusHandle,
1251}
1252
1253#[derive(Clone)]
1254enum JumpData {
1255 MultiBufferRow {
1256 row: MultiBufferRow,
1257 line_offset_from_top: u32,
1258 },
1259 MultiBufferPoint {
1260 excerpt_id: ExcerptId,
1261 position: Point,
1262 anchor: text::Anchor,
1263 line_offset_from_top: u32,
1264 },
1265}
1266
1267pub enum MultibufferSelectionMode {
1268 First,
1269 All,
1270}
1271
1272#[derive(Clone, Copy, Debug, Default)]
1273pub struct RewrapOptions {
1274 pub override_language_settings: bool,
1275 pub preserve_existing_whitespace: bool,
1276}
1277
1278impl Editor {
1279 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1280 let buffer = cx.new(|cx| Buffer::local("", cx));
1281 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1282 Self::new(
1283 EditorMode::SingleLine { auto_width: false },
1284 buffer,
1285 None,
1286 window,
1287 cx,
1288 )
1289 }
1290
1291 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1292 let buffer = cx.new(|cx| Buffer::local("", cx));
1293 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1294 Self::new(EditorMode::full(), buffer, None, window, cx)
1295 }
1296
1297 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1298 let buffer = cx.new(|cx| Buffer::local("", cx));
1299 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1300 Self::new(
1301 EditorMode::SingleLine { auto_width: true },
1302 buffer,
1303 None,
1304 window,
1305 cx,
1306 )
1307 }
1308
1309 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1310 let buffer = cx.new(|cx| Buffer::local("", cx));
1311 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1312 Self::new(
1313 EditorMode::AutoHeight { max_lines },
1314 buffer,
1315 None,
1316 window,
1317 cx,
1318 )
1319 }
1320
1321 pub fn for_buffer(
1322 buffer: Entity<Buffer>,
1323 project: Option<Entity<Project>>,
1324 window: &mut Window,
1325 cx: &mut Context<Self>,
1326 ) -> Self {
1327 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1328 Self::new(EditorMode::full(), buffer, project, window, cx)
1329 }
1330
1331 pub fn for_multibuffer(
1332 buffer: Entity<MultiBuffer>,
1333 project: Option<Entity<Project>>,
1334 window: &mut Window,
1335 cx: &mut Context<Self>,
1336 ) -> Self {
1337 Self::new(EditorMode::full(), buffer, project, window, cx)
1338 }
1339
1340 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1341 let mut clone = Self::new(
1342 self.mode,
1343 self.buffer.clone(),
1344 self.project.clone(),
1345 window,
1346 cx,
1347 );
1348 self.display_map.update(cx, |display_map, cx| {
1349 let snapshot = display_map.snapshot(cx);
1350 clone.display_map.update(cx, |display_map, cx| {
1351 display_map.set_state(&snapshot, cx);
1352 });
1353 });
1354 clone.folds_did_change(cx);
1355 clone.selections.clone_state(&self.selections);
1356 clone.scroll_manager.clone_state(&self.scroll_manager);
1357 clone.searchable = self.searchable;
1358 clone.read_only = self.read_only;
1359 clone
1360 }
1361
1362 pub fn new(
1363 mode: EditorMode,
1364 buffer: Entity<MultiBuffer>,
1365 project: Option<Entity<Project>>,
1366 window: &mut Window,
1367 cx: &mut Context<Self>,
1368 ) -> Self {
1369 let style = window.text_style();
1370 let font_size = style.font_size.to_pixels(window.rem_size());
1371 let editor = cx.entity().downgrade();
1372 let fold_placeholder = FoldPlaceholder {
1373 constrain_width: true,
1374 render: Arc::new(move |fold_id, fold_range, cx| {
1375 let editor = editor.clone();
1376 div()
1377 .id(fold_id)
1378 .bg(cx.theme().colors().ghost_element_background)
1379 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1380 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1381 .rounded_xs()
1382 .size_full()
1383 .cursor_pointer()
1384 .child("⋯")
1385 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1386 .on_click(move |_, _window, cx| {
1387 editor
1388 .update(cx, |editor, cx| {
1389 editor.unfold_ranges(
1390 &[fold_range.start..fold_range.end],
1391 true,
1392 false,
1393 cx,
1394 );
1395 cx.stop_propagation();
1396 })
1397 .ok();
1398 })
1399 .into_any()
1400 }),
1401 merge_adjacent: true,
1402 ..Default::default()
1403 };
1404 let display_map = cx.new(|cx| {
1405 DisplayMap::new(
1406 buffer.clone(),
1407 style.font(),
1408 font_size,
1409 None,
1410 FILE_HEADER_HEIGHT,
1411 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1412 fold_placeholder,
1413 cx,
1414 )
1415 });
1416
1417 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1418
1419 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1420
1421 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1422 .then(|| language_settings::SoftWrap::None);
1423
1424 let mut project_subscriptions = Vec::new();
1425 if mode.is_full() {
1426 if let Some(project) = project.as_ref() {
1427 project_subscriptions.push(cx.subscribe_in(
1428 project,
1429 window,
1430 |editor, _, event, window, cx| match event {
1431 project::Event::RefreshCodeLens => {
1432 // we always query lens with actions, without storing them, always refreshing them
1433 }
1434 project::Event::RefreshInlayHints => {
1435 editor
1436 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1437 }
1438 project::Event::SnippetEdit(id, snippet_edits) => {
1439 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1440 let focus_handle = editor.focus_handle(cx);
1441 if focus_handle.is_focused(window) {
1442 let snapshot = buffer.read(cx).snapshot();
1443 for (range, snippet) in snippet_edits {
1444 let editor_range =
1445 language::range_from_lsp(*range).to_offset(&snapshot);
1446 editor
1447 .insert_snippet(
1448 &[editor_range],
1449 snippet.clone(),
1450 window,
1451 cx,
1452 )
1453 .ok();
1454 }
1455 }
1456 }
1457 }
1458 _ => {}
1459 },
1460 ));
1461 if let Some(task_inventory) = project
1462 .read(cx)
1463 .task_store()
1464 .read(cx)
1465 .task_inventory()
1466 .cloned()
1467 {
1468 project_subscriptions.push(cx.observe_in(
1469 &task_inventory,
1470 window,
1471 |editor, _, window, cx| {
1472 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1473 },
1474 ));
1475 };
1476
1477 project_subscriptions.push(cx.subscribe_in(
1478 &project.read(cx).breakpoint_store(),
1479 window,
1480 |editor, _, event, window, cx| match event {
1481 BreakpointStoreEvent::ActiveDebugLineChanged => {
1482 if editor.go_to_active_debug_line(window, cx) {
1483 cx.stop_propagation();
1484 }
1485 }
1486 _ => {}
1487 },
1488 ));
1489 }
1490 }
1491
1492 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1493
1494 let inlay_hint_settings =
1495 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1496 let focus_handle = cx.focus_handle();
1497 cx.on_focus(&focus_handle, window, Self::handle_focus)
1498 .detach();
1499 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1500 .detach();
1501 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1502 .detach();
1503 cx.on_blur(&focus_handle, window, Self::handle_blur)
1504 .detach();
1505
1506 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1507 Some(false)
1508 } else {
1509 None
1510 };
1511
1512 let breakpoint_store = match (mode, project.as_ref()) {
1513 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1514 _ => None,
1515 };
1516
1517 let mut code_action_providers = Vec::new();
1518 let mut load_uncommitted_diff = None;
1519 if let Some(project) = project.clone() {
1520 load_uncommitted_diff = Some(
1521 get_uncommitted_diff_for_buffer(
1522 &project,
1523 buffer.read(cx).all_buffers(),
1524 buffer.clone(),
1525 cx,
1526 )
1527 .shared(),
1528 );
1529 code_action_providers.push(Rc::new(project) as Rc<_>);
1530 }
1531
1532 let mut this = Self {
1533 focus_handle,
1534 show_cursor_when_unfocused: false,
1535 last_focused_descendant: None,
1536 buffer: buffer.clone(),
1537 display_map: display_map.clone(),
1538 selections,
1539 scroll_manager: ScrollManager::new(cx),
1540 columnar_selection_tail: None,
1541 add_selections_state: None,
1542 select_next_state: None,
1543 select_prev_state: None,
1544 selection_history: Default::default(),
1545 autoclose_regions: Default::default(),
1546 snippet_stack: Default::default(),
1547 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1548 ime_transaction: Default::default(),
1549 active_diagnostics: ActiveDiagnostic::None,
1550 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1551 inline_diagnostics_update: Task::ready(()),
1552 inline_diagnostics: Vec::new(),
1553 soft_wrap_mode_override,
1554 hard_wrap: None,
1555 completion_provider: project.clone().map(|project| Box::new(project) as _),
1556 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1557 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1558 project,
1559 blink_manager: blink_manager.clone(),
1560 show_local_selections: true,
1561 show_scrollbars: true,
1562 mode,
1563 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1564 show_gutter: mode.is_full(),
1565 show_line_numbers: None,
1566 use_relative_line_numbers: None,
1567 show_git_diff_gutter: None,
1568 show_code_actions: None,
1569 show_runnables: None,
1570 show_breakpoints: None,
1571 show_wrap_guides: None,
1572 show_indent_guides,
1573 placeholder_text: None,
1574 highlight_order: 0,
1575 highlighted_rows: HashMap::default(),
1576 background_highlights: Default::default(),
1577 gutter_highlights: TreeMap::default(),
1578 scrollbar_marker_state: ScrollbarMarkerState::default(),
1579 active_indent_guides_state: ActiveIndentGuidesState::default(),
1580 nav_history: None,
1581 context_menu: RefCell::new(None),
1582 context_menu_options: None,
1583 mouse_context_menu: None,
1584 completion_tasks: Default::default(),
1585 signature_help_state: SignatureHelpState::default(),
1586 auto_signature_help: None,
1587 find_all_references_task_sources: Vec::new(),
1588 next_completion_id: 0,
1589 next_inlay_id: 0,
1590 code_action_providers,
1591 available_code_actions: Default::default(),
1592 code_actions_task: Default::default(),
1593 selection_highlight_task: Default::default(),
1594 document_highlights_task: Default::default(),
1595 linked_editing_range_task: Default::default(),
1596 pending_rename: Default::default(),
1597 searchable: true,
1598 cursor_shape: EditorSettings::get_global(cx)
1599 .cursor_shape
1600 .unwrap_or_default(),
1601 current_line_highlight: None,
1602 autoindent_mode: Some(AutoindentMode::EachLine),
1603 collapse_matches: false,
1604 workspace: None,
1605 input_enabled: true,
1606 use_modal_editing: mode.is_full(),
1607 read_only: false,
1608 use_autoclose: true,
1609 use_auto_surround: true,
1610 auto_replace_emoji_shortcode: false,
1611 jsx_tag_auto_close_enabled_in_any_buffer: false,
1612 leader_peer_id: None,
1613 remote_id: None,
1614 hover_state: Default::default(),
1615 pending_mouse_down: None,
1616 hovered_link_state: Default::default(),
1617 edit_prediction_provider: None,
1618 active_inline_completion: None,
1619 stale_inline_completion_in_menu: None,
1620 edit_prediction_preview: EditPredictionPreview::Inactive {
1621 released_too_fast: false,
1622 },
1623 inline_diagnostics_enabled: mode.is_full(),
1624 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1625
1626 gutter_hovered: false,
1627 pixel_position_of_newest_cursor: None,
1628 last_bounds: None,
1629 last_position_map: None,
1630 expect_bounds_change: None,
1631 gutter_dimensions: GutterDimensions::default(),
1632 style: None,
1633 show_cursor_names: false,
1634 hovered_cursors: Default::default(),
1635 next_editor_action_id: EditorActionId::default(),
1636 editor_actions: Rc::default(),
1637 inline_completions_hidden_for_vim_mode: false,
1638 show_inline_completions_override: None,
1639 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1640 edit_prediction_settings: EditPredictionSettings::Disabled,
1641 edit_prediction_indent_conflict: false,
1642 edit_prediction_requires_modifier_in_indent_conflict: true,
1643 custom_context_menu: None,
1644 show_git_blame_gutter: false,
1645 show_git_blame_inline: false,
1646 show_selection_menu: None,
1647 show_git_blame_inline_delay_task: None,
1648 git_blame_inline_tooltip: None,
1649 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1650 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1651 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1652 .session
1653 .restore_unsaved_buffers,
1654 blame: None,
1655 blame_subscription: None,
1656 tasks: Default::default(),
1657
1658 breakpoint_store,
1659 gutter_breakpoint_indicator: (None, None),
1660 _subscriptions: vec![
1661 cx.observe(&buffer, Self::on_buffer_changed),
1662 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1663 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1664 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1665 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1666 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1667 cx.observe_window_activation(window, |editor, window, cx| {
1668 let active = window.is_window_active();
1669 editor.blink_manager.update(cx, |blink_manager, cx| {
1670 if active {
1671 blink_manager.enable(cx);
1672 } else {
1673 blink_manager.disable(cx);
1674 }
1675 });
1676 }),
1677 ],
1678 tasks_update_task: None,
1679 linked_edit_ranges: Default::default(),
1680 in_project_search: false,
1681 previous_search_ranges: None,
1682 breadcrumb_header: None,
1683 focused_block: None,
1684 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1685 addons: HashMap::default(),
1686 registered_buffers: HashMap::default(),
1687 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1688 selection_mark_mode: false,
1689 toggle_fold_multiple_buffers: Task::ready(()),
1690 serialize_selections: Task::ready(()),
1691 serialize_folds: Task::ready(()),
1692 text_style_refinement: None,
1693 load_diff_task: load_uncommitted_diff,
1694 mouse_cursor_hidden: false,
1695 hide_mouse_mode: EditorSettings::get_global(cx)
1696 .hide_mouse
1697 .unwrap_or_default(),
1698 change_list: ChangeList::new(),
1699 };
1700 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1701 this._subscriptions
1702 .push(cx.observe(breakpoints, |_, _, cx| {
1703 cx.notify();
1704 }));
1705 }
1706 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1707 this._subscriptions.extend(project_subscriptions);
1708
1709 this._subscriptions.push(cx.subscribe_in(
1710 &cx.entity(),
1711 window,
1712 |editor, _, e: &EditorEvent, window, cx| match e {
1713 EditorEvent::ScrollPositionChanged { local, .. } => {
1714 if *local {
1715 let new_anchor = editor.scroll_manager.anchor();
1716 let snapshot = editor.snapshot(window, cx);
1717 editor.update_restoration_data(cx, move |data| {
1718 data.scroll_position = (
1719 new_anchor.top_row(&snapshot.buffer_snapshot),
1720 new_anchor.offset,
1721 );
1722 });
1723 }
1724 }
1725 EditorEvent::Edited { .. } => {
1726 if !vim_enabled(cx) {
1727 let (map, selections) = editor.selections.all_adjusted_display(cx);
1728 let pop_state = editor
1729 .change_list
1730 .last()
1731 .map(|previous| {
1732 previous.len() == selections.len()
1733 && previous.iter().enumerate().all(|(ix, p)| {
1734 p.to_display_point(&map).row()
1735 == selections[ix].head().row()
1736 })
1737 })
1738 .unwrap_or(false);
1739 let new_positions = selections
1740 .into_iter()
1741 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1742 .collect();
1743 editor
1744 .change_list
1745 .push_to_change_list(pop_state, new_positions);
1746 }
1747 }
1748 _ => (),
1749 },
1750 ));
1751
1752 this.end_selection(window, cx);
1753 this.scroll_manager.show_scrollbars(window, cx);
1754 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1755
1756 if mode.is_full() {
1757 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1758 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1759
1760 if this.git_blame_inline_enabled {
1761 this.git_blame_inline_enabled = true;
1762 this.start_git_blame_inline(false, window, cx);
1763 }
1764
1765 this.go_to_active_debug_line(window, cx);
1766
1767 if let Some(buffer) = buffer.read(cx).as_singleton() {
1768 if let Some(project) = this.project.as_ref() {
1769 let handle = project.update(cx, |project, cx| {
1770 project.register_buffer_with_language_servers(&buffer, cx)
1771 });
1772 this.registered_buffers
1773 .insert(buffer.read(cx).remote_id(), handle);
1774 }
1775 }
1776 }
1777
1778 this.report_editor_event("Editor Opened", None, cx);
1779 this
1780 }
1781
1782 pub fn deploy_mouse_context_menu(
1783 &mut self,
1784 position: gpui::Point<Pixels>,
1785 context_menu: Entity<ContextMenu>,
1786 window: &mut Window,
1787 cx: &mut Context<Self>,
1788 ) {
1789 self.mouse_context_menu = Some(MouseContextMenu::new(
1790 self,
1791 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1792 context_menu,
1793 None,
1794 window,
1795 cx,
1796 ));
1797 }
1798
1799 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1800 self.mouse_context_menu
1801 .as_ref()
1802 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1803 }
1804
1805 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1806 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1807 }
1808
1809 fn key_context_internal(
1810 &self,
1811 has_active_edit_prediction: bool,
1812 window: &Window,
1813 cx: &App,
1814 ) -> KeyContext {
1815 let mut key_context = KeyContext::new_with_defaults();
1816 key_context.add("Editor");
1817 let mode = match self.mode {
1818 EditorMode::SingleLine { .. } => "single_line",
1819 EditorMode::AutoHeight { .. } => "auto_height",
1820 EditorMode::Full { .. } => "full",
1821 };
1822
1823 if EditorSettings::jupyter_enabled(cx) {
1824 key_context.add("jupyter");
1825 }
1826
1827 key_context.set("mode", mode);
1828 if self.pending_rename.is_some() {
1829 key_context.add("renaming");
1830 }
1831
1832 match self.context_menu.borrow().as_ref() {
1833 Some(CodeContextMenu::Completions(_)) => {
1834 key_context.add("menu");
1835 key_context.add("showing_completions");
1836 }
1837 Some(CodeContextMenu::CodeActions(_)) => {
1838 key_context.add("menu");
1839 key_context.add("showing_code_actions")
1840 }
1841 None => {}
1842 }
1843
1844 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1845 if !self.focus_handle(cx).contains_focused(window, cx)
1846 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1847 {
1848 for addon in self.addons.values() {
1849 addon.extend_key_context(&mut key_context, cx)
1850 }
1851 }
1852
1853 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1854 if let Some(extension) = singleton_buffer
1855 .read(cx)
1856 .file()
1857 .and_then(|file| file.path().extension()?.to_str())
1858 {
1859 key_context.set("extension", extension.to_string());
1860 }
1861 } else {
1862 key_context.add("multibuffer");
1863 }
1864
1865 if has_active_edit_prediction {
1866 if self.edit_prediction_in_conflict() {
1867 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1868 } else {
1869 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1870 key_context.add("copilot_suggestion");
1871 }
1872 }
1873
1874 if self.selection_mark_mode {
1875 key_context.add("selection_mode");
1876 }
1877
1878 key_context
1879 }
1880
1881 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1882 self.mouse_cursor_hidden = match origin {
1883 HideMouseCursorOrigin::TypingAction => {
1884 matches!(
1885 self.hide_mouse_mode,
1886 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1887 )
1888 }
1889 HideMouseCursorOrigin::MovementAction => {
1890 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
1891 }
1892 };
1893 }
1894
1895 pub fn edit_prediction_in_conflict(&self) -> bool {
1896 if !self.show_edit_predictions_in_menu() {
1897 return false;
1898 }
1899
1900 let showing_completions = self
1901 .context_menu
1902 .borrow()
1903 .as_ref()
1904 .map_or(false, |context| {
1905 matches!(context, CodeContextMenu::Completions(_))
1906 });
1907
1908 showing_completions
1909 || self.edit_prediction_requires_modifier()
1910 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1911 // bindings to insert tab characters.
1912 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1913 }
1914
1915 pub fn accept_edit_prediction_keybind(
1916 &self,
1917 window: &Window,
1918 cx: &App,
1919 ) -> AcceptEditPredictionBinding {
1920 let key_context = self.key_context_internal(true, window, cx);
1921 let in_conflict = self.edit_prediction_in_conflict();
1922
1923 AcceptEditPredictionBinding(
1924 window
1925 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1926 .into_iter()
1927 .filter(|binding| {
1928 !in_conflict
1929 || binding
1930 .keystrokes()
1931 .first()
1932 .map_or(false, |keystroke| keystroke.modifiers.modified())
1933 })
1934 .rev()
1935 .min_by_key(|binding| {
1936 binding
1937 .keystrokes()
1938 .first()
1939 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1940 }),
1941 )
1942 }
1943
1944 pub fn new_file(
1945 workspace: &mut Workspace,
1946 _: &workspace::NewFile,
1947 window: &mut Window,
1948 cx: &mut Context<Workspace>,
1949 ) {
1950 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1951 "Failed to create buffer",
1952 window,
1953 cx,
1954 |e, _, _| match e.error_code() {
1955 ErrorCode::RemoteUpgradeRequired => Some(format!(
1956 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1957 e.error_tag("required").unwrap_or("the latest version")
1958 )),
1959 _ => None,
1960 },
1961 );
1962 }
1963
1964 pub fn new_in_workspace(
1965 workspace: &mut Workspace,
1966 window: &mut Window,
1967 cx: &mut Context<Workspace>,
1968 ) -> Task<Result<Entity<Editor>>> {
1969 let project = workspace.project().clone();
1970 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1971
1972 cx.spawn_in(window, async move |workspace, cx| {
1973 let buffer = create.await?;
1974 workspace.update_in(cx, |workspace, window, cx| {
1975 let editor =
1976 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1977 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1978 editor
1979 })
1980 })
1981 }
1982
1983 fn new_file_vertical(
1984 workspace: &mut Workspace,
1985 _: &workspace::NewFileSplitVertical,
1986 window: &mut Window,
1987 cx: &mut Context<Workspace>,
1988 ) {
1989 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1990 }
1991
1992 fn new_file_horizontal(
1993 workspace: &mut Workspace,
1994 _: &workspace::NewFileSplitHorizontal,
1995 window: &mut Window,
1996 cx: &mut Context<Workspace>,
1997 ) {
1998 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1999 }
2000
2001 fn new_file_in_direction(
2002 workspace: &mut Workspace,
2003 direction: SplitDirection,
2004 window: &mut Window,
2005 cx: &mut Context<Workspace>,
2006 ) {
2007 let project = workspace.project().clone();
2008 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2009
2010 cx.spawn_in(window, async move |workspace, cx| {
2011 let buffer = create.await?;
2012 workspace.update_in(cx, move |workspace, window, cx| {
2013 workspace.split_item(
2014 direction,
2015 Box::new(
2016 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2017 ),
2018 window,
2019 cx,
2020 )
2021 })?;
2022 anyhow::Ok(())
2023 })
2024 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2025 match e.error_code() {
2026 ErrorCode::RemoteUpgradeRequired => Some(format!(
2027 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2028 e.error_tag("required").unwrap_or("the latest version")
2029 )),
2030 _ => None,
2031 }
2032 });
2033 }
2034
2035 pub fn leader_peer_id(&self) -> Option<PeerId> {
2036 self.leader_peer_id
2037 }
2038
2039 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2040 &self.buffer
2041 }
2042
2043 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2044 self.workspace.as_ref()?.0.upgrade()
2045 }
2046
2047 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2048 self.buffer().read(cx).title(cx)
2049 }
2050
2051 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2052 let git_blame_gutter_max_author_length = self
2053 .render_git_blame_gutter(cx)
2054 .then(|| {
2055 if let Some(blame) = self.blame.as_ref() {
2056 let max_author_length =
2057 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2058 Some(max_author_length)
2059 } else {
2060 None
2061 }
2062 })
2063 .flatten();
2064
2065 EditorSnapshot {
2066 mode: self.mode,
2067 show_gutter: self.show_gutter,
2068 show_line_numbers: self.show_line_numbers,
2069 show_git_diff_gutter: self.show_git_diff_gutter,
2070 show_code_actions: self.show_code_actions,
2071 show_runnables: self.show_runnables,
2072 show_breakpoints: self.show_breakpoints,
2073 git_blame_gutter_max_author_length,
2074 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2075 scroll_anchor: self.scroll_manager.anchor(),
2076 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2077 placeholder_text: self.placeholder_text.clone(),
2078 is_focused: self.focus_handle.is_focused(window),
2079 current_line_highlight: self
2080 .current_line_highlight
2081 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2082 gutter_hovered: self.gutter_hovered,
2083 }
2084 }
2085
2086 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2087 self.buffer.read(cx).language_at(point, cx)
2088 }
2089
2090 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2091 self.buffer.read(cx).read(cx).file_at(point).cloned()
2092 }
2093
2094 pub fn active_excerpt(
2095 &self,
2096 cx: &App,
2097 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2098 self.buffer
2099 .read(cx)
2100 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2101 }
2102
2103 pub fn mode(&self) -> EditorMode {
2104 self.mode
2105 }
2106
2107 pub fn set_mode(&mut self, mode: EditorMode) {
2108 self.mode = mode;
2109 }
2110
2111 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2112 self.collaboration_hub.as_deref()
2113 }
2114
2115 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2116 self.collaboration_hub = Some(hub);
2117 }
2118
2119 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2120 self.in_project_search = in_project_search;
2121 }
2122
2123 pub fn set_custom_context_menu(
2124 &mut self,
2125 f: impl 'static
2126 + Fn(
2127 &mut Self,
2128 DisplayPoint,
2129 &mut Window,
2130 &mut Context<Self>,
2131 ) -> Option<Entity<ui::ContextMenu>>,
2132 ) {
2133 self.custom_context_menu = Some(Box::new(f))
2134 }
2135
2136 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2137 self.completion_provider = provider;
2138 }
2139
2140 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2141 self.semantics_provider.clone()
2142 }
2143
2144 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2145 self.semantics_provider = provider;
2146 }
2147
2148 pub fn set_edit_prediction_provider<T>(
2149 &mut self,
2150 provider: Option<Entity<T>>,
2151 window: &mut Window,
2152 cx: &mut Context<Self>,
2153 ) where
2154 T: EditPredictionProvider,
2155 {
2156 self.edit_prediction_provider =
2157 provider.map(|provider| RegisteredInlineCompletionProvider {
2158 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2159 if this.focus_handle.is_focused(window) {
2160 this.update_visible_inline_completion(window, cx);
2161 }
2162 }),
2163 provider: Arc::new(provider),
2164 });
2165 self.update_edit_prediction_settings(cx);
2166 self.refresh_inline_completion(false, false, window, cx);
2167 }
2168
2169 pub fn placeholder_text(&self) -> Option<&str> {
2170 self.placeholder_text.as_deref()
2171 }
2172
2173 pub fn set_placeholder_text(
2174 &mut self,
2175 placeholder_text: impl Into<Arc<str>>,
2176 cx: &mut Context<Self>,
2177 ) {
2178 let placeholder_text = Some(placeholder_text.into());
2179 if self.placeholder_text != placeholder_text {
2180 self.placeholder_text = placeholder_text;
2181 cx.notify();
2182 }
2183 }
2184
2185 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2186 self.cursor_shape = cursor_shape;
2187
2188 // Disrupt blink for immediate user feedback that the cursor shape has changed
2189 self.blink_manager.update(cx, BlinkManager::show_cursor);
2190
2191 cx.notify();
2192 }
2193
2194 pub fn set_current_line_highlight(
2195 &mut self,
2196 current_line_highlight: Option<CurrentLineHighlight>,
2197 ) {
2198 self.current_line_highlight = current_line_highlight;
2199 }
2200
2201 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2202 self.collapse_matches = collapse_matches;
2203 }
2204
2205 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2206 let buffers = self.buffer.read(cx).all_buffers();
2207 let Some(project) = self.project.as_ref() else {
2208 return;
2209 };
2210 project.update(cx, |project, cx| {
2211 for buffer in buffers {
2212 self.registered_buffers
2213 .entry(buffer.read(cx).remote_id())
2214 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2215 }
2216 })
2217 }
2218
2219 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2220 if self.collapse_matches {
2221 return range.start..range.start;
2222 }
2223 range.clone()
2224 }
2225
2226 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2227 if self.display_map.read(cx).clip_at_line_ends != clip {
2228 self.display_map
2229 .update(cx, |map, _| map.clip_at_line_ends = clip);
2230 }
2231 }
2232
2233 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2234 self.input_enabled = input_enabled;
2235 }
2236
2237 pub fn set_inline_completions_hidden_for_vim_mode(
2238 &mut self,
2239 hidden: bool,
2240 window: &mut Window,
2241 cx: &mut Context<Self>,
2242 ) {
2243 if hidden != self.inline_completions_hidden_for_vim_mode {
2244 self.inline_completions_hidden_for_vim_mode = hidden;
2245 if hidden {
2246 self.update_visible_inline_completion(window, cx);
2247 } else {
2248 self.refresh_inline_completion(true, false, window, cx);
2249 }
2250 }
2251 }
2252
2253 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2254 self.menu_inline_completions_policy = value;
2255 }
2256
2257 pub fn set_autoindent(&mut self, autoindent: bool) {
2258 if autoindent {
2259 self.autoindent_mode = Some(AutoindentMode::EachLine);
2260 } else {
2261 self.autoindent_mode = None;
2262 }
2263 }
2264
2265 pub fn read_only(&self, cx: &App) -> bool {
2266 self.read_only || self.buffer.read(cx).read_only()
2267 }
2268
2269 pub fn set_read_only(&mut self, read_only: bool) {
2270 self.read_only = read_only;
2271 }
2272
2273 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2274 self.use_autoclose = autoclose;
2275 }
2276
2277 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2278 self.use_auto_surround = auto_surround;
2279 }
2280
2281 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2282 self.auto_replace_emoji_shortcode = auto_replace;
2283 }
2284
2285 pub fn toggle_edit_predictions(
2286 &mut self,
2287 _: &ToggleEditPrediction,
2288 window: &mut Window,
2289 cx: &mut Context<Self>,
2290 ) {
2291 if self.show_inline_completions_override.is_some() {
2292 self.set_show_edit_predictions(None, window, cx);
2293 } else {
2294 let show_edit_predictions = !self.edit_predictions_enabled();
2295 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2296 }
2297 }
2298
2299 pub fn set_show_edit_predictions(
2300 &mut self,
2301 show_edit_predictions: Option<bool>,
2302 window: &mut Window,
2303 cx: &mut Context<Self>,
2304 ) {
2305 self.show_inline_completions_override = show_edit_predictions;
2306 self.update_edit_prediction_settings(cx);
2307
2308 if let Some(false) = show_edit_predictions {
2309 self.discard_inline_completion(false, cx);
2310 } else {
2311 self.refresh_inline_completion(false, true, window, cx);
2312 }
2313 }
2314
2315 fn inline_completions_disabled_in_scope(
2316 &self,
2317 buffer: &Entity<Buffer>,
2318 buffer_position: language::Anchor,
2319 cx: &App,
2320 ) -> bool {
2321 let snapshot = buffer.read(cx).snapshot();
2322 let settings = snapshot.settings_at(buffer_position, cx);
2323
2324 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2325 return false;
2326 };
2327
2328 scope.override_name().map_or(false, |scope_name| {
2329 settings
2330 .edit_predictions_disabled_in
2331 .iter()
2332 .any(|s| s == scope_name)
2333 })
2334 }
2335
2336 pub fn set_use_modal_editing(&mut self, to: bool) {
2337 self.use_modal_editing = to;
2338 }
2339
2340 pub fn use_modal_editing(&self) -> bool {
2341 self.use_modal_editing
2342 }
2343
2344 fn selections_did_change(
2345 &mut self,
2346 local: bool,
2347 old_cursor_position: &Anchor,
2348 show_completions: bool,
2349 window: &mut Window,
2350 cx: &mut Context<Self>,
2351 ) {
2352 window.invalidate_character_coordinates();
2353
2354 // Copy selections to primary selection buffer
2355 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2356 if local {
2357 let selections = self.selections.all::<usize>(cx);
2358 let buffer_handle = self.buffer.read(cx).read(cx);
2359
2360 let mut text = String::new();
2361 for (index, selection) in selections.iter().enumerate() {
2362 let text_for_selection = buffer_handle
2363 .text_for_range(selection.start..selection.end)
2364 .collect::<String>();
2365
2366 text.push_str(&text_for_selection);
2367 if index != selections.len() - 1 {
2368 text.push('\n');
2369 }
2370 }
2371
2372 if !text.is_empty() {
2373 cx.write_to_primary(ClipboardItem::new_string(text));
2374 }
2375 }
2376
2377 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2378 self.buffer.update(cx, |buffer, cx| {
2379 buffer.set_active_selections(
2380 &self.selections.disjoint_anchors(),
2381 self.selections.line_mode,
2382 self.cursor_shape,
2383 cx,
2384 )
2385 });
2386 }
2387 let display_map = self
2388 .display_map
2389 .update(cx, |display_map, cx| display_map.snapshot(cx));
2390 let buffer = &display_map.buffer_snapshot;
2391 self.add_selections_state = None;
2392 self.select_next_state = None;
2393 self.select_prev_state = None;
2394 self.select_syntax_node_history.try_clear();
2395 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2396 self.snippet_stack
2397 .invalidate(&self.selections.disjoint_anchors(), buffer);
2398 self.take_rename(false, window, cx);
2399
2400 let new_cursor_position = self.selections.newest_anchor().head();
2401
2402 self.push_to_nav_history(
2403 *old_cursor_position,
2404 Some(new_cursor_position.to_point(buffer)),
2405 false,
2406 cx,
2407 );
2408
2409 if local {
2410 let new_cursor_position = self.selections.newest_anchor().head();
2411 let mut context_menu = self.context_menu.borrow_mut();
2412 let completion_menu = match context_menu.as_ref() {
2413 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2414 _ => {
2415 *context_menu = None;
2416 None
2417 }
2418 };
2419 if let Some(buffer_id) = new_cursor_position.buffer_id {
2420 if !self.registered_buffers.contains_key(&buffer_id) {
2421 if let Some(project) = self.project.as_ref() {
2422 project.update(cx, |project, cx| {
2423 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2424 return;
2425 };
2426 self.registered_buffers.insert(
2427 buffer_id,
2428 project.register_buffer_with_language_servers(&buffer, cx),
2429 );
2430 })
2431 }
2432 }
2433 }
2434
2435 if let Some(completion_menu) = completion_menu {
2436 let cursor_position = new_cursor_position.to_offset(buffer);
2437 let (word_range, kind) =
2438 buffer.surrounding_word(completion_menu.initial_position, true);
2439 if kind == Some(CharKind::Word)
2440 && word_range.to_inclusive().contains(&cursor_position)
2441 {
2442 let mut completion_menu = completion_menu.clone();
2443 drop(context_menu);
2444
2445 let query = Self::completion_query(buffer, cursor_position);
2446 cx.spawn(async move |this, cx| {
2447 completion_menu
2448 .filter(query.as_deref(), cx.background_executor().clone())
2449 .await;
2450
2451 this.update(cx, |this, cx| {
2452 let mut context_menu = this.context_menu.borrow_mut();
2453 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2454 else {
2455 return;
2456 };
2457
2458 if menu.id > completion_menu.id {
2459 return;
2460 }
2461
2462 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2463 drop(context_menu);
2464 cx.notify();
2465 })
2466 })
2467 .detach();
2468
2469 if show_completions {
2470 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2471 }
2472 } else {
2473 drop(context_menu);
2474 self.hide_context_menu(window, cx);
2475 }
2476 } else {
2477 drop(context_menu);
2478 }
2479
2480 hide_hover(self, cx);
2481
2482 if old_cursor_position.to_display_point(&display_map).row()
2483 != new_cursor_position.to_display_point(&display_map).row()
2484 {
2485 self.available_code_actions.take();
2486 }
2487 self.refresh_code_actions(window, cx);
2488 self.refresh_document_highlights(cx);
2489 self.refresh_selected_text_highlights(window, cx);
2490 refresh_matching_bracket_highlights(self, window, cx);
2491 self.update_visible_inline_completion(window, cx);
2492 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2493 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2494 if self.git_blame_inline_enabled {
2495 self.start_inline_blame_timer(window, cx);
2496 }
2497 }
2498
2499 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2500 cx.emit(EditorEvent::SelectionsChanged { local });
2501
2502 let selections = &self.selections.disjoint;
2503 if selections.len() == 1 {
2504 cx.emit(SearchEvent::ActiveMatchChanged)
2505 }
2506 if local {
2507 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2508 let inmemory_selections = selections
2509 .iter()
2510 .map(|s| {
2511 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2512 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2513 })
2514 .collect();
2515 self.update_restoration_data(cx, |data| {
2516 data.selections = inmemory_selections;
2517 });
2518
2519 if WorkspaceSettings::get(None, cx).restore_on_startup
2520 != RestoreOnStartupBehavior::None
2521 {
2522 if let Some(workspace_id) =
2523 self.workspace.as_ref().and_then(|workspace| workspace.1)
2524 {
2525 let snapshot = self.buffer().read(cx).snapshot(cx);
2526 let selections = selections.clone();
2527 let background_executor = cx.background_executor().clone();
2528 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2529 self.serialize_selections = cx.background_spawn(async move {
2530 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2531 let db_selections = selections
2532 .iter()
2533 .map(|selection| {
2534 (
2535 selection.start.to_offset(&snapshot),
2536 selection.end.to_offset(&snapshot),
2537 )
2538 })
2539 .collect();
2540
2541 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2542 .await
2543 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2544 .log_err();
2545 });
2546 }
2547 }
2548 }
2549 }
2550
2551 cx.notify();
2552 }
2553
2554 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2555 use text::ToOffset as _;
2556 use text::ToPoint as _;
2557
2558 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2559 return;
2560 }
2561
2562 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2563 return;
2564 };
2565
2566 let snapshot = singleton.read(cx).snapshot();
2567 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2568 let display_snapshot = display_map.snapshot(cx);
2569
2570 display_snapshot
2571 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2572 .map(|fold| {
2573 fold.range.start.text_anchor.to_point(&snapshot)
2574 ..fold.range.end.text_anchor.to_point(&snapshot)
2575 })
2576 .collect()
2577 });
2578 self.update_restoration_data(cx, |data| {
2579 data.folds = inmemory_folds;
2580 });
2581
2582 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2583 return;
2584 };
2585 let background_executor = cx.background_executor().clone();
2586 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2587 let db_folds = self.display_map.update(cx, |display_map, cx| {
2588 display_map
2589 .snapshot(cx)
2590 .folds_in_range(0..snapshot.len())
2591 .map(|fold| {
2592 (
2593 fold.range.start.text_anchor.to_offset(&snapshot),
2594 fold.range.end.text_anchor.to_offset(&snapshot),
2595 )
2596 })
2597 .collect()
2598 });
2599 self.serialize_folds = cx.background_spawn(async move {
2600 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2601 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2602 .await
2603 .with_context(|| {
2604 format!(
2605 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2606 )
2607 })
2608 .log_err();
2609 });
2610 }
2611
2612 pub fn sync_selections(
2613 &mut self,
2614 other: Entity<Editor>,
2615 cx: &mut Context<Self>,
2616 ) -> gpui::Subscription {
2617 let other_selections = other.read(cx).selections.disjoint.to_vec();
2618 self.selections.change_with(cx, |selections| {
2619 selections.select_anchors(other_selections);
2620 });
2621
2622 let other_subscription =
2623 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2624 EditorEvent::SelectionsChanged { local: true } => {
2625 let other_selections = other.read(cx).selections.disjoint.to_vec();
2626 if other_selections.is_empty() {
2627 return;
2628 }
2629 this.selections.change_with(cx, |selections| {
2630 selections.select_anchors(other_selections);
2631 });
2632 }
2633 _ => {}
2634 });
2635
2636 let this_subscription =
2637 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2638 EditorEvent::SelectionsChanged { local: true } => {
2639 let these_selections = this.selections.disjoint.to_vec();
2640 if these_selections.is_empty() {
2641 return;
2642 }
2643 other.update(cx, |other_editor, cx| {
2644 other_editor.selections.change_with(cx, |selections| {
2645 selections.select_anchors(these_selections);
2646 })
2647 });
2648 }
2649 _ => {}
2650 });
2651
2652 Subscription::join(other_subscription, this_subscription)
2653 }
2654
2655 pub fn change_selections<R>(
2656 &mut self,
2657 autoscroll: Option<Autoscroll>,
2658 window: &mut Window,
2659 cx: &mut Context<Self>,
2660 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2661 ) -> R {
2662 self.change_selections_inner(autoscroll, true, window, cx, change)
2663 }
2664
2665 fn change_selections_inner<R>(
2666 &mut self,
2667 autoscroll: Option<Autoscroll>,
2668 request_completions: bool,
2669 window: &mut Window,
2670 cx: &mut Context<Self>,
2671 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2672 ) -> R {
2673 let old_cursor_position = self.selections.newest_anchor().head();
2674 self.push_to_selection_history();
2675
2676 let (changed, result) = self.selections.change_with(cx, change);
2677
2678 if changed {
2679 if let Some(autoscroll) = autoscroll {
2680 self.request_autoscroll(autoscroll, cx);
2681 }
2682 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2683
2684 if self.should_open_signature_help_automatically(
2685 &old_cursor_position,
2686 self.signature_help_state.backspace_pressed(),
2687 cx,
2688 ) {
2689 self.show_signature_help(&ShowSignatureHelp, window, cx);
2690 }
2691 self.signature_help_state.set_backspace_pressed(false);
2692 }
2693
2694 result
2695 }
2696
2697 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2698 where
2699 I: IntoIterator<Item = (Range<S>, T)>,
2700 S: ToOffset,
2701 T: Into<Arc<str>>,
2702 {
2703 if self.read_only(cx) {
2704 return;
2705 }
2706
2707 self.buffer
2708 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2709 }
2710
2711 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2712 where
2713 I: IntoIterator<Item = (Range<S>, T)>,
2714 S: ToOffset,
2715 T: Into<Arc<str>>,
2716 {
2717 if self.read_only(cx) {
2718 return;
2719 }
2720
2721 self.buffer.update(cx, |buffer, cx| {
2722 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2723 });
2724 }
2725
2726 pub fn edit_with_block_indent<I, S, T>(
2727 &mut self,
2728 edits: I,
2729 original_indent_columns: Vec<Option<u32>>,
2730 cx: &mut Context<Self>,
2731 ) where
2732 I: IntoIterator<Item = (Range<S>, T)>,
2733 S: ToOffset,
2734 T: Into<Arc<str>>,
2735 {
2736 if self.read_only(cx) {
2737 return;
2738 }
2739
2740 self.buffer.update(cx, |buffer, cx| {
2741 buffer.edit(
2742 edits,
2743 Some(AutoindentMode::Block {
2744 original_indent_columns,
2745 }),
2746 cx,
2747 )
2748 });
2749 }
2750
2751 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2752 self.hide_context_menu(window, cx);
2753
2754 match phase {
2755 SelectPhase::Begin {
2756 position,
2757 add,
2758 click_count,
2759 } => self.begin_selection(position, add, click_count, window, cx),
2760 SelectPhase::BeginColumnar {
2761 position,
2762 goal_column,
2763 reset,
2764 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2765 SelectPhase::Extend {
2766 position,
2767 click_count,
2768 } => self.extend_selection(position, click_count, window, cx),
2769 SelectPhase::Update {
2770 position,
2771 goal_column,
2772 scroll_delta,
2773 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2774 SelectPhase::End => self.end_selection(window, cx),
2775 }
2776 }
2777
2778 fn extend_selection(
2779 &mut self,
2780 position: DisplayPoint,
2781 click_count: usize,
2782 window: &mut Window,
2783 cx: &mut Context<Self>,
2784 ) {
2785 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2786 let tail = self.selections.newest::<usize>(cx).tail();
2787 self.begin_selection(position, false, click_count, window, cx);
2788
2789 let position = position.to_offset(&display_map, Bias::Left);
2790 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2791
2792 let mut pending_selection = self
2793 .selections
2794 .pending_anchor()
2795 .expect("extend_selection not called with pending selection");
2796 if position >= tail {
2797 pending_selection.start = tail_anchor;
2798 } else {
2799 pending_selection.end = tail_anchor;
2800 pending_selection.reversed = true;
2801 }
2802
2803 let mut pending_mode = self.selections.pending_mode().unwrap();
2804 match &mut pending_mode {
2805 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2806 _ => {}
2807 }
2808
2809 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2810 s.set_pending(pending_selection, pending_mode)
2811 });
2812 }
2813
2814 fn begin_selection(
2815 &mut self,
2816 position: DisplayPoint,
2817 add: bool,
2818 click_count: usize,
2819 window: &mut Window,
2820 cx: &mut Context<Self>,
2821 ) {
2822 if !self.focus_handle.is_focused(window) {
2823 self.last_focused_descendant = None;
2824 window.focus(&self.focus_handle);
2825 }
2826
2827 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2828 let buffer = &display_map.buffer_snapshot;
2829 let newest_selection = self.selections.newest_anchor().clone();
2830 let position = display_map.clip_point(position, Bias::Left);
2831
2832 let start;
2833 let end;
2834 let mode;
2835 let mut auto_scroll;
2836 match click_count {
2837 1 => {
2838 start = buffer.anchor_before(position.to_point(&display_map));
2839 end = start;
2840 mode = SelectMode::Character;
2841 auto_scroll = true;
2842 }
2843 2 => {
2844 let range = movement::surrounding_word(&display_map, position);
2845 start = buffer.anchor_before(range.start.to_point(&display_map));
2846 end = buffer.anchor_before(range.end.to_point(&display_map));
2847 mode = SelectMode::Word(start..end);
2848 auto_scroll = true;
2849 }
2850 3 => {
2851 let position = display_map
2852 .clip_point(position, Bias::Left)
2853 .to_point(&display_map);
2854 let line_start = display_map.prev_line_boundary(position).0;
2855 let next_line_start = buffer.clip_point(
2856 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2857 Bias::Left,
2858 );
2859 start = buffer.anchor_before(line_start);
2860 end = buffer.anchor_before(next_line_start);
2861 mode = SelectMode::Line(start..end);
2862 auto_scroll = true;
2863 }
2864 _ => {
2865 start = buffer.anchor_before(0);
2866 end = buffer.anchor_before(buffer.len());
2867 mode = SelectMode::All;
2868 auto_scroll = false;
2869 }
2870 }
2871 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2872
2873 let point_to_delete: Option<usize> = {
2874 let selected_points: Vec<Selection<Point>> =
2875 self.selections.disjoint_in_range(start..end, cx);
2876
2877 if !add || click_count > 1 {
2878 None
2879 } else if !selected_points.is_empty() {
2880 Some(selected_points[0].id)
2881 } else {
2882 let clicked_point_already_selected =
2883 self.selections.disjoint.iter().find(|selection| {
2884 selection.start.to_point(buffer) == start.to_point(buffer)
2885 || selection.end.to_point(buffer) == end.to_point(buffer)
2886 });
2887
2888 clicked_point_already_selected.map(|selection| selection.id)
2889 }
2890 };
2891
2892 let selections_count = self.selections.count();
2893
2894 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2895 if let Some(point_to_delete) = point_to_delete {
2896 s.delete(point_to_delete);
2897
2898 if selections_count == 1 {
2899 s.set_pending_anchor_range(start..end, mode);
2900 }
2901 } else {
2902 if !add {
2903 s.clear_disjoint();
2904 } else if click_count > 1 {
2905 s.delete(newest_selection.id)
2906 }
2907
2908 s.set_pending_anchor_range(start..end, mode);
2909 }
2910 });
2911 }
2912
2913 fn begin_columnar_selection(
2914 &mut self,
2915 position: DisplayPoint,
2916 goal_column: u32,
2917 reset: bool,
2918 window: &mut Window,
2919 cx: &mut Context<Self>,
2920 ) {
2921 if !self.focus_handle.is_focused(window) {
2922 self.last_focused_descendant = None;
2923 window.focus(&self.focus_handle);
2924 }
2925
2926 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2927
2928 if reset {
2929 let pointer_position = display_map
2930 .buffer_snapshot
2931 .anchor_before(position.to_point(&display_map));
2932
2933 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2934 s.clear_disjoint();
2935 s.set_pending_anchor_range(
2936 pointer_position..pointer_position,
2937 SelectMode::Character,
2938 );
2939 });
2940 }
2941
2942 let tail = self.selections.newest::<Point>(cx).tail();
2943 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2944
2945 if !reset {
2946 self.select_columns(
2947 tail.to_display_point(&display_map),
2948 position,
2949 goal_column,
2950 &display_map,
2951 window,
2952 cx,
2953 );
2954 }
2955 }
2956
2957 fn update_selection(
2958 &mut self,
2959 position: DisplayPoint,
2960 goal_column: u32,
2961 scroll_delta: gpui::Point<f32>,
2962 window: &mut Window,
2963 cx: &mut Context<Self>,
2964 ) {
2965 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2966
2967 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2968 let tail = tail.to_display_point(&display_map);
2969 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2970 } else if let Some(mut pending) = self.selections.pending_anchor() {
2971 let buffer = self.buffer.read(cx).snapshot(cx);
2972 let head;
2973 let tail;
2974 let mode = self.selections.pending_mode().unwrap();
2975 match &mode {
2976 SelectMode::Character => {
2977 head = position.to_point(&display_map);
2978 tail = pending.tail().to_point(&buffer);
2979 }
2980 SelectMode::Word(original_range) => {
2981 let original_display_range = original_range.start.to_display_point(&display_map)
2982 ..original_range.end.to_display_point(&display_map);
2983 let original_buffer_range = original_display_range.start.to_point(&display_map)
2984 ..original_display_range.end.to_point(&display_map);
2985 if movement::is_inside_word(&display_map, position)
2986 || original_display_range.contains(&position)
2987 {
2988 let word_range = movement::surrounding_word(&display_map, position);
2989 if word_range.start < original_display_range.start {
2990 head = word_range.start.to_point(&display_map);
2991 } else {
2992 head = word_range.end.to_point(&display_map);
2993 }
2994 } else {
2995 head = position.to_point(&display_map);
2996 }
2997
2998 if head <= original_buffer_range.start {
2999 tail = original_buffer_range.end;
3000 } else {
3001 tail = original_buffer_range.start;
3002 }
3003 }
3004 SelectMode::Line(original_range) => {
3005 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3006
3007 let position = display_map
3008 .clip_point(position, Bias::Left)
3009 .to_point(&display_map);
3010 let line_start = display_map.prev_line_boundary(position).0;
3011 let next_line_start = buffer.clip_point(
3012 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3013 Bias::Left,
3014 );
3015
3016 if line_start < original_range.start {
3017 head = line_start
3018 } else {
3019 head = next_line_start
3020 }
3021
3022 if head <= original_range.start {
3023 tail = original_range.end;
3024 } else {
3025 tail = original_range.start;
3026 }
3027 }
3028 SelectMode::All => {
3029 return;
3030 }
3031 };
3032
3033 if head < tail {
3034 pending.start = buffer.anchor_before(head);
3035 pending.end = buffer.anchor_before(tail);
3036 pending.reversed = true;
3037 } else {
3038 pending.start = buffer.anchor_before(tail);
3039 pending.end = buffer.anchor_before(head);
3040 pending.reversed = false;
3041 }
3042
3043 self.change_selections(None, window, cx, |s| {
3044 s.set_pending(pending, mode);
3045 });
3046 } else {
3047 log::error!("update_selection dispatched with no pending selection");
3048 return;
3049 }
3050
3051 self.apply_scroll_delta(scroll_delta, window, cx);
3052 cx.notify();
3053 }
3054
3055 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3056 self.columnar_selection_tail.take();
3057 if self.selections.pending_anchor().is_some() {
3058 let selections = self.selections.all::<usize>(cx);
3059 self.change_selections(None, window, cx, |s| {
3060 s.select(selections);
3061 s.clear_pending();
3062 });
3063 }
3064 }
3065
3066 fn select_columns(
3067 &mut self,
3068 tail: DisplayPoint,
3069 head: DisplayPoint,
3070 goal_column: u32,
3071 display_map: &DisplaySnapshot,
3072 window: &mut Window,
3073 cx: &mut Context<Self>,
3074 ) {
3075 let start_row = cmp::min(tail.row(), head.row());
3076 let end_row = cmp::max(tail.row(), head.row());
3077 let start_column = cmp::min(tail.column(), goal_column);
3078 let end_column = cmp::max(tail.column(), goal_column);
3079 let reversed = start_column < tail.column();
3080
3081 let selection_ranges = (start_row.0..=end_row.0)
3082 .map(DisplayRow)
3083 .filter_map(|row| {
3084 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3085 let start = display_map
3086 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3087 .to_point(display_map);
3088 let end = display_map
3089 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3090 .to_point(display_map);
3091 if reversed {
3092 Some(end..start)
3093 } else {
3094 Some(start..end)
3095 }
3096 } else {
3097 None
3098 }
3099 })
3100 .collect::<Vec<_>>();
3101
3102 self.change_selections(None, window, cx, |s| {
3103 s.select_ranges(selection_ranges);
3104 });
3105 cx.notify();
3106 }
3107
3108 pub fn has_pending_nonempty_selection(&self) -> bool {
3109 let pending_nonempty_selection = match self.selections.pending_anchor() {
3110 Some(Selection { start, end, .. }) => start != end,
3111 None => false,
3112 };
3113
3114 pending_nonempty_selection
3115 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3116 }
3117
3118 pub fn has_pending_selection(&self) -> bool {
3119 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3120 }
3121
3122 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3123 self.selection_mark_mode = false;
3124
3125 if self.clear_expanded_diff_hunks(cx) {
3126 cx.notify();
3127 return;
3128 }
3129 if self.dismiss_menus_and_popups(true, window, cx) {
3130 return;
3131 }
3132
3133 if self.mode.is_full()
3134 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3135 {
3136 return;
3137 }
3138
3139 cx.propagate();
3140 }
3141
3142 pub fn dismiss_menus_and_popups(
3143 &mut self,
3144 is_user_requested: bool,
3145 window: &mut Window,
3146 cx: &mut Context<Self>,
3147 ) -> bool {
3148 if self.take_rename(false, window, cx).is_some() {
3149 return true;
3150 }
3151
3152 if hide_hover(self, cx) {
3153 return true;
3154 }
3155
3156 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3157 return true;
3158 }
3159
3160 if self.hide_context_menu(window, cx).is_some() {
3161 return true;
3162 }
3163
3164 if self.mouse_context_menu.take().is_some() {
3165 return true;
3166 }
3167
3168 if is_user_requested && self.discard_inline_completion(true, cx) {
3169 return true;
3170 }
3171
3172 if self.snippet_stack.pop().is_some() {
3173 return true;
3174 }
3175
3176 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3177 self.dismiss_diagnostics(cx);
3178 return true;
3179 }
3180
3181 false
3182 }
3183
3184 fn linked_editing_ranges_for(
3185 &self,
3186 selection: Range<text::Anchor>,
3187 cx: &App,
3188 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3189 if self.linked_edit_ranges.is_empty() {
3190 return None;
3191 }
3192 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3193 selection.end.buffer_id.and_then(|end_buffer_id| {
3194 if selection.start.buffer_id != Some(end_buffer_id) {
3195 return None;
3196 }
3197 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3198 let snapshot = buffer.read(cx).snapshot();
3199 self.linked_edit_ranges
3200 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3201 .map(|ranges| (ranges, snapshot, buffer))
3202 })?;
3203 use text::ToOffset as TO;
3204 // find offset from the start of current range to current cursor position
3205 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3206
3207 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3208 let start_difference = start_offset - start_byte_offset;
3209 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3210 let end_difference = end_offset - start_byte_offset;
3211 // Current range has associated linked ranges.
3212 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3213 for range in linked_ranges.iter() {
3214 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3215 let end_offset = start_offset + end_difference;
3216 let start_offset = start_offset + start_difference;
3217 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3218 continue;
3219 }
3220 if self.selections.disjoint_anchor_ranges().any(|s| {
3221 if s.start.buffer_id != selection.start.buffer_id
3222 || s.end.buffer_id != selection.end.buffer_id
3223 {
3224 return false;
3225 }
3226 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3227 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3228 }) {
3229 continue;
3230 }
3231 let start = buffer_snapshot.anchor_after(start_offset);
3232 let end = buffer_snapshot.anchor_after(end_offset);
3233 linked_edits
3234 .entry(buffer.clone())
3235 .or_default()
3236 .push(start..end);
3237 }
3238 Some(linked_edits)
3239 }
3240
3241 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3242 let text: Arc<str> = text.into();
3243
3244 if self.read_only(cx) {
3245 return;
3246 }
3247
3248 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3249
3250 let selections = self.selections.all_adjusted(cx);
3251 let mut bracket_inserted = false;
3252 let mut edits = Vec::new();
3253 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3254 let mut new_selections = Vec::with_capacity(selections.len());
3255 let mut new_autoclose_regions = Vec::new();
3256 let snapshot = self.buffer.read(cx).read(cx);
3257 let mut clear_linked_edit_ranges = false;
3258
3259 for (selection, autoclose_region) in
3260 self.selections_with_autoclose_regions(selections, &snapshot)
3261 {
3262 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3263 // Determine if the inserted text matches the opening or closing
3264 // bracket of any of this language's bracket pairs.
3265 let mut bracket_pair = None;
3266 let mut is_bracket_pair_start = false;
3267 let mut is_bracket_pair_end = false;
3268 if !text.is_empty() {
3269 let mut bracket_pair_matching_end = None;
3270 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3271 // and they are removing the character that triggered IME popup.
3272 for (pair, enabled) in scope.brackets() {
3273 if !pair.close && !pair.surround {
3274 continue;
3275 }
3276
3277 if enabled && pair.start.ends_with(text.as_ref()) {
3278 let prefix_len = pair.start.len() - text.len();
3279 let preceding_text_matches_prefix = prefix_len == 0
3280 || (selection.start.column >= (prefix_len as u32)
3281 && snapshot.contains_str_at(
3282 Point::new(
3283 selection.start.row,
3284 selection.start.column - (prefix_len as u32),
3285 ),
3286 &pair.start[..prefix_len],
3287 ));
3288 if preceding_text_matches_prefix {
3289 bracket_pair = Some(pair.clone());
3290 is_bracket_pair_start = true;
3291 break;
3292 }
3293 }
3294 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3295 {
3296 // take first bracket pair matching end, but don't break in case a later bracket
3297 // pair matches start
3298 bracket_pair_matching_end = Some(pair.clone());
3299 }
3300 }
3301 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3302 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3303 is_bracket_pair_end = true;
3304 }
3305 }
3306
3307 if let Some(bracket_pair) = bracket_pair {
3308 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3309 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3310 let auto_surround =
3311 self.use_auto_surround && snapshot_settings.use_auto_surround;
3312 if selection.is_empty() {
3313 if is_bracket_pair_start {
3314 // If the inserted text is a suffix of an opening bracket and the
3315 // selection is preceded by the rest of the opening bracket, then
3316 // insert the closing bracket.
3317 let following_text_allows_autoclose = snapshot
3318 .chars_at(selection.start)
3319 .next()
3320 .map_or(true, |c| scope.should_autoclose_before(c));
3321
3322 let preceding_text_allows_autoclose = selection.start.column == 0
3323 || snapshot.reversed_chars_at(selection.start).next().map_or(
3324 true,
3325 |c| {
3326 bracket_pair.start != bracket_pair.end
3327 || !snapshot
3328 .char_classifier_at(selection.start)
3329 .is_word(c)
3330 },
3331 );
3332
3333 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3334 && bracket_pair.start.len() == 1
3335 {
3336 let target = bracket_pair.start.chars().next().unwrap();
3337 let current_line_count = snapshot
3338 .reversed_chars_at(selection.start)
3339 .take_while(|&c| c != '\n')
3340 .filter(|&c| c == target)
3341 .count();
3342 current_line_count % 2 == 1
3343 } else {
3344 false
3345 };
3346
3347 if autoclose
3348 && bracket_pair.close
3349 && following_text_allows_autoclose
3350 && preceding_text_allows_autoclose
3351 && !is_closing_quote
3352 {
3353 let anchor = snapshot.anchor_before(selection.end);
3354 new_selections.push((selection.map(|_| anchor), text.len()));
3355 new_autoclose_regions.push((
3356 anchor,
3357 text.len(),
3358 selection.id,
3359 bracket_pair.clone(),
3360 ));
3361 edits.push((
3362 selection.range(),
3363 format!("{}{}", text, bracket_pair.end).into(),
3364 ));
3365 bracket_inserted = true;
3366 continue;
3367 }
3368 }
3369
3370 if let Some(region) = autoclose_region {
3371 // If the selection is followed by an auto-inserted closing bracket,
3372 // then don't insert that closing bracket again; just move the selection
3373 // past the closing bracket.
3374 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3375 && text.as_ref() == region.pair.end.as_str();
3376 if should_skip {
3377 let anchor = snapshot.anchor_after(selection.end);
3378 new_selections
3379 .push((selection.map(|_| anchor), region.pair.end.len()));
3380 continue;
3381 }
3382 }
3383
3384 let always_treat_brackets_as_autoclosed = snapshot
3385 .language_settings_at(selection.start, cx)
3386 .always_treat_brackets_as_autoclosed;
3387 if always_treat_brackets_as_autoclosed
3388 && is_bracket_pair_end
3389 && snapshot.contains_str_at(selection.end, text.as_ref())
3390 {
3391 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3392 // and the inserted text is a closing bracket and the selection is followed
3393 // by the closing bracket then move the selection past the closing bracket.
3394 let anchor = snapshot.anchor_after(selection.end);
3395 new_selections.push((selection.map(|_| anchor), text.len()));
3396 continue;
3397 }
3398 }
3399 // If an opening bracket is 1 character long and is typed while
3400 // text is selected, then surround that text with the bracket pair.
3401 else if auto_surround
3402 && bracket_pair.surround
3403 && is_bracket_pair_start
3404 && bracket_pair.start.chars().count() == 1
3405 {
3406 edits.push((selection.start..selection.start, text.clone()));
3407 edits.push((
3408 selection.end..selection.end,
3409 bracket_pair.end.as_str().into(),
3410 ));
3411 bracket_inserted = true;
3412 new_selections.push((
3413 Selection {
3414 id: selection.id,
3415 start: snapshot.anchor_after(selection.start),
3416 end: snapshot.anchor_before(selection.end),
3417 reversed: selection.reversed,
3418 goal: selection.goal,
3419 },
3420 0,
3421 ));
3422 continue;
3423 }
3424 }
3425 }
3426
3427 if self.auto_replace_emoji_shortcode
3428 && selection.is_empty()
3429 && text.as_ref().ends_with(':')
3430 {
3431 if let Some(possible_emoji_short_code) =
3432 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3433 {
3434 if !possible_emoji_short_code.is_empty() {
3435 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3436 let emoji_shortcode_start = Point::new(
3437 selection.start.row,
3438 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3439 );
3440
3441 // Remove shortcode from buffer
3442 edits.push((
3443 emoji_shortcode_start..selection.start,
3444 "".to_string().into(),
3445 ));
3446 new_selections.push((
3447 Selection {
3448 id: selection.id,
3449 start: snapshot.anchor_after(emoji_shortcode_start),
3450 end: snapshot.anchor_before(selection.start),
3451 reversed: selection.reversed,
3452 goal: selection.goal,
3453 },
3454 0,
3455 ));
3456
3457 // Insert emoji
3458 let selection_start_anchor = snapshot.anchor_after(selection.start);
3459 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3460 edits.push((selection.start..selection.end, emoji.to_string().into()));
3461
3462 continue;
3463 }
3464 }
3465 }
3466 }
3467
3468 // If not handling any auto-close operation, then just replace the selected
3469 // text with the given input and move the selection to the end of the
3470 // newly inserted text.
3471 let anchor = snapshot.anchor_after(selection.end);
3472 if !self.linked_edit_ranges.is_empty() {
3473 let start_anchor = snapshot.anchor_before(selection.start);
3474
3475 let is_word_char = text.chars().next().map_or(true, |char| {
3476 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3477 classifier.is_word(char)
3478 });
3479
3480 if is_word_char {
3481 if let Some(ranges) = self
3482 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3483 {
3484 for (buffer, edits) in ranges {
3485 linked_edits
3486 .entry(buffer.clone())
3487 .or_default()
3488 .extend(edits.into_iter().map(|range| (range, text.clone())));
3489 }
3490 }
3491 } else {
3492 clear_linked_edit_ranges = true;
3493 }
3494 }
3495
3496 new_selections.push((selection.map(|_| anchor), 0));
3497 edits.push((selection.start..selection.end, text.clone()));
3498 }
3499
3500 drop(snapshot);
3501
3502 self.transact(window, cx, |this, window, cx| {
3503 if clear_linked_edit_ranges {
3504 this.linked_edit_ranges.clear();
3505 }
3506 let initial_buffer_versions =
3507 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3508
3509 this.buffer.update(cx, |buffer, cx| {
3510 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3511 });
3512 for (buffer, edits) in linked_edits {
3513 buffer.update(cx, |buffer, cx| {
3514 let snapshot = buffer.snapshot();
3515 let edits = edits
3516 .into_iter()
3517 .map(|(range, text)| {
3518 use text::ToPoint as TP;
3519 let end_point = TP::to_point(&range.end, &snapshot);
3520 let start_point = TP::to_point(&range.start, &snapshot);
3521 (start_point..end_point, text)
3522 })
3523 .sorted_by_key(|(range, _)| range.start);
3524 buffer.edit(edits, None, cx);
3525 })
3526 }
3527 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3528 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3529 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3530 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3531 .zip(new_selection_deltas)
3532 .map(|(selection, delta)| Selection {
3533 id: selection.id,
3534 start: selection.start + delta,
3535 end: selection.end + delta,
3536 reversed: selection.reversed,
3537 goal: SelectionGoal::None,
3538 })
3539 .collect::<Vec<_>>();
3540
3541 let mut i = 0;
3542 for (position, delta, selection_id, pair) in new_autoclose_regions {
3543 let position = position.to_offset(&map.buffer_snapshot) + delta;
3544 let start = map.buffer_snapshot.anchor_before(position);
3545 let end = map.buffer_snapshot.anchor_after(position);
3546 while let Some(existing_state) = this.autoclose_regions.get(i) {
3547 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3548 Ordering::Less => i += 1,
3549 Ordering::Greater => break,
3550 Ordering::Equal => {
3551 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3552 Ordering::Less => i += 1,
3553 Ordering::Equal => break,
3554 Ordering::Greater => break,
3555 }
3556 }
3557 }
3558 }
3559 this.autoclose_regions.insert(
3560 i,
3561 AutocloseRegion {
3562 selection_id,
3563 range: start..end,
3564 pair,
3565 },
3566 );
3567 }
3568
3569 let had_active_inline_completion = this.has_active_inline_completion();
3570 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3571 s.select(new_selections)
3572 });
3573
3574 if !bracket_inserted {
3575 if let Some(on_type_format_task) =
3576 this.trigger_on_type_formatting(text.to_string(), window, cx)
3577 {
3578 on_type_format_task.detach_and_log_err(cx);
3579 }
3580 }
3581
3582 let editor_settings = EditorSettings::get_global(cx);
3583 if bracket_inserted
3584 && (editor_settings.auto_signature_help
3585 || editor_settings.show_signature_help_after_edits)
3586 {
3587 this.show_signature_help(&ShowSignatureHelp, window, cx);
3588 }
3589
3590 let trigger_in_words =
3591 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3592 if this.hard_wrap.is_some() {
3593 let latest: Range<Point> = this.selections.newest(cx).range();
3594 if latest.is_empty()
3595 && this
3596 .buffer()
3597 .read(cx)
3598 .snapshot(cx)
3599 .line_len(MultiBufferRow(latest.start.row))
3600 == latest.start.column
3601 {
3602 this.rewrap_impl(
3603 RewrapOptions {
3604 override_language_settings: true,
3605 preserve_existing_whitespace: true,
3606 },
3607 cx,
3608 )
3609 }
3610 }
3611 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3612 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3613 this.refresh_inline_completion(true, false, window, cx);
3614 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3615 });
3616 }
3617
3618 fn find_possible_emoji_shortcode_at_position(
3619 snapshot: &MultiBufferSnapshot,
3620 position: Point,
3621 ) -> Option<String> {
3622 let mut chars = Vec::new();
3623 let mut found_colon = false;
3624 for char in snapshot.reversed_chars_at(position).take(100) {
3625 // Found a possible emoji shortcode in the middle of the buffer
3626 if found_colon {
3627 if char.is_whitespace() {
3628 chars.reverse();
3629 return Some(chars.iter().collect());
3630 }
3631 // If the previous character is not a whitespace, we are in the middle of a word
3632 // and we only want to complete the shortcode if the word is made up of other emojis
3633 let mut containing_word = String::new();
3634 for ch in snapshot
3635 .reversed_chars_at(position)
3636 .skip(chars.len() + 1)
3637 .take(100)
3638 {
3639 if ch.is_whitespace() {
3640 break;
3641 }
3642 containing_word.push(ch);
3643 }
3644 let containing_word = containing_word.chars().rev().collect::<String>();
3645 if util::word_consists_of_emojis(containing_word.as_str()) {
3646 chars.reverse();
3647 return Some(chars.iter().collect());
3648 }
3649 }
3650
3651 if char.is_whitespace() || !char.is_ascii() {
3652 return None;
3653 }
3654 if char == ':' {
3655 found_colon = true;
3656 } else {
3657 chars.push(char);
3658 }
3659 }
3660 // Found a possible emoji shortcode at the beginning of the buffer
3661 chars.reverse();
3662 Some(chars.iter().collect())
3663 }
3664
3665 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3666 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3667 self.transact(window, cx, |this, window, cx| {
3668 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3669 let selections = this.selections.all::<usize>(cx);
3670 let multi_buffer = this.buffer.read(cx);
3671 let buffer = multi_buffer.snapshot(cx);
3672 selections
3673 .iter()
3674 .map(|selection| {
3675 let start_point = selection.start.to_point(&buffer);
3676 let mut indent =
3677 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3678 indent.len = cmp::min(indent.len, start_point.column);
3679 let start = selection.start;
3680 let end = selection.end;
3681 let selection_is_empty = start == end;
3682 let language_scope = buffer.language_scope_at(start);
3683 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3684 &language_scope
3685 {
3686 let insert_extra_newline =
3687 insert_extra_newline_brackets(&buffer, start..end, language)
3688 || insert_extra_newline_tree_sitter(&buffer, start..end);
3689
3690 // Comment extension on newline is allowed only for cursor selections
3691 let comment_delimiter = maybe!({
3692 if !selection_is_empty {
3693 return None;
3694 }
3695
3696 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3697 return None;
3698 }
3699
3700 let delimiters = language.line_comment_prefixes();
3701 let max_len_of_delimiter =
3702 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3703 let (snapshot, range) =
3704 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3705
3706 let mut index_of_first_non_whitespace = 0;
3707 let comment_candidate = snapshot
3708 .chars_for_range(range)
3709 .skip_while(|c| {
3710 let should_skip = c.is_whitespace();
3711 if should_skip {
3712 index_of_first_non_whitespace += 1;
3713 }
3714 should_skip
3715 })
3716 .take(max_len_of_delimiter)
3717 .collect::<String>();
3718 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3719 comment_candidate.starts_with(comment_prefix.as_ref())
3720 })?;
3721 let cursor_is_placed_after_comment_marker =
3722 index_of_first_non_whitespace + comment_prefix.len()
3723 <= start_point.column as usize;
3724 if cursor_is_placed_after_comment_marker {
3725 Some(comment_prefix.clone())
3726 } else {
3727 None
3728 }
3729 });
3730 (comment_delimiter, insert_extra_newline)
3731 } else {
3732 (None, false)
3733 };
3734
3735 let capacity_for_delimiter = comment_delimiter
3736 .as_deref()
3737 .map(str::len)
3738 .unwrap_or_default();
3739 let mut new_text =
3740 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3741 new_text.push('\n');
3742 new_text.extend(indent.chars());
3743 if let Some(delimiter) = &comment_delimiter {
3744 new_text.push_str(delimiter);
3745 }
3746 if insert_extra_newline {
3747 new_text = new_text.repeat(2);
3748 }
3749
3750 let anchor = buffer.anchor_after(end);
3751 let new_selection = selection.map(|_| anchor);
3752 (
3753 (start..end, new_text),
3754 (insert_extra_newline, new_selection),
3755 )
3756 })
3757 .unzip()
3758 };
3759
3760 this.edit_with_autoindent(edits, cx);
3761 let buffer = this.buffer.read(cx).snapshot(cx);
3762 let new_selections = selection_fixup_info
3763 .into_iter()
3764 .map(|(extra_newline_inserted, new_selection)| {
3765 let mut cursor = new_selection.end.to_point(&buffer);
3766 if extra_newline_inserted {
3767 cursor.row -= 1;
3768 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3769 }
3770 new_selection.map(|_| cursor)
3771 })
3772 .collect();
3773
3774 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3775 s.select(new_selections)
3776 });
3777 this.refresh_inline_completion(true, false, window, cx);
3778 });
3779 }
3780
3781 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3782 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3783
3784 let buffer = self.buffer.read(cx);
3785 let snapshot = buffer.snapshot(cx);
3786
3787 let mut edits = Vec::new();
3788 let mut rows = Vec::new();
3789
3790 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3791 let cursor = selection.head();
3792 let row = cursor.row;
3793
3794 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3795
3796 let newline = "\n".to_string();
3797 edits.push((start_of_line..start_of_line, newline));
3798
3799 rows.push(row + rows_inserted as u32);
3800 }
3801
3802 self.transact(window, cx, |editor, window, cx| {
3803 editor.edit(edits, cx);
3804
3805 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3806 let mut index = 0;
3807 s.move_cursors_with(|map, _, _| {
3808 let row = rows[index];
3809 index += 1;
3810
3811 let point = Point::new(row, 0);
3812 let boundary = map.next_line_boundary(point).1;
3813 let clipped = map.clip_point(boundary, Bias::Left);
3814
3815 (clipped, SelectionGoal::None)
3816 });
3817 });
3818
3819 let mut indent_edits = Vec::new();
3820 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3821 for row in rows {
3822 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3823 for (row, indent) in indents {
3824 if indent.len == 0 {
3825 continue;
3826 }
3827
3828 let text = match indent.kind {
3829 IndentKind::Space => " ".repeat(indent.len as usize),
3830 IndentKind::Tab => "\t".repeat(indent.len as usize),
3831 };
3832 let point = Point::new(row.0, 0);
3833 indent_edits.push((point..point, text));
3834 }
3835 }
3836 editor.edit(indent_edits, cx);
3837 });
3838 }
3839
3840 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3841 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3842
3843 let buffer = self.buffer.read(cx);
3844 let snapshot = buffer.snapshot(cx);
3845
3846 let mut edits = Vec::new();
3847 let mut rows = Vec::new();
3848 let mut rows_inserted = 0;
3849
3850 for selection in self.selections.all_adjusted(cx) {
3851 let cursor = selection.head();
3852 let row = cursor.row;
3853
3854 let point = Point::new(row + 1, 0);
3855 let start_of_line = snapshot.clip_point(point, Bias::Left);
3856
3857 let newline = "\n".to_string();
3858 edits.push((start_of_line..start_of_line, newline));
3859
3860 rows_inserted += 1;
3861 rows.push(row + rows_inserted);
3862 }
3863
3864 self.transact(window, cx, |editor, window, cx| {
3865 editor.edit(edits, cx);
3866
3867 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3868 let mut index = 0;
3869 s.move_cursors_with(|map, _, _| {
3870 let row = rows[index];
3871 index += 1;
3872
3873 let point = Point::new(row, 0);
3874 let boundary = map.next_line_boundary(point).1;
3875 let clipped = map.clip_point(boundary, Bias::Left);
3876
3877 (clipped, SelectionGoal::None)
3878 });
3879 });
3880
3881 let mut indent_edits = Vec::new();
3882 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3883 for row in rows {
3884 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3885 for (row, indent) in indents {
3886 if indent.len == 0 {
3887 continue;
3888 }
3889
3890 let text = match indent.kind {
3891 IndentKind::Space => " ".repeat(indent.len as usize),
3892 IndentKind::Tab => "\t".repeat(indent.len as usize),
3893 };
3894 let point = Point::new(row.0, 0);
3895 indent_edits.push((point..point, text));
3896 }
3897 }
3898 editor.edit(indent_edits, cx);
3899 });
3900 }
3901
3902 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3903 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3904 original_indent_columns: Vec::new(),
3905 });
3906 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3907 }
3908
3909 fn insert_with_autoindent_mode(
3910 &mut self,
3911 text: &str,
3912 autoindent_mode: Option<AutoindentMode>,
3913 window: &mut Window,
3914 cx: &mut Context<Self>,
3915 ) {
3916 if self.read_only(cx) {
3917 return;
3918 }
3919
3920 let text: Arc<str> = text.into();
3921 self.transact(window, cx, |this, window, cx| {
3922 let old_selections = this.selections.all_adjusted(cx);
3923 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3924 let anchors = {
3925 let snapshot = buffer.read(cx);
3926 old_selections
3927 .iter()
3928 .map(|s| {
3929 let anchor = snapshot.anchor_after(s.head());
3930 s.map(|_| anchor)
3931 })
3932 .collect::<Vec<_>>()
3933 };
3934 buffer.edit(
3935 old_selections
3936 .iter()
3937 .map(|s| (s.start..s.end, text.clone())),
3938 autoindent_mode,
3939 cx,
3940 );
3941 anchors
3942 });
3943
3944 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3945 s.select_anchors(selection_anchors);
3946 });
3947
3948 cx.notify();
3949 });
3950 }
3951
3952 fn trigger_completion_on_input(
3953 &mut self,
3954 text: &str,
3955 trigger_in_words: bool,
3956 window: &mut Window,
3957 cx: &mut Context<Self>,
3958 ) {
3959 let ignore_completion_provider = self
3960 .context_menu
3961 .borrow()
3962 .as_ref()
3963 .map(|menu| match menu {
3964 CodeContextMenu::Completions(completions_menu) => {
3965 completions_menu.ignore_completion_provider
3966 }
3967 CodeContextMenu::CodeActions(_) => false,
3968 })
3969 .unwrap_or(false);
3970
3971 if ignore_completion_provider {
3972 self.show_word_completions(&ShowWordCompletions, window, cx);
3973 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3974 self.show_completions(
3975 &ShowCompletions {
3976 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3977 },
3978 window,
3979 cx,
3980 );
3981 } else {
3982 self.hide_context_menu(window, cx);
3983 }
3984 }
3985
3986 fn is_completion_trigger(
3987 &self,
3988 text: &str,
3989 trigger_in_words: bool,
3990 cx: &mut Context<Self>,
3991 ) -> bool {
3992 let position = self.selections.newest_anchor().head();
3993 let multibuffer = self.buffer.read(cx);
3994 let Some(buffer) = position
3995 .buffer_id
3996 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3997 else {
3998 return false;
3999 };
4000
4001 if let Some(completion_provider) = &self.completion_provider {
4002 completion_provider.is_completion_trigger(
4003 &buffer,
4004 position.text_anchor,
4005 text,
4006 trigger_in_words,
4007 cx,
4008 )
4009 } else {
4010 false
4011 }
4012 }
4013
4014 /// If any empty selections is touching the start of its innermost containing autoclose
4015 /// region, expand it to select the brackets.
4016 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4017 let selections = self.selections.all::<usize>(cx);
4018 let buffer = self.buffer.read(cx).read(cx);
4019 let new_selections = self
4020 .selections_with_autoclose_regions(selections, &buffer)
4021 .map(|(mut selection, region)| {
4022 if !selection.is_empty() {
4023 return selection;
4024 }
4025
4026 if let Some(region) = region {
4027 let mut range = region.range.to_offset(&buffer);
4028 if selection.start == range.start && range.start >= region.pair.start.len() {
4029 range.start -= region.pair.start.len();
4030 if buffer.contains_str_at(range.start, ®ion.pair.start)
4031 && buffer.contains_str_at(range.end, ®ion.pair.end)
4032 {
4033 range.end += region.pair.end.len();
4034 selection.start = range.start;
4035 selection.end = range.end;
4036
4037 return selection;
4038 }
4039 }
4040 }
4041
4042 let always_treat_brackets_as_autoclosed = buffer
4043 .language_settings_at(selection.start, cx)
4044 .always_treat_brackets_as_autoclosed;
4045
4046 if !always_treat_brackets_as_autoclosed {
4047 return selection;
4048 }
4049
4050 if let Some(scope) = buffer.language_scope_at(selection.start) {
4051 for (pair, enabled) in scope.brackets() {
4052 if !enabled || !pair.close {
4053 continue;
4054 }
4055
4056 if buffer.contains_str_at(selection.start, &pair.end) {
4057 let pair_start_len = pair.start.len();
4058 if buffer.contains_str_at(
4059 selection.start.saturating_sub(pair_start_len),
4060 &pair.start,
4061 ) {
4062 selection.start -= pair_start_len;
4063 selection.end += pair.end.len();
4064
4065 return selection;
4066 }
4067 }
4068 }
4069 }
4070
4071 selection
4072 })
4073 .collect();
4074
4075 drop(buffer);
4076 self.change_selections(None, window, cx, |selections| {
4077 selections.select(new_selections)
4078 });
4079 }
4080
4081 /// Iterate the given selections, and for each one, find the smallest surrounding
4082 /// autoclose region. This uses the ordering of the selections and the autoclose
4083 /// regions to avoid repeated comparisons.
4084 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4085 &'a self,
4086 selections: impl IntoIterator<Item = Selection<D>>,
4087 buffer: &'a MultiBufferSnapshot,
4088 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4089 let mut i = 0;
4090 let mut regions = self.autoclose_regions.as_slice();
4091 selections.into_iter().map(move |selection| {
4092 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4093
4094 let mut enclosing = None;
4095 while let Some(pair_state) = regions.get(i) {
4096 if pair_state.range.end.to_offset(buffer) < range.start {
4097 regions = ®ions[i + 1..];
4098 i = 0;
4099 } else if pair_state.range.start.to_offset(buffer) > range.end {
4100 break;
4101 } else {
4102 if pair_state.selection_id == selection.id {
4103 enclosing = Some(pair_state);
4104 }
4105 i += 1;
4106 }
4107 }
4108
4109 (selection, enclosing)
4110 })
4111 }
4112
4113 /// Remove any autoclose regions that no longer contain their selection.
4114 fn invalidate_autoclose_regions(
4115 &mut self,
4116 mut selections: &[Selection<Anchor>],
4117 buffer: &MultiBufferSnapshot,
4118 ) {
4119 self.autoclose_regions.retain(|state| {
4120 let mut i = 0;
4121 while let Some(selection) = selections.get(i) {
4122 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4123 selections = &selections[1..];
4124 continue;
4125 }
4126 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4127 break;
4128 }
4129 if selection.id == state.selection_id {
4130 return true;
4131 } else {
4132 i += 1;
4133 }
4134 }
4135 false
4136 });
4137 }
4138
4139 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4140 let offset = position.to_offset(buffer);
4141 let (word_range, kind) = buffer.surrounding_word(offset, true);
4142 if offset > word_range.start && kind == Some(CharKind::Word) {
4143 Some(
4144 buffer
4145 .text_for_range(word_range.start..offset)
4146 .collect::<String>(),
4147 )
4148 } else {
4149 None
4150 }
4151 }
4152
4153 pub fn toggle_inlay_hints(
4154 &mut self,
4155 _: &ToggleInlayHints,
4156 _: &mut Window,
4157 cx: &mut Context<Self>,
4158 ) {
4159 self.refresh_inlay_hints(
4160 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4161 cx,
4162 );
4163 }
4164
4165 pub fn inlay_hints_enabled(&self) -> bool {
4166 self.inlay_hint_cache.enabled
4167 }
4168
4169 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4170 if self.semantics_provider.is_none() || !self.mode.is_full() {
4171 return;
4172 }
4173
4174 let reason_description = reason.description();
4175 let ignore_debounce = matches!(
4176 reason,
4177 InlayHintRefreshReason::SettingsChange(_)
4178 | InlayHintRefreshReason::Toggle(_)
4179 | InlayHintRefreshReason::ExcerptsRemoved(_)
4180 | InlayHintRefreshReason::ModifiersChanged(_)
4181 );
4182 let (invalidate_cache, required_languages) = match reason {
4183 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4184 match self.inlay_hint_cache.modifiers_override(enabled) {
4185 Some(enabled) => {
4186 if enabled {
4187 (InvalidationStrategy::RefreshRequested, None)
4188 } else {
4189 self.splice_inlays(
4190 &self
4191 .visible_inlay_hints(cx)
4192 .iter()
4193 .map(|inlay| inlay.id)
4194 .collect::<Vec<InlayId>>(),
4195 Vec::new(),
4196 cx,
4197 );
4198 return;
4199 }
4200 }
4201 None => return,
4202 }
4203 }
4204 InlayHintRefreshReason::Toggle(enabled) => {
4205 if self.inlay_hint_cache.toggle(enabled) {
4206 if enabled {
4207 (InvalidationStrategy::RefreshRequested, None)
4208 } else {
4209 self.splice_inlays(
4210 &self
4211 .visible_inlay_hints(cx)
4212 .iter()
4213 .map(|inlay| inlay.id)
4214 .collect::<Vec<InlayId>>(),
4215 Vec::new(),
4216 cx,
4217 );
4218 return;
4219 }
4220 } else {
4221 return;
4222 }
4223 }
4224 InlayHintRefreshReason::SettingsChange(new_settings) => {
4225 match self.inlay_hint_cache.update_settings(
4226 &self.buffer,
4227 new_settings,
4228 self.visible_inlay_hints(cx),
4229 cx,
4230 ) {
4231 ControlFlow::Break(Some(InlaySplice {
4232 to_remove,
4233 to_insert,
4234 })) => {
4235 self.splice_inlays(&to_remove, to_insert, cx);
4236 return;
4237 }
4238 ControlFlow::Break(None) => return,
4239 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4240 }
4241 }
4242 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4243 if let Some(InlaySplice {
4244 to_remove,
4245 to_insert,
4246 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4247 {
4248 self.splice_inlays(&to_remove, to_insert, cx);
4249 }
4250 self.display_map.update(cx, |display_map, _| {
4251 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4252 });
4253 return;
4254 }
4255 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4256 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4257 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4258 }
4259 InlayHintRefreshReason::RefreshRequested => {
4260 (InvalidationStrategy::RefreshRequested, None)
4261 }
4262 };
4263
4264 if let Some(InlaySplice {
4265 to_remove,
4266 to_insert,
4267 }) = self.inlay_hint_cache.spawn_hint_refresh(
4268 reason_description,
4269 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4270 invalidate_cache,
4271 ignore_debounce,
4272 cx,
4273 ) {
4274 self.splice_inlays(&to_remove, to_insert, cx);
4275 }
4276 }
4277
4278 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4279 self.display_map
4280 .read(cx)
4281 .current_inlays()
4282 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4283 .cloned()
4284 .collect()
4285 }
4286
4287 pub fn excerpts_for_inlay_hints_query(
4288 &self,
4289 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4290 cx: &mut Context<Editor>,
4291 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4292 let Some(project) = self.project.as_ref() else {
4293 return HashMap::default();
4294 };
4295 let project = project.read(cx);
4296 let multi_buffer = self.buffer().read(cx);
4297 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4298 let multi_buffer_visible_start = self
4299 .scroll_manager
4300 .anchor()
4301 .anchor
4302 .to_point(&multi_buffer_snapshot);
4303 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4304 multi_buffer_visible_start
4305 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4306 Bias::Left,
4307 );
4308 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4309 multi_buffer_snapshot
4310 .range_to_buffer_ranges(multi_buffer_visible_range)
4311 .into_iter()
4312 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4313 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4314 let buffer_file = project::File::from_dyn(buffer.file())?;
4315 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4316 let worktree_entry = buffer_worktree
4317 .read(cx)
4318 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4319 if worktree_entry.is_ignored {
4320 return None;
4321 }
4322
4323 let language = buffer.language()?;
4324 if let Some(restrict_to_languages) = restrict_to_languages {
4325 if !restrict_to_languages.contains(language) {
4326 return None;
4327 }
4328 }
4329 Some((
4330 excerpt_id,
4331 (
4332 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4333 buffer.version().clone(),
4334 excerpt_visible_range,
4335 ),
4336 ))
4337 })
4338 .collect()
4339 }
4340
4341 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4342 TextLayoutDetails {
4343 text_system: window.text_system().clone(),
4344 editor_style: self.style.clone().unwrap(),
4345 rem_size: window.rem_size(),
4346 scroll_anchor: self.scroll_manager.anchor(),
4347 visible_rows: self.visible_line_count(),
4348 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4349 }
4350 }
4351
4352 pub fn splice_inlays(
4353 &self,
4354 to_remove: &[InlayId],
4355 to_insert: Vec<Inlay>,
4356 cx: &mut Context<Self>,
4357 ) {
4358 self.display_map.update(cx, |display_map, cx| {
4359 display_map.splice_inlays(to_remove, to_insert, cx)
4360 });
4361 cx.notify();
4362 }
4363
4364 fn trigger_on_type_formatting(
4365 &self,
4366 input: String,
4367 window: &mut Window,
4368 cx: &mut Context<Self>,
4369 ) -> Option<Task<Result<()>>> {
4370 if input.len() != 1 {
4371 return None;
4372 }
4373
4374 let project = self.project.as_ref()?;
4375 let position = self.selections.newest_anchor().head();
4376 let (buffer, buffer_position) = self
4377 .buffer
4378 .read(cx)
4379 .text_anchor_for_position(position, cx)?;
4380
4381 let settings = language_settings::language_settings(
4382 buffer
4383 .read(cx)
4384 .language_at(buffer_position)
4385 .map(|l| l.name()),
4386 buffer.read(cx).file(),
4387 cx,
4388 );
4389 if !settings.use_on_type_format {
4390 return None;
4391 }
4392
4393 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4394 // hence we do LSP request & edit on host side only — add formats to host's history.
4395 let push_to_lsp_host_history = true;
4396 // If this is not the host, append its history with new edits.
4397 let push_to_client_history = project.read(cx).is_via_collab();
4398
4399 let on_type_formatting = project.update(cx, |project, cx| {
4400 project.on_type_format(
4401 buffer.clone(),
4402 buffer_position,
4403 input,
4404 push_to_lsp_host_history,
4405 cx,
4406 )
4407 });
4408 Some(cx.spawn_in(window, async move |editor, cx| {
4409 if let Some(transaction) = on_type_formatting.await? {
4410 if push_to_client_history {
4411 buffer
4412 .update(cx, |buffer, _| {
4413 buffer.push_transaction(transaction, Instant::now());
4414 buffer.finalize_last_transaction();
4415 })
4416 .ok();
4417 }
4418 editor.update(cx, |editor, cx| {
4419 editor.refresh_document_highlights(cx);
4420 })?;
4421 }
4422 Ok(())
4423 }))
4424 }
4425
4426 pub fn show_word_completions(
4427 &mut self,
4428 _: &ShowWordCompletions,
4429 window: &mut Window,
4430 cx: &mut Context<Self>,
4431 ) {
4432 self.open_completions_menu(true, None, window, cx);
4433 }
4434
4435 pub fn show_completions(
4436 &mut self,
4437 options: &ShowCompletions,
4438 window: &mut Window,
4439 cx: &mut Context<Self>,
4440 ) {
4441 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4442 }
4443
4444 fn open_completions_menu(
4445 &mut self,
4446 ignore_completion_provider: bool,
4447 trigger: Option<&str>,
4448 window: &mut Window,
4449 cx: &mut Context<Self>,
4450 ) {
4451 if self.pending_rename.is_some() {
4452 return;
4453 }
4454 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4455 return;
4456 }
4457
4458 let position = self.selections.newest_anchor().head();
4459 if position.diff_base_anchor.is_some() {
4460 return;
4461 }
4462 let (buffer, buffer_position) =
4463 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4464 output
4465 } else {
4466 return;
4467 };
4468 let buffer_snapshot = buffer.read(cx).snapshot();
4469 let show_completion_documentation = buffer_snapshot
4470 .settings_at(buffer_position, cx)
4471 .show_completion_documentation;
4472
4473 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4474
4475 let trigger_kind = match trigger {
4476 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4477 CompletionTriggerKind::TRIGGER_CHARACTER
4478 }
4479 _ => CompletionTriggerKind::INVOKED,
4480 };
4481 let completion_context = CompletionContext {
4482 trigger_character: trigger.and_then(|trigger| {
4483 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4484 Some(String::from(trigger))
4485 } else {
4486 None
4487 }
4488 }),
4489 trigger_kind,
4490 };
4491
4492 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4493 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4494 let word_to_exclude = buffer_snapshot
4495 .text_for_range(old_range.clone())
4496 .collect::<String>();
4497 (
4498 buffer_snapshot.anchor_before(old_range.start)
4499 ..buffer_snapshot.anchor_after(old_range.end),
4500 Some(word_to_exclude),
4501 )
4502 } else {
4503 (buffer_position..buffer_position, None)
4504 };
4505
4506 let completion_settings = language_settings(
4507 buffer_snapshot
4508 .language_at(buffer_position)
4509 .map(|language| language.name()),
4510 buffer_snapshot.file(),
4511 cx,
4512 )
4513 .completions;
4514
4515 // The document can be large, so stay in reasonable bounds when searching for words,
4516 // otherwise completion pop-up might be slow to appear.
4517 const WORD_LOOKUP_ROWS: u32 = 5_000;
4518 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4519 let min_word_search = buffer_snapshot.clip_point(
4520 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4521 Bias::Left,
4522 );
4523 let max_word_search = buffer_snapshot.clip_point(
4524 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4525 Bias::Right,
4526 );
4527 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4528 ..buffer_snapshot.point_to_offset(max_word_search);
4529
4530 let provider = self
4531 .completion_provider
4532 .as_ref()
4533 .filter(|_| !ignore_completion_provider);
4534 let skip_digits = query
4535 .as_ref()
4536 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4537
4538 let (mut words, provided_completions) = match provider {
4539 Some(provider) => {
4540 let completions = provider.completions(
4541 position.excerpt_id,
4542 &buffer,
4543 buffer_position,
4544 completion_context,
4545 window,
4546 cx,
4547 );
4548
4549 let words = match completion_settings.words {
4550 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4551 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4552 .background_spawn(async move {
4553 buffer_snapshot.words_in_range(WordsQuery {
4554 fuzzy_contents: None,
4555 range: word_search_range,
4556 skip_digits,
4557 })
4558 }),
4559 };
4560
4561 (words, completions)
4562 }
4563 None => (
4564 cx.background_spawn(async move {
4565 buffer_snapshot.words_in_range(WordsQuery {
4566 fuzzy_contents: None,
4567 range: word_search_range,
4568 skip_digits,
4569 })
4570 }),
4571 Task::ready(Ok(None)),
4572 ),
4573 };
4574
4575 let sort_completions = provider
4576 .as_ref()
4577 .map_or(false, |provider| provider.sort_completions());
4578
4579 let filter_completions = provider
4580 .as_ref()
4581 .map_or(true, |provider| provider.filter_completions());
4582
4583 let id = post_inc(&mut self.next_completion_id);
4584 let task = cx.spawn_in(window, async move |editor, cx| {
4585 async move {
4586 editor.update(cx, |this, _| {
4587 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4588 })?;
4589
4590 let mut completions = Vec::new();
4591 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4592 completions.extend(provided_completions);
4593 if completion_settings.words == WordsCompletionMode::Fallback {
4594 words = Task::ready(BTreeMap::default());
4595 }
4596 }
4597
4598 let mut words = words.await;
4599 if let Some(word_to_exclude) = &word_to_exclude {
4600 words.remove(word_to_exclude);
4601 }
4602 for lsp_completion in &completions {
4603 words.remove(&lsp_completion.new_text);
4604 }
4605 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4606 replace_range: old_range.clone(),
4607 new_text: word.clone(),
4608 label: CodeLabel::plain(word, None),
4609 icon_path: None,
4610 documentation: None,
4611 source: CompletionSource::BufferWord {
4612 word_range,
4613 resolved: false,
4614 },
4615 insert_text_mode: Some(InsertTextMode::AS_IS),
4616 confirm: None,
4617 }));
4618
4619 let menu = if completions.is_empty() {
4620 None
4621 } else {
4622 let mut menu = CompletionsMenu::new(
4623 id,
4624 sort_completions,
4625 show_completion_documentation,
4626 ignore_completion_provider,
4627 position,
4628 buffer.clone(),
4629 completions.into(),
4630 );
4631
4632 menu.filter(
4633 if filter_completions {
4634 query.as_deref()
4635 } else {
4636 None
4637 },
4638 cx.background_executor().clone(),
4639 )
4640 .await;
4641
4642 menu.visible().then_some(menu)
4643 };
4644
4645 editor.update_in(cx, |editor, window, cx| {
4646 match editor.context_menu.borrow().as_ref() {
4647 None => {}
4648 Some(CodeContextMenu::Completions(prev_menu)) => {
4649 if prev_menu.id > id {
4650 return;
4651 }
4652 }
4653 _ => return,
4654 }
4655
4656 if editor.focus_handle.is_focused(window) && menu.is_some() {
4657 let mut menu = menu.unwrap();
4658 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4659
4660 *editor.context_menu.borrow_mut() =
4661 Some(CodeContextMenu::Completions(menu));
4662
4663 if editor.show_edit_predictions_in_menu() {
4664 editor.update_visible_inline_completion(window, cx);
4665 } else {
4666 editor.discard_inline_completion(false, cx);
4667 }
4668
4669 cx.notify();
4670 } else if editor.completion_tasks.len() <= 1 {
4671 // If there are no more completion tasks and the last menu was
4672 // empty, we should hide it.
4673 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4674 // If it was already hidden and we don't show inline
4675 // completions in the menu, we should also show the
4676 // inline-completion when available.
4677 if was_hidden && editor.show_edit_predictions_in_menu() {
4678 editor.update_visible_inline_completion(window, cx);
4679 }
4680 }
4681 })?;
4682
4683 anyhow::Ok(())
4684 }
4685 .log_err()
4686 .await
4687 });
4688
4689 self.completion_tasks.push((id, task));
4690 }
4691
4692 #[cfg(feature = "test-support")]
4693 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4694 let menu = self.context_menu.borrow();
4695 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4696 let completions = menu.completions.borrow();
4697 Some(completions.to_vec())
4698 } else {
4699 None
4700 }
4701 }
4702
4703 pub fn confirm_completion(
4704 &mut self,
4705 action: &ConfirmCompletion,
4706 window: &mut Window,
4707 cx: &mut Context<Self>,
4708 ) -> Option<Task<Result<()>>> {
4709 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4710 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4711 }
4712
4713 pub fn confirm_completion_insert(
4714 &mut self,
4715 _: &ConfirmCompletionInsert,
4716 window: &mut Window,
4717 cx: &mut Context<Self>,
4718 ) -> Option<Task<Result<()>>> {
4719 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4720 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4721 }
4722
4723 pub fn confirm_completion_replace(
4724 &mut self,
4725 _: &ConfirmCompletionReplace,
4726 window: &mut Window,
4727 cx: &mut Context<Self>,
4728 ) -> Option<Task<Result<()>>> {
4729 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4730 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4731 }
4732
4733 pub fn compose_completion(
4734 &mut self,
4735 action: &ComposeCompletion,
4736 window: &mut Window,
4737 cx: &mut Context<Self>,
4738 ) -> Option<Task<Result<()>>> {
4739 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4740 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4741 }
4742
4743 fn do_completion(
4744 &mut self,
4745 item_ix: Option<usize>,
4746 intent: CompletionIntent,
4747 window: &mut Window,
4748 cx: &mut Context<Editor>,
4749 ) -> Option<Task<Result<()>>> {
4750 use language::ToOffset as _;
4751
4752 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4753 else {
4754 return None;
4755 };
4756
4757 let candidate_id = {
4758 let entries = completions_menu.entries.borrow();
4759 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4760 if self.show_edit_predictions_in_menu() {
4761 self.discard_inline_completion(true, cx);
4762 }
4763 mat.candidate_id
4764 };
4765
4766 let buffer_handle = completions_menu.buffer;
4767 let completion = completions_menu
4768 .completions
4769 .borrow()
4770 .get(candidate_id)?
4771 .clone();
4772 cx.stop_propagation();
4773
4774 let snippet;
4775 let new_text;
4776 if completion.is_snippet() {
4777 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4778 new_text = snippet.as_ref().unwrap().text.clone();
4779 } else {
4780 snippet = None;
4781 new_text = completion.new_text.clone();
4782 };
4783
4784 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4785 let buffer = buffer_handle.read(cx);
4786 let snapshot = self.buffer.read(cx).snapshot(cx);
4787 let replace_range_multibuffer = {
4788 let excerpt = snapshot
4789 .excerpt_containing(self.selections.newest_anchor().range())
4790 .unwrap();
4791 let multibuffer_anchor = snapshot
4792 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
4793 .unwrap()
4794 ..snapshot
4795 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
4796 .unwrap();
4797 multibuffer_anchor.start.to_offset(&snapshot)
4798 ..multibuffer_anchor.end.to_offset(&snapshot)
4799 };
4800 let newest_anchor = self.selections.newest_anchor();
4801 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
4802 return None;
4803 }
4804
4805 let old_text = buffer
4806 .text_for_range(replace_range.clone())
4807 .collect::<String>();
4808 let lookbehind = newest_anchor
4809 .start
4810 .text_anchor
4811 .to_offset(buffer)
4812 .saturating_sub(replace_range.start);
4813 let lookahead = replace_range
4814 .end
4815 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
4816 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
4817 let suffix = &old_text[lookbehind.min(old_text.len())..];
4818
4819 let selections = self.selections.all::<usize>(cx);
4820 let mut edits = Vec::new();
4821 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4822
4823 for selection in &selections {
4824 let edit = if selection.id == newest_anchor.id {
4825 (replace_range_multibuffer.clone(), new_text.as_str())
4826 } else {
4827 let mut range = selection.range();
4828 let mut text = new_text.as_str();
4829
4830 // if prefix is present, don't duplicate it
4831 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
4832 text = &new_text[lookbehind.min(new_text.len())..];
4833
4834 // if suffix is also present, mimic the newest cursor and replace it
4835 if selection.id != newest_anchor.id
4836 && snapshot.contains_str_at(range.end, suffix)
4837 {
4838 range.end += lookahead;
4839 }
4840 }
4841 (range, text)
4842 };
4843
4844 edits.push(edit);
4845
4846 if !self.linked_edit_ranges.is_empty() {
4847 let start_anchor = snapshot.anchor_before(selection.head());
4848 let end_anchor = snapshot.anchor_after(selection.tail());
4849 if let Some(ranges) = self
4850 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4851 {
4852 for (buffer, edits) in ranges {
4853 linked_edits
4854 .entry(buffer.clone())
4855 .or_default()
4856 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
4857 }
4858 }
4859 }
4860 }
4861
4862 cx.emit(EditorEvent::InputHandled {
4863 utf16_range_to_replace: None,
4864 text: new_text.clone().into(),
4865 });
4866
4867 self.transact(window, cx, |this, window, cx| {
4868 if let Some(mut snippet) = snippet {
4869 snippet.text = new_text.to_string();
4870 let ranges = edits
4871 .iter()
4872 .map(|(range, _)| range.clone())
4873 .collect::<Vec<_>>();
4874 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4875 } else {
4876 this.buffer.update(cx, |buffer, cx| {
4877 let auto_indent = if completion.insert_text_mode == Some(InsertTextMode::AS_IS)
4878 {
4879 None
4880 } else {
4881 this.autoindent_mode.clone()
4882 };
4883 buffer.edit(edits, auto_indent, cx);
4884 });
4885 }
4886 for (buffer, edits) in linked_edits {
4887 buffer.update(cx, |buffer, cx| {
4888 let snapshot = buffer.snapshot();
4889 let edits = edits
4890 .into_iter()
4891 .map(|(range, text)| {
4892 use text::ToPoint as TP;
4893 let end_point = TP::to_point(&range.end, &snapshot);
4894 let start_point = TP::to_point(&range.start, &snapshot);
4895 (start_point..end_point, text)
4896 })
4897 .sorted_by_key(|(range, _)| range.start);
4898 buffer.edit(edits, None, cx);
4899 })
4900 }
4901
4902 this.refresh_inline_completion(true, false, window, cx);
4903 });
4904
4905 let show_new_completions_on_confirm = completion
4906 .confirm
4907 .as_ref()
4908 .map_or(false, |confirm| confirm(intent, window, cx));
4909 if show_new_completions_on_confirm {
4910 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4911 }
4912
4913 let provider = self.completion_provider.as_ref()?;
4914 drop(completion);
4915 let apply_edits = provider.apply_additional_edits_for_completion(
4916 buffer_handle,
4917 completions_menu.completions.clone(),
4918 candidate_id,
4919 true,
4920 cx,
4921 );
4922
4923 let editor_settings = EditorSettings::get_global(cx);
4924 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4925 // After the code completion is finished, users often want to know what signatures are needed.
4926 // so we should automatically call signature_help
4927 self.show_signature_help(&ShowSignatureHelp, window, cx);
4928 }
4929
4930 Some(cx.foreground_executor().spawn(async move {
4931 apply_edits.await?;
4932 Ok(())
4933 }))
4934 }
4935
4936 fn prepare_code_actions_task(
4937 &mut self,
4938 action: &ToggleCodeActions,
4939 window: &mut Window,
4940 cx: &mut Context<Self>,
4941 ) -> Task<Option<(Entity<Buffer>, CodeActionContents)>> {
4942 let snapshot = self.snapshot(window, cx);
4943 let multibuffer_point = action
4944 .deployed_from_indicator
4945 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4946 .unwrap_or_else(|| self.selections.newest::<Point>(cx).head());
4947
4948 let Some((buffer, buffer_row)) = snapshot
4949 .buffer_snapshot
4950 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4951 .and_then(|(buffer_snapshot, range)| {
4952 self.buffer
4953 .read(cx)
4954 .buffer(buffer_snapshot.remote_id())
4955 .map(|buffer| (buffer, range.start.row))
4956 })
4957 else {
4958 return Task::ready(None);
4959 };
4960
4961 let (_, code_actions) = self
4962 .available_code_actions
4963 .clone()
4964 .and_then(|(location, code_actions)| {
4965 let snapshot = location.buffer.read(cx).snapshot();
4966 let point_range = location.range.to_point(&snapshot);
4967 let point_range = point_range.start.row..=point_range.end.row;
4968 if point_range.contains(&buffer_row) {
4969 Some((location, code_actions))
4970 } else {
4971 None
4972 }
4973 })
4974 .unzip();
4975
4976 let buffer_id = buffer.read(cx).remote_id();
4977 let tasks = self
4978 .tasks
4979 .get(&(buffer_id, buffer_row))
4980 .map(|t| Arc::new(t.to_owned()));
4981
4982 if tasks.is_none() && code_actions.is_none() {
4983 return Task::ready(None);
4984 }
4985
4986 self.completion_tasks.clear();
4987 self.discard_inline_completion(false, cx);
4988
4989 let task_context = tasks
4990 .as_ref()
4991 .zip(self.project.clone())
4992 .map(|(tasks, project)| {
4993 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4994 });
4995
4996 cx.spawn_in(window, async move |_, _| {
4997 let task_context = match task_context {
4998 Some(task_context) => task_context.await,
4999 None => None,
5000 };
5001 let resolved_tasks = tasks.zip(task_context).map(|(tasks, task_context)| {
5002 Rc::new(ResolvedTasks {
5003 templates: tasks.resolve(&task_context).collect(),
5004 position: snapshot
5005 .buffer_snapshot
5006 .anchor_before(Point::new(multibuffer_point.row, tasks.column)),
5007 })
5008 });
5009 Some((
5010 buffer,
5011 CodeActionContents {
5012 actions: code_actions,
5013 tasks: resolved_tasks,
5014 },
5015 ))
5016 })
5017 }
5018
5019 pub fn toggle_code_actions(
5020 &mut self,
5021 action: &ToggleCodeActions,
5022 window: &mut Window,
5023 cx: &mut Context<Self>,
5024 ) {
5025 let mut context_menu = self.context_menu.borrow_mut();
5026 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5027 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5028 // Toggle if we're selecting the same one
5029 *context_menu = None;
5030 cx.notify();
5031 return;
5032 } else {
5033 // Otherwise, clear it and start a new one
5034 *context_menu = None;
5035 cx.notify();
5036 }
5037 }
5038 drop(context_menu);
5039
5040 let deployed_from_indicator = action.deployed_from_indicator;
5041 let mut task = self.code_actions_task.take();
5042 let action = action.clone();
5043
5044 cx.spawn_in(window, async move |editor, cx| {
5045 while let Some(prev_task) = task {
5046 prev_task.await.log_err();
5047 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5048 }
5049
5050 let context_menu_task = editor.update_in(cx, |editor, window, cx| {
5051 if !editor.focus_handle.is_focused(window) {
5052 return Some(Task::ready(Ok(())));
5053 }
5054 let debugger_flag = cx.has_flag::<Debugger>();
5055 let code_actions_task = editor.prepare_code_actions_task(&action, window, cx);
5056 Some(cx.spawn_in(window, async move |editor, cx| {
5057 if let Some((buffer, code_action_contents)) = code_actions_task.await {
5058 let spawn_straight_away =
5059 code_action_contents.tasks.as_ref().map_or(false, |tasks| {
5060 tasks
5061 .templates
5062 .iter()
5063 .filter(|task| {
5064 if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
5065 debugger_flag
5066 } else {
5067 true
5068 }
5069 })
5070 .count()
5071 == 1
5072 }) && code_action_contents
5073 .actions
5074 .as_ref()
5075 .map_or(true, |actions| actions.is_empty());
5076 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5077 *editor.context_menu.borrow_mut() =
5078 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5079 buffer,
5080 actions: code_action_contents,
5081 selected_item: Default::default(),
5082 scroll_handle: UniformListScrollHandle::default(),
5083 deployed_from_indicator,
5084 }));
5085 if spawn_straight_away {
5086 if let Some(task) = editor.confirm_code_action(
5087 &ConfirmCodeAction {
5088 item_ix: Some(0),
5089 from_mouse_context_menu: false,
5090 },
5091 window,
5092 cx,
5093 ) {
5094 cx.notify();
5095 return task;
5096 }
5097 }
5098 cx.notify();
5099 Task::ready(Ok(()))
5100 }) {
5101 task.await
5102 } else {
5103 Ok(())
5104 }
5105 } else {
5106 Ok(())
5107 }
5108 }))
5109 })?;
5110 if let Some(task) = context_menu_task {
5111 task.await?;
5112 }
5113
5114 Ok::<_, anyhow::Error>(())
5115 })
5116 .detach_and_log_err(cx);
5117 }
5118
5119 pub fn confirm_code_action(
5120 &mut self,
5121 action: &ConfirmCodeAction,
5122 window: &mut Window,
5123 cx: &mut Context<Self>,
5124 ) -> Option<Task<Result<()>>> {
5125 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5126
5127 let (action, buffer) = if action.from_mouse_context_menu {
5128 if let Some(menu) = self.mouse_context_menu.take() {
5129 let code_action = menu.code_action?;
5130 let index = action.item_ix?;
5131 let action = code_action.actions.get(index)?;
5132 (action, code_action.buffer)
5133 } else {
5134 return None;
5135 }
5136 } else {
5137 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5138 let action_ix = action.item_ix.unwrap_or(menu.selected_item);
5139 let action = menu.actions.get(action_ix)?;
5140 let buffer = menu.buffer;
5141 (action, buffer)
5142 } else {
5143 return None;
5144 }
5145 };
5146
5147 let title = action.label();
5148 let workspace = self.workspace()?;
5149
5150 match action {
5151 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5152 match resolved_task.task_type() {
5153 task::TaskType::Script => workspace.update(cx, |workspace, cx| {
5154 workspace::tasks::schedule_resolved_task(
5155 workspace,
5156 task_source_kind,
5157 resolved_task,
5158 false,
5159 cx,
5160 );
5161
5162 Some(Task::ready(Ok(())))
5163 }),
5164 task::TaskType::Debug(debug_args) => {
5165 if debug_args.locator.is_some() {
5166 workspace.update(cx, |workspace, cx| {
5167 workspace::tasks::schedule_resolved_task(
5168 workspace,
5169 task_source_kind,
5170 resolved_task,
5171 false,
5172 cx,
5173 );
5174 });
5175
5176 return Some(Task::ready(Ok(())));
5177 }
5178
5179 if let Some(project) = self.project.as_ref() {
5180 project
5181 .update(cx, |project, cx| {
5182 project.start_debug_session(
5183 resolved_task.resolved_debug_adapter_config().unwrap(),
5184 cx,
5185 )
5186 })
5187 .detach_and_log_err(cx);
5188 Some(Task::ready(Ok(())))
5189 } else {
5190 Some(Task::ready(Ok(())))
5191 }
5192 }
5193 }
5194 }
5195 CodeActionsItem::CodeAction {
5196 excerpt_id,
5197 action,
5198 provider,
5199 } => {
5200 let apply_code_action =
5201 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5202 let workspace = workspace.downgrade();
5203 Some(cx.spawn_in(window, async move |editor, cx| {
5204 let project_transaction = apply_code_action.await?;
5205 Self::open_project_transaction(
5206 &editor,
5207 workspace,
5208 project_transaction,
5209 title,
5210 cx,
5211 )
5212 .await
5213 }))
5214 }
5215 }
5216 }
5217
5218 pub async fn open_project_transaction(
5219 this: &WeakEntity<Editor>,
5220 workspace: WeakEntity<Workspace>,
5221 transaction: ProjectTransaction,
5222 title: String,
5223 cx: &mut AsyncWindowContext,
5224 ) -> Result<()> {
5225 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5226 cx.update(|_, cx| {
5227 entries.sort_unstable_by_key(|(buffer, _)| {
5228 buffer.read(cx).file().map(|f| f.path().clone())
5229 });
5230 })?;
5231
5232 // If the project transaction's edits are all contained within this editor, then
5233 // avoid opening a new editor to display them.
5234
5235 if let Some((buffer, transaction)) = entries.first() {
5236 if entries.len() == 1 {
5237 let excerpt = this.update(cx, |editor, cx| {
5238 editor
5239 .buffer()
5240 .read(cx)
5241 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5242 })?;
5243 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5244 if excerpted_buffer == *buffer {
5245 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5246 let excerpt_range = excerpt_range.to_offset(buffer);
5247 buffer
5248 .edited_ranges_for_transaction::<usize>(transaction)
5249 .all(|range| {
5250 excerpt_range.start <= range.start
5251 && excerpt_range.end >= range.end
5252 })
5253 })?;
5254
5255 if all_edits_within_excerpt {
5256 return Ok(());
5257 }
5258 }
5259 }
5260 }
5261 } else {
5262 return Ok(());
5263 }
5264
5265 let mut ranges_to_highlight = Vec::new();
5266 let excerpt_buffer = cx.new(|cx| {
5267 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5268 for (buffer_handle, transaction) in &entries {
5269 let edited_ranges = buffer_handle
5270 .read(cx)
5271 .edited_ranges_for_transaction::<Point>(transaction)
5272 .collect::<Vec<_>>();
5273 let (ranges, _) = multibuffer.set_excerpts_for_path(
5274 PathKey::for_buffer(buffer_handle, cx),
5275 buffer_handle.clone(),
5276 edited_ranges,
5277 DEFAULT_MULTIBUFFER_CONTEXT,
5278 cx,
5279 );
5280
5281 ranges_to_highlight.extend(ranges);
5282 }
5283 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5284 multibuffer
5285 })?;
5286
5287 workspace.update_in(cx, |workspace, window, cx| {
5288 let project = workspace.project().clone();
5289 let editor =
5290 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5291 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5292 editor.update(cx, |editor, cx| {
5293 editor.highlight_background::<Self>(
5294 &ranges_to_highlight,
5295 |theme| theme.editor_highlighted_line_background,
5296 cx,
5297 );
5298 });
5299 })?;
5300
5301 Ok(())
5302 }
5303
5304 pub fn clear_code_action_providers(&mut self) {
5305 self.code_action_providers.clear();
5306 self.available_code_actions.take();
5307 }
5308
5309 pub fn add_code_action_provider(
5310 &mut self,
5311 provider: Rc<dyn CodeActionProvider>,
5312 window: &mut Window,
5313 cx: &mut Context<Self>,
5314 ) {
5315 if self
5316 .code_action_providers
5317 .iter()
5318 .any(|existing_provider| existing_provider.id() == provider.id())
5319 {
5320 return;
5321 }
5322
5323 self.code_action_providers.push(provider);
5324 self.refresh_code_actions(window, cx);
5325 }
5326
5327 pub fn remove_code_action_provider(
5328 &mut self,
5329 id: Arc<str>,
5330 window: &mut Window,
5331 cx: &mut Context<Self>,
5332 ) {
5333 self.code_action_providers
5334 .retain(|provider| provider.id() != id);
5335 self.refresh_code_actions(window, cx);
5336 }
5337
5338 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5339 let newest_selection = self.selections.newest_anchor().clone();
5340 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5341 let buffer = self.buffer.read(cx);
5342 if newest_selection.head().diff_base_anchor.is_some() {
5343 return None;
5344 }
5345 let (start_buffer, start) =
5346 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5347 let (end_buffer, end) =
5348 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5349 if start_buffer != end_buffer {
5350 return None;
5351 }
5352
5353 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5354 cx.background_executor()
5355 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5356 .await;
5357
5358 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5359 let providers = this.code_action_providers.clone();
5360 let tasks = this
5361 .code_action_providers
5362 .iter()
5363 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5364 .collect::<Vec<_>>();
5365 (providers, tasks)
5366 })?;
5367
5368 let mut actions = Vec::new();
5369 for (provider, provider_actions) in
5370 providers.into_iter().zip(future::join_all(tasks).await)
5371 {
5372 if let Some(provider_actions) = provider_actions.log_err() {
5373 actions.extend(provider_actions.into_iter().map(|action| {
5374 AvailableCodeAction {
5375 excerpt_id: newest_selection.start.excerpt_id,
5376 action,
5377 provider: provider.clone(),
5378 }
5379 }));
5380 }
5381 }
5382
5383 this.update(cx, |this, cx| {
5384 this.available_code_actions = if actions.is_empty() {
5385 None
5386 } else {
5387 Some((
5388 Location {
5389 buffer: start_buffer,
5390 range: start..end,
5391 },
5392 actions.into(),
5393 ))
5394 };
5395 cx.notify();
5396 })
5397 }));
5398 None
5399 }
5400
5401 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5402 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5403 self.show_git_blame_inline = false;
5404
5405 self.show_git_blame_inline_delay_task =
5406 Some(cx.spawn_in(window, async move |this, cx| {
5407 cx.background_executor().timer(delay).await;
5408
5409 this.update(cx, |this, cx| {
5410 this.show_git_blame_inline = true;
5411 cx.notify();
5412 })
5413 .log_err();
5414 }));
5415 }
5416 }
5417
5418 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5419 if self.pending_rename.is_some() {
5420 return None;
5421 }
5422
5423 let provider = self.semantics_provider.clone()?;
5424 let buffer = self.buffer.read(cx);
5425 let newest_selection = self.selections.newest_anchor().clone();
5426 let cursor_position = newest_selection.head();
5427 let (cursor_buffer, cursor_buffer_position) =
5428 buffer.text_anchor_for_position(cursor_position, cx)?;
5429 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5430 if cursor_buffer != tail_buffer {
5431 return None;
5432 }
5433 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5434 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5435 cx.background_executor()
5436 .timer(Duration::from_millis(debounce))
5437 .await;
5438
5439 let highlights = if let Some(highlights) = cx
5440 .update(|cx| {
5441 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5442 })
5443 .ok()
5444 .flatten()
5445 {
5446 highlights.await.log_err()
5447 } else {
5448 None
5449 };
5450
5451 if let Some(highlights) = highlights {
5452 this.update(cx, |this, cx| {
5453 if this.pending_rename.is_some() {
5454 return;
5455 }
5456
5457 let buffer_id = cursor_position.buffer_id;
5458 let buffer = this.buffer.read(cx);
5459 if !buffer
5460 .text_anchor_for_position(cursor_position, cx)
5461 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5462 {
5463 return;
5464 }
5465
5466 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5467 let mut write_ranges = Vec::new();
5468 let mut read_ranges = Vec::new();
5469 for highlight in highlights {
5470 for (excerpt_id, excerpt_range) in
5471 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5472 {
5473 let start = highlight
5474 .range
5475 .start
5476 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5477 let end = highlight
5478 .range
5479 .end
5480 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5481 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5482 continue;
5483 }
5484
5485 let range = Anchor {
5486 buffer_id,
5487 excerpt_id,
5488 text_anchor: start,
5489 diff_base_anchor: None,
5490 }..Anchor {
5491 buffer_id,
5492 excerpt_id,
5493 text_anchor: end,
5494 diff_base_anchor: None,
5495 };
5496 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5497 write_ranges.push(range);
5498 } else {
5499 read_ranges.push(range);
5500 }
5501 }
5502 }
5503
5504 this.highlight_background::<DocumentHighlightRead>(
5505 &read_ranges,
5506 |theme| theme.editor_document_highlight_read_background,
5507 cx,
5508 );
5509 this.highlight_background::<DocumentHighlightWrite>(
5510 &write_ranges,
5511 |theme| theme.editor_document_highlight_write_background,
5512 cx,
5513 );
5514 cx.notify();
5515 })
5516 .log_err();
5517 }
5518 }));
5519 None
5520 }
5521
5522 pub fn refresh_selected_text_highlights(
5523 &mut self,
5524 window: &mut Window,
5525 cx: &mut Context<Editor>,
5526 ) {
5527 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5528 return;
5529 }
5530 self.selection_highlight_task.take();
5531 if !EditorSettings::get_global(cx).selection_highlight {
5532 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5533 return;
5534 }
5535 if self.selections.count() != 1 || self.selections.line_mode {
5536 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5537 return;
5538 }
5539 let selection = self.selections.newest::<Point>(cx);
5540 if selection.is_empty() || selection.start.row != selection.end.row {
5541 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5542 return;
5543 }
5544 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5545 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5546 cx.background_executor()
5547 .timer(Duration::from_millis(debounce))
5548 .await;
5549 let Some(Some(matches_task)) = editor
5550 .update_in(cx, |editor, _, cx| {
5551 if editor.selections.count() != 1 || editor.selections.line_mode {
5552 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5553 return None;
5554 }
5555 let selection = editor.selections.newest::<Point>(cx);
5556 if selection.is_empty() || selection.start.row != selection.end.row {
5557 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5558 return None;
5559 }
5560 let buffer = editor.buffer().read(cx).snapshot(cx);
5561 let query = buffer.text_for_range(selection.range()).collect::<String>();
5562 if query.trim().is_empty() {
5563 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5564 return None;
5565 }
5566 Some(cx.background_spawn(async move {
5567 let mut ranges = Vec::new();
5568 let selection_anchors = selection.range().to_anchors(&buffer);
5569 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5570 for (search_buffer, search_range, excerpt_id) in
5571 buffer.range_to_buffer_ranges(range)
5572 {
5573 ranges.extend(
5574 project::search::SearchQuery::text(
5575 query.clone(),
5576 false,
5577 false,
5578 false,
5579 Default::default(),
5580 Default::default(),
5581 None,
5582 )
5583 .unwrap()
5584 .search(search_buffer, Some(search_range.clone()))
5585 .await
5586 .into_iter()
5587 .filter_map(
5588 |match_range| {
5589 let start = search_buffer.anchor_after(
5590 search_range.start + match_range.start,
5591 );
5592 let end = search_buffer.anchor_before(
5593 search_range.start + match_range.end,
5594 );
5595 let range = Anchor::range_in_buffer(
5596 excerpt_id,
5597 search_buffer.remote_id(),
5598 start..end,
5599 );
5600 (range != selection_anchors).then_some(range)
5601 },
5602 ),
5603 );
5604 }
5605 }
5606 ranges
5607 }))
5608 })
5609 .log_err()
5610 else {
5611 return;
5612 };
5613 let matches = matches_task.await;
5614 editor
5615 .update_in(cx, |editor, _, cx| {
5616 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5617 if !matches.is_empty() {
5618 editor.highlight_background::<SelectedTextHighlight>(
5619 &matches,
5620 |theme| theme.editor_document_highlight_bracket_background,
5621 cx,
5622 )
5623 }
5624 })
5625 .log_err();
5626 }));
5627 }
5628
5629 pub fn refresh_inline_completion(
5630 &mut self,
5631 debounce: bool,
5632 user_requested: bool,
5633 window: &mut Window,
5634 cx: &mut Context<Self>,
5635 ) -> Option<()> {
5636 let provider = self.edit_prediction_provider()?;
5637 let cursor = self.selections.newest_anchor().head();
5638 let (buffer, cursor_buffer_position) =
5639 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5640
5641 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5642 self.discard_inline_completion(false, cx);
5643 return None;
5644 }
5645
5646 if !user_requested
5647 && (!self.should_show_edit_predictions()
5648 || !self.is_focused(window)
5649 || buffer.read(cx).is_empty())
5650 {
5651 self.discard_inline_completion(false, cx);
5652 return None;
5653 }
5654
5655 self.update_visible_inline_completion(window, cx);
5656 provider.refresh(
5657 self.project.clone(),
5658 buffer,
5659 cursor_buffer_position,
5660 debounce,
5661 cx,
5662 );
5663 Some(())
5664 }
5665
5666 fn show_edit_predictions_in_menu(&self) -> bool {
5667 match self.edit_prediction_settings {
5668 EditPredictionSettings::Disabled => false,
5669 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5670 }
5671 }
5672
5673 pub fn edit_predictions_enabled(&self) -> bool {
5674 match self.edit_prediction_settings {
5675 EditPredictionSettings::Disabled => false,
5676 EditPredictionSettings::Enabled { .. } => true,
5677 }
5678 }
5679
5680 fn edit_prediction_requires_modifier(&self) -> bool {
5681 match self.edit_prediction_settings {
5682 EditPredictionSettings::Disabled => false,
5683 EditPredictionSettings::Enabled {
5684 preview_requires_modifier,
5685 ..
5686 } => preview_requires_modifier,
5687 }
5688 }
5689
5690 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5691 if self.edit_prediction_provider.is_none() {
5692 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5693 } else {
5694 let selection = self.selections.newest_anchor();
5695 let cursor = selection.head();
5696
5697 if let Some((buffer, cursor_buffer_position)) =
5698 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5699 {
5700 self.edit_prediction_settings =
5701 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5702 }
5703 }
5704 }
5705
5706 fn edit_prediction_settings_at_position(
5707 &self,
5708 buffer: &Entity<Buffer>,
5709 buffer_position: language::Anchor,
5710 cx: &App,
5711 ) -> EditPredictionSettings {
5712 if !self.mode.is_full()
5713 || !self.show_inline_completions_override.unwrap_or(true)
5714 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5715 {
5716 return EditPredictionSettings::Disabled;
5717 }
5718
5719 let buffer = buffer.read(cx);
5720
5721 let file = buffer.file();
5722
5723 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5724 return EditPredictionSettings::Disabled;
5725 };
5726
5727 let by_provider = matches!(
5728 self.menu_inline_completions_policy,
5729 MenuInlineCompletionsPolicy::ByProvider
5730 );
5731
5732 let show_in_menu = by_provider
5733 && self
5734 .edit_prediction_provider
5735 .as_ref()
5736 .map_or(false, |provider| {
5737 provider.provider.show_completions_in_menu()
5738 });
5739
5740 let preview_requires_modifier =
5741 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5742
5743 EditPredictionSettings::Enabled {
5744 show_in_menu,
5745 preview_requires_modifier,
5746 }
5747 }
5748
5749 fn should_show_edit_predictions(&self) -> bool {
5750 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5751 }
5752
5753 pub fn edit_prediction_preview_is_active(&self) -> bool {
5754 matches!(
5755 self.edit_prediction_preview,
5756 EditPredictionPreview::Active { .. }
5757 )
5758 }
5759
5760 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5761 let cursor = self.selections.newest_anchor().head();
5762 if let Some((buffer, cursor_position)) =
5763 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5764 {
5765 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5766 } else {
5767 false
5768 }
5769 }
5770
5771 fn edit_predictions_enabled_in_buffer(
5772 &self,
5773 buffer: &Entity<Buffer>,
5774 buffer_position: language::Anchor,
5775 cx: &App,
5776 ) -> bool {
5777 maybe!({
5778 if self.read_only(cx) {
5779 return Some(false);
5780 }
5781 let provider = self.edit_prediction_provider()?;
5782 if !provider.is_enabled(&buffer, buffer_position, cx) {
5783 return Some(false);
5784 }
5785 let buffer = buffer.read(cx);
5786 let Some(file) = buffer.file() else {
5787 return Some(true);
5788 };
5789 let settings = all_language_settings(Some(file), cx);
5790 Some(settings.edit_predictions_enabled_for_file(file, cx))
5791 })
5792 .unwrap_or(false)
5793 }
5794
5795 fn cycle_inline_completion(
5796 &mut self,
5797 direction: Direction,
5798 window: &mut Window,
5799 cx: &mut Context<Self>,
5800 ) -> Option<()> {
5801 let provider = self.edit_prediction_provider()?;
5802 let cursor = self.selections.newest_anchor().head();
5803 let (buffer, cursor_buffer_position) =
5804 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5805 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5806 return None;
5807 }
5808
5809 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5810 self.update_visible_inline_completion(window, cx);
5811
5812 Some(())
5813 }
5814
5815 pub fn show_inline_completion(
5816 &mut self,
5817 _: &ShowEditPrediction,
5818 window: &mut Window,
5819 cx: &mut Context<Self>,
5820 ) {
5821 if !self.has_active_inline_completion() {
5822 self.refresh_inline_completion(false, true, window, cx);
5823 return;
5824 }
5825
5826 self.update_visible_inline_completion(window, cx);
5827 }
5828
5829 pub fn display_cursor_names(
5830 &mut self,
5831 _: &DisplayCursorNames,
5832 window: &mut Window,
5833 cx: &mut Context<Self>,
5834 ) {
5835 self.show_cursor_names(window, cx);
5836 }
5837
5838 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5839 self.show_cursor_names = true;
5840 cx.notify();
5841 cx.spawn_in(window, async move |this, cx| {
5842 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5843 this.update(cx, |this, cx| {
5844 this.show_cursor_names = false;
5845 cx.notify()
5846 })
5847 .ok()
5848 })
5849 .detach();
5850 }
5851
5852 pub fn next_edit_prediction(
5853 &mut self,
5854 _: &NextEditPrediction,
5855 window: &mut Window,
5856 cx: &mut Context<Self>,
5857 ) {
5858 if self.has_active_inline_completion() {
5859 self.cycle_inline_completion(Direction::Next, window, cx);
5860 } else {
5861 let is_copilot_disabled = self
5862 .refresh_inline_completion(false, true, window, cx)
5863 .is_none();
5864 if is_copilot_disabled {
5865 cx.propagate();
5866 }
5867 }
5868 }
5869
5870 pub fn previous_edit_prediction(
5871 &mut self,
5872 _: &PreviousEditPrediction,
5873 window: &mut Window,
5874 cx: &mut Context<Self>,
5875 ) {
5876 if self.has_active_inline_completion() {
5877 self.cycle_inline_completion(Direction::Prev, window, cx);
5878 } else {
5879 let is_copilot_disabled = self
5880 .refresh_inline_completion(false, true, window, cx)
5881 .is_none();
5882 if is_copilot_disabled {
5883 cx.propagate();
5884 }
5885 }
5886 }
5887
5888 pub fn accept_edit_prediction(
5889 &mut self,
5890 _: &AcceptEditPrediction,
5891 window: &mut Window,
5892 cx: &mut Context<Self>,
5893 ) {
5894 if self.show_edit_predictions_in_menu() {
5895 self.hide_context_menu(window, cx);
5896 }
5897
5898 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5899 return;
5900 };
5901
5902 self.report_inline_completion_event(
5903 active_inline_completion.completion_id.clone(),
5904 true,
5905 cx,
5906 );
5907
5908 match &active_inline_completion.completion {
5909 InlineCompletion::Move { target, .. } => {
5910 let target = *target;
5911
5912 if let Some(position_map) = &self.last_position_map {
5913 if position_map
5914 .visible_row_range
5915 .contains(&target.to_display_point(&position_map.snapshot).row())
5916 || !self.edit_prediction_requires_modifier()
5917 {
5918 self.unfold_ranges(&[target..target], true, false, cx);
5919 // Note that this is also done in vim's handler of the Tab action.
5920 self.change_selections(
5921 Some(Autoscroll::newest()),
5922 window,
5923 cx,
5924 |selections| {
5925 selections.select_anchor_ranges([target..target]);
5926 },
5927 );
5928 self.clear_row_highlights::<EditPredictionPreview>();
5929
5930 self.edit_prediction_preview
5931 .set_previous_scroll_position(None);
5932 } else {
5933 self.edit_prediction_preview
5934 .set_previous_scroll_position(Some(
5935 position_map.snapshot.scroll_anchor,
5936 ));
5937
5938 self.highlight_rows::<EditPredictionPreview>(
5939 target..target,
5940 cx.theme().colors().editor_highlighted_line_background,
5941 true,
5942 cx,
5943 );
5944 self.request_autoscroll(Autoscroll::fit(), cx);
5945 }
5946 }
5947 }
5948 InlineCompletion::Edit { edits, .. } => {
5949 if let Some(provider) = self.edit_prediction_provider() {
5950 provider.accept(cx);
5951 }
5952
5953 let snapshot = self.buffer.read(cx).snapshot(cx);
5954 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5955
5956 self.buffer.update(cx, |buffer, cx| {
5957 buffer.edit(edits.iter().cloned(), None, cx)
5958 });
5959
5960 self.change_selections(None, window, cx, |s| {
5961 s.select_anchor_ranges([last_edit_end..last_edit_end])
5962 });
5963
5964 self.update_visible_inline_completion(window, cx);
5965 if self.active_inline_completion.is_none() {
5966 self.refresh_inline_completion(true, true, window, cx);
5967 }
5968
5969 cx.notify();
5970 }
5971 }
5972
5973 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5974 }
5975
5976 pub fn accept_partial_inline_completion(
5977 &mut self,
5978 _: &AcceptPartialEditPrediction,
5979 window: &mut Window,
5980 cx: &mut Context<Self>,
5981 ) {
5982 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5983 return;
5984 };
5985 if self.selections.count() != 1 {
5986 return;
5987 }
5988
5989 self.report_inline_completion_event(
5990 active_inline_completion.completion_id.clone(),
5991 true,
5992 cx,
5993 );
5994
5995 match &active_inline_completion.completion {
5996 InlineCompletion::Move { target, .. } => {
5997 let target = *target;
5998 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5999 selections.select_anchor_ranges([target..target]);
6000 });
6001 }
6002 InlineCompletion::Edit { edits, .. } => {
6003 // Find an insertion that starts at the cursor position.
6004 let snapshot = self.buffer.read(cx).snapshot(cx);
6005 let cursor_offset = self.selections.newest::<usize>(cx).head();
6006 let insertion = edits.iter().find_map(|(range, text)| {
6007 let range = range.to_offset(&snapshot);
6008 if range.is_empty() && range.start == cursor_offset {
6009 Some(text)
6010 } else {
6011 None
6012 }
6013 });
6014
6015 if let Some(text) = insertion {
6016 let mut partial_completion = text
6017 .chars()
6018 .by_ref()
6019 .take_while(|c| c.is_alphabetic())
6020 .collect::<String>();
6021 if partial_completion.is_empty() {
6022 partial_completion = text
6023 .chars()
6024 .by_ref()
6025 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6026 .collect::<String>();
6027 }
6028
6029 cx.emit(EditorEvent::InputHandled {
6030 utf16_range_to_replace: None,
6031 text: partial_completion.clone().into(),
6032 });
6033
6034 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6035
6036 self.refresh_inline_completion(true, true, window, cx);
6037 cx.notify();
6038 } else {
6039 self.accept_edit_prediction(&Default::default(), window, cx);
6040 }
6041 }
6042 }
6043 }
6044
6045 fn discard_inline_completion(
6046 &mut self,
6047 should_report_inline_completion_event: bool,
6048 cx: &mut Context<Self>,
6049 ) -> bool {
6050 if should_report_inline_completion_event {
6051 let completion_id = self
6052 .active_inline_completion
6053 .as_ref()
6054 .and_then(|active_completion| active_completion.completion_id.clone());
6055
6056 self.report_inline_completion_event(completion_id, false, cx);
6057 }
6058
6059 if let Some(provider) = self.edit_prediction_provider() {
6060 provider.discard(cx);
6061 }
6062
6063 self.take_active_inline_completion(cx)
6064 }
6065
6066 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6067 let Some(provider) = self.edit_prediction_provider() else {
6068 return;
6069 };
6070
6071 let Some((_, buffer, _)) = self
6072 .buffer
6073 .read(cx)
6074 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6075 else {
6076 return;
6077 };
6078
6079 let extension = buffer
6080 .read(cx)
6081 .file()
6082 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6083
6084 let event_type = match accepted {
6085 true => "Edit Prediction Accepted",
6086 false => "Edit Prediction Discarded",
6087 };
6088 telemetry::event!(
6089 event_type,
6090 provider = provider.name(),
6091 prediction_id = id,
6092 suggestion_accepted = accepted,
6093 file_extension = extension,
6094 );
6095 }
6096
6097 pub fn has_active_inline_completion(&self) -> bool {
6098 self.active_inline_completion.is_some()
6099 }
6100
6101 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6102 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6103 return false;
6104 };
6105
6106 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6107 self.clear_highlights::<InlineCompletionHighlight>(cx);
6108 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6109 true
6110 }
6111
6112 /// Returns true when we're displaying the edit prediction popover below the cursor
6113 /// like we are not previewing and the LSP autocomplete menu is visible
6114 /// or we are in `when_holding_modifier` mode.
6115 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6116 if self.edit_prediction_preview_is_active()
6117 || !self.show_edit_predictions_in_menu()
6118 || !self.edit_predictions_enabled()
6119 {
6120 return false;
6121 }
6122
6123 if self.has_visible_completions_menu() {
6124 return true;
6125 }
6126
6127 has_completion && self.edit_prediction_requires_modifier()
6128 }
6129
6130 fn handle_modifiers_changed(
6131 &mut self,
6132 modifiers: Modifiers,
6133 position_map: &PositionMap,
6134 window: &mut Window,
6135 cx: &mut Context<Self>,
6136 ) {
6137 if self.show_edit_predictions_in_menu() {
6138 self.update_edit_prediction_preview(&modifiers, window, cx);
6139 }
6140
6141 self.update_selection_mode(&modifiers, position_map, window, cx);
6142
6143 let mouse_position = window.mouse_position();
6144 if !position_map.text_hitbox.is_hovered(window) {
6145 return;
6146 }
6147
6148 self.update_hovered_link(
6149 position_map.point_for_position(mouse_position),
6150 &position_map.snapshot,
6151 modifiers,
6152 window,
6153 cx,
6154 )
6155 }
6156
6157 fn update_selection_mode(
6158 &mut self,
6159 modifiers: &Modifiers,
6160 position_map: &PositionMap,
6161 window: &mut Window,
6162 cx: &mut Context<Self>,
6163 ) {
6164 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6165 return;
6166 }
6167
6168 let mouse_position = window.mouse_position();
6169 let point_for_position = position_map.point_for_position(mouse_position);
6170 let position = point_for_position.previous_valid;
6171
6172 self.select(
6173 SelectPhase::BeginColumnar {
6174 position,
6175 reset: false,
6176 goal_column: point_for_position.exact_unclipped.column(),
6177 },
6178 window,
6179 cx,
6180 );
6181 }
6182
6183 fn update_edit_prediction_preview(
6184 &mut self,
6185 modifiers: &Modifiers,
6186 window: &mut Window,
6187 cx: &mut Context<Self>,
6188 ) {
6189 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6190 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6191 return;
6192 };
6193
6194 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6195 if matches!(
6196 self.edit_prediction_preview,
6197 EditPredictionPreview::Inactive { .. }
6198 ) {
6199 self.edit_prediction_preview = EditPredictionPreview::Active {
6200 previous_scroll_position: None,
6201 since: Instant::now(),
6202 };
6203
6204 self.update_visible_inline_completion(window, cx);
6205 cx.notify();
6206 }
6207 } else if let EditPredictionPreview::Active {
6208 previous_scroll_position,
6209 since,
6210 } = self.edit_prediction_preview
6211 {
6212 if let (Some(previous_scroll_position), Some(position_map)) =
6213 (previous_scroll_position, self.last_position_map.as_ref())
6214 {
6215 self.set_scroll_position(
6216 previous_scroll_position
6217 .scroll_position(&position_map.snapshot.display_snapshot),
6218 window,
6219 cx,
6220 );
6221 }
6222
6223 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6224 released_too_fast: since.elapsed() < Duration::from_millis(200),
6225 };
6226 self.clear_row_highlights::<EditPredictionPreview>();
6227 self.update_visible_inline_completion(window, cx);
6228 cx.notify();
6229 }
6230 }
6231
6232 fn update_visible_inline_completion(
6233 &mut self,
6234 _window: &mut Window,
6235 cx: &mut Context<Self>,
6236 ) -> Option<()> {
6237 let selection = self.selections.newest_anchor();
6238 let cursor = selection.head();
6239 let multibuffer = self.buffer.read(cx).snapshot(cx);
6240 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6241 let excerpt_id = cursor.excerpt_id;
6242
6243 let show_in_menu = self.show_edit_predictions_in_menu();
6244 let completions_menu_has_precedence = !show_in_menu
6245 && (self.context_menu.borrow().is_some()
6246 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6247
6248 if completions_menu_has_precedence
6249 || !offset_selection.is_empty()
6250 || self
6251 .active_inline_completion
6252 .as_ref()
6253 .map_or(false, |completion| {
6254 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6255 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6256 !invalidation_range.contains(&offset_selection.head())
6257 })
6258 {
6259 self.discard_inline_completion(false, cx);
6260 return None;
6261 }
6262
6263 self.take_active_inline_completion(cx);
6264 let Some(provider) = self.edit_prediction_provider() else {
6265 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6266 return None;
6267 };
6268
6269 let (buffer, cursor_buffer_position) =
6270 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6271
6272 self.edit_prediction_settings =
6273 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6274
6275 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6276
6277 if self.edit_prediction_indent_conflict {
6278 let cursor_point = cursor.to_point(&multibuffer);
6279
6280 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6281
6282 if let Some((_, indent)) = indents.iter().next() {
6283 if indent.len == cursor_point.column {
6284 self.edit_prediction_indent_conflict = false;
6285 }
6286 }
6287 }
6288
6289 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6290 let edits = inline_completion
6291 .edits
6292 .into_iter()
6293 .flat_map(|(range, new_text)| {
6294 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6295 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6296 Some((start..end, new_text))
6297 })
6298 .collect::<Vec<_>>();
6299 if edits.is_empty() {
6300 return None;
6301 }
6302
6303 let first_edit_start = edits.first().unwrap().0.start;
6304 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6305 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6306
6307 let last_edit_end = edits.last().unwrap().0.end;
6308 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6309 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6310
6311 let cursor_row = cursor.to_point(&multibuffer).row;
6312
6313 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6314
6315 let mut inlay_ids = Vec::new();
6316 let invalidation_row_range;
6317 let move_invalidation_row_range = if cursor_row < edit_start_row {
6318 Some(cursor_row..edit_end_row)
6319 } else if cursor_row > edit_end_row {
6320 Some(edit_start_row..cursor_row)
6321 } else {
6322 None
6323 };
6324 let is_move =
6325 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6326 let completion = if is_move {
6327 invalidation_row_range =
6328 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6329 let target = first_edit_start;
6330 InlineCompletion::Move { target, snapshot }
6331 } else {
6332 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6333 && !self.inline_completions_hidden_for_vim_mode;
6334
6335 if show_completions_in_buffer {
6336 if edits
6337 .iter()
6338 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6339 {
6340 let mut inlays = Vec::new();
6341 for (range, new_text) in &edits {
6342 let inlay = Inlay::inline_completion(
6343 post_inc(&mut self.next_inlay_id),
6344 range.start,
6345 new_text.as_str(),
6346 );
6347 inlay_ids.push(inlay.id);
6348 inlays.push(inlay);
6349 }
6350
6351 self.splice_inlays(&[], inlays, cx);
6352 } else {
6353 let background_color = cx.theme().status().deleted_background;
6354 self.highlight_text::<InlineCompletionHighlight>(
6355 edits.iter().map(|(range, _)| range.clone()).collect(),
6356 HighlightStyle {
6357 background_color: Some(background_color),
6358 ..Default::default()
6359 },
6360 cx,
6361 );
6362 }
6363 }
6364
6365 invalidation_row_range = edit_start_row..edit_end_row;
6366
6367 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6368 if provider.show_tab_accept_marker() {
6369 EditDisplayMode::TabAccept
6370 } else {
6371 EditDisplayMode::Inline
6372 }
6373 } else {
6374 EditDisplayMode::DiffPopover
6375 };
6376
6377 InlineCompletion::Edit {
6378 edits,
6379 edit_preview: inline_completion.edit_preview,
6380 display_mode,
6381 snapshot,
6382 }
6383 };
6384
6385 let invalidation_range = multibuffer
6386 .anchor_before(Point::new(invalidation_row_range.start, 0))
6387 ..multibuffer.anchor_after(Point::new(
6388 invalidation_row_range.end,
6389 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6390 ));
6391
6392 self.stale_inline_completion_in_menu = None;
6393 self.active_inline_completion = Some(InlineCompletionState {
6394 inlay_ids,
6395 completion,
6396 completion_id: inline_completion.id,
6397 invalidation_range,
6398 });
6399
6400 cx.notify();
6401
6402 Some(())
6403 }
6404
6405 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6406 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6407 }
6408
6409 fn render_code_actions_indicator(
6410 &self,
6411 _style: &EditorStyle,
6412 row: DisplayRow,
6413 is_active: bool,
6414 breakpoint: Option<&(Anchor, Breakpoint)>,
6415 cx: &mut Context<Self>,
6416 ) -> Option<IconButton> {
6417 let color = Color::Muted;
6418 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6419 let show_tooltip = !self.context_menu_visible();
6420
6421 if self.available_code_actions.is_some() {
6422 Some(
6423 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6424 .shape(ui::IconButtonShape::Square)
6425 .icon_size(IconSize::XSmall)
6426 .icon_color(color)
6427 .toggle_state(is_active)
6428 .when(show_tooltip, |this| {
6429 this.tooltip({
6430 let focus_handle = self.focus_handle.clone();
6431 move |window, cx| {
6432 Tooltip::for_action_in(
6433 "Toggle Code Actions",
6434 &ToggleCodeActions {
6435 deployed_from_indicator: None,
6436 },
6437 &focus_handle,
6438 window,
6439 cx,
6440 )
6441 }
6442 })
6443 })
6444 .on_click(cx.listener(move |editor, _e, window, cx| {
6445 window.focus(&editor.focus_handle(cx));
6446 editor.toggle_code_actions(
6447 &ToggleCodeActions {
6448 deployed_from_indicator: Some(row),
6449 },
6450 window,
6451 cx,
6452 );
6453 }))
6454 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6455 editor.set_breakpoint_context_menu(
6456 row,
6457 position,
6458 event.down.position,
6459 window,
6460 cx,
6461 );
6462 })),
6463 )
6464 } else {
6465 None
6466 }
6467 }
6468
6469 fn clear_tasks(&mut self) {
6470 self.tasks.clear()
6471 }
6472
6473 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6474 if self.tasks.insert(key, value).is_some() {
6475 // This case should hopefully be rare, but just in case...
6476 log::error!(
6477 "multiple different run targets found on a single line, only the last target will be rendered"
6478 )
6479 }
6480 }
6481
6482 /// Get all display points of breakpoints that will be rendered within editor
6483 ///
6484 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6485 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6486 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6487 fn active_breakpoints(
6488 &self,
6489 range: Range<DisplayRow>,
6490 window: &mut Window,
6491 cx: &mut Context<Self>,
6492 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6493 let mut breakpoint_display_points = HashMap::default();
6494
6495 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6496 return breakpoint_display_points;
6497 };
6498
6499 let snapshot = self.snapshot(window, cx);
6500
6501 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6502 let Some(project) = self.project.as_ref() else {
6503 return breakpoint_display_points;
6504 };
6505
6506 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6507 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6508
6509 for (buffer_snapshot, range, excerpt_id) in
6510 multi_buffer_snapshot.range_to_buffer_ranges(range)
6511 {
6512 let Some(buffer) = project.read_with(cx, |this, cx| {
6513 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6514 }) else {
6515 continue;
6516 };
6517 let breakpoints = breakpoint_store.read(cx).breakpoints(
6518 &buffer,
6519 Some(
6520 buffer_snapshot.anchor_before(range.start)
6521 ..buffer_snapshot.anchor_after(range.end),
6522 ),
6523 buffer_snapshot,
6524 cx,
6525 );
6526 for (anchor, breakpoint) in breakpoints {
6527 let multi_buffer_anchor =
6528 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6529 let position = multi_buffer_anchor
6530 .to_point(&multi_buffer_snapshot)
6531 .to_display_point(&snapshot);
6532
6533 breakpoint_display_points
6534 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6535 }
6536 }
6537
6538 breakpoint_display_points
6539 }
6540
6541 fn breakpoint_context_menu(
6542 &self,
6543 anchor: Anchor,
6544 window: &mut Window,
6545 cx: &mut Context<Self>,
6546 ) -> Entity<ui::ContextMenu> {
6547 let weak_editor = cx.weak_entity();
6548 let focus_handle = self.focus_handle(cx);
6549
6550 let row = self
6551 .buffer
6552 .read(cx)
6553 .snapshot(cx)
6554 .summary_for_anchor::<Point>(&anchor)
6555 .row;
6556
6557 let breakpoint = self
6558 .breakpoint_at_row(row, window, cx)
6559 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6560
6561 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6562 "Edit Log Breakpoint"
6563 } else {
6564 "Set Log Breakpoint"
6565 };
6566
6567 let condition_breakpoint_msg = if breakpoint
6568 .as_ref()
6569 .is_some_and(|bp| bp.1.condition.is_some())
6570 {
6571 "Edit Condition Breakpoint"
6572 } else {
6573 "Set Condition Breakpoint"
6574 };
6575
6576 let hit_condition_breakpoint_msg = if breakpoint
6577 .as_ref()
6578 .is_some_and(|bp| bp.1.hit_condition.is_some())
6579 {
6580 "Edit Hit Condition Breakpoint"
6581 } else {
6582 "Set Hit Condition Breakpoint"
6583 };
6584
6585 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6586 "Unset Breakpoint"
6587 } else {
6588 "Set Breakpoint"
6589 };
6590
6591 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6592 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6593
6594 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6595 BreakpointState::Enabled => Some("Disable"),
6596 BreakpointState::Disabled => Some("Enable"),
6597 });
6598
6599 let (anchor, breakpoint) =
6600 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6601
6602 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6603 menu.on_blur_subscription(Subscription::new(|| {}))
6604 .context(focus_handle)
6605 .when(run_to_cursor, |this| {
6606 let weak_editor = weak_editor.clone();
6607 this.entry("Run to cursor", None, move |window, cx| {
6608 weak_editor
6609 .update(cx, |editor, cx| {
6610 editor.change_selections(None, window, cx, |s| {
6611 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6612 });
6613 })
6614 .ok();
6615
6616 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6617 })
6618 .separator()
6619 })
6620 .when_some(toggle_state_msg, |this, msg| {
6621 this.entry(msg, None, {
6622 let weak_editor = weak_editor.clone();
6623 let breakpoint = breakpoint.clone();
6624 move |_window, cx| {
6625 weak_editor
6626 .update(cx, |this, cx| {
6627 this.edit_breakpoint_at_anchor(
6628 anchor,
6629 breakpoint.as_ref().clone(),
6630 BreakpointEditAction::InvertState,
6631 cx,
6632 );
6633 })
6634 .log_err();
6635 }
6636 })
6637 })
6638 .entry(set_breakpoint_msg, None, {
6639 let weak_editor = weak_editor.clone();
6640 let breakpoint = breakpoint.clone();
6641 move |_window, cx| {
6642 weak_editor
6643 .update(cx, |this, cx| {
6644 this.edit_breakpoint_at_anchor(
6645 anchor,
6646 breakpoint.as_ref().clone(),
6647 BreakpointEditAction::Toggle,
6648 cx,
6649 );
6650 })
6651 .log_err();
6652 }
6653 })
6654 .entry(log_breakpoint_msg, None, {
6655 let breakpoint = breakpoint.clone();
6656 let weak_editor = weak_editor.clone();
6657 move |window, cx| {
6658 weak_editor
6659 .update(cx, |this, cx| {
6660 this.add_edit_breakpoint_block(
6661 anchor,
6662 breakpoint.as_ref(),
6663 BreakpointPromptEditAction::Log,
6664 window,
6665 cx,
6666 );
6667 })
6668 .log_err();
6669 }
6670 })
6671 .entry(condition_breakpoint_msg, None, {
6672 let breakpoint = breakpoint.clone();
6673 let weak_editor = weak_editor.clone();
6674 move |window, cx| {
6675 weak_editor
6676 .update(cx, |this, cx| {
6677 this.add_edit_breakpoint_block(
6678 anchor,
6679 breakpoint.as_ref(),
6680 BreakpointPromptEditAction::Condition,
6681 window,
6682 cx,
6683 );
6684 })
6685 .log_err();
6686 }
6687 })
6688 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6689 weak_editor
6690 .update(cx, |this, cx| {
6691 this.add_edit_breakpoint_block(
6692 anchor,
6693 breakpoint.as_ref(),
6694 BreakpointPromptEditAction::HitCondition,
6695 window,
6696 cx,
6697 );
6698 })
6699 .log_err();
6700 })
6701 })
6702 }
6703
6704 fn render_breakpoint(
6705 &self,
6706 position: Anchor,
6707 row: DisplayRow,
6708 breakpoint: &Breakpoint,
6709 cx: &mut Context<Self>,
6710 ) -> IconButton {
6711 let (color, icon) = {
6712 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6713 (false, false) => ui::IconName::DebugBreakpoint,
6714 (true, false) => ui::IconName::DebugLogBreakpoint,
6715 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6716 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6717 };
6718
6719 let color = if self
6720 .gutter_breakpoint_indicator
6721 .0
6722 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6723 {
6724 Color::Hint
6725 } else {
6726 Color::Debugger
6727 };
6728
6729 (color, icon)
6730 };
6731
6732 let breakpoint = Arc::from(breakpoint.clone());
6733
6734 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6735 .icon_size(IconSize::XSmall)
6736 .size(ui::ButtonSize::None)
6737 .icon_color(color)
6738 .style(ButtonStyle::Transparent)
6739 .on_click(cx.listener({
6740 let breakpoint = breakpoint.clone();
6741
6742 move |editor, event: &ClickEvent, window, cx| {
6743 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6744 BreakpointEditAction::InvertState
6745 } else {
6746 BreakpointEditAction::Toggle
6747 };
6748
6749 window.focus(&editor.focus_handle(cx));
6750 editor.edit_breakpoint_at_anchor(
6751 position,
6752 breakpoint.as_ref().clone(),
6753 edit_action,
6754 cx,
6755 );
6756 }
6757 }))
6758 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6759 editor.set_breakpoint_context_menu(
6760 row,
6761 Some(position),
6762 event.down.position,
6763 window,
6764 cx,
6765 );
6766 }))
6767 }
6768
6769 fn build_tasks_context(
6770 project: &Entity<Project>,
6771 buffer: &Entity<Buffer>,
6772 buffer_row: u32,
6773 tasks: &Arc<RunnableTasks>,
6774 cx: &mut Context<Self>,
6775 ) -> Task<Option<task::TaskContext>> {
6776 let position = Point::new(buffer_row, tasks.column);
6777 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6778 let location = Location {
6779 buffer: buffer.clone(),
6780 range: range_start..range_start,
6781 };
6782 // Fill in the environmental variables from the tree-sitter captures
6783 let mut captured_task_variables = TaskVariables::default();
6784 for (capture_name, value) in tasks.extra_variables.clone() {
6785 captured_task_variables.insert(
6786 task::VariableName::Custom(capture_name.into()),
6787 value.clone(),
6788 );
6789 }
6790 project.update(cx, |project, cx| {
6791 project.task_store().update(cx, |task_store, cx| {
6792 task_store.task_context_for_location(captured_task_variables, location, cx)
6793 })
6794 })
6795 }
6796
6797 pub fn spawn_nearest_task(
6798 &mut self,
6799 action: &SpawnNearestTask,
6800 window: &mut Window,
6801 cx: &mut Context<Self>,
6802 ) {
6803 let Some((workspace, _)) = self.workspace.clone() else {
6804 return;
6805 };
6806 let Some(project) = self.project.clone() else {
6807 return;
6808 };
6809
6810 // Try to find a closest, enclosing node using tree-sitter that has a
6811 // task
6812 let Some((buffer, buffer_row, tasks)) = self
6813 .find_enclosing_node_task(cx)
6814 // Or find the task that's closest in row-distance.
6815 .or_else(|| self.find_closest_task(cx))
6816 else {
6817 return;
6818 };
6819
6820 let reveal_strategy = action.reveal;
6821 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6822 cx.spawn_in(window, async move |_, cx| {
6823 let context = task_context.await?;
6824 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6825
6826 let resolved = resolved_task.resolved.as_mut()?;
6827 resolved.reveal = reveal_strategy;
6828
6829 workspace
6830 .update(cx, |workspace, cx| {
6831 workspace::tasks::schedule_resolved_task(
6832 workspace,
6833 task_source_kind,
6834 resolved_task,
6835 false,
6836 cx,
6837 );
6838 })
6839 .ok()
6840 })
6841 .detach();
6842 }
6843
6844 fn find_closest_task(
6845 &mut self,
6846 cx: &mut Context<Self>,
6847 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6848 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6849
6850 let ((buffer_id, row), tasks) = self
6851 .tasks
6852 .iter()
6853 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6854
6855 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6856 let tasks = Arc::new(tasks.to_owned());
6857 Some((buffer, *row, tasks))
6858 }
6859
6860 fn find_enclosing_node_task(
6861 &mut self,
6862 cx: &mut Context<Self>,
6863 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6864 let snapshot = self.buffer.read(cx).snapshot(cx);
6865 let offset = self.selections.newest::<usize>(cx).head();
6866 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6867 let buffer_id = excerpt.buffer().remote_id();
6868
6869 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6870 let mut cursor = layer.node().walk();
6871
6872 while cursor.goto_first_child_for_byte(offset).is_some() {
6873 if cursor.node().end_byte() == offset {
6874 cursor.goto_next_sibling();
6875 }
6876 }
6877
6878 // Ascend to the smallest ancestor that contains the range and has a task.
6879 loop {
6880 let node = cursor.node();
6881 let node_range = node.byte_range();
6882 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6883
6884 // Check if this node contains our offset
6885 if node_range.start <= offset && node_range.end >= offset {
6886 // If it contains offset, check for task
6887 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6888 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6889 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6890 }
6891 }
6892
6893 if !cursor.goto_parent() {
6894 break;
6895 }
6896 }
6897 None
6898 }
6899
6900 fn render_run_indicator(
6901 &self,
6902 _style: &EditorStyle,
6903 is_active: bool,
6904 row: DisplayRow,
6905 breakpoint: Option<(Anchor, Breakpoint)>,
6906 cx: &mut Context<Self>,
6907 ) -> IconButton {
6908 let color = Color::Muted;
6909 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6910
6911 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6912 .shape(ui::IconButtonShape::Square)
6913 .icon_size(IconSize::XSmall)
6914 .icon_color(color)
6915 .toggle_state(is_active)
6916 .on_click(cx.listener(move |editor, _e, window, cx| {
6917 window.focus(&editor.focus_handle(cx));
6918 editor.toggle_code_actions(
6919 &ToggleCodeActions {
6920 deployed_from_indicator: Some(row),
6921 },
6922 window,
6923 cx,
6924 );
6925 }))
6926 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6927 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6928 }))
6929 }
6930
6931 pub fn context_menu_visible(&self) -> bool {
6932 !self.edit_prediction_preview_is_active()
6933 && self
6934 .context_menu
6935 .borrow()
6936 .as_ref()
6937 .map_or(false, |menu| menu.visible())
6938 }
6939
6940 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6941 self.context_menu
6942 .borrow()
6943 .as_ref()
6944 .map(|menu| menu.origin())
6945 }
6946
6947 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6948 self.context_menu_options = Some(options);
6949 }
6950
6951 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6952 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6953
6954 fn render_edit_prediction_popover(
6955 &mut self,
6956 text_bounds: &Bounds<Pixels>,
6957 content_origin: gpui::Point<Pixels>,
6958 editor_snapshot: &EditorSnapshot,
6959 visible_row_range: Range<DisplayRow>,
6960 scroll_top: f32,
6961 scroll_bottom: f32,
6962 line_layouts: &[LineWithInvisibles],
6963 line_height: Pixels,
6964 scroll_pixel_position: gpui::Point<Pixels>,
6965 newest_selection_head: Option<DisplayPoint>,
6966 editor_width: Pixels,
6967 style: &EditorStyle,
6968 window: &mut Window,
6969 cx: &mut App,
6970 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6971 let active_inline_completion = self.active_inline_completion.as_ref()?;
6972
6973 if self.edit_prediction_visible_in_cursor_popover(true) {
6974 return None;
6975 }
6976
6977 match &active_inline_completion.completion {
6978 InlineCompletion::Move { target, .. } => {
6979 let target_display_point = target.to_display_point(editor_snapshot);
6980
6981 if self.edit_prediction_requires_modifier() {
6982 if !self.edit_prediction_preview_is_active() {
6983 return None;
6984 }
6985
6986 self.render_edit_prediction_modifier_jump_popover(
6987 text_bounds,
6988 content_origin,
6989 visible_row_range,
6990 line_layouts,
6991 line_height,
6992 scroll_pixel_position,
6993 newest_selection_head,
6994 target_display_point,
6995 window,
6996 cx,
6997 )
6998 } else {
6999 self.render_edit_prediction_eager_jump_popover(
7000 text_bounds,
7001 content_origin,
7002 editor_snapshot,
7003 visible_row_range,
7004 scroll_top,
7005 scroll_bottom,
7006 line_height,
7007 scroll_pixel_position,
7008 target_display_point,
7009 editor_width,
7010 window,
7011 cx,
7012 )
7013 }
7014 }
7015 InlineCompletion::Edit {
7016 display_mode: EditDisplayMode::Inline,
7017 ..
7018 } => None,
7019 InlineCompletion::Edit {
7020 display_mode: EditDisplayMode::TabAccept,
7021 edits,
7022 ..
7023 } => {
7024 let range = &edits.first()?.0;
7025 let target_display_point = range.end.to_display_point(editor_snapshot);
7026
7027 self.render_edit_prediction_end_of_line_popover(
7028 "Accept",
7029 editor_snapshot,
7030 visible_row_range,
7031 target_display_point,
7032 line_height,
7033 scroll_pixel_position,
7034 content_origin,
7035 editor_width,
7036 window,
7037 cx,
7038 )
7039 }
7040 InlineCompletion::Edit {
7041 edits,
7042 edit_preview,
7043 display_mode: EditDisplayMode::DiffPopover,
7044 snapshot,
7045 } => self.render_edit_prediction_diff_popover(
7046 text_bounds,
7047 content_origin,
7048 editor_snapshot,
7049 visible_row_range,
7050 line_layouts,
7051 line_height,
7052 scroll_pixel_position,
7053 newest_selection_head,
7054 editor_width,
7055 style,
7056 edits,
7057 edit_preview,
7058 snapshot,
7059 window,
7060 cx,
7061 ),
7062 }
7063 }
7064
7065 fn render_edit_prediction_modifier_jump_popover(
7066 &mut self,
7067 text_bounds: &Bounds<Pixels>,
7068 content_origin: gpui::Point<Pixels>,
7069 visible_row_range: Range<DisplayRow>,
7070 line_layouts: &[LineWithInvisibles],
7071 line_height: Pixels,
7072 scroll_pixel_position: gpui::Point<Pixels>,
7073 newest_selection_head: Option<DisplayPoint>,
7074 target_display_point: DisplayPoint,
7075 window: &mut Window,
7076 cx: &mut App,
7077 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7078 let scrolled_content_origin =
7079 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7080
7081 const SCROLL_PADDING_Y: Pixels = px(12.);
7082
7083 if target_display_point.row() < visible_row_range.start {
7084 return self.render_edit_prediction_scroll_popover(
7085 |_| SCROLL_PADDING_Y,
7086 IconName::ArrowUp,
7087 visible_row_range,
7088 line_layouts,
7089 newest_selection_head,
7090 scrolled_content_origin,
7091 window,
7092 cx,
7093 );
7094 } else if target_display_point.row() >= visible_row_range.end {
7095 return self.render_edit_prediction_scroll_popover(
7096 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7097 IconName::ArrowDown,
7098 visible_row_range,
7099 line_layouts,
7100 newest_selection_head,
7101 scrolled_content_origin,
7102 window,
7103 cx,
7104 );
7105 }
7106
7107 const POLE_WIDTH: Pixels = px(2.);
7108
7109 let line_layout =
7110 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7111 let target_column = target_display_point.column() as usize;
7112
7113 let target_x = line_layout.x_for_index(target_column);
7114 let target_y =
7115 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7116
7117 let flag_on_right = target_x < text_bounds.size.width / 2.;
7118
7119 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7120 border_color.l += 0.001;
7121
7122 let mut element = v_flex()
7123 .items_end()
7124 .when(flag_on_right, |el| el.items_start())
7125 .child(if flag_on_right {
7126 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7127 .rounded_bl(px(0.))
7128 .rounded_tl(px(0.))
7129 .border_l_2()
7130 .border_color(border_color)
7131 } else {
7132 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7133 .rounded_br(px(0.))
7134 .rounded_tr(px(0.))
7135 .border_r_2()
7136 .border_color(border_color)
7137 })
7138 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7139 .into_any();
7140
7141 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7142
7143 let mut origin = scrolled_content_origin + point(target_x, target_y)
7144 - point(
7145 if flag_on_right {
7146 POLE_WIDTH
7147 } else {
7148 size.width - POLE_WIDTH
7149 },
7150 size.height - line_height,
7151 );
7152
7153 origin.x = origin.x.max(content_origin.x);
7154
7155 element.prepaint_at(origin, window, cx);
7156
7157 Some((element, origin))
7158 }
7159
7160 fn render_edit_prediction_scroll_popover(
7161 &mut self,
7162 to_y: impl Fn(Size<Pixels>) -> Pixels,
7163 scroll_icon: IconName,
7164 visible_row_range: Range<DisplayRow>,
7165 line_layouts: &[LineWithInvisibles],
7166 newest_selection_head: Option<DisplayPoint>,
7167 scrolled_content_origin: gpui::Point<Pixels>,
7168 window: &mut Window,
7169 cx: &mut App,
7170 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7171 let mut element = self
7172 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7173 .into_any();
7174
7175 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7176
7177 let cursor = newest_selection_head?;
7178 let cursor_row_layout =
7179 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7180 let cursor_column = cursor.column() as usize;
7181
7182 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7183
7184 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7185
7186 element.prepaint_at(origin, window, cx);
7187 Some((element, origin))
7188 }
7189
7190 fn render_edit_prediction_eager_jump_popover(
7191 &mut self,
7192 text_bounds: &Bounds<Pixels>,
7193 content_origin: gpui::Point<Pixels>,
7194 editor_snapshot: &EditorSnapshot,
7195 visible_row_range: Range<DisplayRow>,
7196 scroll_top: f32,
7197 scroll_bottom: f32,
7198 line_height: Pixels,
7199 scroll_pixel_position: gpui::Point<Pixels>,
7200 target_display_point: DisplayPoint,
7201 editor_width: Pixels,
7202 window: &mut Window,
7203 cx: &mut App,
7204 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7205 if target_display_point.row().as_f32() < scroll_top {
7206 let mut element = self
7207 .render_edit_prediction_line_popover(
7208 "Jump to Edit",
7209 Some(IconName::ArrowUp),
7210 window,
7211 cx,
7212 )?
7213 .into_any();
7214
7215 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7216 let offset = point(
7217 (text_bounds.size.width - size.width) / 2.,
7218 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7219 );
7220
7221 let origin = text_bounds.origin + offset;
7222 element.prepaint_at(origin, window, cx);
7223 Some((element, origin))
7224 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7225 let mut element = self
7226 .render_edit_prediction_line_popover(
7227 "Jump to Edit",
7228 Some(IconName::ArrowDown),
7229 window,
7230 cx,
7231 )?
7232 .into_any();
7233
7234 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7235 let offset = point(
7236 (text_bounds.size.width - size.width) / 2.,
7237 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7238 );
7239
7240 let origin = text_bounds.origin + offset;
7241 element.prepaint_at(origin, window, cx);
7242 Some((element, origin))
7243 } else {
7244 self.render_edit_prediction_end_of_line_popover(
7245 "Jump to Edit",
7246 editor_snapshot,
7247 visible_row_range,
7248 target_display_point,
7249 line_height,
7250 scroll_pixel_position,
7251 content_origin,
7252 editor_width,
7253 window,
7254 cx,
7255 )
7256 }
7257 }
7258
7259 fn render_edit_prediction_end_of_line_popover(
7260 self: &mut Editor,
7261 label: &'static str,
7262 editor_snapshot: &EditorSnapshot,
7263 visible_row_range: Range<DisplayRow>,
7264 target_display_point: DisplayPoint,
7265 line_height: Pixels,
7266 scroll_pixel_position: gpui::Point<Pixels>,
7267 content_origin: gpui::Point<Pixels>,
7268 editor_width: Pixels,
7269 window: &mut Window,
7270 cx: &mut App,
7271 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7272 let target_line_end = DisplayPoint::new(
7273 target_display_point.row(),
7274 editor_snapshot.line_len(target_display_point.row()),
7275 );
7276
7277 let mut element = self
7278 .render_edit_prediction_line_popover(label, None, window, cx)?
7279 .into_any();
7280
7281 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7282
7283 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7284
7285 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7286 let mut origin = start_point
7287 + line_origin
7288 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7289 origin.x = origin.x.max(content_origin.x);
7290
7291 let max_x = content_origin.x + editor_width - size.width;
7292
7293 if origin.x > max_x {
7294 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7295
7296 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7297 origin.y += offset;
7298 IconName::ArrowUp
7299 } else {
7300 origin.y -= offset;
7301 IconName::ArrowDown
7302 };
7303
7304 element = self
7305 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7306 .into_any();
7307
7308 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7309
7310 origin.x = content_origin.x + editor_width - size.width - px(2.);
7311 }
7312
7313 element.prepaint_at(origin, window, cx);
7314 Some((element, origin))
7315 }
7316
7317 fn render_edit_prediction_diff_popover(
7318 self: &Editor,
7319 text_bounds: &Bounds<Pixels>,
7320 content_origin: gpui::Point<Pixels>,
7321 editor_snapshot: &EditorSnapshot,
7322 visible_row_range: Range<DisplayRow>,
7323 line_layouts: &[LineWithInvisibles],
7324 line_height: Pixels,
7325 scroll_pixel_position: gpui::Point<Pixels>,
7326 newest_selection_head: Option<DisplayPoint>,
7327 editor_width: Pixels,
7328 style: &EditorStyle,
7329 edits: &Vec<(Range<Anchor>, String)>,
7330 edit_preview: &Option<language::EditPreview>,
7331 snapshot: &language::BufferSnapshot,
7332 window: &mut Window,
7333 cx: &mut App,
7334 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7335 let edit_start = edits
7336 .first()
7337 .unwrap()
7338 .0
7339 .start
7340 .to_display_point(editor_snapshot);
7341 let edit_end = edits
7342 .last()
7343 .unwrap()
7344 .0
7345 .end
7346 .to_display_point(editor_snapshot);
7347
7348 let is_visible = visible_row_range.contains(&edit_start.row())
7349 || visible_row_range.contains(&edit_end.row());
7350 if !is_visible {
7351 return None;
7352 }
7353
7354 let highlighted_edits =
7355 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7356
7357 let styled_text = highlighted_edits.to_styled_text(&style.text);
7358 let line_count = highlighted_edits.text.lines().count();
7359
7360 const BORDER_WIDTH: Pixels = px(1.);
7361
7362 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7363 let has_keybind = keybind.is_some();
7364
7365 let mut element = h_flex()
7366 .items_start()
7367 .child(
7368 h_flex()
7369 .bg(cx.theme().colors().editor_background)
7370 .border(BORDER_WIDTH)
7371 .shadow_sm()
7372 .border_color(cx.theme().colors().border)
7373 .rounded_l_lg()
7374 .when(line_count > 1, |el| el.rounded_br_lg())
7375 .pr_1()
7376 .child(styled_text),
7377 )
7378 .child(
7379 h_flex()
7380 .h(line_height + BORDER_WIDTH * 2.)
7381 .px_1p5()
7382 .gap_1()
7383 // Workaround: For some reason, there's a gap if we don't do this
7384 .ml(-BORDER_WIDTH)
7385 .shadow(smallvec![gpui::BoxShadow {
7386 color: gpui::black().opacity(0.05),
7387 offset: point(px(1.), px(1.)),
7388 blur_radius: px(2.),
7389 spread_radius: px(0.),
7390 }])
7391 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7392 .border(BORDER_WIDTH)
7393 .border_color(cx.theme().colors().border)
7394 .rounded_r_lg()
7395 .id("edit_prediction_diff_popover_keybind")
7396 .when(!has_keybind, |el| {
7397 let status_colors = cx.theme().status();
7398
7399 el.bg(status_colors.error_background)
7400 .border_color(status_colors.error.opacity(0.6))
7401 .child(Icon::new(IconName::Info).color(Color::Error))
7402 .cursor_default()
7403 .hoverable_tooltip(move |_window, cx| {
7404 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7405 })
7406 })
7407 .children(keybind),
7408 )
7409 .into_any();
7410
7411 let longest_row =
7412 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7413 let longest_line_width = if visible_row_range.contains(&longest_row) {
7414 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7415 } else {
7416 layout_line(
7417 longest_row,
7418 editor_snapshot,
7419 style,
7420 editor_width,
7421 |_| false,
7422 window,
7423 cx,
7424 )
7425 .width
7426 };
7427
7428 let viewport_bounds =
7429 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7430 right: -EditorElement::SCROLLBAR_WIDTH,
7431 ..Default::default()
7432 });
7433
7434 let x_after_longest =
7435 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7436 - scroll_pixel_position.x;
7437
7438 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7439
7440 // Fully visible if it can be displayed within the window (allow overlapping other
7441 // panes). However, this is only allowed if the popover starts within text_bounds.
7442 let can_position_to_the_right = x_after_longest < text_bounds.right()
7443 && x_after_longest + element_bounds.width < viewport_bounds.right();
7444
7445 let mut origin = if can_position_to_the_right {
7446 point(
7447 x_after_longest,
7448 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7449 - scroll_pixel_position.y,
7450 )
7451 } else {
7452 let cursor_row = newest_selection_head.map(|head| head.row());
7453 let above_edit = edit_start
7454 .row()
7455 .0
7456 .checked_sub(line_count as u32)
7457 .map(DisplayRow);
7458 let below_edit = Some(edit_end.row() + 1);
7459 let above_cursor =
7460 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7461 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7462
7463 // Place the edit popover adjacent to the edit if there is a location
7464 // available that is onscreen and does not obscure the cursor. Otherwise,
7465 // place it adjacent to the cursor.
7466 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7467 .into_iter()
7468 .flatten()
7469 .find(|&start_row| {
7470 let end_row = start_row + line_count as u32;
7471 visible_row_range.contains(&start_row)
7472 && visible_row_range.contains(&end_row)
7473 && cursor_row.map_or(true, |cursor_row| {
7474 !((start_row..end_row).contains(&cursor_row))
7475 })
7476 })?;
7477
7478 content_origin
7479 + point(
7480 -scroll_pixel_position.x,
7481 row_target.as_f32() * line_height - scroll_pixel_position.y,
7482 )
7483 };
7484
7485 origin.x -= BORDER_WIDTH;
7486
7487 window.defer_draw(element, origin, 1);
7488
7489 // Do not return an element, since it will already be drawn due to defer_draw.
7490 None
7491 }
7492
7493 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7494 px(30.)
7495 }
7496
7497 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7498 if self.read_only(cx) {
7499 cx.theme().players().read_only()
7500 } else {
7501 self.style.as_ref().unwrap().local_player
7502 }
7503 }
7504
7505 fn render_edit_prediction_accept_keybind(
7506 &self,
7507 window: &mut Window,
7508 cx: &App,
7509 ) -> Option<AnyElement> {
7510 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7511 let accept_keystroke = accept_binding.keystroke()?;
7512
7513 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7514
7515 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7516 Color::Accent
7517 } else {
7518 Color::Muted
7519 };
7520
7521 h_flex()
7522 .px_0p5()
7523 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7524 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7525 .text_size(TextSize::XSmall.rems(cx))
7526 .child(h_flex().children(ui::render_modifiers(
7527 &accept_keystroke.modifiers,
7528 PlatformStyle::platform(),
7529 Some(modifiers_color),
7530 Some(IconSize::XSmall.rems().into()),
7531 true,
7532 )))
7533 .when(is_platform_style_mac, |parent| {
7534 parent.child(accept_keystroke.key.clone())
7535 })
7536 .when(!is_platform_style_mac, |parent| {
7537 parent.child(
7538 Key::new(
7539 util::capitalize(&accept_keystroke.key),
7540 Some(Color::Default),
7541 )
7542 .size(Some(IconSize::XSmall.rems().into())),
7543 )
7544 })
7545 .into_any()
7546 .into()
7547 }
7548
7549 fn render_edit_prediction_line_popover(
7550 &self,
7551 label: impl Into<SharedString>,
7552 icon: Option<IconName>,
7553 window: &mut Window,
7554 cx: &App,
7555 ) -> Option<Stateful<Div>> {
7556 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7557
7558 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7559 let has_keybind = keybind.is_some();
7560
7561 let result = h_flex()
7562 .id("ep-line-popover")
7563 .py_0p5()
7564 .pl_1()
7565 .pr(padding_right)
7566 .gap_1()
7567 .rounded_md()
7568 .border_1()
7569 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7570 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7571 .shadow_sm()
7572 .when(!has_keybind, |el| {
7573 let status_colors = cx.theme().status();
7574
7575 el.bg(status_colors.error_background)
7576 .border_color(status_colors.error.opacity(0.6))
7577 .pl_2()
7578 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7579 .cursor_default()
7580 .hoverable_tooltip(move |_window, cx| {
7581 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7582 })
7583 })
7584 .children(keybind)
7585 .child(
7586 Label::new(label)
7587 .size(LabelSize::Small)
7588 .when(!has_keybind, |el| {
7589 el.color(cx.theme().status().error.into()).strikethrough()
7590 }),
7591 )
7592 .when(!has_keybind, |el| {
7593 el.child(
7594 h_flex().ml_1().child(
7595 Icon::new(IconName::Info)
7596 .size(IconSize::Small)
7597 .color(cx.theme().status().error.into()),
7598 ),
7599 )
7600 })
7601 .when_some(icon, |element, icon| {
7602 element.child(
7603 div()
7604 .mt(px(1.5))
7605 .child(Icon::new(icon).size(IconSize::Small)),
7606 )
7607 });
7608
7609 Some(result)
7610 }
7611
7612 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7613 let accent_color = cx.theme().colors().text_accent;
7614 let editor_bg_color = cx.theme().colors().editor_background;
7615 editor_bg_color.blend(accent_color.opacity(0.1))
7616 }
7617
7618 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7619 let accent_color = cx.theme().colors().text_accent;
7620 let editor_bg_color = cx.theme().colors().editor_background;
7621 editor_bg_color.blend(accent_color.opacity(0.6))
7622 }
7623
7624 fn render_edit_prediction_cursor_popover(
7625 &self,
7626 min_width: Pixels,
7627 max_width: Pixels,
7628 cursor_point: Point,
7629 style: &EditorStyle,
7630 accept_keystroke: Option<&gpui::Keystroke>,
7631 _window: &Window,
7632 cx: &mut Context<Editor>,
7633 ) -> Option<AnyElement> {
7634 let provider = self.edit_prediction_provider.as_ref()?;
7635
7636 if provider.provider.needs_terms_acceptance(cx) {
7637 return Some(
7638 h_flex()
7639 .min_w(min_width)
7640 .flex_1()
7641 .px_2()
7642 .py_1()
7643 .gap_3()
7644 .elevation_2(cx)
7645 .hover(|style| style.bg(cx.theme().colors().element_hover))
7646 .id("accept-terms")
7647 .cursor_pointer()
7648 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7649 .on_click(cx.listener(|this, _event, window, cx| {
7650 cx.stop_propagation();
7651 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7652 window.dispatch_action(
7653 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7654 cx,
7655 );
7656 }))
7657 .child(
7658 h_flex()
7659 .flex_1()
7660 .gap_2()
7661 .child(Icon::new(IconName::ZedPredict))
7662 .child(Label::new("Accept Terms of Service"))
7663 .child(div().w_full())
7664 .child(
7665 Icon::new(IconName::ArrowUpRight)
7666 .color(Color::Muted)
7667 .size(IconSize::Small),
7668 )
7669 .into_any_element(),
7670 )
7671 .into_any(),
7672 );
7673 }
7674
7675 let is_refreshing = provider.provider.is_refreshing(cx);
7676
7677 fn pending_completion_container() -> Div {
7678 h_flex()
7679 .h_full()
7680 .flex_1()
7681 .gap_2()
7682 .child(Icon::new(IconName::ZedPredict))
7683 }
7684
7685 let completion = match &self.active_inline_completion {
7686 Some(prediction) => {
7687 if !self.has_visible_completions_menu() {
7688 const RADIUS: Pixels = px(6.);
7689 const BORDER_WIDTH: Pixels = px(1.);
7690
7691 return Some(
7692 h_flex()
7693 .elevation_2(cx)
7694 .border(BORDER_WIDTH)
7695 .border_color(cx.theme().colors().border)
7696 .when(accept_keystroke.is_none(), |el| {
7697 el.border_color(cx.theme().status().error)
7698 })
7699 .rounded(RADIUS)
7700 .rounded_tl(px(0.))
7701 .overflow_hidden()
7702 .child(div().px_1p5().child(match &prediction.completion {
7703 InlineCompletion::Move { target, snapshot } => {
7704 use text::ToPoint as _;
7705 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7706 {
7707 Icon::new(IconName::ZedPredictDown)
7708 } else {
7709 Icon::new(IconName::ZedPredictUp)
7710 }
7711 }
7712 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7713 }))
7714 .child(
7715 h_flex()
7716 .gap_1()
7717 .py_1()
7718 .px_2()
7719 .rounded_r(RADIUS - BORDER_WIDTH)
7720 .border_l_1()
7721 .border_color(cx.theme().colors().border)
7722 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7723 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7724 el.child(
7725 Label::new("Hold")
7726 .size(LabelSize::Small)
7727 .when(accept_keystroke.is_none(), |el| {
7728 el.strikethrough()
7729 })
7730 .line_height_style(LineHeightStyle::UiLabel),
7731 )
7732 })
7733 .id("edit_prediction_cursor_popover_keybind")
7734 .when(accept_keystroke.is_none(), |el| {
7735 let status_colors = cx.theme().status();
7736
7737 el.bg(status_colors.error_background)
7738 .border_color(status_colors.error.opacity(0.6))
7739 .child(Icon::new(IconName::Info).color(Color::Error))
7740 .cursor_default()
7741 .hoverable_tooltip(move |_window, cx| {
7742 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7743 .into()
7744 })
7745 })
7746 .when_some(
7747 accept_keystroke.as_ref(),
7748 |el, accept_keystroke| {
7749 el.child(h_flex().children(ui::render_modifiers(
7750 &accept_keystroke.modifiers,
7751 PlatformStyle::platform(),
7752 Some(Color::Default),
7753 Some(IconSize::XSmall.rems().into()),
7754 false,
7755 )))
7756 },
7757 ),
7758 )
7759 .into_any(),
7760 );
7761 }
7762
7763 self.render_edit_prediction_cursor_popover_preview(
7764 prediction,
7765 cursor_point,
7766 style,
7767 cx,
7768 )?
7769 }
7770
7771 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7772 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7773 stale_completion,
7774 cursor_point,
7775 style,
7776 cx,
7777 )?,
7778
7779 None => {
7780 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7781 }
7782 },
7783
7784 None => pending_completion_container().child(Label::new("No Prediction")),
7785 };
7786
7787 let completion = if is_refreshing {
7788 completion
7789 .with_animation(
7790 "loading-completion",
7791 Animation::new(Duration::from_secs(2))
7792 .repeat()
7793 .with_easing(pulsating_between(0.4, 0.8)),
7794 |label, delta| label.opacity(delta),
7795 )
7796 .into_any_element()
7797 } else {
7798 completion.into_any_element()
7799 };
7800
7801 let has_completion = self.active_inline_completion.is_some();
7802
7803 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7804 Some(
7805 h_flex()
7806 .min_w(min_width)
7807 .max_w(max_width)
7808 .flex_1()
7809 .elevation_2(cx)
7810 .border_color(cx.theme().colors().border)
7811 .child(
7812 div()
7813 .flex_1()
7814 .py_1()
7815 .px_2()
7816 .overflow_hidden()
7817 .child(completion),
7818 )
7819 .when_some(accept_keystroke, |el, accept_keystroke| {
7820 if !accept_keystroke.modifiers.modified() {
7821 return el;
7822 }
7823
7824 el.child(
7825 h_flex()
7826 .h_full()
7827 .border_l_1()
7828 .rounded_r_lg()
7829 .border_color(cx.theme().colors().border)
7830 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7831 .gap_1()
7832 .py_1()
7833 .px_2()
7834 .child(
7835 h_flex()
7836 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7837 .when(is_platform_style_mac, |parent| parent.gap_1())
7838 .child(h_flex().children(ui::render_modifiers(
7839 &accept_keystroke.modifiers,
7840 PlatformStyle::platform(),
7841 Some(if !has_completion {
7842 Color::Muted
7843 } else {
7844 Color::Default
7845 }),
7846 None,
7847 false,
7848 ))),
7849 )
7850 .child(Label::new("Preview").into_any_element())
7851 .opacity(if has_completion { 1.0 } else { 0.4 }),
7852 )
7853 })
7854 .into_any(),
7855 )
7856 }
7857
7858 fn render_edit_prediction_cursor_popover_preview(
7859 &self,
7860 completion: &InlineCompletionState,
7861 cursor_point: Point,
7862 style: &EditorStyle,
7863 cx: &mut Context<Editor>,
7864 ) -> Option<Div> {
7865 use text::ToPoint as _;
7866
7867 fn render_relative_row_jump(
7868 prefix: impl Into<String>,
7869 current_row: u32,
7870 target_row: u32,
7871 ) -> Div {
7872 let (row_diff, arrow) = if target_row < current_row {
7873 (current_row - target_row, IconName::ArrowUp)
7874 } else {
7875 (target_row - current_row, IconName::ArrowDown)
7876 };
7877
7878 h_flex()
7879 .child(
7880 Label::new(format!("{}{}", prefix.into(), row_diff))
7881 .color(Color::Muted)
7882 .size(LabelSize::Small),
7883 )
7884 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7885 }
7886
7887 match &completion.completion {
7888 InlineCompletion::Move {
7889 target, snapshot, ..
7890 } => Some(
7891 h_flex()
7892 .px_2()
7893 .gap_2()
7894 .flex_1()
7895 .child(
7896 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7897 Icon::new(IconName::ZedPredictDown)
7898 } else {
7899 Icon::new(IconName::ZedPredictUp)
7900 },
7901 )
7902 .child(Label::new("Jump to Edit")),
7903 ),
7904
7905 InlineCompletion::Edit {
7906 edits,
7907 edit_preview,
7908 snapshot,
7909 display_mode: _,
7910 } => {
7911 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7912
7913 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7914 &snapshot,
7915 &edits,
7916 edit_preview.as_ref()?,
7917 true,
7918 cx,
7919 )
7920 .first_line_preview();
7921
7922 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7923 .with_default_highlights(&style.text, highlighted_edits.highlights);
7924
7925 let preview = h_flex()
7926 .gap_1()
7927 .min_w_16()
7928 .child(styled_text)
7929 .when(has_more_lines, |parent| parent.child("…"));
7930
7931 let left = if first_edit_row != cursor_point.row {
7932 render_relative_row_jump("", cursor_point.row, first_edit_row)
7933 .into_any_element()
7934 } else {
7935 Icon::new(IconName::ZedPredict).into_any_element()
7936 };
7937
7938 Some(
7939 h_flex()
7940 .h_full()
7941 .flex_1()
7942 .gap_2()
7943 .pr_1()
7944 .overflow_x_hidden()
7945 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7946 .child(left)
7947 .child(preview),
7948 )
7949 }
7950 }
7951 }
7952
7953 fn render_context_menu(
7954 &self,
7955 style: &EditorStyle,
7956 max_height_in_lines: u32,
7957 window: &mut Window,
7958 cx: &mut Context<Editor>,
7959 ) -> Option<AnyElement> {
7960 let menu = self.context_menu.borrow();
7961 let menu = menu.as_ref()?;
7962 if !menu.visible() {
7963 return None;
7964 };
7965 Some(menu.render(style, max_height_in_lines, window, cx))
7966 }
7967
7968 fn render_context_menu_aside(
7969 &mut self,
7970 max_size: Size<Pixels>,
7971 window: &mut Window,
7972 cx: &mut Context<Editor>,
7973 ) -> Option<AnyElement> {
7974 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7975 if menu.visible() {
7976 menu.render_aside(self, max_size, window, cx)
7977 } else {
7978 None
7979 }
7980 })
7981 }
7982
7983 fn hide_context_menu(
7984 &mut self,
7985 window: &mut Window,
7986 cx: &mut Context<Self>,
7987 ) -> Option<CodeContextMenu> {
7988 cx.notify();
7989 self.completion_tasks.clear();
7990 let context_menu = self.context_menu.borrow_mut().take();
7991 self.stale_inline_completion_in_menu.take();
7992 self.update_visible_inline_completion(window, cx);
7993 context_menu
7994 }
7995
7996 fn show_snippet_choices(
7997 &mut self,
7998 choices: &Vec<String>,
7999 selection: Range<Anchor>,
8000 cx: &mut Context<Self>,
8001 ) {
8002 if selection.start.buffer_id.is_none() {
8003 return;
8004 }
8005 let buffer_id = selection.start.buffer_id.unwrap();
8006 let buffer = self.buffer().read(cx).buffer(buffer_id);
8007 let id = post_inc(&mut self.next_completion_id);
8008
8009 if let Some(buffer) = buffer {
8010 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8011 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
8012 ));
8013 }
8014 }
8015
8016 pub fn insert_snippet(
8017 &mut self,
8018 insertion_ranges: &[Range<usize>],
8019 snippet: Snippet,
8020 window: &mut Window,
8021 cx: &mut Context<Self>,
8022 ) -> Result<()> {
8023 struct Tabstop<T> {
8024 is_end_tabstop: bool,
8025 ranges: Vec<Range<T>>,
8026 choices: Option<Vec<String>>,
8027 }
8028
8029 let tabstops = self.buffer.update(cx, |buffer, cx| {
8030 let snippet_text: Arc<str> = snippet.text.clone().into();
8031 let edits = insertion_ranges
8032 .iter()
8033 .cloned()
8034 .map(|range| (range, snippet_text.clone()));
8035 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8036
8037 let snapshot = &*buffer.read(cx);
8038 let snippet = &snippet;
8039 snippet
8040 .tabstops
8041 .iter()
8042 .map(|tabstop| {
8043 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8044 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8045 });
8046 let mut tabstop_ranges = tabstop
8047 .ranges
8048 .iter()
8049 .flat_map(|tabstop_range| {
8050 let mut delta = 0_isize;
8051 insertion_ranges.iter().map(move |insertion_range| {
8052 let insertion_start = insertion_range.start as isize + delta;
8053 delta +=
8054 snippet.text.len() as isize - insertion_range.len() as isize;
8055
8056 let start = ((insertion_start + tabstop_range.start) as usize)
8057 .min(snapshot.len());
8058 let end = ((insertion_start + tabstop_range.end) as usize)
8059 .min(snapshot.len());
8060 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8061 })
8062 })
8063 .collect::<Vec<_>>();
8064 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8065
8066 Tabstop {
8067 is_end_tabstop,
8068 ranges: tabstop_ranges,
8069 choices: tabstop.choices.clone(),
8070 }
8071 })
8072 .collect::<Vec<_>>()
8073 });
8074 if let Some(tabstop) = tabstops.first() {
8075 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8076 s.select_ranges(tabstop.ranges.iter().cloned());
8077 });
8078
8079 if let Some(choices) = &tabstop.choices {
8080 if let Some(selection) = tabstop.ranges.first() {
8081 self.show_snippet_choices(choices, selection.clone(), cx)
8082 }
8083 }
8084
8085 // If we're already at the last tabstop and it's at the end of the snippet,
8086 // we're done, we don't need to keep the state around.
8087 if !tabstop.is_end_tabstop {
8088 let choices = tabstops
8089 .iter()
8090 .map(|tabstop| tabstop.choices.clone())
8091 .collect();
8092
8093 let ranges = tabstops
8094 .into_iter()
8095 .map(|tabstop| tabstop.ranges)
8096 .collect::<Vec<_>>();
8097
8098 self.snippet_stack.push(SnippetState {
8099 active_index: 0,
8100 ranges,
8101 choices,
8102 });
8103 }
8104
8105 // Check whether the just-entered snippet ends with an auto-closable bracket.
8106 if self.autoclose_regions.is_empty() {
8107 let snapshot = self.buffer.read(cx).snapshot(cx);
8108 for selection in &mut self.selections.all::<Point>(cx) {
8109 let selection_head = selection.head();
8110 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8111 continue;
8112 };
8113
8114 let mut bracket_pair = None;
8115 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8116 let prev_chars = snapshot
8117 .reversed_chars_at(selection_head)
8118 .collect::<String>();
8119 for (pair, enabled) in scope.brackets() {
8120 if enabled
8121 && pair.close
8122 && prev_chars.starts_with(pair.start.as_str())
8123 && next_chars.starts_with(pair.end.as_str())
8124 {
8125 bracket_pair = Some(pair.clone());
8126 break;
8127 }
8128 }
8129 if let Some(pair) = bracket_pair {
8130 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8131 let autoclose_enabled =
8132 self.use_autoclose && snapshot_settings.use_autoclose;
8133 if autoclose_enabled {
8134 let start = snapshot.anchor_after(selection_head);
8135 let end = snapshot.anchor_after(selection_head);
8136 self.autoclose_regions.push(AutocloseRegion {
8137 selection_id: selection.id,
8138 range: start..end,
8139 pair,
8140 });
8141 }
8142 }
8143 }
8144 }
8145 }
8146 Ok(())
8147 }
8148
8149 pub fn move_to_next_snippet_tabstop(
8150 &mut self,
8151 window: &mut Window,
8152 cx: &mut Context<Self>,
8153 ) -> bool {
8154 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8155 }
8156
8157 pub fn move_to_prev_snippet_tabstop(
8158 &mut self,
8159 window: &mut Window,
8160 cx: &mut Context<Self>,
8161 ) -> bool {
8162 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8163 }
8164
8165 pub fn move_to_snippet_tabstop(
8166 &mut self,
8167 bias: Bias,
8168 window: &mut Window,
8169 cx: &mut Context<Self>,
8170 ) -> bool {
8171 if let Some(mut snippet) = self.snippet_stack.pop() {
8172 match bias {
8173 Bias::Left => {
8174 if snippet.active_index > 0 {
8175 snippet.active_index -= 1;
8176 } else {
8177 self.snippet_stack.push(snippet);
8178 return false;
8179 }
8180 }
8181 Bias::Right => {
8182 if snippet.active_index + 1 < snippet.ranges.len() {
8183 snippet.active_index += 1;
8184 } else {
8185 self.snippet_stack.push(snippet);
8186 return false;
8187 }
8188 }
8189 }
8190 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8191 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8192 s.select_anchor_ranges(current_ranges.iter().cloned())
8193 });
8194
8195 if let Some(choices) = &snippet.choices[snippet.active_index] {
8196 if let Some(selection) = current_ranges.first() {
8197 self.show_snippet_choices(&choices, selection.clone(), cx);
8198 }
8199 }
8200
8201 // If snippet state is not at the last tabstop, push it back on the stack
8202 if snippet.active_index + 1 < snippet.ranges.len() {
8203 self.snippet_stack.push(snippet);
8204 }
8205 return true;
8206 }
8207 }
8208
8209 false
8210 }
8211
8212 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8213 self.transact(window, cx, |this, window, cx| {
8214 this.select_all(&SelectAll, window, cx);
8215 this.insert("", window, cx);
8216 });
8217 }
8218
8219 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8220 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8221 self.transact(window, cx, |this, window, cx| {
8222 this.select_autoclose_pair(window, cx);
8223 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8224 if !this.linked_edit_ranges.is_empty() {
8225 let selections = this.selections.all::<MultiBufferPoint>(cx);
8226 let snapshot = this.buffer.read(cx).snapshot(cx);
8227
8228 for selection in selections.iter() {
8229 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8230 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8231 if selection_start.buffer_id != selection_end.buffer_id {
8232 continue;
8233 }
8234 if let Some(ranges) =
8235 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8236 {
8237 for (buffer, entries) in ranges {
8238 linked_ranges.entry(buffer).or_default().extend(entries);
8239 }
8240 }
8241 }
8242 }
8243
8244 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8245 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8246 for selection in &mut selections {
8247 if selection.is_empty() {
8248 let old_head = selection.head();
8249 let mut new_head =
8250 movement::left(&display_map, old_head.to_display_point(&display_map))
8251 .to_point(&display_map);
8252 if let Some((buffer, line_buffer_range)) = display_map
8253 .buffer_snapshot
8254 .buffer_line_for_row(MultiBufferRow(old_head.row))
8255 {
8256 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8257 let indent_len = match indent_size.kind {
8258 IndentKind::Space => {
8259 buffer.settings_at(line_buffer_range.start, cx).tab_size
8260 }
8261 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8262 };
8263 if old_head.column <= indent_size.len && old_head.column > 0 {
8264 let indent_len = indent_len.get();
8265 new_head = cmp::min(
8266 new_head,
8267 MultiBufferPoint::new(
8268 old_head.row,
8269 ((old_head.column - 1) / indent_len) * indent_len,
8270 ),
8271 );
8272 }
8273 }
8274
8275 selection.set_head(new_head, SelectionGoal::None);
8276 }
8277 }
8278
8279 this.signature_help_state.set_backspace_pressed(true);
8280 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8281 s.select(selections)
8282 });
8283 this.insert("", window, cx);
8284 let empty_str: Arc<str> = Arc::from("");
8285 for (buffer, edits) in linked_ranges {
8286 let snapshot = buffer.read(cx).snapshot();
8287 use text::ToPoint as TP;
8288
8289 let edits = edits
8290 .into_iter()
8291 .map(|range| {
8292 let end_point = TP::to_point(&range.end, &snapshot);
8293 let mut start_point = TP::to_point(&range.start, &snapshot);
8294
8295 if end_point == start_point {
8296 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8297 .saturating_sub(1);
8298 start_point =
8299 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8300 };
8301
8302 (start_point..end_point, empty_str.clone())
8303 })
8304 .sorted_by_key(|(range, _)| range.start)
8305 .collect::<Vec<_>>();
8306 buffer.update(cx, |this, cx| {
8307 this.edit(edits, None, cx);
8308 })
8309 }
8310 this.refresh_inline_completion(true, false, window, cx);
8311 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8312 });
8313 }
8314
8315 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8316 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8317 self.transact(window, cx, |this, window, cx| {
8318 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8319 s.move_with(|map, selection| {
8320 if selection.is_empty() {
8321 let cursor = movement::right(map, selection.head());
8322 selection.end = cursor;
8323 selection.reversed = true;
8324 selection.goal = SelectionGoal::None;
8325 }
8326 })
8327 });
8328 this.insert("", window, cx);
8329 this.refresh_inline_completion(true, false, window, cx);
8330 });
8331 }
8332
8333 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8334 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8335 if self.move_to_prev_snippet_tabstop(window, cx) {
8336 return;
8337 }
8338 self.outdent(&Outdent, window, cx);
8339 }
8340
8341 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8342 if self.move_to_next_snippet_tabstop(window, cx) {
8343 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8344 return;
8345 }
8346 if self.read_only(cx) {
8347 return;
8348 }
8349 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8350 let mut selections = self.selections.all_adjusted(cx);
8351 let buffer = self.buffer.read(cx);
8352 let snapshot = buffer.snapshot(cx);
8353 let rows_iter = selections.iter().map(|s| s.head().row);
8354 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8355
8356 let mut edits = Vec::new();
8357 let mut prev_edited_row = 0;
8358 let mut row_delta = 0;
8359 for selection in &mut selections {
8360 if selection.start.row != prev_edited_row {
8361 row_delta = 0;
8362 }
8363 prev_edited_row = selection.end.row;
8364
8365 // If the selection is non-empty, then increase the indentation of the selected lines.
8366 if !selection.is_empty() {
8367 row_delta =
8368 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8369 continue;
8370 }
8371
8372 // If the selection is empty and the cursor is in the leading whitespace before the
8373 // suggested indentation, then auto-indent the line.
8374 let cursor = selection.head();
8375 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8376 if let Some(suggested_indent) =
8377 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8378 {
8379 if cursor.column < suggested_indent.len
8380 && cursor.column <= current_indent.len
8381 && current_indent.len <= suggested_indent.len
8382 {
8383 selection.start = Point::new(cursor.row, suggested_indent.len);
8384 selection.end = selection.start;
8385 if row_delta == 0 {
8386 edits.extend(Buffer::edit_for_indent_size_adjustment(
8387 cursor.row,
8388 current_indent,
8389 suggested_indent,
8390 ));
8391 row_delta = suggested_indent.len - current_indent.len;
8392 }
8393 continue;
8394 }
8395 }
8396
8397 // Otherwise, insert a hard or soft tab.
8398 let settings = buffer.language_settings_at(cursor, cx);
8399 let tab_size = if settings.hard_tabs {
8400 IndentSize::tab()
8401 } else {
8402 let tab_size = settings.tab_size.get();
8403 let indent_remainder = snapshot
8404 .text_for_range(Point::new(cursor.row, 0)..cursor)
8405 .flat_map(str::chars)
8406 .fold(row_delta % tab_size, |counter: u32, c| {
8407 if c == '\t' {
8408 0
8409 } else {
8410 (counter + 1) % tab_size
8411 }
8412 });
8413
8414 let chars_to_next_tab_stop = tab_size - indent_remainder;
8415 IndentSize::spaces(chars_to_next_tab_stop)
8416 };
8417 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8418 selection.end = selection.start;
8419 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8420 row_delta += tab_size.len;
8421 }
8422
8423 self.transact(window, cx, |this, window, cx| {
8424 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8425 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8426 s.select(selections)
8427 });
8428 this.refresh_inline_completion(true, false, window, cx);
8429 });
8430 }
8431
8432 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8433 if self.read_only(cx) {
8434 return;
8435 }
8436 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8437 let mut selections = self.selections.all::<Point>(cx);
8438 let mut prev_edited_row = 0;
8439 let mut row_delta = 0;
8440 let mut edits = Vec::new();
8441 let buffer = self.buffer.read(cx);
8442 let snapshot = buffer.snapshot(cx);
8443 for selection in &mut selections {
8444 if selection.start.row != prev_edited_row {
8445 row_delta = 0;
8446 }
8447 prev_edited_row = selection.end.row;
8448
8449 row_delta =
8450 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8451 }
8452
8453 self.transact(window, cx, |this, window, cx| {
8454 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8455 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8456 s.select(selections)
8457 });
8458 });
8459 }
8460
8461 fn indent_selection(
8462 buffer: &MultiBuffer,
8463 snapshot: &MultiBufferSnapshot,
8464 selection: &mut Selection<Point>,
8465 edits: &mut Vec<(Range<Point>, String)>,
8466 delta_for_start_row: u32,
8467 cx: &App,
8468 ) -> u32 {
8469 let settings = buffer.language_settings_at(selection.start, cx);
8470 let tab_size = settings.tab_size.get();
8471 let indent_kind = if settings.hard_tabs {
8472 IndentKind::Tab
8473 } else {
8474 IndentKind::Space
8475 };
8476 let mut start_row = selection.start.row;
8477 let mut end_row = selection.end.row + 1;
8478
8479 // If a selection ends at the beginning of a line, don't indent
8480 // that last line.
8481 if selection.end.column == 0 && selection.end.row > selection.start.row {
8482 end_row -= 1;
8483 }
8484
8485 // Avoid re-indenting a row that has already been indented by a
8486 // previous selection, but still update this selection's column
8487 // to reflect that indentation.
8488 if delta_for_start_row > 0 {
8489 start_row += 1;
8490 selection.start.column += delta_for_start_row;
8491 if selection.end.row == selection.start.row {
8492 selection.end.column += delta_for_start_row;
8493 }
8494 }
8495
8496 let mut delta_for_end_row = 0;
8497 let has_multiple_rows = start_row + 1 != end_row;
8498 for row in start_row..end_row {
8499 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8500 let indent_delta = match (current_indent.kind, indent_kind) {
8501 (IndentKind::Space, IndentKind::Space) => {
8502 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8503 IndentSize::spaces(columns_to_next_tab_stop)
8504 }
8505 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8506 (_, IndentKind::Tab) => IndentSize::tab(),
8507 };
8508
8509 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8510 0
8511 } else {
8512 selection.start.column
8513 };
8514 let row_start = Point::new(row, start);
8515 edits.push((
8516 row_start..row_start,
8517 indent_delta.chars().collect::<String>(),
8518 ));
8519
8520 // Update this selection's endpoints to reflect the indentation.
8521 if row == selection.start.row {
8522 selection.start.column += indent_delta.len;
8523 }
8524 if row == selection.end.row {
8525 selection.end.column += indent_delta.len;
8526 delta_for_end_row = indent_delta.len;
8527 }
8528 }
8529
8530 if selection.start.row == selection.end.row {
8531 delta_for_start_row + delta_for_end_row
8532 } else {
8533 delta_for_end_row
8534 }
8535 }
8536
8537 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8538 if self.read_only(cx) {
8539 return;
8540 }
8541 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8542 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8543 let selections = self.selections.all::<Point>(cx);
8544 let mut deletion_ranges = Vec::new();
8545 let mut last_outdent = None;
8546 {
8547 let buffer = self.buffer.read(cx);
8548 let snapshot = buffer.snapshot(cx);
8549 for selection in &selections {
8550 let settings = buffer.language_settings_at(selection.start, cx);
8551 let tab_size = settings.tab_size.get();
8552 let mut rows = selection.spanned_rows(false, &display_map);
8553
8554 // Avoid re-outdenting a row that has already been outdented by a
8555 // previous selection.
8556 if let Some(last_row) = last_outdent {
8557 if last_row == rows.start {
8558 rows.start = rows.start.next_row();
8559 }
8560 }
8561 let has_multiple_rows = rows.len() > 1;
8562 for row in rows.iter_rows() {
8563 let indent_size = snapshot.indent_size_for_line(row);
8564 if indent_size.len > 0 {
8565 let deletion_len = match indent_size.kind {
8566 IndentKind::Space => {
8567 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8568 if columns_to_prev_tab_stop == 0 {
8569 tab_size
8570 } else {
8571 columns_to_prev_tab_stop
8572 }
8573 }
8574 IndentKind::Tab => 1,
8575 };
8576 let start = if has_multiple_rows
8577 || deletion_len > selection.start.column
8578 || indent_size.len < selection.start.column
8579 {
8580 0
8581 } else {
8582 selection.start.column - deletion_len
8583 };
8584 deletion_ranges.push(
8585 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8586 );
8587 last_outdent = Some(row);
8588 }
8589 }
8590 }
8591 }
8592
8593 self.transact(window, cx, |this, window, cx| {
8594 this.buffer.update(cx, |buffer, cx| {
8595 let empty_str: Arc<str> = Arc::default();
8596 buffer.edit(
8597 deletion_ranges
8598 .into_iter()
8599 .map(|range| (range, empty_str.clone())),
8600 None,
8601 cx,
8602 );
8603 });
8604 let selections = this.selections.all::<usize>(cx);
8605 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8606 s.select(selections)
8607 });
8608 });
8609 }
8610
8611 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8612 if self.read_only(cx) {
8613 return;
8614 }
8615 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8616 let selections = self
8617 .selections
8618 .all::<usize>(cx)
8619 .into_iter()
8620 .map(|s| s.range());
8621
8622 self.transact(window, cx, |this, window, cx| {
8623 this.buffer.update(cx, |buffer, cx| {
8624 buffer.autoindent_ranges(selections, cx);
8625 });
8626 let selections = this.selections.all::<usize>(cx);
8627 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8628 s.select(selections)
8629 });
8630 });
8631 }
8632
8633 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8634 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8635 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8636 let selections = self.selections.all::<Point>(cx);
8637
8638 let mut new_cursors = Vec::new();
8639 let mut edit_ranges = Vec::new();
8640 let mut selections = selections.iter().peekable();
8641 while let Some(selection) = selections.next() {
8642 let mut rows = selection.spanned_rows(false, &display_map);
8643 let goal_display_column = selection.head().to_display_point(&display_map).column();
8644
8645 // Accumulate contiguous regions of rows that we want to delete.
8646 while let Some(next_selection) = selections.peek() {
8647 let next_rows = next_selection.spanned_rows(false, &display_map);
8648 if next_rows.start <= rows.end {
8649 rows.end = next_rows.end;
8650 selections.next().unwrap();
8651 } else {
8652 break;
8653 }
8654 }
8655
8656 let buffer = &display_map.buffer_snapshot;
8657 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8658 let edit_end;
8659 let cursor_buffer_row;
8660 if buffer.max_point().row >= rows.end.0 {
8661 // If there's a line after the range, delete the \n from the end of the row range
8662 // and position the cursor on the next line.
8663 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8664 cursor_buffer_row = rows.end;
8665 } else {
8666 // If there isn't a line after the range, delete the \n from the line before the
8667 // start of the row range and position the cursor there.
8668 edit_start = edit_start.saturating_sub(1);
8669 edit_end = buffer.len();
8670 cursor_buffer_row = rows.start.previous_row();
8671 }
8672
8673 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8674 *cursor.column_mut() =
8675 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8676
8677 new_cursors.push((
8678 selection.id,
8679 buffer.anchor_after(cursor.to_point(&display_map)),
8680 ));
8681 edit_ranges.push(edit_start..edit_end);
8682 }
8683
8684 self.transact(window, cx, |this, window, cx| {
8685 let buffer = this.buffer.update(cx, |buffer, cx| {
8686 let empty_str: Arc<str> = Arc::default();
8687 buffer.edit(
8688 edit_ranges
8689 .into_iter()
8690 .map(|range| (range, empty_str.clone())),
8691 None,
8692 cx,
8693 );
8694 buffer.snapshot(cx)
8695 });
8696 let new_selections = new_cursors
8697 .into_iter()
8698 .map(|(id, cursor)| {
8699 let cursor = cursor.to_point(&buffer);
8700 Selection {
8701 id,
8702 start: cursor,
8703 end: cursor,
8704 reversed: false,
8705 goal: SelectionGoal::None,
8706 }
8707 })
8708 .collect();
8709
8710 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8711 s.select(new_selections);
8712 });
8713 });
8714 }
8715
8716 pub fn join_lines_impl(
8717 &mut self,
8718 insert_whitespace: bool,
8719 window: &mut Window,
8720 cx: &mut Context<Self>,
8721 ) {
8722 if self.read_only(cx) {
8723 return;
8724 }
8725 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8726 for selection in self.selections.all::<Point>(cx) {
8727 let start = MultiBufferRow(selection.start.row);
8728 // Treat single line selections as if they include the next line. Otherwise this action
8729 // would do nothing for single line selections individual cursors.
8730 let end = if selection.start.row == selection.end.row {
8731 MultiBufferRow(selection.start.row + 1)
8732 } else {
8733 MultiBufferRow(selection.end.row)
8734 };
8735
8736 if let Some(last_row_range) = row_ranges.last_mut() {
8737 if start <= last_row_range.end {
8738 last_row_range.end = end;
8739 continue;
8740 }
8741 }
8742 row_ranges.push(start..end);
8743 }
8744
8745 let snapshot = self.buffer.read(cx).snapshot(cx);
8746 let mut cursor_positions = Vec::new();
8747 for row_range in &row_ranges {
8748 let anchor = snapshot.anchor_before(Point::new(
8749 row_range.end.previous_row().0,
8750 snapshot.line_len(row_range.end.previous_row()),
8751 ));
8752 cursor_positions.push(anchor..anchor);
8753 }
8754
8755 self.transact(window, cx, |this, window, cx| {
8756 for row_range in row_ranges.into_iter().rev() {
8757 for row in row_range.iter_rows().rev() {
8758 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8759 let next_line_row = row.next_row();
8760 let indent = snapshot.indent_size_for_line(next_line_row);
8761 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8762
8763 let replace =
8764 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8765 " "
8766 } else {
8767 ""
8768 };
8769
8770 this.buffer.update(cx, |buffer, cx| {
8771 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8772 });
8773 }
8774 }
8775
8776 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8777 s.select_anchor_ranges(cursor_positions)
8778 });
8779 });
8780 }
8781
8782 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8783 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8784 self.join_lines_impl(true, window, cx);
8785 }
8786
8787 pub fn sort_lines_case_sensitive(
8788 &mut self,
8789 _: &SortLinesCaseSensitive,
8790 window: &mut Window,
8791 cx: &mut Context<Self>,
8792 ) {
8793 self.manipulate_lines(window, cx, |lines| lines.sort())
8794 }
8795
8796 pub fn sort_lines_case_insensitive(
8797 &mut self,
8798 _: &SortLinesCaseInsensitive,
8799 window: &mut Window,
8800 cx: &mut Context<Self>,
8801 ) {
8802 self.manipulate_lines(window, cx, |lines| {
8803 lines.sort_by_key(|line| line.to_lowercase())
8804 })
8805 }
8806
8807 pub fn unique_lines_case_insensitive(
8808 &mut self,
8809 _: &UniqueLinesCaseInsensitive,
8810 window: &mut Window,
8811 cx: &mut Context<Self>,
8812 ) {
8813 self.manipulate_lines(window, cx, |lines| {
8814 let mut seen = HashSet::default();
8815 lines.retain(|line| seen.insert(line.to_lowercase()));
8816 })
8817 }
8818
8819 pub fn unique_lines_case_sensitive(
8820 &mut self,
8821 _: &UniqueLinesCaseSensitive,
8822 window: &mut Window,
8823 cx: &mut Context<Self>,
8824 ) {
8825 self.manipulate_lines(window, cx, |lines| {
8826 let mut seen = HashSet::default();
8827 lines.retain(|line| seen.insert(*line));
8828 })
8829 }
8830
8831 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8832 let Some(project) = self.project.clone() else {
8833 return;
8834 };
8835 self.reload(project, window, cx)
8836 .detach_and_notify_err(window, cx);
8837 }
8838
8839 pub fn restore_file(
8840 &mut self,
8841 _: &::git::RestoreFile,
8842 window: &mut Window,
8843 cx: &mut Context<Self>,
8844 ) {
8845 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8846 let mut buffer_ids = HashSet::default();
8847 let snapshot = self.buffer().read(cx).snapshot(cx);
8848 for selection in self.selections.all::<usize>(cx) {
8849 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8850 }
8851
8852 let buffer = self.buffer().read(cx);
8853 let ranges = buffer_ids
8854 .into_iter()
8855 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8856 .collect::<Vec<_>>();
8857
8858 self.restore_hunks_in_ranges(ranges, window, cx);
8859 }
8860
8861 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8862 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8863 let selections = self
8864 .selections
8865 .all(cx)
8866 .into_iter()
8867 .map(|s| s.range())
8868 .collect();
8869 self.restore_hunks_in_ranges(selections, window, cx);
8870 }
8871
8872 pub fn restore_hunks_in_ranges(
8873 &mut self,
8874 ranges: Vec<Range<Point>>,
8875 window: &mut Window,
8876 cx: &mut Context<Editor>,
8877 ) {
8878 let mut revert_changes = HashMap::default();
8879 let chunk_by = self
8880 .snapshot(window, cx)
8881 .hunks_for_ranges(ranges)
8882 .into_iter()
8883 .chunk_by(|hunk| hunk.buffer_id);
8884 for (buffer_id, hunks) in &chunk_by {
8885 let hunks = hunks.collect::<Vec<_>>();
8886 for hunk in &hunks {
8887 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8888 }
8889 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8890 }
8891 drop(chunk_by);
8892 if !revert_changes.is_empty() {
8893 self.transact(window, cx, |editor, window, cx| {
8894 editor.restore(revert_changes, window, cx);
8895 });
8896 }
8897 }
8898
8899 pub fn open_active_item_in_terminal(
8900 &mut self,
8901 _: &OpenInTerminal,
8902 window: &mut Window,
8903 cx: &mut Context<Self>,
8904 ) {
8905 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8906 let project_path = buffer.read(cx).project_path(cx)?;
8907 let project = self.project.as_ref()?.read(cx);
8908 let entry = project.entry_for_path(&project_path, cx)?;
8909 let parent = match &entry.canonical_path {
8910 Some(canonical_path) => canonical_path.to_path_buf(),
8911 None => project.absolute_path(&project_path, cx)?,
8912 }
8913 .parent()?
8914 .to_path_buf();
8915 Some(parent)
8916 }) {
8917 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8918 }
8919 }
8920
8921 fn set_breakpoint_context_menu(
8922 &mut self,
8923 display_row: DisplayRow,
8924 position: Option<Anchor>,
8925 clicked_point: gpui::Point<Pixels>,
8926 window: &mut Window,
8927 cx: &mut Context<Self>,
8928 ) {
8929 if !cx.has_flag::<Debugger>() {
8930 return;
8931 }
8932 let source = self
8933 .buffer
8934 .read(cx)
8935 .snapshot(cx)
8936 .anchor_before(Point::new(display_row.0, 0u32));
8937
8938 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8939
8940 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8941 self,
8942 source,
8943 clicked_point,
8944 None,
8945 context_menu,
8946 window,
8947 cx,
8948 );
8949 }
8950
8951 fn add_edit_breakpoint_block(
8952 &mut self,
8953 anchor: Anchor,
8954 breakpoint: &Breakpoint,
8955 edit_action: BreakpointPromptEditAction,
8956 window: &mut Window,
8957 cx: &mut Context<Self>,
8958 ) {
8959 let weak_editor = cx.weak_entity();
8960 let bp_prompt = cx.new(|cx| {
8961 BreakpointPromptEditor::new(
8962 weak_editor,
8963 anchor,
8964 breakpoint.clone(),
8965 edit_action,
8966 window,
8967 cx,
8968 )
8969 });
8970
8971 let height = bp_prompt.update(cx, |this, cx| {
8972 this.prompt
8973 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8974 });
8975 let cloned_prompt = bp_prompt.clone();
8976 let blocks = vec![BlockProperties {
8977 style: BlockStyle::Sticky,
8978 placement: BlockPlacement::Above(anchor),
8979 height: Some(height),
8980 render: Arc::new(move |cx| {
8981 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8982 cloned_prompt.clone().into_any_element()
8983 }),
8984 priority: 0,
8985 }];
8986
8987 let focus_handle = bp_prompt.focus_handle(cx);
8988 window.focus(&focus_handle);
8989
8990 let block_ids = self.insert_blocks(blocks, None, cx);
8991 bp_prompt.update(cx, |prompt, _| {
8992 prompt.add_block_ids(block_ids);
8993 });
8994 }
8995
8996 pub(crate) fn breakpoint_at_row(
8997 &self,
8998 row: u32,
8999 window: &mut Window,
9000 cx: &mut Context<Self>,
9001 ) -> Option<(Anchor, Breakpoint)> {
9002 let snapshot = self.snapshot(window, cx);
9003 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9004
9005 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9006 }
9007
9008 pub(crate) fn breakpoint_at_anchor(
9009 &self,
9010 breakpoint_position: Anchor,
9011 snapshot: &EditorSnapshot,
9012 cx: &mut Context<Self>,
9013 ) -> Option<(Anchor, Breakpoint)> {
9014 let project = self.project.clone()?;
9015
9016 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9017 snapshot
9018 .buffer_snapshot
9019 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9020 })?;
9021
9022 let enclosing_excerpt = breakpoint_position.excerpt_id;
9023 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9024 let buffer_snapshot = buffer.read(cx).snapshot();
9025
9026 let row = buffer_snapshot
9027 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9028 .row;
9029
9030 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9031 let anchor_end = snapshot
9032 .buffer_snapshot
9033 .anchor_after(Point::new(row, line_len));
9034
9035 let bp = self
9036 .breakpoint_store
9037 .as_ref()?
9038 .read_with(cx, |breakpoint_store, cx| {
9039 breakpoint_store
9040 .breakpoints(
9041 &buffer,
9042 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9043 &buffer_snapshot,
9044 cx,
9045 )
9046 .next()
9047 .and_then(|(anchor, bp)| {
9048 let breakpoint_row = buffer_snapshot
9049 .summary_for_anchor::<text::PointUtf16>(anchor)
9050 .row;
9051
9052 if breakpoint_row == row {
9053 snapshot
9054 .buffer_snapshot
9055 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9056 .map(|anchor| (anchor, bp.clone()))
9057 } else {
9058 None
9059 }
9060 })
9061 });
9062 bp
9063 }
9064
9065 pub fn edit_log_breakpoint(
9066 &mut self,
9067 _: &EditLogBreakpoint,
9068 window: &mut Window,
9069 cx: &mut Context<Self>,
9070 ) {
9071 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9072 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9073 message: None,
9074 state: BreakpointState::Enabled,
9075 condition: None,
9076 hit_condition: None,
9077 });
9078
9079 self.add_edit_breakpoint_block(
9080 anchor,
9081 &breakpoint,
9082 BreakpointPromptEditAction::Log,
9083 window,
9084 cx,
9085 );
9086 }
9087 }
9088
9089 fn breakpoints_at_cursors(
9090 &self,
9091 window: &mut Window,
9092 cx: &mut Context<Self>,
9093 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9094 let snapshot = self.snapshot(window, cx);
9095 let cursors = self
9096 .selections
9097 .disjoint_anchors()
9098 .into_iter()
9099 .map(|selection| {
9100 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9101
9102 let breakpoint_position = self
9103 .breakpoint_at_row(cursor_position.row, window, cx)
9104 .map(|bp| bp.0)
9105 .unwrap_or_else(|| {
9106 snapshot
9107 .display_snapshot
9108 .buffer_snapshot
9109 .anchor_after(Point::new(cursor_position.row, 0))
9110 });
9111
9112 let breakpoint = self
9113 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9114 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9115
9116 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9117 })
9118 // There might be multiple cursors on the same line; all of them should have the same anchors though as their breakpoints positions, which makes it possible to sort and dedup the list.
9119 .collect::<HashMap<Anchor, _>>();
9120
9121 cursors.into_iter().collect()
9122 }
9123
9124 pub fn enable_breakpoint(
9125 &mut self,
9126 _: &crate::actions::EnableBreakpoint,
9127 window: &mut Window,
9128 cx: &mut Context<Self>,
9129 ) {
9130 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9131 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9132 continue;
9133 };
9134 self.edit_breakpoint_at_anchor(
9135 anchor,
9136 breakpoint,
9137 BreakpointEditAction::InvertState,
9138 cx,
9139 );
9140 }
9141 }
9142
9143 pub fn disable_breakpoint(
9144 &mut self,
9145 _: &crate::actions::DisableBreakpoint,
9146 window: &mut Window,
9147 cx: &mut Context<Self>,
9148 ) {
9149 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9150 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9151 continue;
9152 };
9153 self.edit_breakpoint_at_anchor(
9154 anchor,
9155 breakpoint,
9156 BreakpointEditAction::InvertState,
9157 cx,
9158 );
9159 }
9160 }
9161
9162 pub fn toggle_breakpoint(
9163 &mut self,
9164 _: &crate::actions::ToggleBreakpoint,
9165 window: &mut Window,
9166 cx: &mut Context<Self>,
9167 ) {
9168 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9169 if let Some(breakpoint) = breakpoint {
9170 self.edit_breakpoint_at_anchor(
9171 anchor,
9172 breakpoint,
9173 BreakpointEditAction::Toggle,
9174 cx,
9175 );
9176 } else {
9177 self.edit_breakpoint_at_anchor(
9178 anchor,
9179 Breakpoint::new_standard(),
9180 BreakpointEditAction::Toggle,
9181 cx,
9182 );
9183 }
9184 }
9185 }
9186
9187 pub fn edit_breakpoint_at_anchor(
9188 &mut self,
9189 breakpoint_position: Anchor,
9190 breakpoint: Breakpoint,
9191 edit_action: BreakpointEditAction,
9192 cx: &mut Context<Self>,
9193 ) {
9194 let Some(breakpoint_store) = &self.breakpoint_store else {
9195 return;
9196 };
9197
9198 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9199 if breakpoint_position == Anchor::min() {
9200 self.buffer()
9201 .read(cx)
9202 .excerpt_buffer_ids()
9203 .into_iter()
9204 .next()
9205 } else {
9206 None
9207 }
9208 }) else {
9209 return;
9210 };
9211
9212 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9213 return;
9214 };
9215
9216 breakpoint_store.update(cx, |breakpoint_store, cx| {
9217 breakpoint_store.toggle_breakpoint(
9218 buffer,
9219 (breakpoint_position.text_anchor, breakpoint),
9220 edit_action,
9221 cx,
9222 );
9223 });
9224
9225 cx.notify();
9226 }
9227
9228 #[cfg(any(test, feature = "test-support"))]
9229 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9230 self.breakpoint_store.clone()
9231 }
9232
9233 pub fn prepare_restore_change(
9234 &self,
9235 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9236 hunk: &MultiBufferDiffHunk,
9237 cx: &mut App,
9238 ) -> Option<()> {
9239 if hunk.is_created_file() {
9240 return None;
9241 }
9242 let buffer = self.buffer.read(cx);
9243 let diff = buffer.diff_for(hunk.buffer_id)?;
9244 let buffer = buffer.buffer(hunk.buffer_id)?;
9245 let buffer = buffer.read(cx);
9246 let original_text = diff
9247 .read(cx)
9248 .base_text()
9249 .as_rope()
9250 .slice(hunk.diff_base_byte_range.clone());
9251 let buffer_snapshot = buffer.snapshot();
9252 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9253 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9254 probe
9255 .0
9256 .start
9257 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9258 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9259 }) {
9260 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9261 Some(())
9262 } else {
9263 None
9264 }
9265 }
9266
9267 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9268 self.manipulate_lines(window, cx, |lines| lines.reverse())
9269 }
9270
9271 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9272 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9273 }
9274
9275 fn manipulate_lines<Fn>(
9276 &mut self,
9277 window: &mut Window,
9278 cx: &mut Context<Self>,
9279 mut callback: Fn,
9280 ) where
9281 Fn: FnMut(&mut Vec<&str>),
9282 {
9283 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9284
9285 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9286 let buffer = self.buffer.read(cx).snapshot(cx);
9287
9288 let mut edits = Vec::new();
9289
9290 let selections = self.selections.all::<Point>(cx);
9291 let mut selections = selections.iter().peekable();
9292 let mut contiguous_row_selections = Vec::new();
9293 let mut new_selections = Vec::new();
9294 let mut added_lines = 0;
9295 let mut removed_lines = 0;
9296
9297 while let Some(selection) = selections.next() {
9298 let (start_row, end_row) = consume_contiguous_rows(
9299 &mut contiguous_row_selections,
9300 selection,
9301 &display_map,
9302 &mut selections,
9303 );
9304
9305 let start_point = Point::new(start_row.0, 0);
9306 let end_point = Point::new(
9307 end_row.previous_row().0,
9308 buffer.line_len(end_row.previous_row()),
9309 );
9310 let text = buffer
9311 .text_for_range(start_point..end_point)
9312 .collect::<String>();
9313
9314 let mut lines = text.split('\n').collect_vec();
9315
9316 let lines_before = lines.len();
9317 callback(&mut lines);
9318 let lines_after = lines.len();
9319
9320 edits.push((start_point..end_point, lines.join("\n")));
9321
9322 // Selections must change based on added and removed line count
9323 let start_row =
9324 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9325 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9326 new_selections.push(Selection {
9327 id: selection.id,
9328 start: start_row,
9329 end: end_row,
9330 goal: SelectionGoal::None,
9331 reversed: selection.reversed,
9332 });
9333
9334 if lines_after > lines_before {
9335 added_lines += lines_after - lines_before;
9336 } else if lines_before > lines_after {
9337 removed_lines += lines_before - lines_after;
9338 }
9339 }
9340
9341 self.transact(window, cx, |this, window, cx| {
9342 let buffer = this.buffer.update(cx, |buffer, cx| {
9343 buffer.edit(edits, None, cx);
9344 buffer.snapshot(cx)
9345 });
9346
9347 // Recalculate offsets on newly edited buffer
9348 let new_selections = new_selections
9349 .iter()
9350 .map(|s| {
9351 let start_point = Point::new(s.start.0, 0);
9352 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9353 Selection {
9354 id: s.id,
9355 start: buffer.point_to_offset(start_point),
9356 end: buffer.point_to_offset(end_point),
9357 goal: s.goal,
9358 reversed: s.reversed,
9359 }
9360 })
9361 .collect();
9362
9363 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9364 s.select(new_selections);
9365 });
9366
9367 this.request_autoscroll(Autoscroll::fit(), cx);
9368 });
9369 }
9370
9371 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9372 self.manipulate_text(window, cx, |text| {
9373 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9374 if has_upper_case_characters {
9375 text.to_lowercase()
9376 } else {
9377 text.to_uppercase()
9378 }
9379 })
9380 }
9381
9382 pub fn convert_to_upper_case(
9383 &mut self,
9384 _: &ConvertToUpperCase,
9385 window: &mut Window,
9386 cx: &mut Context<Self>,
9387 ) {
9388 self.manipulate_text(window, cx, |text| text.to_uppercase())
9389 }
9390
9391 pub fn convert_to_lower_case(
9392 &mut self,
9393 _: &ConvertToLowerCase,
9394 window: &mut Window,
9395 cx: &mut Context<Self>,
9396 ) {
9397 self.manipulate_text(window, cx, |text| text.to_lowercase())
9398 }
9399
9400 pub fn convert_to_title_case(
9401 &mut self,
9402 _: &ConvertToTitleCase,
9403 window: &mut Window,
9404 cx: &mut Context<Self>,
9405 ) {
9406 self.manipulate_text(window, cx, |text| {
9407 text.split('\n')
9408 .map(|line| line.to_case(Case::Title))
9409 .join("\n")
9410 })
9411 }
9412
9413 pub fn convert_to_snake_case(
9414 &mut self,
9415 _: &ConvertToSnakeCase,
9416 window: &mut Window,
9417 cx: &mut Context<Self>,
9418 ) {
9419 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9420 }
9421
9422 pub fn convert_to_kebab_case(
9423 &mut self,
9424 _: &ConvertToKebabCase,
9425 window: &mut Window,
9426 cx: &mut Context<Self>,
9427 ) {
9428 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9429 }
9430
9431 pub fn convert_to_upper_camel_case(
9432 &mut self,
9433 _: &ConvertToUpperCamelCase,
9434 window: &mut Window,
9435 cx: &mut Context<Self>,
9436 ) {
9437 self.manipulate_text(window, cx, |text| {
9438 text.split('\n')
9439 .map(|line| line.to_case(Case::UpperCamel))
9440 .join("\n")
9441 })
9442 }
9443
9444 pub fn convert_to_lower_camel_case(
9445 &mut self,
9446 _: &ConvertToLowerCamelCase,
9447 window: &mut Window,
9448 cx: &mut Context<Self>,
9449 ) {
9450 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9451 }
9452
9453 pub fn convert_to_opposite_case(
9454 &mut self,
9455 _: &ConvertToOppositeCase,
9456 window: &mut Window,
9457 cx: &mut Context<Self>,
9458 ) {
9459 self.manipulate_text(window, cx, |text| {
9460 text.chars()
9461 .fold(String::with_capacity(text.len()), |mut t, c| {
9462 if c.is_uppercase() {
9463 t.extend(c.to_lowercase());
9464 } else {
9465 t.extend(c.to_uppercase());
9466 }
9467 t
9468 })
9469 })
9470 }
9471
9472 pub fn convert_to_rot13(
9473 &mut self,
9474 _: &ConvertToRot13,
9475 window: &mut Window,
9476 cx: &mut Context<Self>,
9477 ) {
9478 self.manipulate_text(window, cx, |text| {
9479 text.chars()
9480 .map(|c| match c {
9481 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9482 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9483 _ => c,
9484 })
9485 .collect()
9486 })
9487 }
9488
9489 pub fn convert_to_rot47(
9490 &mut self,
9491 _: &ConvertToRot47,
9492 window: &mut Window,
9493 cx: &mut Context<Self>,
9494 ) {
9495 self.manipulate_text(window, cx, |text| {
9496 text.chars()
9497 .map(|c| {
9498 let code_point = c as u32;
9499 if code_point >= 33 && code_point <= 126 {
9500 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9501 }
9502 c
9503 })
9504 .collect()
9505 })
9506 }
9507
9508 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9509 where
9510 Fn: FnMut(&str) -> String,
9511 {
9512 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9513 let buffer = self.buffer.read(cx).snapshot(cx);
9514
9515 let mut new_selections = Vec::new();
9516 let mut edits = Vec::new();
9517 let mut selection_adjustment = 0i32;
9518
9519 for selection in self.selections.all::<usize>(cx) {
9520 let selection_is_empty = selection.is_empty();
9521
9522 let (start, end) = if selection_is_empty {
9523 let word_range = movement::surrounding_word(
9524 &display_map,
9525 selection.start.to_display_point(&display_map),
9526 );
9527 let start = word_range.start.to_offset(&display_map, Bias::Left);
9528 let end = word_range.end.to_offset(&display_map, Bias::Left);
9529 (start, end)
9530 } else {
9531 (selection.start, selection.end)
9532 };
9533
9534 let text = buffer.text_for_range(start..end).collect::<String>();
9535 let old_length = text.len() as i32;
9536 let text = callback(&text);
9537
9538 new_selections.push(Selection {
9539 start: (start as i32 - selection_adjustment) as usize,
9540 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9541 goal: SelectionGoal::None,
9542 ..selection
9543 });
9544
9545 selection_adjustment += old_length - text.len() as i32;
9546
9547 edits.push((start..end, text));
9548 }
9549
9550 self.transact(window, cx, |this, window, cx| {
9551 this.buffer.update(cx, |buffer, cx| {
9552 buffer.edit(edits, None, cx);
9553 });
9554
9555 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9556 s.select(new_selections);
9557 });
9558
9559 this.request_autoscroll(Autoscroll::fit(), cx);
9560 });
9561 }
9562
9563 pub fn duplicate(
9564 &mut self,
9565 upwards: bool,
9566 whole_lines: bool,
9567 window: &mut Window,
9568 cx: &mut Context<Self>,
9569 ) {
9570 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9571
9572 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9573 let buffer = &display_map.buffer_snapshot;
9574 let selections = self.selections.all::<Point>(cx);
9575
9576 let mut edits = Vec::new();
9577 let mut selections_iter = selections.iter().peekable();
9578 while let Some(selection) = selections_iter.next() {
9579 let mut rows = selection.spanned_rows(false, &display_map);
9580 // duplicate line-wise
9581 if whole_lines || selection.start == selection.end {
9582 // Avoid duplicating the same lines twice.
9583 while let Some(next_selection) = selections_iter.peek() {
9584 let next_rows = next_selection.spanned_rows(false, &display_map);
9585 if next_rows.start < rows.end {
9586 rows.end = next_rows.end;
9587 selections_iter.next().unwrap();
9588 } else {
9589 break;
9590 }
9591 }
9592
9593 // Copy the text from the selected row region and splice it either at the start
9594 // or end of the region.
9595 let start = Point::new(rows.start.0, 0);
9596 let end = Point::new(
9597 rows.end.previous_row().0,
9598 buffer.line_len(rows.end.previous_row()),
9599 );
9600 let text = buffer
9601 .text_for_range(start..end)
9602 .chain(Some("\n"))
9603 .collect::<String>();
9604 let insert_location = if upwards {
9605 Point::new(rows.end.0, 0)
9606 } else {
9607 start
9608 };
9609 edits.push((insert_location..insert_location, text));
9610 } else {
9611 // duplicate character-wise
9612 let start = selection.start;
9613 let end = selection.end;
9614 let text = buffer.text_for_range(start..end).collect::<String>();
9615 edits.push((selection.end..selection.end, text));
9616 }
9617 }
9618
9619 self.transact(window, cx, |this, _, cx| {
9620 this.buffer.update(cx, |buffer, cx| {
9621 buffer.edit(edits, None, cx);
9622 });
9623
9624 this.request_autoscroll(Autoscroll::fit(), cx);
9625 });
9626 }
9627
9628 pub fn duplicate_line_up(
9629 &mut self,
9630 _: &DuplicateLineUp,
9631 window: &mut Window,
9632 cx: &mut Context<Self>,
9633 ) {
9634 self.duplicate(true, true, window, cx);
9635 }
9636
9637 pub fn duplicate_line_down(
9638 &mut self,
9639 _: &DuplicateLineDown,
9640 window: &mut Window,
9641 cx: &mut Context<Self>,
9642 ) {
9643 self.duplicate(false, true, window, cx);
9644 }
9645
9646 pub fn duplicate_selection(
9647 &mut self,
9648 _: &DuplicateSelection,
9649 window: &mut Window,
9650 cx: &mut Context<Self>,
9651 ) {
9652 self.duplicate(false, false, window, cx);
9653 }
9654
9655 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9656 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9657
9658 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9659 let buffer = self.buffer.read(cx).snapshot(cx);
9660
9661 let mut edits = Vec::new();
9662 let mut unfold_ranges = Vec::new();
9663 let mut refold_creases = Vec::new();
9664
9665 let selections = self.selections.all::<Point>(cx);
9666 let mut selections = selections.iter().peekable();
9667 let mut contiguous_row_selections = Vec::new();
9668 let mut new_selections = Vec::new();
9669
9670 while let Some(selection) = selections.next() {
9671 // Find all the selections that span a contiguous row range
9672 let (start_row, end_row) = consume_contiguous_rows(
9673 &mut contiguous_row_selections,
9674 selection,
9675 &display_map,
9676 &mut selections,
9677 );
9678
9679 // Move the text spanned by the row range to be before the line preceding the row range
9680 if start_row.0 > 0 {
9681 let range_to_move = Point::new(
9682 start_row.previous_row().0,
9683 buffer.line_len(start_row.previous_row()),
9684 )
9685 ..Point::new(
9686 end_row.previous_row().0,
9687 buffer.line_len(end_row.previous_row()),
9688 );
9689 let insertion_point = display_map
9690 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9691 .0;
9692
9693 // Don't move lines across excerpts
9694 if buffer
9695 .excerpt_containing(insertion_point..range_to_move.end)
9696 .is_some()
9697 {
9698 let text = buffer
9699 .text_for_range(range_to_move.clone())
9700 .flat_map(|s| s.chars())
9701 .skip(1)
9702 .chain(['\n'])
9703 .collect::<String>();
9704
9705 edits.push((
9706 buffer.anchor_after(range_to_move.start)
9707 ..buffer.anchor_before(range_to_move.end),
9708 String::new(),
9709 ));
9710 let insertion_anchor = buffer.anchor_after(insertion_point);
9711 edits.push((insertion_anchor..insertion_anchor, text));
9712
9713 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9714
9715 // Move selections up
9716 new_selections.extend(contiguous_row_selections.drain(..).map(
9717 |mut selection| {
9718 selection.start.row -= row_delta;
9719 selection.end.row -= row_delta;
9720 selection
9721 },
9722 ));
9723
9724 // Move folds up
9725 unfold_ranges.push(range_to_move.clone());
9726 for fold in display_map.folds_in_range(
9727 buffer.anchor_before(range_to_move.start)
9728 ..buffer.anchor_after(range_to_move.end),
9729 ) {
9730 let mut start = fold.range.start.to_point(&buffer);
9731 let mut end = fold.range.end.to_point(&buffer);
9732 start.row -= row_delta;
9733 end.row -= row_delta;
9734 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9735 }
9736 }
9737 }
9738
9739 // If we didn't move line(s), preserve the existing selections
9740 new_selections.append(&mut contiguous_row_selections);
9741 }
9742
9743 self.transact(window, cx, |this, window, cx| {
9744 this.unfold_ranges(&unfold_ranges, true, true, cx);
9745 this.buffer.update(cx, |buffer, cx| {
9746 for (range, text) in edits {
9747 buffer.edit([(range, text)], None, cx);
9748 }
9749 });
9750 this.fold_creases(refold_creases, true, window, cx);
9751 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9752 s.select(new_selections);
9753 })
9754 });
9755 }
9756
9757 pub fn move_line_down(
9758 &mut self,
9759 _: &MoveLineDown,
9760 window: &mut Window,
9761 cx: &mut Context<Self>,
9762 ) {
9763 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9764
9765 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9766 let buffer = self.buffer.read(cx).snapshot(cx);
9767
9768 let mut edits = Vec::new();
9769 let mut unfold_ranges = Vec::new();
9770 let mut refold_creases = Vec::new();
9771
9772 let selections = self.selections.all::<Point>(cx);
9773 let mut selections = selections.iter().peekable();
9774 let mut contiguous_row_selections = Vec::new();
9775 let mut new_selections = Vec::new();
9776
9777 while let Some(selection) = selections.next() {
9778 // Find all the selections that span a contiguous row range
9779 let (start_row, end_row) = consume_contiguous_rows(
9780 &mut contiguous_row_selections,
9781 selection,
9782 &display_map,
9783 &mut selections,
9784 );
9785
9786 // Move the text spanned by the row range to be after the last line of the row range
9787 if end_row.0 <= buffer.max_point().row {
9788 let range_to_move =
9789 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9790 let insertion_point = display_map
9791 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9792 .0;
9793
9794 // Don't move lines across excerpt boundaries
9795 if buffer
9796 .excerpt_containing(range_to_move.start..insertion_point)
9797 .is_some()
9798 {
9799 let mut text = String::from("\n");
9800 text.extend(buffer.text_for_range(range_to_move.clone()));
9801 text.pop(); // Drop trailing newline
9802 edits.push((
9803 buffer.anchor_after(range_to_move.start)
9804 ..buffer.anchor_before(range_to_move.end),
9805 String::new(),
9806 ));
9807 let insertion_anchor = buffer.anchor_after(insertion_point);
9808 edits.push((insertion_anchor..insertion_anchor, text));
9809
9810 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9811
9812 // Move selections down
9813 new_selections.extend(contiguous_row_selections.drain(..).map(
9814 |mut selection| {
9815 selection.start.row += row_delta;
9816 selection.end.row += row_delta;
9817 selection
9818 },
9819 ));
9820
9821 // Move folds down
9822 unfold_ranges.push(range_to_move.clone());
9823 for fold in display_map.folds_in_range(
9824 buffer.anchor_before(range_to_move.start)
9825 ..buffer.anchor_after(range_to_move.end),
9826 ) {
9827 let mut start = fold.range.start.to_point(&buffer);
9828 let mut end = fold.range.end.to_point(&buffer);
9829 start.row += row_delta;
9830 end.row += row_delta;
9831 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9832 }
9833 }
9834 }
9835
9836 // If we didn't move line(s), preserve the existing selections
9837 new_selections.append(&mut contiguous_row_selections);
9838 }
9839
9840 self.transact(window, cx, |this, window, cx| {
9841 this.unfold_ranges(&unfold_ranges, true, true, cx);
9842 this.buffer.update(cx, |buffer, cx| {
9843 for (range, text) in edits {
9844 buffer.edit([(range, text)], None, cx);
9845 }
9846 });
9847 this.fold_creases(refold_creases, true, window, cx);
9848 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9849 s.select(new_selections)
9850 });
9851 });
9852 }
9853
9854 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9855 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9856 let text_layout_details = &self.text_layout_details(window);
9857 self.transact(window, cx, |this, window, cx| {
9858 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9859 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9860 s.move_with(|display_map, selection| {
9861 if !selection.is_empty() {
9862 return;
9863 }
9864
9865 let mut head = selection.head();
9866 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9867 if head.column() == display_map.line_len(head.row()) {
9868 transpose_offset = display_map
9869 .buffer_snapshot
9870 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9871 }
9872
9873 if transpose_offset == 0 {
9874 return;
9875 }
9876
9877 *head.column_mut() += 1;
9878 head = display_map.clip_point(head, Bias::Right);
9879 let goal = SelectionGoal::HorizontalPosition(
9880 display_map
9881 .x_for_display_point(head, text_layout_details)
9882 .into(),
9883 );
9884 selection.collapse_to(head, goal);
9885
9886 let transpose_start = display_map
9887 .buffer_snapshot
9888 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9889 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9890 let transpose_end = display_map
9891 .buffer_snapshot
9892 .clip_offset(transpose_offset + 1, Bias::Right);
9893 if let Some(ch) =
9894 display_map.buffer_snapshot.chars_at(transpose_start).next()
9895 {
9896 edits.push((transpose_start..transpose_offset, String::new()));
9897 edits.push((transpose_end..transpose_end, ch.to_string()));
9898 }
9899 }
9900 });
9901 edits
9902 });
9903 this.buffer
9904 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9905 let selections = this.selections.all::<usize>(cx);
9906 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9907 s.select(selections);
9908 });
9909 });
9910 }
9911
9912 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9913 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9914 self.rewrap_impl(RewrapOptions::default(), cx)
9915 }
9916
9917 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9918 let buffer = self.buffer.read(cx).snapshot(cx);
9919 let selections = self.selections.all::<Point>(cx);
9920 let mut selections = selections.iter().peekable();
9921
9922 let mut edits = Vec::new();
9923 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9924
9925 while let Some(selection) = selections.next() {
9926 let mut start_row = selection.start.row;
9927 let mut end_row = selection.end.row;
9928
9929 // Skip selections that overlap with a range that has already been rewrapped.
9930 let selection_range = start_row..end_row;
9931 if rewrapped_row_ranges
9932 .iter()
9933 .any(|range| range.overlaps(&selection_range))
9934 {
9935 continue;
9936 }
9937
9938 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9939
9940 // Since not all lines in the selection may be at the same indent
9941 // level, choose the indent size that is the most common between all
9942 // of the lines.
9943 //
9944 // If there is a tie, we use the deepest indent.
9945 let (indent_size, indent_end) = {
9946 let mut indent_size_occurrences = HashMap::default();
9947 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9948
9949 for row in start_row..=end_row {
9950 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9951 rows_by_indent_size.entry(indent).or_default().push(row);
9952 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9953 }
9954
9955 let indent_size = indent_size_occurrences
9956 .into_iter()
9957 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9958 .map(|(indent, _)| indent)
9959 .unwrap_or_default();
9960 let row = rows_by_indent_size[&indent_size][0];
9961 let indent_end = Point::new(row, indent_size.len);
9962
9963 (indent_size, indent_end)
9964 };
9965
9966 let mut line_prefix = indent_size.chars().collect::<String>();
9967
9968 let mut inside_comment = false;
9969 if let Some(comment_prefix) =
9970 buffer
9971 .language_scope_at(selection.head())
9972 .and_then(|language| {
9973 language
9974 .line_comment_prefixes()
9975 .iter()
9976 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9977 .cloned()
9978 })
9979 {
9980 line_prefix.push_str(&comment_prefix);
9981 inside_comment = true;
9982 }
9983
9984 let language_settings = buffer.language_settings_at(selection.head(), cx);
9985 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9986 RewrapBehavior::InComments => inside_comment,
9987 RewrapBehavior::InSelections => !selection.is_empty(),
9988 RewrapBehavior::Anywhere => true,
9989 };
9990
9991 let should_rewrap = options.override_language_settings
9992 || allow_rewrap_based_on_language
9993 || self.hard_wrap.is_some();
9994 if !should_rewrap {
9995 continue;
9996 }
9997
9998 if selection.is_empty() {
9999 'expand_upwards: while start_row > 0 {
10000 let prev_row = start_row - 1;
10001 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10002 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10003 {
10004 start_row = prev_row;
10005 } else {
10006 break 'expand_upwards;
10007 }
10008 }
10009
10010 'expand_downwards: while end_row < buffer.max_point().row {
10011 let next_row = end_row + 1;
10012 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10013 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10014 {
10015 end_row = next_row;
10016 } else {
10017 break 'expand_downwards;
10018 }
10019 }
10020 }
10021
10022 let start = Point::new(start_row, 0);
10023 let start_offset = start.to_offset(&buffer);
10024 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10025 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10026 let Some(lines_without_prefixes) = selection_text
10027 .lines()
10028 .map(|line| {
10029 line.strip_prefix(&line_prefix)
10030 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10031 .ok_or_else(|| {
10032 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10033 })
10034 })
10035 .collect::<Result<Vec<_>, _>>()
10036 .log_err()
10037 else {
10038 continue;
10039 };
10040
10041 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10042 buffer
10043 .language_settings_at(Point::new(start_row, 0), cx)
10044 .preferred_line_length as usize
10045 });
10046 let wrapped_text = wrap_with_prefix(
10047 line_prefix,
10048 lines_without_prefixes.join("\n"),
10049 wrap_column,
10050 tab_size,
10051 options.preserve_existing_whitespace,
10052 );
10053
10054 // TODO: should always use char-based diff while still supporting cursor behavior that
10055 // matches vim.
10056 let mut diff_options = DiffOptions::default();
10057 if options.override_language_settings {
10058 diff_options.max_word_diff_len = 0;
10059 diff_options.max_word_diff_line_count = 0;
10060 } else {
10061 diff_options.max_word_diff_len = usize::MAX;
10062 diff_options.max_word_diff_line_count = usize::MAX;
10063 }
10064
10065 for (old_range, new_text) in
10066 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10067 {
10068 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10069 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10070 edits.push((edit_start..edit_end, new_text));
10071 }
10072
10073 rewrapped_row_ranges.push(start_row..=end_row);
10074 }
10075
10076 self.buffer
10077 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10078 }
10079
10080 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10081 let mut text = String::new();
10082 let buffer = self.buffer.read(cx).snapshot(cx);
10083 let mut selections = self.selections.all::<Point>(cx);
10084 let mut clipboard_selections = Vec::with_capacity(selections.len());
10085 {
10086 let max_point = buffer.max_point();
10087 let mut is_first = true;
10088 for selection in &mut selections {
10089 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10090 if is_entire_line {
10091 selection.start = Point::new(selection.start.row, 0);
10092 if !selection.is_empty() && selection.end.column == 0 {
10093 selection.end = cmp::min(max_point, selection.end);
10094 } else {
10095 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10096 }
10097 selection.goal = SelectionGoal::None;
10098 }
10099 if is_first {
10100 is_first = false;
10101 } else {
10102 text += "\n";
10103 }
10104 let mut len = 0;
10105 for chunk in buffer.text_for_range(selection.start..selection.end) {
10106 text.push_str(chunk);
10107 len += chunk.len();
10108 }
10109 clipboard_selections.push(ClipboardSelection {
10110 len,
10111 is_entire_line,
10112 first_line_indent: buffer
10113 .indent_size_for_line(MultiBufferRow(selection.start.row))
10114 .len,
10115 });
10116 }
10117 }
10118
10119 self.transact(window, cx, |this, window, cx| {
10120 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10121 s.select(selections);
10122 });
10123 this.insert("", window, cx);
10124 });
10125 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10126 }
10127
10128 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10129 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10130 let item = self.cut_common(window, cx);
10131 cx.write_to_clipboard(item);
10132 }
10133
10134 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10135 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10136 self.change_selections(None, window, cx, |s| {
10137 s.move_with(|snapshot, sel| {
10138 if sel.is_empty() {
10139 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10140 }
10141 });
10142 });
10143 let item = self.cut_common(window, cx);
10144 cx.set_global(KillRing(item))
10145 }
10146
10147 pub fn kill_ring_yank(
10148 &mut self,
10149 _: &KillRingYank,
10150 window: &mut Window,
10151 cx: &mut Context<Self>,
10152 ) {
10153 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10154 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10155 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10156 (kill_ring.text().to_string(), kill_ring.metadata_json())
10157 } else {
10158 return;
10159 }
10160 } else {
10161 return;
10162 };
10163 self.do_paste(&text, metadata, false, window, cx);
10164 }
10165
10166 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10167 self.do_copy(true, cx);
10168 }
10169
10170 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10171 self.do_copy(false, cx);
10172 }
10173
10174 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10175 let selections = self.selections.all::<Point>(cx);
10176 let buffer = self.buffer.read(cx).read(cx);
10177 let mut text = String::new();
10178
10179 let mut clipboard_selections = Vec::with_capacity(selections.len());
10180 {
10181 let max_point = buffer.max_point();
10182 let mut is_first = true;
10183 for selection in &selections {
10184 let mut start = selection.start;
10185 let mut end = selection.end;
10186 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10187 if is_entire_line {
10188 start = Point::new(start.row, 0);
10189 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10190 }
10191
10192 let mut trimmed_selections = Vec::new();
10193 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10194 let row = MultiBufferRow(start.row);
10195 let first_indent = buffer.indent_size_for_line(row);
10196 if first_indent.len == 0 || start.column > first_indent.len {
10197 trimmed_selections.push(start..end);
10198 } else {
10199 trimmed_selections.push(
10200 Point::new(row.0, first_indent.len)
10201 ..Point::new(row.0, buffer.line_len(row)),
10202 );
10203 for row in start.row + 1..=end.row {
10204 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10205 if row_indent_size.len >= first_indent.len {
10206 trimmed_selections.push(
10207 Point::new(row, first_indent.len)
10208 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
10209 );
10210 } else {
10211 trimmed_selections.clear();
10212 trimmed_selections.push(start..end);
10213 break;
10214 }
10215 }
10216 }
10217 } else {
10218 trimmed_selections.push(start..end);
10219 }
10220
10221 for trimmed_range in trimmed_selections {
10222 if is_first {
10223 is_first = false;
10224 } else {
10225 text += "\n";
10226 }
10227 let mut len = 0;
10228 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10229 text.push_str(chunk);
10230 len += chunk.len();
10231 }
10232 clipboard_selections.push(ClipboardSelection {
10233 len,
10234 is_entire_line,
10235 first_line_indent: buffer
10236 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10237 .len,
10238 });
10239 }
10240 }
10241 }
10242
10243 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10244 text,
10245 clipboard_selections,
10246 ));
10247 }
10248
10249 pub fn do_paste(
10250 &mut self,
10251 text: &String,
10252 clipboard_selections: Option<Vec<ClipboardSelection>>,
10253 handle_entire_lines: bool,
10254 window: &mut Window,
10255 cx: &mut Context<Self>,
10256 ) {
10257 if self.read_only(cx) {
10258 return;
10259 }
10260
10261 let clipboard_text = Cow::Borrowed(text);
10262
10263 self.transact(window, cx, |this, window, cx| {
10264 if let Some(mut clipboard_selections) = clipboard_selections {
10265 let old_selections = this.selections.all::<usize>(cx);
10266 let all_selections_were_entire_line =
10267 clipboard_selections.iter().all(|s| s.is_entire_line);
10268 let first_selection_indent_column =
10269 clipboard_selections.first().map(|s| s.first_line_indent);
10270 if clipboard_selections.len() != old_selections.len() {
10271 clipboard_selections.drain(..);
10272 }
10273 let cursor_offset = this.selections.last::<usize>(cx).head();
10274 let mut auto_indent_on_paste = true;
10275
10276 this.buffer.update(cx, |buffer, cx| {
10277 let snapshot = buffer.read(cx);
10278 auto_indent_on_paste = snapshot
10279 .language_settings_at(cursor_offset, cx)
10280 .auto_indent_on_paste;
10281
10282 let mut start_offset = 0;
10283 let mut edits = Vec::new();
10284 let mut original_indent_columns = Vec::new();
10285 for (ix, selection) in old_selections.iter().enumerate() {
10286 let to_insert;
10287 let entire_line;
10288 let original_indent_column;
10289 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10290 let end_offset = start_offset + clipboard_selection.len;
10291 to_insert = &clipboard_text[start_offset..end_offset];
10292 entire_line = clipboard_selection.is_entire_line;
10293 start_offset = end_offset + 1;
10294 original_indent_column = Some(clipboard_selection.first_line_indent);
10295 } else {
10296 to_insert = clipboard_text.as_str();
10297 entire_line = all_selections_were_entire_line;
10298 original_indent_column = first_selection_indent_column
10299 }
10300
10301 // If the corresponding selection was empty when this slice of the
10302 // clipboard text was written, then the entire line containing the
10303 // selection was copied. If this selection is also currently empty,
10304 // then paste the line before the current line of the buffer.
10305 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10306 let column = selection.start.to_point(&snapshot).column as usize;
10307 let line_start = selection.start - column;
10308 line_start..line_start
10309 } else {
10310 selection.range()
10311 };
10312
10313 edits.push((range, to_insert));
10314 original_indent_columns.push(original_indent_column);
10315 }
10316 drop(snapshot);
10317
10318 buffer.edit(
10319 edits,
10320 if auto_indent_on_paste {
10321 Some(AutoindentMode::Block {
10322 original_indent_columns,
10323 })
10324 } else {
10325 None
10326 },
10327 cx,
10328 );
10329 });
10330
10331 let selections = this.selections.all::<usize>(cx);
10332 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10333 s.select(selections)
10334 });
10335 } else {
10336 this.insert(&clipboard_text, window, cx);
10337 }
10338 });
10339 }
10340
10341 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10342 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10343 if let Some(item) = cx.read_from_clipboard() {
10344 let entries = item.entries();
10345
10346 match entries.first() {
10347 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10348 // of all the pasted entries.
10349 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10350 .do_paste(
10351 clipboard_string.text(),
10352 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10353 true,
10354 window,
10355 cx,
10356 ),
10357 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10358 }
10359 }
10360 }
10361
10362 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10363 if self.read_only(cx) {
10364 return;
10365 }
10366
10367 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10368
10369 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10370 if let Some((selections, _)) =
10371 self.selection_history.transaction(transaction_id).cloned()
10372 {
10373 self.change_selections(None, window, cx, |s| {
10374 s.select_anchors(selections.to_vec());
10375 });
10376 } else {
10377 log::error!(
10378 "No entry in selection_history found for undo. \
10379 This may correspond to a bug where undo does not update the selection. \
10380 If this is occurring, please add details to \
10381 https://github.com/zed-industries/zed/issues/22692"
10382 );
10383 }
10384 self.request_autoscroll(Autoscroll::fit(), cx);
10385 self.unmark_text(window, cx);
10386 self.refresh_inline_completion(true, false, window, cx);
10387 cx.emit(EditorEvent::Edited { transaction_id });
10388 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10389 }
10390 }
10391
10392 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10393 if self.read_only(cx) {
10394 return;
10395 }
10396
10397 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10398
10399 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10400 if let Some((_, Some(selections))) =
10401 self.selection_history.transaction(transaction_id).cloned()
10402 {
10403 self.change_selections(None, window, cx, |s| {
10404 s.select_anchors(selections.to_vec());
10405 });
10406 } else {
10407 log::error!(
10408 "No entry in selection_history found for redo. \
10409 This may correspond to a bug where undo does not update the selection. \
10410 If this is occurring, please add details to \
10411 https://github.com/zed-industries/zed/issues/22692"
10412 );
10413 }
10414 self.request_autoscroll(Autoscroll::fit(), cx);
10415 self.unmark_text(window, cx);
10416 self.refresh_inline_completion(true, false, window, cx);
10417 cx.emit(EditorEvent::Edited { transaction_id });
10418 }
10419 }
10420
10421 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10422 self.buffer
10423 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10424 }
10425
10426 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10427 self.buffer
10428 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10429 }
10430
10431 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10432 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10433 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10434 s.move_with(|map, selection| {
10435 let cursor = if selection.is_empty() {
10436 movement::left(map, selection.start)
10437 } else {
10438 selection.start
10439 };
10440 selection.collapse_to(cursor, SelectionGoal::None);
10441 });
10442 })
10443 }
10444
10445 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10446 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10447 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10448 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10449 })
10450 }
10451
10452 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10453 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10454 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10455 s.move_with(|map, selection| {
10456 let cursor = if selection.is_empty() {
10457 movement::right(map, selection.end)
10458 } else {
10459 selection.end
10460 };
10461 selection.collapse_to(cursor, SelectionGoal::None)
10462 });
10463 })
10464 }
10465
10466 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10467 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10468 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10469 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10470 })
10471 }
10472
10473 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10474 if self.take_rename(true, window, cx).is_some() {
10475 return;
10476 }
10477
10478 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10479 cx.propagate();
10480 return;
10481 }
10482
10483 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10484
10485 let text_layout_details = &self.text_layout_details(window);
10486 let selection_count = self.selections.count();
10487 let first_selection = self.selections.first_anchor();
10488
10489 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10490 s.move_with(|map, selection| {
10491 if !selection.is_empty() {
10492 selection.goal = SelectionGoal::None;
10493 }
10494 let (cursor, goal) = movement::up(
10495 map,
10496 selection.start,
10497 selection.goal,
10498 false,
10499 text_layout_details,
10500 );
10501 selection.collapse_to(cursor, goal);
10502 });
10503 });
10504
10505 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10506 {
10507 cx.propagate();
10508 }
10509 }
10510
10511 pub fn move_up_by_lines(
10512 &mut self,
10513 action: &MoveUpByLines,
10514 window: &mut Window,
10515 cx: &mut Context<Self>,
10516 ) {
10517 if self.take_rename(true, window, cx).is_some() {
10518 return;
10519 }
10520
10521 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10522 cx.propagate();
10523 return;
10524 }
10525
10526 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10527
10528 let text_layout_details = &self.text_layout_details(window);
10529
10530 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10531 s.move_with(|map, selection| {
10532 if !selection.is_empty() {
10533 selection.goal = SelectionGoal::None;
10534 }
10535 let (cursor, goal) = movement::up_by_rows(
10536 map,
10537 selection.start,
10538 action.lines,
10539 selection.goal,
10540 false,
10541 text_layout_details,
10542 );
10543 selection.collapse_to(cursor, goal);
10544 });
10545 })
10546 }
10547
10548 pub fn move_down_by_lines(
10549 &mut self,
10550 action: &MoveDownByLines,
10551 window: &mut Window,
10552 cx: &mut Context<Self>,
10553 ) {
10554 if self.take_rename(true, window, cx).is_some() {
10555 return;
10556 }
10557
10558 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10559 cx.propagate();
10560 return;
10561 }
10562
10563 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10564
10565 let text_layout_details = &self.text_layout_details(window);
10566
10567 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10568 s.move_with(|map, selection| {
10569 if !selection.is_empty() {
10570 selection.goal = SelectionGoal::None;
10571 }
10572 let (cursor, goal) = movement::down_by_rows(
10573 map,
10574 selection.start,
10575 action.lines,
10576 selection.goal,
10577 false,
10578 text_layout_details,
10579 );
10580 selection.collapse_to(cursor, goal);
10581 });
10582 })
10583 }
10584
10585 pub fn select_down_by_lines(
10586 &mut self,
10587 action: &SelectDownByLines,
10588 window: &mut Window,
10589 cx: &mut Context<Self>,
10590 ) {
10591 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10592 let text_layout_details = &self.text_layout_details(window);
10593 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10594 s.move_heads_with(|map, head, goal| {
10595 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10596 })
10597 })
10598 }
10599
10600 pub fn select_up_by_lines(
10601 &mut self,
10602 action: &SelectUpByLines,
10603 window: &mut Window,
10604 cx: &mut Context<Self>,
10605 ) {
10606 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10607 let text_layout_details = &self.text_layout_details(window);
10608 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10609 s.move_heads_with(|map, head, goal| {
10610 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10611 })
10612 })
10613 }
10614
10615 pub fn select_page_up(
10616 &mut self,
10617 _: &SelectPageUp,
10618 window: &mut Window,
10619 cx: &mut Context<Self>,
10620 ) {
10621 let Some(row_count) = self.visible_row_count() else {
10622 return;
10623 };
10624
10625 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10626
10627 let text_layout_details = &self.text_layout_details(window);
10628
10629 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10630 s.move_heads_with(|map, head, goal| {
10631 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10632 })
10633 })
10634 }
10635
10636 pub fn move_page_up(
10637 &mut self,
10638 action: &MovePageUp,
10639 window: &mut Window,
10640 cx: &mut Context<Self>,
10641 ) {
10642 if self.take_rename(true, window, cx).is_some() {
10643 return;
10644 }
10645
10646 if self
10647 .context_menu
10648 .borrow_mut()
10649 .as_mut()
10650 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10651 .unwrap_or(false)
10652 {
10653 return;
10654 }
10655
10656 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10657 cx.propagate();
10658 return;
10659 }
10660
10661 let Some(row_count) = self.visible_row_count() else {
10662 return;
10663 };
10664
10665 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10666
10667 let autoscroll = if action.center_cursor {
10668 Autoscroll::center()
10669 } else {
10670 Autoscroll::fit()
10671 };
10672
10673 let text_layout_details = &self.text_layout_details(window);
10674
10675 self.change_selections(Some(autoscroll), window, cx, |s| {
10676 s.move_with(|map, selection| {
10677 if !selection.is_empty() {
10678 selection.goal = SelectionGoal::None;
10679 }
10680 let (cursor, goal) = movement::up_by_rows(
10681 map,
10682 selection.end,
10683 row_count,
10684 selection.goal,
10685 false,
10686 text_layout_details,
10687 );
10688 selection.collapse_to(cursor, goal);
10689 });
10690 });
10691 }
10692
10693 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10694 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10695 let text_layout_details = &self.text_layout_details(window);
10696 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10697 s.move_heads_with(|map, head, goal| {
10698 movement::up(map, head, goal, false, text_layout_details)
10699 })
10700 })
10701 }
10702
10703 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10704 self.take_rename(true, window, cx);
10705
10706 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10707 cx.propagate();
10708 return;
10709 }
10710
10711 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10712
10713 let text_layout_details = &self.text_layout_details(window);
10714 let selection_count = self.selections.count();
10715 let first_selection = self.selections.first_anchor();
10716
10717 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10718 s.move_with(|map, selection| {
10719 if !selection.is_empty() {
10720 selection.goal = SelectionGoal::None;
10721 }
10722 let (cursor, goal) = movement::down(
10723 map,
10724 selection.end,
10725 selection.goal,
10726 false,
10727 text_layout_details,
10728 );
10729 selection.collapse_to(cursor, goal);
10730 });
10731 });
10732
10733 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10734 {
10735 cx.propagate();
10736 }
10737 }
10738
10739 pub fn select_page_down(
10740 &mut self,
10741 _: &SelectPageDown,
10742 window: &mut Window,
10743 cx: &mut Context<Self>,
10744 ) {
10745 let Some(row_count) = self.visible_row_count() else {
10746 return;
10747 };
10748
10749 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10750
10751 let text_layout_details = &self.text_layout_details(window);
10752
10753 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10754 s.move_heads_with(|map, head, goal| {
10755 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10756 })
10757 })
10758 }
10759
10760 pub fn move_page_down(
10761 &mut self,
10762 action: &MovePageDown,
10763 window: &mut Window,
10764 cx: &mut Context<Self>,
10765 ) {
10766 if self.take_rename(true, window, cx).is_some() {
10767 return;
10768 }
10769
10770 if self
10771 .context_menu
10772 .borrow_mut()
10773 .as_mut()
10774 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10775 .unwrap_or(false)
10776 {
10777 return;
10778 }
10779
10780 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10781 cx.propagate();
10782 return;
10783 }
10784
10785 let Some(row_count) = self.visible_row_count() else {
10786 return;
10787 };
10788
10789 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10790
10791 let autoscroll = if action.center_cursor {
10792 Autoscroll::center()
10793 } else {
10794 Autoscroll::fit()
10795 };
10796
10797 let text_layout_details = &self.text_layout_details(window);
10798 self.change_selections(Some(autoscroll), window, cx, |s| {
10799 s.move_with(|map, selection| {
10800 if !selection.is_empty() {
10801 selection.goal = SelectionGoal::None;
10802 }
10803 let (cursor, goal) = movement::down_by_rows(
10804 map,
10805 selection.end,
10806 row_count,
10807 selection.goal,
10808 false,
10809 text_layout_details,
10810 );
10811 selection.collapse_to(cursor, goal);
10812 });
10813 });
10814 }
10815
10816 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10817 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10818 let text_layout_details = &self.text_layout_details(window);
10819 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10820 s.move_heads_with(|map, head, goal| {
10821 movement::down(map, head, goal, false, text_layout_details)
10822 })
10823 });
10824 }
10825
10826 pub fn context_menu_first(
10827 &mut self,
10828 _: &ContextMenuFirst,
10829 _window: &mut Window,
10830 cx: &mut Context<Self>,
10831 ) {
10832 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10833 context_menu.select_first(self.completion_provider.as_deref(), cx);
10834 }
10835 }
10836
10837 pub fn context_menu_prev(
10838 &mut self,
10839 _: &ContextMenuPrevious,
10840 _window: &mut Window,
10841 cx: &mut Context<Self>,
10842 ) {
10843 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10844 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10845 }
10846 }
10847
10848 pub fn context_menu_next(
10849 &mut self,
10850 _: &ContextMenuNext,
10851 _window: &mut Window,
10852 cx: &mut Context<Self>,
10853 ) {
10854 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10855 context_menu.select_next(self.completion_provider.as_deref(), cx);
10856 }
10857 }
10858
10859 pub fn context_menu_last(
10860 &mut self,
10861 _: &ContextMenuLast,
10862 _window: &mut Window,
10863 cx: &mut Context<Self>,
10864 ) {
10865 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10866 context_menu.select_last(self.completion_provider.as_deref(), cx);
10867 }
10868 }
10869
10870 pub fn move_to_previous_word_start(
10871 &mut self,
10872 _: &MoveToPreviousWordStart,
10873 window: &mut Window,
10874 cx: &mut Context<Self>,
10875 ) {
10876 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10877 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10878 s.move_cursors_with(|map, head, _| {
10879 (
10880 movement::previous_word_start(map, head),
10881 SelectionGoal::None,
10882 )
10883 });
10884 })
10885 }
10886
10887 pub fn move_to_previous_subword_start(
10888 &mut self,
10889 _: &MoveToPreviousSubwordStart,
10890 window: &mut Window,
10891 cx: &mut Context<Self>,
10892 ) {
10893 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10894 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10895 s.move_cursors_with(|map, head, _| {
10896 (
10897 movement::previous_subword_start(map, head),
10898 SelectionGoal::None,
10899 )
10900 });
10901 })
10902 }
10903
10904 pub fn select_to_previous_word_start(
10905 &mut self,
10906 _: &SelectToPreviousWordStart,
10907 window: &mut Window,
10908 cx: &mut Context<Self>,
10909 ) {
10910 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10911 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10912 s.move_heads_with(|map, head, _| {
10913 (
10914 movement::previous_word_start(map, head),
10915 SelectionGoal::None,
10916 )
10917 });
10918 })
10919 }
10920
10921 pub fn select_to_previous_subword_start(
10922 &mut self,
10923 _: &SelectToPreviousSubwordStart,
10924 window: &mut Window,
10925 cx: &mut Context<Self>,
10926 ) {
10927 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10928 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10929 s.move_heads_with(|map, head, _| {
10930 (
10931 movement::previous_subword_start(map, head),
10932 SelectionGoal::None,
10933 )
10934 });
10935 })
10936 }
10937
10938 pub fn delete_to_previous_word_start(
10939 &mut self,
10940 action: &DeleteToPreviousWordStart,
10941 window: &mut Window,
10942 cx: &mut Context<Self>,
10943 ) {
10944 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10945 self.transact(window, cx, |this, window, cx| {
10946 this.select_autoclose_pair(window, cx);
10947 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10948 s.move_with(|map, selection| {
10949 if selection.is_empty() {
10950 let cursor = if action.ignore_newlines {
10951 movement::previous_word_start(map, selection.head())
10952 } else {
10953 movement::previous_word_start_or_newline(map, selection.head())
10954 };
10955 selection.set_head(cursor, SelectionGoal::None);
10956 }
10957 });
10958 });
10959 this.insert("", window, cx);
10960 });
10961 }
10962
10963 pub fn delete_to_previous_subword_start(
10964 &mut self,
10965 _: &DeleteToPreviousSubwordStart,
10966 window: &mut Window,
10967 cx: &mut Context<Self>,
10968 ) {
10969 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10970 self.transact(window, cx, |this, window, cx| {
10971 this.select_autoclose_pair(window, cx);
10972 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10973 s.move_with(|map, selection| {
10974 if selection.is_empty() {
10975 let cursor = movement::previous_subword_start(map, selection.head());
10976 selection.set_head(cursor, SelectionGoal::None);
10977 }
10978 });
10979 });
10980 this.insert("", window, cx);
10981 });
10982 }
10983
10984 pub fn move_to_next_word_end(
10985 &mut self,
10986 _: &MoveToNextWordEnd,
10987 window: &mut Window,
10988 cx: &mut Context<Self>,
10989 ) {
10990 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10991 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10992 s.move_cursors_with(|map, head, _| {
10993 (movement::next_word_end(map, head), SelectionGoal::None)
10994 });
10995 })
10996 }
10997
10998 pub fn move_to_next_subword_end(
10999 &mut self,
11000 _: &MoveToNextSubwordEnd,
11001 window: &mut Window,
11002 cx: &mut Context<Self>,
11003 ) {
11004 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11005 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11006 s.move_cursors_with(|map, head, _| {
11007 (movement::next_subword_end(map, head), SelectionGoal::None)
11008 });
11009 })
11010 }
11011
11012 pub fn select_to_next_word_end(
11013 &mut self,
11014 _: &SelectToNextWordEnd,
11015 window: &mut Window,
11016 cx: &mut Context<Self>,
11017 ) {
11018 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11019 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11020 s.move_heads_with(|map, head, _| {
11021 (movement::next_word_end(map, head), SelectionGoal::None)
11022 });
11023 })
11024 }
11025
11026 pub fn select_to_next_subword_end(
11027 &mut self,
11028 _: &SelectToNextSubwordEnd,
11029 window: &mut Window,
11030 cx: &mut Context<Self>,
11031 ) {
11032 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11033 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11034 s.move_heads_with(|map, head, _| {
11035 (movement::next_subword_end(map, head), SelectionGoal::None)
11036 });
11037 })
11038 }
11039
11040 pub fn delete_to_next_word_end(
11041 &mut self,
11042 action: &DeleteToNextWordEnd,
11043 window: &mut Window,
11044 cx: &mut Context<Self>,
11045 ) {
11046 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11047 self.transact(window, cx, |this, window, cx| {
11048 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11049 s.move_with(|map, selection| {
11050 if selection.is_empty() {
11051 let cursor = if action.ignore_newlines {
11052 movement::next_word_end(map, selection.head())
11053 } else {
11054 movement::next_word_end_or_newline(map, selection.head())
11055 };
11056 selection.set_head(cursor, SelectionGoal::None);
11057 }
11058 });
11059 });
11060 this.insert("", window, cx);
11061 });
11062 }
11063
11064 pub fn delete_to_next_subword_end(
11065 &mut self,
11066 _: &DeleteToNextSubwordEnd,
11067 window: &mut Window,
11068 cx: &mut Context<Self>,
11069 ) {
11070 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11071 self.transact(window, cx, |this, window, cx| {
11072 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11073 s.move_with(|map, selection| {
11074 if selection.is_empty() {
11075 let cursor = movement::next_subword_end(map, selection.head());
11076 selection.set_head(cursor, SelectionGoal::None);
11077 }
11078 });
11079 });
11080 this.insert("", window, cx);
11081 });
11082 }
11083
11084 pub fn move_to_beginning_of_line(
11085 &mut self,
11086 action: &MoveToBeginningOfLine,
11087 window: &mut Window,
11088 cx: &mut Context<Self>,
11089 ) {
11090 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11091 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11092 s.move_cursors_with(|map, head, _| {
11093 (
11094 movement::indented_line_beginning(
11095 map,
11096 head,
11097 action.stop_at_soft_wraps,
11098 action.stop_at_indent,
11099 ),
11100 SelectionGoal::None,
11101 )
11102 });
11103 })
11104 }
11105
11106 pub fn select_to_beginning_of_line(
11107 &mut self,
11108 action: &SelectToBeginningOfLine,
11109 window: &mut Window,
11110 cx: &mut Context<Self>,
11111 ) {
11112 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11113 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11114 s.move_heads_with(|map, head, _| {
11115 (
11116 movement::indented_line_beginning(
11117 map,
11118 head,
11119 action.stop_at_soft_wraps,
11120 action.stop_at_indent,
11121 ),
11122 SelectionGoal::None,
11123 )
11124 });
11125 });
11126 }
11127
11128 pub fn delete_to_beginning_of_line(
11129 &mut self,
11130 action: &DeleteToBeginningOfLine,
11131 window: &mut Window,
11132 cx: &mut Context<Self>,
11133 ) {
11134 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11135 self.transact(window, cx, |this, window, cx| {
11136 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11137 s.move_with(|_, selection| {
11138 selection.reversed = true;
11139 });
11140 });
11141
11142 this.select_to_beginning_of_line(
11143 &SelectToBeginningOfLine {
11144 stop_at_soft_wraps: false,
11145 stop_at_indent: action.stop_at_indent,
11146 },
11147 window,
11148 cx,
11149 );
11150 this.backspace(&Backspace, window, cx);
11151 });
11152 }
11153
11154 pub fn move_to_end_of_line(
11155 &mut self,
11156 action: &MoveToEndOfLine,
11157 window: &mut Window,
11158 cx: &mut Context<Self>,
11159 ) {
11160 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11161 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11162 s.move_cursors_with(|map, head, _| {
11163 (
11164 movement::line_end(map, head, action.stop_at_soft_wraps),
11165 SelectionGoal::None,
11166 )
11167 });
11168 })
11169 }
11170
11171 pub fn select_to_end_of_line(
11172 &mut self,
11173 action: &SelectToEndOfLine,
11174 window: &mut Window,
11175 cx: &mut Context<Self>,
11176 ) {
11177 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11178 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11179 s.move_heads_with(|map, head, _| {
11180 (
11181 movement::line_end(map, head, action.stop_at_soft_wraps),
11182 SelectionGoal::None,
11183 )
11184 });
11185 })
11186 }
11187
11188 pub fn delete_to_end_of_line(
11189 &mut self,
11190 _: &DeleteToEndOfLine,
11191 window: &mut Window,
11192 cx: &mut Context<Self>,
11193 ) {
11194 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11195 self.transact(window, cx, |this, window, cx| {
11196 this.select_to_end_of_line(
11197 &SelectToEndOfLine {
11198 stop_at_soft_wraps: false,
11199 },
11200 window,
11201 cx,
11202 );
11203 this.delete(&Delete, window, cx);
11204 });
11205 }
11206
11207 pub fn cut_to_end_of_line(
11208 &mut self,
11209 _: &CutToEndOfLine,
11210 window: &mut Window,
11211 cx: &mut Context<Self>,
11212 ) {
11213 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11214 self.transact(window, cx, |this, window, cx| {
11215 this.select_to_end_of_line(
11216 &SelectToEndOfLine {
11217 stop_at_soft_wraps: false,
11218 },
11219 window,
11220 cx,
11221 );
11222 this.cut(&Cut, window, cx);
11223 });
11224 }
11225
11226 pub fn move_to_start_of_paragraph(
11227 &mut self,
11228 _: &MoveToStartOfParagraph,
11229 window: &mut Window,
11230 cx: &mut Context<Self>,
11231 ) {
11232 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11233 cx.propagate();
11234 return;
11235 }
11236 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11237 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11238 s.move_with(|map, selection| {
11239 selection.collapse_to(
11240 movement::start_of_paragraph(map, selection.head(), 1),
11241 SelectionGoal::None,
11242 )
11243 });
11244 })
11245 }
11246
11247 pub fn move_to_end_of_paragraph(
11248 &mut self,
11249 _: &MoveToEndOfParagraph,
11250 window: &mut Window,
11251 cx: &mut Context<Self>,
11252 ) {
11253 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11254 cx.propagate();
11255 return;
11256 }
11257 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11258 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11259 s.move_with(|map, selection| {
11260 selection.collapse_to(
11261 movement::end_of_paragraph(map, selection.head(), 1),
11262 SelectionGoal::None,
11263 )
11264 });
11265 })
11266 }
11267
11268 pub fn select_to_start_of_paragraph(
11269 &mut self,
11270 _: &SelectToStartOfParagraph,
11271 window: &mut Window,
11272 cx: &mut Context<Self>,
11273 ) {
11274 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11275 cx.propagate();
11276 return;
11277 }
11278 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11279 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11280 s.move_heads_with(|map, head, _| {
11281 (
11282 movement::start_of_paragraph(map, head, 1),
11283 SelectionGoal::None,
11284 )
11285 });
11286 })
11287 }
11288
11289 pub fn select_to_end_of_paragraph(
11290 &mut self,
11291 _: &SelectToEndOfParagraph,
11292 window: &mut Window,
11293 cx: &mut Context<Self>,
11294 ) {
11295 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11296 cx.propagate();
11297 return;
11298 }
11299 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11300 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11301 s.move_heads_with(|map, head, _| {
11302 (
11303 movement::end_of_paragraph(map, head, 1),
11304 SelectionGoal::None,
11305 )
11306 });
11307 })
11308 }
11309
11310 pub fn move_to_start_of_excerpt(
11311 &mut self,
11312 _: &MoveToStartOfExcerpt,
11313 window: &mut Window,
11314 cx: &mut Context<Self>,
11315 ) {
11316 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11317 cx.propagate();
11318 return;
11319 }
11320 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11321 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11322 s.move_with(|map, selection| {
11323 selection.collapse_to(
11324 movement::start_of_excerpt(
11325 map,
11326 selection.head(),
11327 workspace::searchable::Direction::Prev,
11328 ),
11329 SelectionGoal::None,
11330 )
11331 });
11332 })
11333 }
11334
11335 pub fn move_to_start_of_next_excerpt(
11336 &mut self,
11337 _: &MoveToStartOfNextExcerpt,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 ) {
11341 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11342 cx.propagate();
11343 return;
11344 }
11345
11346 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11347 s.move_with(|map, selection| {
11348 selection.collapse_to(
11349 movement::start_of_excerpt(
11350 map,
11351 selection.head(),
11352 workspace::searchable::Direction::Next,
11353 ),
11354 SelectionGoal::None,
11355 )
11356 });
11357 })
11358 }
11359
11360 pub fn move_to_end_of_excerpt(
11361 &mut self,
11362 _: &MoveToEndOfExcerpt,
11363 window: &mut Window,
11364 cx: &mut Context<Self>,
11365 ) {
11366 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11367 cx.propagate();
11368 return;
11369 }
11370 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11371 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11372 s.move_with(|map, selection| {
11373 selection.collapse_to(
11374 movement::end_of_excerpt(
11375 map,
11376 selection.head(),
11377 workspace::searchable::Direction::Next,
11378 ),
11379 SelectionGoal::None,
11380 )
11381 });
11382 })
11383 }
11384
11385 pub fn move_to_end_of_previous_excerpt(
11386 &mut self,
11387 _: &MoveToEndOfPreviousExcerpt,
11388 window: &mut Window,
11389 cx: &mut Context<Self>,
11390 ) {
11391 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11392 cx.propagate();
11393 return;
11394 }
11395 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11396 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11397 s.move_with(|map, selection| {
11398 selection.collapse_to(
11399 movement::end_of_excerpt(
11400 map,
11401 selection.head(),
11402 workspace::searchable::Direction::Prev,
11403 ),
11404 SelectionGoal::None,
11405 )
11406 });
11407 })
11408 }
11409
11410 pub fn select_to_start_of_excerpt(
11411 &mut self,
11412 _: &SelectToStartOfExcerpt,
11413 window: &mut Window,
11414 cx: &mut Context<Self>,
11415 ) {
11416 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11417 cx.propagate();
11418 return;
11419 }
11420 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11421 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11422 s.move_heads_with(|map, head, _| {
11423 (
11424 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11425 SelectionGoal::None,
11426 )
11427 });
11428 })
11429 }
11430
11431 pub fn select_to_start_of_next_excerpt(
11432 &mut self,
11433 _: &SelectToStartOfNextExcerpt,
11434 window: &mut Window,
11435 cx: &mut Context<Self>,
11436 ) {
11437 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11438 cx.propagate();
11439 return;
11440 }
11441 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11442 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11443 s.move_heads_with(|map, head, _| {
11444 (
11445 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11446 SelectionGoal::None,
11447 )
11448 });
11449 })
11450 }
11451
11452 pub fn select_to_end_of_excerpt(
11453 &mut self,
11454 _: &SelectToEndOfExcerpt,
11455 window: &mut Window,
11456 cx: &mut Context<Self>,
11457 ) {
11458 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11459 cx.propagate();
11460 return;
11461 }
11462 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11463 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11464 s.move_heads_with(|map, head, _| {
11465 (
11466 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11467 SelectionGoal::None,
11468 )
11469 });
11470 })
11471 }
11472
11473 pub fn select_to_end_of_previous_excerpt(
11474 &mut self,
11475 _: &SelectToEndOfPreviousExcerpt,
11476 window: &mut Window,
11477 cx: &mut Context<Self>,
11478 ) {
11479 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11480 cx.propagate();
11481 return;
11482 }
11483 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11484 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11485 s.move_heads_with(|map, head, _| {
11486 (
11487 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11488 SelectionGoal::None,
11489 )
11490 });
11491 })
11492 }
11493
11494 pub fn move_to_beginning(
11495 &mut self,
11496 _: &MoveToBeginning,
11497 window: &mut Window,
11498 cx: &mut Context<Self>,
11499 ) {
11500 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11501 cx.propagate();
11502 return;
11503 }
11504 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11505 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11506 s.select_ranges(vec![0..0]);
11507 });
11508 }
11509
11510 pub fn select_to_beginning(
11511 &mut self,
11512 _: &SelectToBeginning,
11513 window: &mut Window,
11514 cx: &mut Context<Self>,
11515 ) {
11516 let mut selection = self.selections.last::<Point>(cx);
11517 selection.set_head(Point::zero(), SelectionGoal::None);
11518 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11519 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11520 s.select(vec![selection]);
11521 });
11522 }
11523
11524 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11525 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11526 cx.propagate();
11527 return;
11528 }
11529 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11530 let cursor = self.buffer.read(cx).read(cx).len();
11531 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11532 s.select_ranges(vec![cursor..cursor])
11533 });
11534 }
11535
11536 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11537 self.nav_history = nav_history;
11538 }
11539
11540 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11541 self.nav_history.as_ref()
11542 }
11543
11544 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11545 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11546 }
11547
11548 fn push_to_nav_history(
11549 &mut self,
11550 cursor_anchor: Anchor,
11551 new_position: Option<Point>,
11552 is_deactivate: bool,
11553 cx: &mut Context<Self>,
11554 ) {
11555 if let Some(nav_history) = self.nav_history.as_mut() {
11556 let buffer = self.buffer.read(cx).read(cx);
11557 let cursor_position = cursor_anchor.to_point(&buffer);
11558 let scroll_state = self.scroll_manager.anchor();
11559 let scroll_top_row = scroll_state.top_row(&buffer);
11560 drop(buffer);
11561
11562 if let Some(new_position) = new_position {
11563 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11564 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11565 return;
11566 }
11567 }
11568
11569 nav_history.push(
11570 Some(NavigationData {
11571 cursor_anchor,
11572 cursor_position,
11573 scroll_anchor: scroll_state,
11574 scroll_top_row,
11575 }),
11576 cx,
11577 );
11578 cx.emit(EditorEvent::PushedToNavHistory {
11579 anchor: cursor_anchor,
11580 is_deactivate,
11581 })
11582 }
11583 }
11584
11585 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11586 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11587 let buffer = self.buffer.read(cx).snapshot(cx);
11588 let mut selection = self.selections.first::<usize>(cx);
11589 selection.set_head(buffer.len(), SelectionGoal::None);
11590 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11591 s.select(vec![selection]);
11592 });
11593 }
11594
11595 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11596 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11597 let end = self.buffer.read(cx).read(cx).len();
11598 self.change_selections(None, window, cx, |s| {
11599 s.select_ranges(vec![0..end]);
11600 });
11601 }
11602
11603 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11604 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11605 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11606 let mut selections = self.selections.all::<Point>(cx);
11607 let max_point = display_map.buffer_snapshot.max_point();
11608 for selection in &mut selections {
11609 let rows = selection.spanned_rows(true, &display_map);
11610 selection.start = Point::new(rows.start.0, 0);
11611 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11612 selection.reversed = false;
11613 }
11614 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11615 s.select(selections);
11616 });
11617 }
11618
11619 pub fn split_selection_into_lines(
11620 &mut self,
11621 _: &SplitSelectionIntoLines,
11622 window: &mut Window,
11623 cx: &mut Context<Self>,
11624 ) {
11625 let selections = self
11626 .selections
11627 .all::<Point>(cx)
11628 .into_iter()
11629 .map(|selection| selection.start..selection.end)
11630 .collect::<Vec<_>>();
11631 self.unfold_ranges(&selections, true, true, cx);
11632
11633 let mut new_selection_ranges = Vec::new();
11634 {
11635 let buffer = self.buffer.read(cx).read(cx);
11636 for selection in selections {
11637 for row in selection.start.row..selection.end.row {
11638 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11639 new_selection_ranges.push(cursor..cursor);
11640 }
11641
11642 let is_multiline_selection = selection.start.row != selection.end.row;
11643 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11644 // so this action feels more ergonomic when paired with other selection operations
11645 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11646 if !should_skip_last {
11647 new_selection_ranges.push(selection.end..selection.end);
11648 }
11649 }
11650 }
11651 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11652 s.select_ranges(new_selection_ranges);
11653 });
11654 }
11655
11656 pub fn add_selection_above(
11657 &mut self,
11658 _: &AddSelectionAbove,
11659 window: &mut Window,
11660 cx: &mut Context<Self>,
11661 ) {
11662 self.add_selection(true, window, cx);
11663 }
11664
11665 pub fn add_selection_below(
11666 &mut self,
11667 _: &AddSelectionBelow,
11668 window: &mut Window,
11669 cx: &mut Context<Self>,
11670 ) {
11671 self.add_selection(false, window, cx);
11672 }
11673
11674 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11675 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11676
11677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11678 let mut selections = self.selections.all::<Point>(cx);
11679 let text_layout_details = self.text_layout_details(window);
11680 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11681 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11682 let range = oldest_selection.display_range(&display_map).sorted();
11683
11684 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11685 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11686 let positions = start_x.min(end_x)..start_x.max(end_x);
11687
11688 selections.clear();
11689 let mut stack = Vec::new();
11690 for row in range.start.row().0..=range.end.row().0 {
11691 if let Some(selection) = self.selections.build_columnar_selection(
11692 &display_map,
11693 DisplayRow(row),
11694 &positions,
11695 oldest_selection.reversed,
11696 &text_layout_details,
11697 ) {
11698 stack.push(selection.id);
11699 selections.push(selection);
11700 }
11701 }
11702
11703 if above {
11704 stack.reverse();
11705 }
11706
11707 AddSelectionsState { above, stack }
11708 });
11709
11710 let last_added_selection = *state.stack.last().unwrap();
11711 let mut new_selections = Vec::new();
11712 if above == state.above {
11713 let end_row = if above {
11714 DisplayRow(0)
11715 } else {
11716 display_map.max_point().row()
11717 };
11718
11719 'outer: for selection in selections {
11720 if selection.id == last_added_selection {
11721 let range = selection.display_range(&display_map).sorted();
11722 debug_assert_eq!(range.start.row(), range.end.row());
11723 let mut row = range.start.row();
11724 let positions =
11725 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11726 px(start)..px(end)
11727 } else {
11728 let start_x =
11729 display_map.x_for_display_point(range.start, &text_layout_details);
11730 let end_x =
11731 display_map.x_for_display_point(range.end, &text_layout_details);
11732 start_x.min(end_x)..start_x.max(end_x)
11733 };
11734
11735 while row != end_row {
11736 if above {
11737 row.0 -= 1;
11738 } else {
11739 row.0 += 1;
11740 }
11741
11742 if let Some(new_selection) = self.selections.build_columnar_selection(
11743 &display_map,
11744 row,
11745 &positions,
11746 selection.reversed,
11747 &text_layout_details,
11748 ) {
11749 state.stack.push(new_selection.id);
11750 if above {
11751 new_selections.push(new_selection);
11752 new_selections.push(selection);
11753 } else {
11754 new_selections.push(selection);
11755 new_selections.push(new_selection);
11756 }
11757
11758 continue 'outer;
11759 }
11760 }
11761 }
11762
11763 new_selections.push(selection);
11764 }
11765 } else {
11766 new_selections = selections;
11767 new_selections.retain(|s| s.id != last_added_selection);
11768 state.stack.pop();
11769 }
11770
11771 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11772 s.select(new_selections);
11773 });
11774 if state.stack.len() > 1 {
11775 self.add_selections_state = Some(state);
11776 }
11777 }
11778
11779 pub fn select_next_match_internal(
11780 &mut self,
11781 display_map: &DisplaySnapshot,
11782 replace_newest: bool,
11783 autoscroll: Option<Autoscroll>,
11784 window: &mut Window,
11785 cx: &mut Context<Self>,
11786 ) -> Result<()> {
11787 fn select_next_match_ranges(
11788 this: &mut Editor,
11789 range: Range<usize>,
11790 replace_newest: bool,
11791 auto_scroll: Option<Autoscroll>,
11792 window: &mut Window,
11793 cx: &mut Context<Editor>,
11794 ) {
11795 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
11796 this.change_selections(auto_scroll, window, cx, |s| {
11797 if replace_newest {
11798 s.delete(s.newest_anchor().id);
11799 }
11800 s.insert_range(range.clone());
11801 });
11802 }
11803
11804 let buffer = &display_map.buffer_snapshot;
11805 let mut selections = self.selections.all::<usize>(cx);
11806 if let Some(mut select_next_state) = self.select_next_state.take() {
11807 let query = &select_next_state.query;
11808 if !select_next_state.done {
11809 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11810 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11811 let mut next_selected_range = None;
11812
11813 let bytes_after_last_selection =
11814 buffer.bytes_in_range(last_selection.end..buffer.len());
11815 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11816 let query_matches = query
11817 .stream_find_iter(bytes_after_last_selection)
11818 .map(|result| (last_selection.end, result))
11819 .chain(
11820 query
11821 .stream_find_iter(bytes_before_first_selection)
11822 .map(|result| (0, result)),
11823 );
11824
11825 for (start_offset, query_match) in query_matches {
11826 let query_match = query_match.unwrap(); // can only fail due to I/O
11827 let offset_range =
11828 start_offset + query_match.start()..start_offset + query_match.end();
11829 let display_range = offset_range.start.to_display_point(display_map)
11830 ..offset_range.end.to_display_point(display_map);
11831
11832 if !select_next_state.wordwise
11833 || (!movement::is_inside_word(display_map, display_range.start)
11834 && !movement::is_inside_word(display_map, display_range.end))
11835 {
11836 // TODO: This is n^2, because we might check all the selections
11837 if !selections
11838 .iter()
11839 .any(|selection| selection.range().overlaps(&offset_range))
11840 {
11841 next_selected_range = Some(offset_range);
11842 break;
11843 }
11844 }
11845 }
11846
11847 if let Some(next_selected_range) = next_selected_range {
11848 select_next_match_ranges(
11849 self,
11850 next_selected_range,
11851 replace_newest,
11852 autoscroll,
11853 window,
11854 cx,
11855 );
11856 } else {
11857 select_next_state.done = true;
11858 }
11859 }
11860
11861 self.select_next_state = Some(select_next_state);
11862 } else {
11863 let mut only_carets = true;
11864 let mut same_text_selected = true;
11865 let mut selected_text = None;
11866
11867 let mut selections_iter = selections.iter().peekable();
11868 while let Some(selection) = selections_iter.next() {
11869 if selection.start != selection.end {
11870 only_carets = false;
11871 }
11872
11873 if same_text_selected {
11874 if selected_text.is_none() {
11875 selected_text =
11876 Some(buffer.text_for_range(selection.range()).collect::<String>());
11877 }
11878
11879 if let Some(next_selection) = selections_iter.peek() {
11880 if next_selection.range().len() == selection.range().len() {
11881 let next_selected_text = buffer
11882 .text_for_range(next_selection.range())
11883 .collect::<String>();
11884 if Some(next_selected_text) != selected_text {
11885 same_text_selected = false;
11886 selected_text = None;
11887 }
11888 } else {
11889 same_text_selected = false;
11890 selected_text = None;
11891 }
11892 }
11893 }
11894 }
11895
11896 if only_carets {
11897 for selection in &mut selections {
11898 let word_range = movement::surrounding_word(
11899 display_map,
11900 selection.start.to_display_point(display_map),
11901 );
11902 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11903 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11904 selection.goal = SelectionGoal::None;
11905 selection.reversed = false;
11906 select_next_match_ranges(
11907 self,
11908 selection.start..selection.end,
11909 replace_newest,
11910 autoscroll,
11911 window,
11912 cx,
11913 );
11914 }
11915
11916 if selections.len() == 1 {
11917 let selection = selections
11918 .last()
11919 .expect("ensured that there's only one selection");
11920 let query = buffer
11921 .text_for_range(selection.start..selection.end)
11922 .collect::<String>();
11923 let is_empty = query.is_empty();
11924 let select_state = SelectNextState {
11925 query: AhoCorasick::new(&[query])?,
11926 wordwise: true,
11927 done: is_empty,
11928 };
11929 self.select_next_state = Some(select_state);
11930 } else {
11931 self.select_next_state = None;
11932 }
11933 } else if let Some(selected_text) = selected_text {
11934 self.select_next_state = Some(SelectNextState {
11935 query: AhoCorasick::new(&[selected_text])?,
11936 wordwise: false,
11937 done: false,
11938 });
11939 self.select_next_match_internal(
11940 display_map,
11941 replace_newest,
11942 autoscroll,
11943 window,
11944 cx,
11945 )?;
11946 }
11947 }
11948 Ok(())
11949 }
11950
11951 pub fn select_all_matches(
11952 &mut self,
11953 _action: &SelectAllMatches,
11954 window: &mut Window,
11955 cx: &mut Context<Self>,
11956 ) -> Result<()> {
11957 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11958
11959 self.push_to_selection_history();
11960 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11961
11962 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11963 let Some(select_next_state) = self.select_next_state.as_mut() else {
11964 return Ok(());
11965 };
11966 if select_next_state.done {
11967 return Ok(());
11968 }
11969
11970 let mut new_selections = Vec::new();
11971
11972 let reversed = self.selections.oldest::<usize>(cx).reversed;
11973 let buffer = &display_map.buffer_snapshot;
11974 let query_matches = select_next_state
11975 .query
11976 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11977
11978 for query_match in query_matches.into_iter() {
11979 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
11980 let offset_range = if reversed {
11981 query_match.end()..query_match.start()
11982 } else {
11983 query_match.start()..query_match.end()
11984 };
11985 let display_range = offset_range.start.to_display_point(&display_map)
11986 ..offset_range.end.to_display_point(&display_map);
11987
11988 if !select_next_state.wordwise
11989 || (!movement::is_inside_word(&display_map, display_range.start)
11990 && !movement::is_inside_word(&display_map, display_range.end))
11991 {
11992 new_selections.push(offset_range.start..offset_range.end);
11993 }
11994 }
11995
11996 select_next_state.done = true;
11997 self.unfold_ranges(&new_selections.clone(), false, false, cx);
11998 self.change_selections(None, window, cx, |selections| {
11999 selections.select_ranges(new_selections)
12000 });
12001
12002 Ok(())
12003 }
12004
12005 pub fn select_next(
12006 &mut self,
12007 action: &SelectNext,
12008 window: &mut Window,
12009 cx: &mut Context<Self>,
12010 ) -> Result<()> {
12011 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12012 self.push_to_selection_history();
12013 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12014 self.select_next_match_internal(
12015 &display_map,
12016 action.replace_newest,
12017 Some(Autoscroll::newest()),
12018 window,
12019 cx,
12020 )?;
12021 Ok(())
12022 }
12023
12024 pub fn select_previous(
12025 &mut self,
12026 action: &SelectPrevious,
12027 window: &mut Window,
12028 cx: &mut Context<Self>,
12029 ) -> Result<()> {
12030 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12031 self.push_to_selection_history();
12032 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12033 let buffer = &display_map.buffer_snapshot;
12034 let mut selections = self.selections.all::<usize>(cx);
12035 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12036 let query = &select_prev_state.query;
12037 if !select_prev_state.done {
12038 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12039 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12040 let mut next_selected_range = None;
12041 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12042 let bytes_before_last_selection =
12043 buffer.reversed_bytes_in_range(0..last_selection.start);
12044 let bytes_after_first_selection =
12045 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12046 let query_matches = query
12047 .stream_find_iter(bytes_before_last_selection)
12048 .map(|result| (last_selection.start, result))
12049 .chain(
12050 query
12051 .stream_find_iter(bytes_after_first_selection)
12052 .map(|result| (buffer.len(), result)),
12053 );
12054 for (end_offset, query_match) in query_matches {
12055 let query_match = query_match.unwrap(); // can only fail due to I/O
12056 let offset_range =
12057 end_offset - query_match.end()..end_offset - query_match.start();
12058 let display_range = offset_range.start.to_display_point(&display_map)
12059 ..offset_range.end.to_display_point(&display_map);
12060
12061 if !select_prev_state.wordwise
12062 || (!movement::is_inside_word(&display_map, display_range.start)
12063 && !movement::is_inside_word(&display_map, display_range.end))
12064 {
12065 next_selected_range = Some(offset_range);
12066 break;
12067 }
12068 }
12069
12070 if let Some(next_selected_range) = next_selected_range {
12071 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
12072 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12073 if action.replace_newest {
12074 s.delete(s.newest_anchor().id);
12075 }
12076 s.insert_range(next_selected_range);
12077 });
12078 } else {
12079 select_prev_state.done = true;
12080 }
12081 }
12082
12083 self.select_prev_state = Some(select_prev_state);
12084 } else {
12085 let mut only_carets = true;
12086 let mut same_text_selected = true;
12087 let mut selected_text = None;
12088
12089 let mut selections_iter = selections.iter().peekable();
12090 while let Some(selection) = selections_iter.next() {
12091 if selection.start != selection.end {
12092 only_carets = false;
12093 }
12094
12095 if same_text_selected {
12096 if selected_text.is_none() {
12097 selected_text =
12098 Some(buffer.text_for_range(selection.range()).collect::<String>());
12099 }
12100
12101 if let Some(next_selection) = selections_iter.peek() {
12102 if next_selection.range().len() == selection.range().len() {
12103 let next_selected_text = buffer
12104 .text_for_range(next_selection.range())
12105 .collect::<String>();
12106 if Some(next_selected_text) != selected_text {
12107 same_text_selected = false;
12108 selected_text = None;
12109 }
12110 } else {
12111 same_text_selected = false;
12112 selected_text = None;
12113 }
12114 }
12115 }
12116 }
12117
12118 if only_carets {
12119 for selection in &mut selections {
12120 let word_range = movement::surrounding_word(
12121 &display_map,
12122 selection.start.to_display_point(&display_map),
12123 );
12124 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12125 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12126 selection.goal = SelectionGoal::None;
12127 selection.reversed = false;
12128 }
12129 if selections.len() == 1 {
12130 let selection = selections
12131 .last()
12132 .expect("ensured that there's only one selection");
12133 let query = buffer
12134 .text_for_range(selection.start..selection.end)
12135 .collect::<String>();
12136 let is_empty = query.is_empty();
12137 let select_state = SelectNextState {
12138 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12139 wordwise: true,
12140 done: is_empty,
12141 };
12142 self.select_prev_state = Some(select_state);
12143 } else {
12144 self.select_prev_state = None;
12145 }
12146
12147 self.unfold_ranges(
12148 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
12149 false,
12150 true,
12151 cx,
12152 );
12153 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12154 s.select(selections);
12155 });
12156 } else if let Some(selected_text) = selected_text {
12157 self.select_prev_state = Some(SelectNextState {
12158 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12159 wordwise: false,
12160 done: false,
12161 });
12162 self.select_previous(action, window, cx)?;
12163 }
12164 }
12165 Ok(())
12166 }
12167
12168 pub fn find_next_match(
12169 &mut self,
12170 _: &FindNextMatch,
12171 window: &mut Window,
12172 cx: &mut Context<Self>,
12173 ) -> Result<()> {
12174 let selections = self.selections.disjoint_anchors();
12175 match selections.first() {
12176 Some(first) if selections.len() >= 2 => {
12177 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12178 s.select_ranges([first.range()]);
12179 });
12180 }
12181 _ => self.select_next(
12182 &SelectNext {
12183 replace_newest: true,
12184 },
12185 window,
12186 cx,
12187 )?,
12188 }
12189 Ok(())
12190 }
12191
12192 pub fn find_previous_match(
12193 &mut self,
12194 _: &FindPreviousMatch,
12195 window: &mut Window,
12196 cx: &mut Context<Self>,
12197 ) -> Result<()> {
12198 let selections = self.selections.disjoint_anchors();
12199 match selections.last() {
12200 Some(last) if selections.len() >= 2 => {
12201 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12202 s.select_ranges([last.range()]);
12203 });
12204 }
12205 _ => self.select_previous(
12206 &SelectPrevious {
12207 replace_newest: true,
12208 },
12209 window,
12210 cx,
12211 )?,
12212 }
12213 Ok(())
12214 }
12215
12216 pub fn toggle_comments(
12217 &mut self,
12218 action: &ToggleComments,
12219 window: &mut Window,
12220 cx: &mut Context<Self>,
12221 ) {
12222 if self.read_only(cx) {
12223 return;
12224 }
12225 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12226 let text_layout_details = &self.text_layout_details(window);
12227 self.transact(window, cx, |this, window, cx| {
12228 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12229 let mut edits = Vec::new();
12230 let mut selection_edit_ranges = Vec::new();
12231 let mut last_toggled_row = None;
12232 let snapshot = this.buffer.read(cx).read(cx);
12233 let empty_str: Arc<str> = Arc::default();
12234 let mut suffixes_inserted = Vec::new();
12235 let ignore_indent = action.ignore_indent;
12236
12237 fn comment_prefix_range(
12238 snapshot: &MultiBufferSnapshot,
12239 row: MultiBufferRow,
12240 comment_prefix: &str,
12241 comment_prefix_whitespace: &str,
12242 ignore_indent: bool,
12243 ) -> Range<Point> {
12244 let indent_size = if ignore_indent {
12245 0
12246 } else {
12247 snapshot.indent_size_for_line(row).len
12248 };
12249
12250 let start = Point::new(row.0, indent_size);
12251
12252 let mut line_bytes = snapshot
12253 .bytes_in_range(start..snapshot.max_point())
12254 .flatten()
12255 .copied();
12256
12257 // If this line currently begins with the line comment prefix, then record
12258 // the range containing the prefix.
12259 if line_bytes
12260 .by_ref()
12261 .take(comment_prefix.len())
12262 .eq(comment_prefix.bytes())
12263 {
12264 // Include any whitespace that matches the comment prefix.
12265 let matching_whitespace_len = line_bytes
12266 .zip(comment_prefix_whitespace.bytes())
12267 .take_while(|(a, b)| a == b)
12268 .count() as u32;
12269 let end = Point::new(
12270 start.row,
12271 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12272 );
12273 start..end
12274 } else {
12275 start..start
12276 }
12277 }
12278
12279 fn comment_suffix_range(
12280 snapshot: &MultiBufferSnapshot,
12281 row: MultiBufferRow,
12282 comment_suffix: &str,
12283 comment_suffix_has_leading_space: bool,
12284 ) -> Range<Point> {
12285 let end = Point::new(row.0, snapshot.line_len(row));
12286 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12287
12288 let mut line_end_bytes = snapshot
12289 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12290 .flatten()
12291 .copied();
12292
12293 let leading_space_len = if suffix_start_column > 0
12294 && line_end_bytes.next() == Some(b' ')
12295 && comment_suffix_has_leading_space
12296 {
12297 1
12298 } else {
12299 0
12300 };
12301
12302 // If this line currently begins with the line comment prefix, then record
12303 // the range containing the prefix.
12304 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12305 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12306 start..end
12307 } else {
12308 end..end
12309 }
12310 }
12311
12312 // TODO: Handle selections that cross excerpts
12313 for selection in &mut selections {
12314 let start_column = snapshot
12315 .indent_size_for_line(MultiBufferRow(selection.start.row))
12316 .len;
12317 let language = if let Some(language) =
12318 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12319 {
12320 language
12321 } else {
12322 continue;
12323 };
12324
12325 selection_edit_ranges.clear();
12326
12327 // If multiple selections contain a given row, avoid processing that
12328 // row more than once.
12329 let mut start_row = MultiBufferRow(selection.start.row);
12330 if last_toggled_row == Some(start_row) {
12331 start_row = start_row.next_row();
12332 }
12333 let end_row =
12334 if selection.end.row > selection.start.row && selection.end.column == 0 {
12335 MultiBufferRow(selection.end.row - 1)
12336 } else {
12337 MultiBufferRow(selection.end.row)
12338 };
12339 last_toggled_row = Some(end_row);
12340
12341 if start_row > end_row {
12342 continue;
12343 }
12344
12345 // If the language has line comments, toggle those.
12346 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12347
12348 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12349 if ignore_indent {
12350 full_comment_prefixes = full_comment_prefixes
12351 .into_iter()
12352 .map(|s| Arc::from(s.trim_end()))
12353 .collect();
12354 }
12355
12356 if !full_comment_prefixes.is_empty() {
12357 let first_prefix = full_comment_prefixes
12358 .first()
12359 .expect("prefixes is non-empty");
12360 let prefix_trimmed_lengths = full_comment_prefixes
12361 .iter()
12362 .map(|p| p.trim_end_matches(' ').len())
12363 .collect::<SmallVec<[usize; 4]>>();
12364
12365 let mut all_selection_lines_are_comments = true;
12366
12367 for row in start_row.0..=end_row.0 {
12368 let row = MultiBufferRow(row);
12369 if start_row < end_row && snapshot.is_line_blank(row) {
12370 continue;
12371 }
12372
12373 let prefix_range = full_comment_prefixes
12374 .iter()
12375 .zip(prefix_trimmed_lengths.iter().copied())
12376 .map(|(prefix, trimmed_prefix_len)| {
12377 comment_prefix_range(
12378 snapshot.deref(),
12379 row,
12380 &prefix[..trimmed_prefix_len],
12381 &prefix[trimmed_prefix_len..],
12382 ignore_indent,
12383 )
12384 })
12385 .max_by_key(|range| range.end.column - range.start.column)
12386 .expect("prefixes is non-empty");
12387
12388 if prefix_range.is_empty() {
12389 all_selection_lines_are_comments = false;
12390 }
12391
12392 selection_edit_ranges.push(prefix_range);
12393 }
12394
12395 if all_selection_lines_are_comments {
12396 edits.extend(
12397 selection_edit_ranges
12398 .iter()
12399 .cloned()
12400 .map(|range| (range, empty_str.clone())),
12401 );
12402 } else {
12403 let min_column = selection_edit_ranges
12404 .iter()
12405 .map(|range| range.start.column)
12406 .min()
12407 .unwrap_or(0);
12408 edits.extend(selection_edit_ranges.iter().map(|range| {
12409 let position = Point::new(range.start.row, min_column);
12410 (position..position, first_prefix.clone())
12411 }));
12412 }
12413 } else if let Some((full_comment_prefix, comment_suffix)) =
12414 language.block_comment_delimiters()
12415 {
12416 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12417 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12418 let prefix_range = comment_prefix_range(
12419 snapshot.deref(),
12420 start_row,
12421 comment_prefix,
12422 comment_prefix_whitespace,
12423 ignore_indent,
12424 );
12425 let suffix_range = comment_suffix_range(
12426 snapshot.deref(),
12427 end_row,
12428 comment_suffix.trim_start_matches(' '),
12429 comment_suffix.starts_with(' '),
12430 );
12431
12432 if prefix_range.is_empty() || suffix_range.is_empty() {
12433 edits.push((
12434 prefix_range.start..prefix_range.start,
12435 full_comment_prefix.clone(),
12436 ));
12437 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12438 suffixes_inserted.push((end_row, comment_suffix.len()));
12439 } else {
12440 edits.push((prefix_range, empty_str.clone()));
12441 edits.push((suffix_range, empty_str.clone()));
12442 }
12443 } else {
12444 continue;
12445 }
12446 }
12447
12448 drop(snapshot);
12449 this.buffer.update(cx, |buffer, cx| {
12450 buffer.edit(edits, None, cx);
12451 });
12452
12453 // Adjust selections so that they end before any comment suffixes that
12454 // were inserted.
12455 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12456 let mut selections = this.selections.all::<Point>(cx);
12457 let snapshot = this.buffer.read(cx).read(cx);
12458 for selection in &mut selections {
12459 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12460 match row.cmp(&MultiBufferRow(selection.end.row)) {
12461 Ordering::Less => {
12462 suffixes_inserted.next();
12463 continue;
12464 }
12465 Ordering::Greater => break,
12466 Ordering::Equal => {
12467 if selection.end.column == snapshot.line_len(row) {
12468 if selection.is_empty() {
12469 selection.start.column -= suffix_len as u32;
12470 }
12471 selection.end.column -= suffix_len as u32;
12472 }
12473 break;
12474 }
12475 }
12476 }
12477 }
12478
12479 drop(snapshot);
12480 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12481 s.select(selections)
12482 });
12483
12484 let selections = this.selections.all::<Point>(cx);
12485 let selections_on_single_row = selections.windows(2).all(|selections| {
12486 selections[0].start.row == selections[1].start.row
12487 && selections[0].end.row == selections[1].end.row
12488 && selections[0].start.row == selections[0].end.row
12489 });
12490 let selections_selecting = selections
12491 .iter()
12492 .any(|selection| selection.start != selection.end);
12493 let advance_downwards = action.advance_downwards
12494 && selections_on_single_row
12495 && !selections_selecting
12496 && !matches!(this.mode, EditorMode::SingleLine { .. });
12497
12498 if advance_downwards {
12499 let snapshot = this.buffer.read(cx).snapshot(cx);
12500
12501 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12502 s.move_cursors_with(|display_snapshot, display_point, _| {
12503 let mut point = display_point.to_point(display_snapshot);
12504 point.row += 1;
12505 point = snapshot.clip_point(point, Bias::Left);
12506 let display_point = point.to_display_point(display_snapshot);
12507 let goal = SelectionGoal::HorizontalPosition(
12508 display_snapshot
12509 .x_for_display_point(display_point, text_layout_details)
12510 .into(),
12511 );
12512 (display_point, goal)
12513 })
12514 });
12515 }
12516 });
12517 }
12518
12519 pub fn select_enclosing_symbol(
12520 &mut self,
12521 _: &SelectEnclosingSymbol,
12522 window: &mut Window,
12523 cx: &mut Context<Self>,
12524 ) {
12525 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12526
12527 let buffer = self.buffer.read(cx).snapshot(cx);
12528 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12529
12530 fn update_selection(
12531 selection: &Selection<usize>,
12532 buffer_snap: &MultiBufferSnapshot,
12533 ) -> Option<Selection<usize>> {
12534 let cursor = selection.head();
12535 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12536 for symbol in symbols.iter().rev() {
12537 let start = symbol.range.start.to_offset(buffer_snap);
12538 let end = symbol.range.end.to_offset(buffer_snap);
12539 let new_range = start..end;
12540 if start < selection.start || end > selection.end {
12541 return Some(Selection {
12542 id: selection.id,
12543 start: new_range.start,
12544 end: new_range.end,
12545 goal: SelectionGoal::None,
12546 reversed: selection.reversed,
12547 });
12548 }
12549 }
12550 None
12551 }
12552
12553 let mut selected_larger_symbol = false;
12554 let new_selections = old_selections
12555 .iter()
12556 .map(|selection| match update_selection(selection, &buffer) {
12557 Some(new_selection) => {
12558 if new_selection.range() != selection.range() {
12559 selected_larger_symbol = true;
12560 }
12561 new_selection
12562 }
12563 None => selection.clone(),
12564 })
12565 .collect::<Vec<_>>();
12566
12567 if selected_larger_symbol {
12568 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12569 s.select(new_selections);
12570 });
12571 }
12572 }
12573
12574 pub fn select_larger_syntax_node(
12575 &mut self,
12576 _: &SelectLargerSyntaxNode,
12577 window: &mut Window,
12578 cx: &mut Context<Self>,
12579 ) {
12580 let Some(visible_row_count) = self.visible_row_count() else {
12581 return;
12582 };
12583 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12584 if old_selections.is_empty() {
12585 return;
12586 }
12587
12588 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12589
12590 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12591 let buffer = self.buffer.read(cx).snapshot(cx);
12592
12593 let mut selected_larger_node = false;
12594 let mut new_selections = old_selections
12595 .iter()
12596 .map(|selection| {
12597 let old_range = selection.start..selection.end;
12598
12599 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
12600 // manually select word at selection
12601 if ["string_content", "inline"].contains(&node.kind()) {
12602 let word_range = {
12603 let display_point = buffer
12604 .offset_to_point(old_range.start)
12605 .to_display_point(&display_map);
12606 let Range { start, end } =
12607 movement::surrounding_word(&display_map, display_point);
12608 start.to_point(&display_map).to_offset(&buffer)
12609 ..end.to_point(&display_map).to_offset(&buffer)
12610 };
12611 // ignore if word is already selected
12612 if !word_range.is_empty() && old_range != word_range {
12613 let last_word_range = {
12614 let display_point = buffer
12615 .offset_to_point(old_range.end)
12616 .to_display_point(&display_map);
12617 let Range { start, end } =
12618 movement::surrounding_word(&display_map, display_point);
12619 start.to_point(&display_map).to_offset(&buffer)
12620 ..end.to_point(&display_map).to_offset(&buffer)
12621 };
12622 // only select word if start and end point belongs to same word
12623 if word_range == last_word_range {
12624 selected_larger_node = true;
12625 return Selection {
12626 id: selection.id,
12627 start: word_range.start,
12628 end: word_range.end,
12629 goal: SelectionGoal::None,
12630 reversed: selection.reversed,
12631 };
12632 }
12633 }
12634 }
12635 }
12636
12637 let mut new_range = old_range.clone();
12638 let mut new_node = None;
12639 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12640 {
12641 new_node = Some(node);
12642 new_range = match containing_range {
12643 MultiOrSingleBufferOffsetRange::Single(_) => break,
12644 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12645 };
12646 if !display_map.intersects_fold(new_range.start)
12647 && !display_map.intersects_fold(new_range.end)
12648 {
12649 break;
12650 }
12651 }
12652
12653 if let Some(node) = new_node {
12654 // Log the ancestor, to support using this action as a way to explore TreeSitter
12655 // nodes. Parent and grandparent are also logged because this operation will not
12656 // visit nodes that have the same range as their parent.
12657 log::info!("Node: {node:?}");
12658 let parent = node.parent();
12659 log::info!("Parent: {parent:?}");
12660 let grandparent = parent.and_then(|x| x.parent());
12661 log::info!("Grandparent: {grandparent:?}");
12662 }
12663
12664 selected_larger_node |= new_range != old_range;
12665 Selection {
12666 id: selection.id,
12667 start: new_range.start,
12668 end: new_range.end,
12669 goal: SelectionGoal::None,
12670 reversed: selection.reversed,
12671 }
12672 })
12673 .collect::<Vec<_>>();
12674
12675 if !selected_larger_node {
12676 return; // don't put this call in the history
12677 }
12678
12679 // scroll based on transformation done to the last selection created by the user
12680 let (last_old, last_new) = old_selections
12681 .last()
12682 .zip(new_selections.last().cloned())
12683 .expect("old_selections isn't empty");
12684
12685 // revert selection
12686 let is_selection_reversed = {
12687 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12688 new_selections.last_mut().expect("checked above").reversed =
12689 should_newest_selection_be_reversed;
12690 should_newest_selection_be_reversed
12691 };
12692
12693 if selected_larger_node {
12694 self.select_syntax_node_history.disable_clearing = true;
12695 self.change_selections(None, window, cx, |s| {
12696 s.select(new_selections.clone());
12697 });
12698 self.select_syntax_node_history.disable_clearing = false;
12699 }
12700
12701 let start_row = last_new.start.to_display_point(&display_map).row().0;
12702 let end_row = last_new.end.to_display_point(&display_map).row().0;
12703 let selection_height = end_row - start_row + 1;
12704 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12705
12706 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
12707 let scroll_behavior = if fits_on_the_screen {
12708 self.request_autoscroll(Autoscroll::fit(), cx);
12709 SelectSyntaxNodeScrollBehavior::FitSelection
12710 } else if is_selection_reversed {
12711 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12712 SelectSyntaxNodeScrollBehavior::CursorTop
12713 } else {
12714 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12715 SelectSyntaxNodeScrollBehavior::CursorBottom
12716 };
12717
12718 self.select_syntax_node_history.push((
12719 old_selections,
12720 scroll_behavior,
12721 is_selection_reversed,
12722 ));
12723 }
12724
12725 pub fn select_smaller_syntax_node(
12726 &mut self,
12727 _: &SelectSmallerSyntaxNode,
12728 window: &mut Window,
12729 cx: &mut Context<Self>,
12730 ) {
12731 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12732
12733 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12734 self.select_syntax_node_history.pop()
12735 {
12736 if let Some(selection) = selections.last_mut() {
12737 selection.reversed = is_selection_reversed;
12738 }
12739
12740 self.select_syntax_node_history.disable_clearing = true;
12741 self.change_selections(None, window, cx, |s| {
12742 s.select(selections.to_vec());
12743 });
12744 self.select_syntax_node_history.disable_clearing = false;
12745
12746 match scroll_behavior {
12747 SelectSyntaxNodeScrollBehavior::CursorTop => {
12748 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12749 }
12750 SelectSyntaxNodeScrollBehavior::FitSelection => {
12751 self.request_autoscroll(Autoscroll::fit(), cx);
12752 }
12753 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12754 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12755 }
12756 }
12757 }
12758 }
12759
12760 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12761 if !EditorSettings::get_global(cx).gutter.runnables {
12762 self.clear_tasks();
12763 return Task::ready(());
12764 }
12765 let project = self.project.as_ref().map(Entity::downgrade);
12766 let task_sources = self.lsp_task_sources(cx);
12767 cx.spawn_in(window, async move |editor, cx| {
12768 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12769 let Some(project) = project.and_then(|p| p.upgrade()) else {
12770 return;
12771 };
12772 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
12773 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12774 }) else {
12775 return;
12776 };
12777
12778 let hide_runnables = project
12779 .update(cx, |project, cx| {
12780 // Do not display any test indicators in non-dev server remote projects.
12781 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12782 })
12783 .unwrap_or(true);
12784 if hide_runnables {
12785 return;
12786 }
12787 let new_rows =
12788 cx.background_spawn({
12789 let snapshot = display_snapshot.clone();
12790 async move {
12791 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12792 }
12793 })
12794 .await;
12795 let Ok(lsp_tasks) =
12796 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
12797 else {
12798 return;
12799 };
12800 let lsp_tasks = lsp_tasks.await;
12801
12802 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
12803 lsp_tasks
12804 .into_iter()
12805 .flat_map(|(kind, tasks)| {
12806 tasks.into_iter().filter_map(move |(location, task)| {
12807 Some((kind.clone(), location?, task))
12808 })
12809 })
12810 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
12811 let buffer = location.target.buffer;
12812 let buffer_snapshot = buffer.read(cx).snapshot();
12813 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
12814 |(excerpt_id, snapshot, _)| {
12815 if snapshot.remote_id() == buffer_snapshot.remote_id() {
12816 display_snapshot
12817 .buffer_snapshot
12818 .anchor_in_excerpt(excerpt_id, location.target.range.start)
12819 } else {
12820 None
12821 }
12822 },
12823 );
12824 if let Some(offset) = offset {
12825 let task_buffer_range =
12826 location.target.range.to_point(&buffer_snapshot);
12827 let context_buffer_range =
12828 task_buffer_range.to_offset(&buffer_snapshot);
12829 let context_range = BufferOffset(context_buffer_range.start)
12830 ..BufferOffset(context_buffer_range.end);
12831
12832 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
12833 .or_insert_with(|| RunnableTasks {
12834 templates: Vec::new(),
12835 offset,
12836 column: task_buffer_range.start.column,
12837 extra_variables: HashMap::default(),
12838 context_range,
12839 })
12840 .templates
12841 .push((kind, task.original_task().clone()));
12842 }
12843
12844 acc
12845 })
12846 }) else {
12847 return;
12848 };
12849
12850 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12851 editor
12852 .update(cx, |editor, _| {
12853 editor.clear_tasks();
12854 for (key, mut value) in rows {
12855 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
12856 value.templates.extend(lsp_tasks.templates);
12857 }
12858
12859 editor.insert_tasks(key, value);
12860 }
12861 for (key, value) in lsp_tasks_by_rows {
12862 editor.insert_tasks(key, value);
12863 }
12864 })
12865 .ok();
12866 })
12867 }
12868 fn fetch_runnable_ranges(
12869 snapshot: &DisplaySnapshot,
12870 range: Range<Anchor>,
12871 ) -> Vec<language::RunnableRange> {
12872 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12873 }
12874
12875 fn runnable_rows(
12876 project: Entity<Project>,
12877 snapshot: DisplaySnapshot,
12878 runnable_ranges: Vec<RunnableRange>,
12879 mut cx: AsyncWindowContext,
12880 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
12881 runnable_ranges
12882 .into_iter()
12883 .filter_map(|mut runnable| {
12884 let tasks = cx
12885 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12886 .ok()?;
12887 if tasks.is_empty() {
12888 return None;
12889 }
12890
12891 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12892
12893 let row = snapshot
12894 .buffer_snapshot
12895 .buffer_line_for_row(MultiBufferRow(point.row))?
12896 .1
12897 .start
12898 .row;
12899
12900 let context_range =
12901 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12902 Some((
12903 (runnable.buffer_id, row),
12904 RunnableTasks {
12905 templates: tasks,
12906 offset: snapshot
12907 .buffer_snapshot
12908 .anchor_before(runnable.run_range.start),
12909 context_range,
12910 column: point.column,
12911 extra_variables: runnable.extra_captures,
12912 },
12913 ))
12914 })
12915 .collect()
12916 }
12917
12918 fn templates_with_tags(
12919 project: &Entity<Project>,
12920 runnable: &mut Runnable,
12921 cx: &mut App,
12922 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12923 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12924 let (worktree_id, file) = project
12925 .buffer_for_id(runnable.buffer, cx)
12926 .and_then(|buffer| buffer.read(cx).file())
12927 .map(|file| (file.worktree_id(cx), file.clone()))
12928 .unzip();
12929
12930 (
12931 project.task_store().read(cx).task_inventory().cloned(),
12932 worktree_id,
12933 file,
12934 )
12935 });
12936
12937 let mut templates_with_tags = mem::take(&mut runnable.tags)
12938 .into_iter()
12939 .flat_map(|RunnableTag(tag)| {
12940 inventory
12941 .as_ref()
12942 .into_iter()
12943 .flat_map(|inventory| {
12944 inventory.read(cx).list_tasks(
12945 file.clone(),
12946 Some(runnable.language.clone()),
12947 worktree_id,
12948 cx,
12949 )
12950 })
12951 .filter(move |(_, template)| {
12952 template.tags.iter().any(|source_tag| source_tag == &tag)
12953 })
12954 })
12955 .sorted_by_key(|(kind, _)| kind.to_owned())
12956 .collect::<Vec<_>>();
12957 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
12958 // Strongest source wins; if we have worktree tag binding, prefer that to
12959 // global and language bindings;
12960 // if we have a global binding, prefer that to language binding.
12961 let first_mismatch = templates_with_tags
12962 .iter()
12963 .position(|(tag_source, _)| tag_source != leading_tag_source);
12964 if let Some(index) = first_mismatch {
12965 templates_with_tags.truncate(index);
12966 }
12967 }
12968
12969 templates_with_tags
12970 }
12971
12972 pub fn move_to_enclosing_bracket(
12973 &mut self,
12974 _: &MoveToEnclosingBracket,
12975 window: &mut Window,
12976 cx: &mut Context<Self>,
12977 ) {
12978 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12979 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12980 s.move_offsets_with(|snapshot, selection| {
12981 let Some(enclosing_bracket_ranges) =
12982 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12983 else {
12984 return;
12985 };
12986
12987 let mut best_length = usize::MAX;
12988 let mut best_inside = false;
12989 let mut best_in_bracket_range = false;
12990 let mut best_destination = None;
12991 for (open, close) in enclosing_bracket_ranges {
12992 let close = close.to_inclusive();
12993 let length = close.end() - open.start;
12994 let inside = selection.start >= open.end && selection.end <= *close.start();
12995 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12996 || close.contains(&selection.head());
12997
12998 // If best is next to a bracket and current isn't, skip
12999 if !in_bracket_range && best_in_bracket_range {
13000 continue;
13001 }
13002
13003 // Prefer smaller lengths unless best is inside and current isn't
13004 if length > best_length && (best_inside || !inside) {
13005 continue;
13006 }
13007
13008 best_length = length;
13009 best_inside = inside;
13010 best_in_bracket_range = in_bracket_range;
13011 best_destination = Some(
13012 if close.contains(&selection.start) && close.contains(&selection.end) {
13013 if inside { open.end } else { open.start }
13014 } else if inside {
13015 *close.start()
13016 } else {
13017 *close.end()
13018 },
13019 );
13020 }
13021
13022 if let Some(destination) = best_destination {
13023 selection.collapse_to(destination, SelectionGoal::None);
13024 }
13025 })
13026 });
13027 }
13028
13029 pub fn undo_selection(
13030 &mut self,
13031 _: &UndoSelection,
13032 window: &mut Window,
13033 cx: &mut Context<Self>,
13034 ) {
13035 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13036 self.end_selection(window, cx);
13037 self.selection_history.mode = SelectionHistoryMode::Undoing;
13038 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13039 self.change_selections(None, window, cx, |s| {
13040 s.select_anchors(entry.selections.to_vec())
13041 });
13042 self.select_next_state = entry.select_next_state;
13043 self.select_prev_state = entry.select_prev_state;
13044 self.add_selections_state = entry.add_selections_state;
13045 self.request_autoscroll(Autoscroll::newest(), cx);
13046 }
13047 self.selection_history.mode = SelectionHistoryMode::Normal;
13048 }
13049
13050 pub fn redo_selection(
13051 &mut self,
13052 _: &RedoSelection,
13053 window: &mut Window,
13054 cx: &mut Context<Self>,
13055 ) {
13056 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13057 self.end_selection(window, cx);
13058 self.selection_history.mode = SelectionHistoryMode::Redoing;
13059 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13060 self.change_selections(None, window, cx, |s| {
13061 s.select_anchors(entry.selections.to_vec())
13062 });
13063 self.select_next_state = entry.select_next_state;
13064 self.select_prev_state = entry.select_prev_state;
13065 self.add_selections_state = entry.add_selections_state;
13066 self.request_autoscroll(Autoscroll::newest(), cx);
13067 }
13068 self.selection_history.mode = SelectionHistoryMode::Normal;
13069 }
13070
13071 pub fn expand_excerpts(
13072 &mut self,
13073 action: &ExpandExcerpts,
13074 _: &mut Window,
13075 cx: &mut Context<Self>,
13076 ) {
13077 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13078 }
13079
13080 pub fn expand_excerpts_down(
13081 &mut self,
13082 action: &ExpandExcerptsDown,
13083 _: &mut Window,
13084 cx: &mut Context<Self>,
13085 ) {
13086 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13087 }
13088
13089 pub fn expand_excerpts_up(
13090 &mut self,
13091 action: &ExpandExcerptsUp,
13092 _: &mut Window,
13093 cx: &mut Context<Self>,
13094 ) {
13095 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13096 }
13097
13098 pub fn expand_excerpts_for_direction(
13099 &mut self,
13100 lines: u32,
13101 direction: ExpandExcerptDirection,
13102
13103 cx: &mut Context<Self>,
13104 ) {
13105 let selections = self.selections.disjoint_anchors();
13106
13107 let lines = if lines == 0 {
13108 EditorSettings::get_global(cx).expand_excerpt_lines
13109 } else {
13110 lines
13111 };
13112
13113 self.buffer.update(cx, |buffer, cx| {
13114 let snapshot = buffer.snapshot(cx);
13115 let mut excerpt_ids = selections
13116 .iter()
13117 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13118 .collect::<Vec<_>>();
13119 excerpt_ids.sort();
13120 excerpt_ids.dedup();
13121 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13122 })
13123 }
13124
13125 pub fn expand_excerpt(
13126 &mut self,
13127 excerpt: ExcerptId,
13128 direction: ExpandExcerptDirection,
13129 window: &mut Window,
13130 cx: &mut Context<Self>,
13131 ) {
13132 let current_scroll_position = self.scroll_position(cx);
13133 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13134 let mut should_scroll_up = false;
13135
13136 if direction == ExpandExcerptDirection::Down {
13137 let multi_buffer = self.buffer.read(cx);
13138 let snapshot = multi_buffer.snapshot(cx);
13139 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13140 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13141 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13142 let buffer_snapshot = buffer.read(cx).snapshot();
13143 let excerpt_end_row =
13144 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13145 let last_row = buffer_snapshot.max_point().row;
13146 let lines_below = last_row.saturating_sub(excerpt_end_row);
13147 should_scroll_up = lines_below >= lines_to_expand;
13148 }
13149 }
13150 }
13151 }
13152
13153 self.buffer.update(cx, |buffer, cx| {
13154 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13155 });
13156
13157 if should_scroll_up {
13158 let new_scroll_position =
13159 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13160 self.set_scroll_position(new_scroll_position, window, cx);
13161 }
13162 }
13163
13164 pub fn go_to_singleton_buffer_point(
13165 &mut self,
13166 point: Point,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 self.go_to_singleton_buffer_range(point..point, window, cx);
13171 }
13172
13173 pub fn go_to_singleton_buffer_range(
13174 &mut self,
13175 range: Range<Point>,
13176 window: &mut Window,
13177 cx: &mut Context<Self>,
13178 ) {
13179 let multibuffer = self.buffer().read(cx);
13180 let Some(buffer) = multibuffer.as_singleton() else {
13181 return;
13182 };
13183 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13184 return;
13185 };
13186 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13187 return;
13188 };
13189 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13190 s.select_anchor_ranges([start..end])
13191 });
13192 }
13193
13194 pub fn go_to_diagnostic(
13195 &mut self,
13196 _: &GoToDiagnostic,
13197 window: &mut Window,
13198 cx: &mut Context<Self>,
13199 ) {
13200 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13201 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13202 }
13203
13204 pub fn go_to_prev_diagnostic(
13205 &mut self,
13206 _: &GoToPreviousDiagnostic,
13207 window: &mut Window,
13208 cx: &mut Context<Self>,
13209 ) {
13210 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13211 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13212 }
13213
13214 pub fn go_to_diagnostic_impl(
13215 &mut self,
13216 direction: Direction,
13217 window: &mut Window,
13218 cx: &mut Context<Self>,
13219 ) {
13220 let buffer = self.buffer.read(cx).snapshot(cx);
13221 let selection = self.selections.newest::<usize>(cx);
13222
13223 let mut active_group_id = None;
13224 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13225 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13226 active_group_id = Some(active_group.group_id);
13227 }
13228 }
13229
13230 fn filtered(
13231 snapshot: EditorSnapshot,
13232 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13233 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13234 diagnostics
13235 .filter(|entry| entry.range.start != entry.range.end)
13236 .filter(|entry| !entry.diagnostic.is_unnecessary)
13237 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13238 }
13239
13240 let snapshot = self.snapshot(window, cx);
13241 let before = filtered(
13242 snapshot.clone(),
13243 buffer
13244 .diagnostics_in_range(0..selection.start)
13245 .filter(|entry| entry.range.start <= selection.start),
13246 );
13247 let after = filtered(
13248 snapshot,
13249 buffer
13250 .diagnostics_in_range(selection.start..buffer.len())
13251 .filter(|entry| entry.range.start >= selection.start),
13252 );
13253
13254 let mut found: Option<DiagnosticEntry<usize>> = None;
13255 if direction == Direction::Prev {
13256 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13257 {
13258 for diagnostic in prev_diagnostics.into_iter().rev() {
13259 if diagnostic.range.start != selection.start
13260 || active_group_id
13261 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13262 {
13263 found = Some(diagnostic);
13264 break 'outer;
13265 }
13266 }
13267 }
13268 } else {
13269 for diagnostic in after.chain(before) {
13270 if diagnostic.range.start != selection.start
13271 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13272 {
13273 found = Some(diagnostic);
13274 break;
13275 }
13276 }
13277 }
13278 let Some(next_diagnostic) = found else {
13279 return;
13280 };
13281
13282 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13283 return;
13284 };
13285 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13286 s.select_ranges(vec![
13287 next_diagnostic.range.start..next_diagnostic.range.start,
13288 ])
13289 });
13290 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13291 self.refresh_inline_completion(false, true, window, cx);
13292 }
13293
13294 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13295 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13296 let snapshot = self.snapshot(window, cx);
13297 let selection = self.selections.newest::<Point>(cx);
13298 self.go_to_hunk_before_or_after_position(
13299 &snapshot,
13300 selection.head(),
13301 Direction::Next,
13302 window,
13303 cx,
13304 );
13305 }
13306
13307 pub fn go_to_hunk_before_or_after_position(
13308 &mut self,
13309 snapshot: &EditorSnapshot,
13310 position: Point,
13311 direction: Direction,
13312 window: &mut Window,
13313 cx: &mut Context<Editor>,
13314 ) {
13315 let row = if direction == Direction::Next {
13316 self.hunk_after_position(snapshot, position)
13317 .map(|hunk| hunk.row_range.start)
13318 } else {
13319 self.hunk_before_position(snapshot, position)
13320 };
13321
13322 if let Some(row) = row {
13323 let destination = Point::new(row.0, 0);
13324 let autoscroll = Autoscroll::center();
13325
13326 self.unfold_ranges(&[destination..destination], false, false, cx);
13327 self.change_selections(Some(autoscroll), window, cx, |s| {
13328 s.select_ranges([destination..destination]);
13329 });
13330 }
13331 }
13332
13333 fn hunk_after_position(
13334 &mut self,
13335 snapshot: &EditorSnapshot,
13336 position: Point,
13337 ) -> Option<MultiBufferDiffHunk> {
13338 snapshot
13339 .buffer_snapshot
13340 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13341 .find(|hunk| hunk.row_range.start.0 > position.row)
13342 .or_else(|| {
13343 snapshot
13344 .buffer_snapshot
13345 .diff_hunks_in_range(Point::zero()..position)
13346 .find(|hunk| hunk.row_range.end.0 < position.row)
13347 })
13348 }
13349
13350 fn go_to_prev_hunk(
13351 &mut self,
13352 _: &GoToPreviousHunk,
13353 window: &mut Window,
13354 cx: &mut Context<Self>,
13355 ) {
13356 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13357 let snapshot = self.snapshot(window, cx);
13358 let selection = self.selections.newest::<Point>(cx);
13359 self.go_to_hunk_before_or_after_position(
13360 &snapshot,
13361 selection.head(),
13362 Direction::Prev,
13363 window,
13364 cx,
13365 );
13366 }
13367
13368 fn hunk_before_position(
13369 &mut self,
13370 snapshot: &EditorSnapshot,
13371 position: Point,
13372 ) -> Option<MultiBufferRow> {
13373 snapshot
13374 .buffer_snapshot
13375 .diff_hunk_before(position)
13376 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13377 }
13378
13379 fn go_to_next_change(
13380 &mut self,
13381 _: &GoToNextChange,
13382 window: &mut Window,
13383 cx: &mut Context<Self>,
13384 ) {
13385 if let Some(selections) = self
13386 .change_list
13387 .next_change(1, Direction::Next)
13388 .map(|s| s.to_vec())
13389 {
13390 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13391 let map = s.display_map();
13392 s.select_display_ranges(selections.iter().map(|a| {
13393 let point = a.to_display_point(&map);
13394 point..point
13395 }))
13396 })
13397 }
13398 }
13399
13400 fn go_to_previous_change(
13401 &mut self,
13402 _: &GoToPreviousChange,
13403 window: &mut Window,
13404 cx: &mut Context<Self>,
13405 ) {
13406 if let Some(selections) = self
13407 .change_list
13408 .next_change(1, Direction::Prev)
13409 .map(|s| s.to_vec())
13410 {
13411 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13412 let map = s.display_map();
13413 s.select_display_ranges(selections.iter().map(|a| {
13414 let point = a.to_display_point(&map);
13415 point..point
13416 }))
13417 })
13418 }
13419 }
13420
13421 fn go_to_line<T: 'static>(
13422 &mut self,
13423 position: Anchor,
13424 highlight_color: Option<Hsla>,
13425 window: &mut Window,
13426 cx: &mut Context<Self>,
13427 ) {
13428 let snapshot = self.snapshot(window, cx).display_snapshot;
13429 let position = position.to_point(&snapshot.buffer_snapshot);
13430 let start = snapshot
13431 .buffer_snapshot
13432 .clip_point(Point::new(position.row, 0), Bias::Left);
13433 let end = start + Point::new(1, 0);
13434 let start = snapshot.buffer_snapshot.anchor_before(start);
13435 let end = snapshot.buffer_snapshot.anchor_before(end);
13436
13437 self.highlight_rows::<T>(
13438 start..end,
13439 highlight_color
13440 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13441 false,
13442 cx,
13443 );
13444 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13445 }
13446
13447 pub fn go_to_definition(
13448 &mut self,
13449 _: &GoToDefinition,
13450 window: &mut Window,
13451 cx: &mut Context<Self>,
13452 ) -> Task<Result<Navigated>> {
13453 let definition =
13454 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13455 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13456 cx.spawn_in(window, async move |editor, cx| {
13457 if definition.await? == Navigated::Yes {
13458 return Ok(Navigated::Yes);
13459 }
13460 match fallback_strategy {
13461 GoToDefinitionFallback::None => Ok(Navigated::No),
13462 GoToDefinitionFallback::FindAllReferences => {
13463 match editor.update_in(cx, |editor, window, cx| {
13464 editor.find_all_references(&FindAllReferences, window, cx)
13465 })? {
13466 Some(references) => references.await,
13467 None => Ok(Navigated::No),
13468 }
13469 }
13470 }
13471 })
13472 }
13473
13474 pub fn go_to_declaration(
13475 &mut self,
13476 _: &GoToDeclaration,
13477 window: &mut Window,
13478 cx: &mut Context<Self>,
13479 ) -> Task<Result<Navigated>> {
13480 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13481 }
13482
13483 pub fn go_to_declaration_split(
13484 &mut self,
13485 _: &GoToDeclaration,
13486 window: &mut Window,
13487 cx: &mut Context<Self>,
13488 ) -> Task<Result<Navigated>> {
13489 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13490 }
13491
13492 pub fn go_to_implementation(
13493 &mut self,
13494 _: &GoToImplementation,
13495 window: &mut Window,
13496 cx: &mut Context<Self>,
13497 ) -> Task<Result<Navigated>> {
13498 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13499 }
13500
13501 pub fn go_to_implementation_split(
13502 &mut self,
13503 _: &GoToImplementationSplit,
13504 window: &mut Window,
13505 cx: &mut Context<Self>,
13506 ) -> Task<Result<Navigated>> {
13507 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13508 }
13509
13510 pub fn go_to_type_definition(
13511 &mut self,
13512 _: &GoToTypeDefinition,
13513 window: &mut Window,
13514 cx: &mut Context<Self>,
13515 ) -> Task<Result<Navigated>> {
13516 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13517 }
13518
13519 pub fn go_to_definition_split(
13520 &mut self,
13521 _: &GoToDefinitionSplit,
13522 window: &mut Window,
13523 cx: &mut Context<Self>,
13524 ) -> Task<Result<Navigated>> {
13525 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13526 }
13527
13528 pub fn go_to_type_definition_split(
13529 &mut self,
13530 _: &GoToTypeDefinitionSplit,
13531 window: &mut Window,
13532 cx: &mut Context<Self>,
13533 ) -> Task<Result<Navigated>> {
13534 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13535 }
13536
13537 fn go_to_definition_of_kind(
13538 &mut self,
13539 kind: GotoDefinitionKind,
13540 split: bool,
13541 window: &mut Window,
13542 cx: &mut Context<Self>,
13543 ) -> Task<Result<Navigated>> {
13544 let Some(provider) = self.semantics_provider.clone() else {
13545 return Task::ready(Ok(Navigated::No));
13546 };
13547 let head = self.selections.newest::<usize>(cx).head();
13548 let buffer = self.buffer.read(cx);
13549 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13550 text_anchor
13551 } else {
13552 return Task::ready(Ok(Navigated::No));
13553 };
13554
13555 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13556 return Task::ready(Ok(Navigated::No));
13557 };
13558
13559 cx.spawn_in(window, async move |editor, cx| {
13560 let definitions = definitions.await?;
13561 let navigated = editor
13562 .update_in(cx, |editor, window, cx| {
13563 editor.navigate_to_hover_links(
13564 Some(kind),
13565 definitions
13566 .into_iter()
13567 .filter(|location| {
13568 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13569 })
13570 .map(HoverLink::Text)
13571 .collect::<Vec<_>>(),
13572 split,
13573 window,
13574 cx,
13575 )
13576 })?
13577 .await?;
13578 anyhow::Ok(navigated)
13579 })
13580 }
13581
13582 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13583 let selection = self.selections.newest_anchor();
13584 let head = selection.head();
13585 let tail = selection.tail();
13586
13587 let Some((buffer, start_position)) =
13588 self.buffer.read(cx).text_anchor_for_position(head, cx)
13589 else {
13590 return;
13591 };
13592
13593 let end_position = if head != tail {
13594 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13595 return;
13596 };
13597 Some(pos)
13598 } else {
13599 None
13600 };
13601
13602 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13603 let url = if let Some(end_pos) = end_position {
13604 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13605 } else {
13606 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13607 };
13608
13609 if let Some(url) = url {
13610 editor.update(cx, |_, cx| {
13611 cx.open_url(&url);
13612 })
13613 } else {
13614 Ok(())
13615 }
13616 });
13617
13618 url_finder.detach();
13619 }
13620
13621 pub fn open_selected_filename(
13622 &mut self,
13623 _: &OpenSelectedFilename,
13624 window: &mut Window,
13625 cx: &mut Context<Self>,
13626 ) {
13627 let Some(workspace) = self.workspace() else {
13628 return;
13629 };
13630
13631 let position = self.selections.newest_anchor().head();
13632
13633 let Some((buffer, buffer_position)) =
13634 self.buffer.read(cx).text_anchor_for_position(position, cx)
13635 else {
13636 return;
13637 };
13638
13639 let project = self.project.clone();
13640
13641 cx.spawn_in(window, async move |_, cx| {
13642 let result = find_file(&buffer, project, buffer_position, cx).await;
13643
13644 if let Some((_, path)) = result {
13645 workspace
13646 .update_in(cx, |workspace, window, cx| {
13647 workspace.open_resolved_path(path, window, cx)
13648 })?
13649 .await?;
13650 }
13651 anyhow::Ok(())
13652 })
13653 .detach();
13654 }
13655
13656 pub(crate) fn navigate_to_hover_links(
13657 &mut self,
13658 kind: Option<GotoDefinitionKind>,
13659 mut definitions: Vec<HoverLink>,
13660 split: bool,
13661 window: &mut Window,
13662 cx: &mut Context<Editor>,
13663 ) -> Task<Result<Navigated>> {
13664 // If there is one definition, just open it directly
13665 if definitions.len() == 1 {
13666 let definition = definitions.pop().unwrap();
13667
13668 enum TargetTaskResult {
13669 Location(Option<Location>),
13670 AlreadyNavigated,
13671 }
13672
13673 let target_task = match definition {
13674 HoverLink::Text(link) => {
13675 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13676 }
13677 HoverLink::InlayHint(lsp_location, server_id) => {
13678 let computation =
13679 self.compute_target_location(lsp_location, server_id, window, cx);
13680 cx.background_spawn(async move {
13681 let location = computation.await?;
13682 Ok(TargetTaskResult::Location(location))
13683 })
13684 }
13685 HoverLink::Url(url) => {
13686 cx.open_url(&url);
13687 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13688 }
13689 HoverLink::File(path) => {
13690 if let Some(workspace) = self.workspace() {
13691 cx.spawn_in(window, async move |_, cx| {
13692 workspace
13693 .update_in(cx, |workspace, window, cx| {
13694 workspace.open_resolved_path(path, window, cx)
13695 })?
13696 .await
13697 .map(|_| TargetTaskResult::AlreadyNavigated)
13698 })
13699 } else {
13700 Task::ready(Ok(TargetTaskResult::Location(None)))
13701 }
13702 }
13703 };
13704 cx.spawn_in(window, async move |editor, cx| {
13705 let target = match target_task.await.context("target resolution task")? {
13706 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13707 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13708 TargetTaskResult::Location(Some(target)) => target,
13709 };
13710
13711 editor.update_in(cx, |editor, window, cx| {
13712 let Some(workspace) = editor.workspace() else {
13713 return Navigated::No;
13714 };
13715 let pane = workspace.read(cx).active_pane().clone();
13716
13717 let range = target.range.to_point(target.buffer.read(cx));
13718 let range = editor.range_for_match(&range);
13719 let range = collapse_multiline_range(range);
13720
13721 if !split
13722 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13723 {
13724 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13725 } else {
13726 window.defer(cx, move |window, cx| {
13727 let target_editor: Entity<Self> =
13728 workspace.update(cx, |workspace, cx| {
13729 let pane = if split {
13730 workspace.adjacent_pane(window, cx)
13731 } else {
13732 workspace.active_pane().clone()
13733 };
13734
13735 workspace.open_project_item(
13736 pane,
13737 target.buffer.clone(),
13738 true,
13739 true,
13740 window,
13741 cx,
13742 )
13743 });
13744 target_editor.update(cx, |target_editor, cx| {
13745 // When selecting a definition in a different buffer, disable the nav history
13746 // to avoid creating a history entry at the previous cursor location.
13747 pane.update(cx, |pane, _| pane.disable_history());
13748 target_editor.go_to_singleton_buffer_range(range, window, cx);
13749 pane.update(cx, |pane, _| pane.enable_history());
13750 });
13751 });
13752 }
13753 Navigated::Yes
13754 })
13755 })
13756 } else if !definitions.is_empty() {
13757 cx.spawn_in(window, async move |editor, cx| {
13758 let (title, location_tasks, workspace) = editor
13759 .update_in(cx, |editor, window, cx| {
13760 let tab_kind = match kind {
13761 Some(GotoDefinitionKind::Implementation) => "Implementations",
13762 _ => "Definitions",
13763 };
13764 let title = definitions
13765 .iter()
13766 .find_map(|definition| match definition {
13767 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13768 let buffer = origin.buffer.read(cx);
13769 format!(
13770 "{} for {}",
13771 tab_kind,
13772 buffer
13773 .text_for_range(origin.range.clone())
13774 .collect::<String>()
13775 )
13776 }),
13777 HoverLink::InlayHint(_, _) => None,
13778 HoverLink::Url(_) => None,
13779 HoverLink::File(_) => None,
13780 })
13781 .unwrap_or(tab_kind.to_string());
13782 let location_tasks = definitions
13783 .into_iter()
13784 .map(|definition| match definition {
13785 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13786 HoverLink::InlayHint(lsp_location, server_id) => editor
13787 .compute_target_location(lsp_location, server_id, window, cx),
13788 HoverLink::Url(_) => Task::ready(Ok(None)),
13789 HoverLink::File(_) => Task::ready(Ok(None)),
13790 })
13791 .collect::<Vec<_>>();
13792 (title, location_tasks, editor.workspace().clone())
13793 })
13794 .context("location tasks preparation")?;
13795
13796 let locations = future::join_all(location_tasks)
13797 .await
13798 .into_iter()
13799 .filter_map(|location| location.transpose())
13800 .collect::<Result<_>>()
13801 .context("location tasks")?;
13802
13803 let Some(workspace) = workspace else {
13804 return Ok(Navigated::No);
13805 };
13806 let opened = workspace
13807 .update_in(cx, |workspace, window, cx| {
13808 Self::open_locations_in_multibuffer(
13809 workspace,
13810 locations,
13811 title,
13812 split,
13813 MultibufferSelectionMode::First,
13814 window,
13815 cx,
13816 )
13817 })
13818 .ok();
13819
13820 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13821 })
13822 } else {
13823 Task::ready(Ok(Navigated::No))
13824 }
13825 }
13826
13827 fn compute_target_location(
13828 &self,
13829 lsp_location: lsp::Location,
13830 server_id: LanguageServerId,
13831 window: &mut Window,
13832 cx: &mut Context<Self>,
13833 ) -> Task<anyhow::Result<Option<Location>>> {
13834 let Some(project) = self.project.clone() else {
13835 return Task::ready(Ok(None));
13836 };
13837
13838 cx.spawn_in(window, async move |editor, cx| {
13839 let location_task = editor.update(cx, |_, cx| {
13840 project.update(cx, |project, cx| {
13841 let language_server_name = project
13842 .language_server_statuses(cx)
13843 .find(|(id, _)| server_id == *id)
13844 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13845 language_server_name.map(|language_server_name| {
13846 project.open_local_buffer_via_lsp(
13847 lsp_location.uri.clone(),
13848 server_id,
13849 language_server_name,
13850 cx,
13851 )
13852 })
13853 })
13854 })?;
13855 let location = match location_task {
13856 Some(task) => Some({
13857 let target_buffer_handle = task.await.context("open local buffer")?;
13858 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13859 let target_start = target_buffer
13860 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13861 let target_end = target_buffer
13862 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13863 target_buffer.anchor_after(target_start)
13864 ..target_buffer.anchor_before(target_end)
13865 })?;
13866 Location {
13867 buffer: target_buffer_handle,
13868 range,
13869 }
13870 }),
13871 None => None,
13872 };
13873 Ok(location)
13874 })
13875 }
13876
13877 pub fn find_all_references(
13878 &mut self,
13879 _: &FindAllReferences,
13880 window: &mut Window,
13881 cx: &mut Context<Self>,
13882 ) -> Option<Task<Result<Navigated>>> {
13883 let selection = self.selections.newest::<usize>(cx);
13884 let multi_buffer = self.buffer.read(cx);
13885 let head = selection.head();
13886
13887 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13888 let head_anchor = multi_buffer_snapshot.anchor_at(
13889 head,
13890 if head < selection.tail() {
13891 Bias::Right
13892 } else {
13893 Bias::Left
13894 },
13895 );
13896
13897 match self
13898 .find_all_references_task_sources
13899 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13900 {
13901 Ok(_) => {
13902 log::info!(
13903 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13904 );
13905 return None;
13906 }
13907 Err(i) => {
13908 self.find_all_references_task_sources.insert(i, head_anchor);
13909 }
13910 }
13911
13912 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13913 let workspace = self.workspace()?;
13914 let project = workspace.read(cx).project().clone();
13915 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13916 Some(cx.spawn_in(window, async move |editor, cx| {
13917 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13918 if let Ok(i) = editor
13919 .find_all_references_task_sources
13920 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13921 {
13922 editor.find_all_references_task_sources.remove(i);
13923 }
13924 });
13925
13926 let locations = references.await?;
13927 if locations.is_empty() {
13928 return anyhow::Ok(Navigated::No);
13929 }
13930
13931 workspace.update_in(cx, |workspace, window, cx| {
13932 let title = locations
13933 .first()
13934 .as_ref()
13935 .map(|location| {
13936 let buffer = location.buffer.read(cx);
13937 format!(
13938 "References to `{}`",
13939 buffer
13940 .text_for_range(location.range.clone())
13941 .collect::<String>()
13942 )
13943 })
13944 .unwrap();
13945 Self::open_locations_in_multibuffer(
13946 workspace,
13947 locations,
13948 title,
13949 false,
13950 MultibufferSelectionMode::First,
13951 window,
13952 cx,
13953 );
13954 Navigated::Yes
13955 })
13956 }))
13957 }
13958
13959 /// Opens a multibuffer with the given project locations in it
13960 pub fn open_locations_in_multibuffer(
13961 workspace: &mut Workspace,
13962 mut locations: Vec<Location>,
13963 title: String,
13964 split: bool,
13965 multibuffer_selection_mode: MultibufferSelectionMode,
13966 window: &mut Window,
13967 cx: &mut Context<Workspace>,
13968 ) {
13969 // If there are multiple definitions, open them in a multibuffer
13970 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13971 let mut locations = locations.into_iter().peekable();
13972 let mut ranges: Vec<Range<Anchor>> = Vec::new();
13973 let capability = workspace.project().read(cx).capability();
13974
13975 let excerpt_buffer = cx.new(|cx| {
13976 let mut multibuffer = MultiBuffer::new(capability);
13977 while let Some(location) = locations.next() {
13978 let buffer = location.buffer.read(cx);
13979 let mut ranges_for_buffer = Vec::new();
13980 let range = location.range.to_point(buffer);
13981 ranges_for_buffer.push(range.clone());
13982
13983 while let Some(next_location) = locations.peek() {
13984 if next_location.buffer == location.buffer {
13985 ranges_for_buffer.push(next_location.range.to_point(buffer));
13986 locations.next();
13987 } else {
13988 break;
13989 }
13990 }
13991
13992 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13993 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
13994 PathKey::for_buffer(&location.buffer, cx),
13995 location.buffer.clone(),
13996 ranges_for_buffer,
13997 DEFAULT_MULTIBUFFER_CONTEXT,
13998 cx,
13999 );
14000 ranges.extend(new_ranges)
14001 }
14002
14003 multibuffer.with_title(title)
14004 });
14005
14006 let editor = cx.new(|cx| {
14007 Editor::for_multibuffer(
14008 excerpt_buffer,
14009 Some(workspace.project().clone()),
14010 window,
14011 cx,
14012 )
14013 });
14014 editor.update(cx, |editor, cx| {
14015 match multibuffer_selection_mode {
14016 MultibufferSelectionMode::First => {
14017 if let Some(first_range) = ranges.first() {
14018 editor.change_selections(None, window, cx, |selections| {
14019 selections.clear_disjoint();
14020 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14021 });
14022 }
14023 editor.highlight_background::<Self>(
14024 &ranges,
14025 |theme| theme.editor_highlighted_line_background,
14026 cx,
14027 );
14028 }
14029 MultibufferSelectionMode::All => {
14030 editor.change_selections(None, window, cx, |selections| {
14031 selections.clear_disjoint();
14032 selections.select_anchor_ranges(ranges);
14033 });
14034 }
14035 }
14036 editor.register_buffers_with_language_servers(cx);
14037 });
14038
14039 let item = Box::new(editor);
14040 let item_id = item.item_id();
14041
14042 if split {
14043 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14044 } else {
14045 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14046 let (preview_item_id, preview_item_idx) =
14047 workspace.active_pane().update(cx, |pane, _| {
14048 (pane.preview_item_id(), pane.preview_item_idx())
14049 });
14050
14051 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14052
14053 if let Some(preview_item_id) = preview_item_id {
14054 workspace.active_pane().update(cx, |pane, cx| {
14055 pane.remove_item(preview_item_id, false, false, window, cx);
14056 });
14057 }
14058 } else {
14059 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14060 }
14061 }
14062 workspace.active_pane().update(cx, |pane, cx| {
14063 pane.set_preview_item_id(Some(item_id), cx);
14064 });
14065 }
14066
14067 pub fn rename(
14068 &mut self,
14069 _: &Rename,
14070 window: &mut Window,
14071 cx: &mut Context<Self>,
14072 ) -> Option<Task<Result<()>>> {
14073 use language::ToOffset as _;
14074
14075 let provider = self.semantics_provider.clone()?;
14076 let selection = self.selections.newest_anchor().clone();
14077 let (cursor_buffer, cursor_buffer_position) = self
14078 .buffer
14079 .read(cx)
14080 .text_anchor_for_position(selection.head(), cx)?;
14081 let (tail_buffer, cursor_buffer_position_end) = self
14082 .buffer
14083 .read(cx)
14084 .text_anchor_for_position(selection.tail(), cx)?;
14085 if tail_buffer != cursor_buffer {
14086 return None;
14087 }
14088
14089 let snapshot = cursor_buffer.read(cx).snapshot();
14090 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14091 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14092 let prepare_rename = provider
14093 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14094 .unwrap_or_else(|| Task::ready(Ok(None)));
14095 drop(snapshot);
14096
14097 Some(cx.spawn_in(window, async move |this, cx| {
14098 let rename_range = if let Some(range) = prepare_rename.await? {
14099 Some(range)
14100 } else {
14101 this.update(cx, |this, cx| {
14102 let buffer = this.buffer.read(cx).snapshot(cx);
14103 let mut buffer_highlights = this
14104 .document_highlights_for_position(selection.head(), &buffer)
14105 .filter(|highlight| {
14106 highlight.start.excerpt_id == selection.head().excerpt_id
14107 && highlight.end.excerpt_id == selection.head().excerpt_id
14108 });
14109 buffer_highlights
14110 .next()
14111 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14112 })?
14113 };
14114 if let Some(rename_range) = rename_range {
14115 this.update_in(cx, |this, window, cx| {
14116 let snapshot = cursor_buffer.read(cx).snapshot();
14117 let rename_buffer_range = rename_range.to_offset(&snapshot);
14118 let cursor_offset_in_rename_range =
14119 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14120 let cursor_offset_in_rename_range_end =
14121 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14122
14123 this.take_rename(false, window, cx);
14124 let buffer = this.buffer.read(cx).read(cx);
14125 let cursor_offset = selection.head().to_offset(&buffer);
14126 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14127 let rename_end = rename_start + rename_buffer_range.len();
14128 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14129 let mut old_highlight_id = None;
14130 let old_name: Arc<str> = buffer
14131 .chunks(rename_start..rename_end, true)
14132 .map(|chunk| {
14133 if old_highlight_id.is_none() {
14134 old_highlight_id = chunk.syntax_highlight_id;
14135 }
14136 chunk.text
14137 })
14138 .collect::<String>()
14139 .into();
14140
14141 drop(buffer);
14142
14143 // Position the selection in the rename editor so that it matches the current selection.
14144 this.show_local_selections = false;
14145 let rename_editor = cx.new(|cx| {
14146 let mut editor = Editor::single_line(window, cx);
14147 editor.buffer.update(cx, |buffer, cx| {
14148 buffer.edit([(0..0, old_name.clone())], None, cx)
14149 });
14150 let rename_selection_range = match cursor_offset_in_rename_range
14151 .cmp(&cursor_offset_in_rename_range_end)
14152 {
14153 Ordering::Equal => {
14154 editor.select_all(&SelectAll, window, cx);
14155 return editor;
14156 }
14157 Ordering::Less => {
14158 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14159 }
14160 Ordering::Greater => {
14161 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14162 }
14163 };
14164 if rename_selection_range.end > old_name.len() {
14165 editor.select_all(&SelectAll, window, cx);
14166 } else {
14167 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14168 s.select_ranges([rename_selection_range]);
14169 });
14170 }
14171 editor
14172 });
14173 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14174 if e == &EditorEvent::Focused {
14175 cx.emit(EditorEvent::FocusedIn)
14176 }
14177 })
14178 .detach();
14179
14180 let write_highlights =
14181 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14182 let read_highlights =
14183 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14184 let ranges = write_highlights
14185 .iter()
14186 .flat_map(|(_, ranges)| ranges.iter())
14187 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14188 .cloned()
14189 .collect();
14190
14191 this.highlight_text::<Rename>(
14192 ranges,
14193 HighlightStyle {
14194 fade_out: Some(0.6),
14195 ..Default::default()
14196 },
14197 cx,
14198 );
14199 let rename_focus_handle = rename_editor.focus_handle(cx);
14200 window.focus(&rename_focus_handle);
14201 let block_id = this.insert_blocks(
14202 [BlockProperties {
14203 style: BlockStyle::Flex,
14204 placement: BlockPlacement::Below(range.start),
14205 height: Some(1),
14206 render: Arc::new({
14207 let rename_editor = rename_editor.clone();
14208 move |cx: &mut BlockContext| {
14209 let mut text_style = cx.editor_style.text.clone();
14210 if let Some(highlight_style) = old_highlight_id
14211 .and_then(|h| h.style(&cx.editor_style.syntax))
14212 {
14213 text_style = text_style.highlight(highlight_style);
14214 }
14215 div()
14216 .block_mouse_down()
14217 .pl(cx.anchor_x)
14218 .child(EditorElement::new(
14219 &rename_editor,
14220 EditorStyle {
14221 background: cx.theme().system().transparent,
14222 local_player: cx.editor_style.local_player,
14223 text: text_style,
14224 scrollbar_width: cx.editor_style.scrollbar_width,
14225 syntax: cx.editor_style.syntax.clone(),
14226 status: cx.editor_style.status.clone(),
14227 inlay_hints_style: HighlightStyle {
14228 font_weight: Some(FontWeight::BOLD),
14229 ..make_inlay_hints_style(cx.app)
14230 },
14231 inline_completion_styles: make_suggestion_styles(
14232 cx.app,
14233 ),
14234 ..EditorStyle::default()
14235 },
14236 ))
14237 .into_any_element()
14238 }
14239 }),
14240 priority: 0,
14241 }],
14242 Some(Autoscroll::fit()),
14243 cx,
14244 )[0];
14245 this.pending_rename = Some(RenameState {
14246 range,
14247 old_name,
14248 editor: rename_editor,
14249 block_id,
14250 });
14251 })?;
14252 }
14253
14254 Ok(())
14255 }))
14256 }
14257
14258 pub fn confirm_rename(
14259 &mut self,
14260 _: &ConfirmRename,
14261 window: &mut Window,
14262 cx: &mut Context<Self>,
14263 ) -> Option<Task<Result<()>>> {
14264 let rename = self.take_rename(false, window, cx)?;
14265 let workspace = self.workspace()?.downgrade();
14266 let (buffer, start) = self
14267 .buffer
14268 .read(cx)
14269 .text_anchor_for_position(rename.range.start, cx)?;
14270 let (end_buffer, _) = self
14271 .buffer
14272 .read(cx)
14273 .text_anchor_for_position(rename.range.end, cx)?;
14274 if buffer != end_buffer {
14275 return None;
14276 }
14277
14278 let old_name = rename.old_name;
14279 let new_name = rename.editor.read(cx).text(cx);
14280
14281 let rename = self.semantics_provider.as_ref()?.perform_rename(
14282 &buffer,
14283 start,
14284 new_name.clone(),
14285 cx,
14286 )?;
14287
14288 Some(cx.spawn_in(window, async move |editor, cx| {
14289 let project_transaction = rename.await?;
14290 Self::open_project_transaction(
14291 &editor,
14292 workspace,
14293 project_transaction,
14294 format!("Rename: {} → {}", old_name, new_name),
14295 cx,
14296 )
14297 .await?;
14298
14299 editor.update(cx, |editor, cx| {
14300 editor.refresh_document_highlights(cx);
14301 })?;
14302 Ok(())
14303 }))
14304 }
14305
14306 fn take_rename(
14307 &mut self,
14308 moving_cursor: bool,
14309 window: &mut Window,
14310 cx: &mut Context<Self>,
14311 ) -> Option<RenameState> {
14312 let rename = self.pending_rename.take()?;
14313 if rename.editor.focus_handle(cx).is_focused(window) {
14314 window.focus(&self.focus_handle);
14315 }
14316
14317 self.remove_blocks(
14318 [rename.block_id].into_iter().collect(),
14319 Some(Autoscroll::fit()),
14320 cx,
14321 );
14322 self.clear_highlights::<Rename>(cx);
14323 self.show_local_selections = true;
14324
14325 if moving_cursor {
14326 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14327 editor.selections.newest::<usize>(cx).head()
14328 });
14329
14330 // Update the selection to match the position of the selection inside
14331 // the rename editor.
14332 let snapshot = self.buffer.read(cx).read(cx);
14333 let rename_range = rename.range.to_offset(&snapshot);
14334 let cursor_in_editor = snapshot
14335 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14336 .min(rename_range.end);
14337 drop(snapshot);
14338
14339 self.change_selections(None, window, cx, |s| {
14340 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14341 });
14342 } else {
14343 self.refresh_document_highlights(cx);
14344 }
14345
14346 Some(rename)
14347 }
14348
14349 pub fn pending_rename(&self) -> Option<&RenameState> {
14350 self.pending_rename.as_ref()
14351 }
14352
14353 fn format(
14354 &mut self,
14355 _: &Format,
14356 window: &mut Window,
14357 cx: &mut Context<Self>,
14358 ) -> Option<Task<Result<()>>> {
14359 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14360
14361 let project = match &self.project {
14362 Some(project) => project.clone(),
14363 None => return None,
14364 };
14365
14366 Some(self.perform_format(
14367 project,
14368 FormatTrigger::Manual,
14369 FormatTarget::Buffers,
14370 window,
14371 cx,
14372 ))
14373 }
14374
14375 fn format_selections(
14376 &mut self,
14377 _: &FormatSelections,
14378 window: &mut Window,
14379 cx: &mut Context<Self>,
14380 ) -> Option<Task<Result<()>>> {
14381 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14382
14383 let project = match &self.project {
14384 Some(project) => project.clone(),
14385 None => return None,
14386 };
14387
14388 let ranges = self
14389 .selections
14390 .all_adjusted(cx)
14391 .into_iter()
14392 .map(|selection| selection.range())
14393 .collect_vec();
14394
14395 Some(self.perform_format(
14396 project,
14397 FormatTrigger::Manual,
14398 FormatTarget::Ranges(ranges),
14399 window,
14400 cx,
14401 ))
14402 }
14403
14404 fn perform_format(
14405 &mut self,
14406 project: Entity<Project>,
14407 trigger: FormatTrigger,
14408 target: FormatTarget,
14409 window: &mut Window,
14410 cx: &mut Context<Self>,
14411 ) -> Task<Result<()>> {
14412 let buffer = self.buffer.clone();
14413 let (buffers, target) = match target {
14414 FormatTarget::Buffers => {
14415 let mut buffers = buffer.read(cx).all_buffers();
14416 if trigger == FormatTrigger::Save {
14417 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14418 }
14419 (buffers, LspFormatTarget::Buffers)
14420 }
14421 FormatTarget::Ranges(selection_ranges) => {
14422 let multi_buffer = buffer.read(cx);
14423 let snapshot = multi_buffer.read(cx);
14424 let mut buffers = HashSet::default();
14425 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14426 BTreeMap::new();
14427 for selection_range in selection_ranges {
14428 for (buffer, buffer_range, _) in
14429 snapshot.range_to_buffer_ranges(selection_range)
14430 {
14431 let buffer_id = buffer.remote_id();
14432 let start = buffer.anchor_before(buffer_range.start);
14433 let end = buffer.anchor_after(buffer_range.end);
14434 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14435 buffer_id_to_ranges
14436 .entry(buffer_id)
14437 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14438 .or_insert_with(|| vec![start..end]);
14439 }
14440 }
14441 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14442 }
14443 };
14444
14445 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14446 let selections_prev = transaction_id_prev
14447 .and_then(|transaction_id_prev| {
14448 // default to selections as they were after the last edit, if we have them,
14449 // instead of how they are now.
14450 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14451 // will take you back to where you made the last edit, instead of staying where you scrolled
14452 self.selection_history
14453 .transaction(transaction_id_prev)
14454 .map(|t| t.0.clone())
14455 })
14456 .unwrap_or_else(|| {
14457 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14458 self.selections.disjoint_anchors()
14459 });
14460
14461 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14462 let format = project.update(cx, |project, cx| {
14463 project.format(buffers, target, true, trigger, cx)
14464 });
14465
14466 cx.spawn_in(window, async move |editor, cx| {
14467 let transaction = futures::select_biased! {
14468 transaction = format.log_err().fuse() => transaction,
14469 () = timeout => {
14470 log::warn!("timed out waiting for formatting");
14471 None
14472 }
14473 };
14474
14475 buffer
14476 .update(cx, |buffer, cx| {
14477 if let Some(transaction) = transaction {
14478 if !buffer.is_singleton() {
14479 buffer.push_transaction(&transaction.0, cx);
14480 }
14481 }
14482 cx.notify();
14483 })
14484 .ok();
14485
14486 if let Some(transaction_id_now) =
14487 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14488 {
14489 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14490 if has_new_transaction {
14491 _ = editor.update(cx, |editor, _| {
14492 editor
14493 .selection_history
14494 .insert_transaction(transaction_id_now, selections_prev);
14495 });
14496 }
14497 }
14498
14499 Ok(())
14500 })
14501 }
14502
14503 fn organize_imports(
14504 &mut self,
14505 _: &OrganizeImports,
14506 window: &mut Window,
14507 cx: &mut Context<Self>,
14508 ) -> Option<Task<Result<()>>> {
14509 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14510 let project = match &self.project {
14511 Some(project) => project.clone(),
14512 None => return None,
14513 };
14514 Some(self.perform_code_action_kind(
14515 project,
14516 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14517 window,
14518 cx,
14519 ))
14520 }
14521
14522 fn perform_code_action_kind(
14523 &mut self,
14524 project: Entity<Project>,
14525 kind: CodeActionKind,
14526 window: &mut Window,
14527 cx: &mut Context<Self>,
14528 ) -> Task<Result<()>> {
14529 let buffer = self.buffer.clone();
14530 let buffers = buffer.read(cx).all_buffers();
14531 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14532 let apply_action = project.update(cx, |project, cx| {
14533 project.apply_code_action_kind(buffers, kind, true, cx)
14534 });
14535 cx.spawn_in(window, async move |_, cx| {
14536 let transaction = futures::select_biased! {
14537 () = timeout => {
14538 log::warn!("timed out waiting for executing code action");
14539 None
14540 }
14541 transaction = apply_action.log_err().fuse() => transaction,
14542 };
14543 buffer
14544 .update(cx, |buffer, cx| {
14545 // check if we need this
14546 if let Some(transaction) = transaction {
14547 if !buffer.is_singleton() {
14548 buffer.push_transaction(&transaction.0, cx);
14549 }
14550 }
14551 cx.notify();
14552 })
14553 .ok();
14554 Ok(())
14555 })
14556 }
14557
14558 fn restart_language_server(
14559 &mut self,
14560 _: &RestartLanguageServer,
14561 _: &mut Window,
14562 cx: &mut Context<Self>,
14563 ) {
14564 if let Some(project) = self.project.clone() {
14565 self.buffer.update(cx, |multi_buffer, cx| {
14566 project.update(cx, |project, cx| {
14567 project.restart_language_servers_for_buffers(
14568 multi_buffer.all_buffers().into_iter().collect(),
14569 cx,
14570 );
14571 });
14572 })
14573 }
14574 }
14575
14576 fn stop_language_server(
14577 &mut self,
14578 _: &StopLanguageServer,
14579 _: &mut Window,
14580 cx: &mut Context<Self>,
14581 ) {
14582 if let Some(project) = self.project.clone() {
14583 self.buffer.update(cx, |multi_buffer, cx| {
14584 project.update(cx, |project, cx| {
14585 project.stop_language_servers_for_buffers(
14586 multi_buffer.all_buffers().into_iter().collect(),
14587 cx,
14588 );
14589 cx.emit(project::Event::RefreshInlayHints);
14590 });
14591 });
14592 }
14593 }
14594
14595 fn cancel_language_server_work(
14596 workspace: &mut Workspace,
14597 _: &actions::CancelLanguageServerWork,
14598 _: &mut Window,
14599 cx: &mut Context<Workspace>,
14600 ) {
14601 let project = workspace.project();
14602 let buffers = workspace
14603 .active_item(cx)
14604 .and_then(|item| item.act_as::<Editor>(cx))
14605 .map_or(HashSet::default(), |editor| {
14606 editor.read(cx).buffer.read(cx).all_buffers()
14607 });
14608 project.update(cx, |project, cx| {
14609 project.cancel_language_server_work_for_buffers(buffers, cx);
14610 });
14611 }
14612
14613 fn show_character_palette(
14614 &mut self,
14615 _: &ShowCharacterPalette,
14616 window: &mut Window,
14617 _: &mut Context<Self>,
14618 ) {
14619 window.show_character_palette();
14620 }
14621
14622 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14623 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14624 let buffer = self.buffer.read(cx).snapshot(cx);
14625 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
14626 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
14627 let is_valid = buffer
14628 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14629 .any(|entry| {
14630 entry.diagnostic.is_primary
14631 && !entry.range.is_empty()
14632 && entry.range.start == primary_range_start
14633 && entry.diagnostic.message == active_diagnostics.active_message
14634 });
14635
14636 if !is_valid {
14637 self.dismiss_diagnostics(cx);
14638 }
14639 }
14640 }
14641
14642 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
14643 match &self.active_diagnostics {
14644 ActiveDiagnostic::Group(group) => Some(group),
14645 _ => None,
14646 }
14647 }
14648
14649 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
14650 self.dismiss_diagnostics(cx);
14651 self.active_diagnostics = ActiveDiagnostic::All;
14652 }
14653
14654 fn activate_diagnostics(
14655 &mut self,
14656 buffer_id: BufferId,
14657 diagnostic: DiagnosticEntry<usize>,
14658 window: &mut Window,
14659 cx: &mut Context<Self>,
14660 ) {
14661 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14662 return;
14663 }
14664 self.dismiss_diagnostics(cx);
14665 let snapshot = self.snapshot(window, cx);
14666 let Some(diagnostic_renderer) = cx
14667 .try_global::<GlobalDiagnosticRenderer>()
14668 .map(|g| g.0.clone())
14669 else {
14670 return;
14671 };
14672 let buffer = self.buffer.read(cx).snapshot(cx);
14673
14674 let diagnostic_group = buffer
14675 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
14676 .collect::<Vec<_>>();
14677
14678 let blocks = diagnostic_renderer.render_group(
14679 diagnostic_group,
14680 buffer_id,
14681 snapshot,
14682 cx.weak_entity(),
14683 cx,
14684 );
14685
14686 let blocks = self.display_map.update(cx, |display_map, cx| {
14687 display_map.insert_blocks(blocks, cx).into_iter().collect()
14688 });
14689 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
14690 active_range: buffer.anchor_before(diagnostic.range.start)
14691 ..buffer.anchor_after(diagnostic.range.end),
14692 active_message: diagnostic.diagnostic.message.clone(),
14693 group_id: diagnostic.diagnostic.group_id,
14694 blocks,
14695 });
14696 cx.notify();
14697 }
14698
14699 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14700 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14701 return;
14702 };
14703
14704 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
14705 if let ActiveDiagnostic::Group(group) = prev {
14706 self.display_map.update(cx, |display_map, cx| {
14707 display_map.remove_blocks(group.blocks, cx);
14708 });
14709 cx.notify();
14710 }
14711 }
14712
14713 /// Disable inline diagnostics rendering for this editor.
14714 pub fn disable_inline_diagnostics(&mut self) {
14715 self.inline_diagnostics_enabled = false;
14716 self.inline_diagnostics_update = Task::ready(());
14717 self.inline_diagnostics.clear();
14718 }
14719
14720 pub fn inline_diagnostics_enabled(&self) -> bool {
14721 self.inline_diagnostics_enabled
14722 }
14723
14724 pub fn show_inline_diagnostics(&self) -> bool {
14725 self.show_inline_diagnostics
14726 }
14727
14728 pub fn toggle_inline_diagnostics(
14729 &mut self,
14730 _: &ToggleInlineDiagnostics,
14731 window: &mut Window,
14732 cx: &mut Context<Editor>,
14733 ) {
14734 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14735 self.refresh_inline_diagnostics(false, window, cx);
14736 }
14737
14738 fn refresh_inline_diagnostics(
14739 &mut self,
14740 debounce: bool,
14741 window: &mut Window,
14742 cx: &mut Context<Self>,
14743 ) {
14744 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14745 self.inline_diagnostics_update = Task::ready(());
14746 self.inline_diagnostics.clear();
14747 return;
14748 }
14749
14750 let debounce_ms = ProjectSettings::get_global(cx)
14751 .diagnostics
14752 .inline
14753 .update_debounce_ms;
14754 let debounce = if debounce && debounce_ms > 0 {
14755 Some(Duration::from_millis(debounce_ms))
14756 } else {
14757 None
14758 };
14759 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14760 let editor = editor.upgrade().unwrap();
14761
14762 if let Some(debounce) = debounce {
14763 cx.background_executor().timer(debounce).await;
14764 }
14765 let Some(snapshot) = editor
14766 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14767 .ok()
14768 else {
14769 return;
14770 };
14771
14772 let new_inline_diagnostics = cx
14773 .background_spawn(async move {
14774 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14775 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14776 let message = diagnostic_entry
14777 .diagnostic
14778 .message
14779 .split_once('\n')
14780 .map(|(line, _)| line)
14781 .map(SharedString::new)
14782 .unwrap_or_else(|| {
14783 SharedString::from(diagnostic_entry.diagnostic.message)
14784 });
14785 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14786 let (Ok(i) | Err(i)) = inline_diagnostics
14787 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14788 inline_diagnostics.insert(
14789 i,
14790 (
14791 start_anchor,
14792 InlineDiagnostic {
14793 message,
14794 group_id: diagnostic_entry.diagnostic.group_id,
14795 start: diagnostic_entry.range.start.to_point(&snapshot),
14796 is_primary: diagnostic_entry.diagnostic.is_primary,
14797 severity: diagnostic_entry.diagnostic.severity,
14798 },
14799 ),
14800 );
14801 }
14802 inline_diagnostics
14803 })
14804 .await;
14805
14806 editor
14807 .update(cx, |editor, cx| {
14808 editor.inline_diagnostics = new_inline_diagnostics;
14809 cx.notify();
14810 })
14811 .ok();
14812 });
14813 }
14814
14815 pub fn set_selections_from_remote(
14816 &mut self,
14817 selections: Vec<Selection<Anchor>>,
14818 pending_selection: Option<Selection<Anchor>>,
14819 window: &mut Window,
14820 cx: &mut Context<Self>,
14821 ) {
14822 let old_cursor_position = self.selections.newest_anchor().head();
14823 self.selections.change_with(cx, |s| {
14824 s.select_anchors(selections);
14825 if let Some(pending_selection) = pending_selection {
14826 s.set_pending(pending_selection, SelectMode::Character);
14827 } else {
14828 s.clear_pending();
14829 }
14830 });
14831 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14832 }
14833
14834 fn push_to_selection_history(&mut self) {
14835 self.selection_history.push(SelectionHistoryEntry {
14836 selections: self.selections.disjoint_anchors(),
14837 select_next_state: self.select_next_state.clone(),
14838 select_prev_state: self.select_prev_state.clone(),
14839 add_selections_state: self.add_selections_state.clone(),
14840 });
14841 }
14842
14843 pub fn transact(
14844 &mut self,
14845 window: &mut Window,
14846 cx: &mut Context<Self>,
14847 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14848 ) -> Option<TransactionId> {
14849 self.start_transaction_at(Instant::now(), window, cx);
14850 update(self, window, cx);
14851 self.end_transaction_at(Instant::now(), cx)
14852 }
14853
14854 pub fn start_transaction_at(
14855 &mut self,
14856 now: Instant,
14857 window: &mut Window,
14858 cx: &mut Context<Self>,
14859 ) {
14860 self.end_selection(window, cx);
14861 if let Some(tx_id) = self
14862 .buffer
14863 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14864 {
14865 self.selection_history
14866 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14867 cx.emit(EditorEvent::TransactionBegun {
14868 transaction_id: tx_id,
14869 })
14870 }
14871 }
14872
14873 pub fn end_transaction_at(
14874 &mut self,
14875 now: Instant,
14876 cx: &mut Context<Self>,
14877 ) -> Option<TransactionId> {
14878 if let Some(transaction_id) = self
14879 .buffer
14880 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14881 {
14882 if let Some((_, end_selections)) =
14883 self.selection_history.transaction_mut(transaction_id)
14884 {
14885 *end_selections = Some(self.selections.disjoint_anchors());
14886 } else {
14887 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14888 }
14889
14890 cx.emit(EditorEvent::Edited { transaction_id });
14891 Some(transaction_id)
14892 } else {
14893 None
14894 }
14895 }
14896
14897 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14898 if self.selection_mark_mode {
14899 self.change_selections(None, window, cx, |s| {
14900 s.move_with(|_, sel| {
14901 sel.collapse_to(sel.head(), SelectionGoal::None);
14902 });
14903 })
14904 }
14905 self.selection_mark_mode = true;
14906 cx.notify();
14907 }
14908
14909 pub fn swap_selection_ends(
14910 &mut self,
14911 _: &actions::SwapSelectionEnds,
14912 window: &mut Window,
14913 cx: &mut Context<Self>,
14914 ) {
14915 self.change_selections(None, window, cx, |s| {
14916 s.move_with(|_, sel| {
14917 if sel.start != sel.end {
14918 sel.reversed = !sel.reversed
14919 }
14920 });
14921 });
14922 self.request_autoscroll(Autoscroll::newest(), cx);
14923 cx.notify();
14924 }
14925
14926 pub fn toggle_fold(
14927 &mut self,
14928 _: &actions::ToggleFold,
14929 window: &mut Window,
14930 cx: &mut Context<Self>,
14931 ) {
14932 if self.is_singleton(cx) {
14933 let selection = self.selections.newest::<Point>(cx);
14934
14935 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14936 let range = if selection.is_empty() {
14937 let point = selection.head().to_display_point(&display_map);
14938 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14939 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14940 .to_point(&display_map);
14941 start..end
14942 } else {
14943 selection.range()
14944 };
14945 if display_map.folds_in_range(range).next().is_some() {
14946 self.unfold_lines(&Default::default(), window, cx)
14947 } else {
14948 self.fold(&Default::default(), window, cx)
14949 }
14950 } else {
14951 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14952 let buffer_ids: HashSet<_> = self
14953 .selections
14954 .disjoint_anchor_ranges()
14955 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14956 .collect();
14957
14958 let should_unfold = buffer_ids
14959 .iter()
14960 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14961
14962 for buffer_id in buffer_ids {
14963 if should_unfold {
14964 self.unfold_buffer(buffer_id, cx);
14965 } else {
14966 self.fold_buffer(buffer_id, cx);
14967 }
14968 }
14969 }
14970 }
14971
14972 pub fn toggle_fold_recursive(
14973 &mut self,
14974 _: &actions::ToggleFoldRecursive,
14975 window: &mut Window,
14976 cx: &mut Context<Self>,
14977 ) {
14978 let selection = self.selections.newest::<Point>(cx);
14979
14980 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14981 let range = if selection.is_empty() {
14982 let point = selection.head().to_display_point(&display_map);
14983 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14984 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14985 .to_point(&display_map);
14986 start..end
14987 } else {
14988 selection.range()
14989 };
14990 if display_map.folds_in_range(range).next().is_some() {
14991 self.unfold_recursive(&Default::default(), window, cx)
14992 } else {
14993 self.fold_recursive(&Default::default(), window, cx)
14994 }
14995 }
14996
14997 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14998 if self.is_singleton(cx) {
14999 let mut to_fold = Vec::new();
15000 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15001 let selections = self.selections.all_adjusted(cx);
15002
15003 for selection in selections {
15004 let range = selection.range().sorted();
15005 let buffer_start_row = range.start.row;
15006
15007 if range.start.row != range.end.row {
15008 let mut found = false;
15009 let mut row = range.start.row;
15010 while row <= range.end.row {
15011 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15012 {
15013 found = true;
15014 row = crease.range().end.row + 1;
15015 to_fold.push(crease);
15016 } else {
15017 row += 1
15018 }
15019 }
15020 if found {
15021 continue;
15022 }
15023 }
15024
15025 for row in (0..=range.start.row).rev() {
15026 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15027 if crease.range().end.row >= buffer_start_row {
15028 to_fold.push(crease);
15029 if row <= range.start.row {
15030 break;
15031 }
15032 }
15033 }
15034 }
15035 }
15036
15037 self.fold_creases(to_fold, true, window, cx);
15038 } else {
15039 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15040 let buffer_ids = self
15041 .selections
15042 .disjoint_anchor_ranges()
15043 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15044 .collect::<HashSet<_>>();
15045 for buffer_id in buffer_ids {
15046 self.fold_buffer(buffer_id, cx);
15047 }
15048 }
15049 }
15050
15051 fn fold_at_level(
15052 &mut self,
15053 fold_at: &FoldAtLevel,
15054 window: &mut Window,
15055 cx: &mut Context<Self>,
15056 ) {
15057 if !self.buffer.read(cx).is_singleton() {
15058 return;
15059 }
15060
15061 let fold_at_level = fold_at.0;
15062 let snapshot = self.buffer.read(cx).snapshot(cx);
15063 let mut to_fold = Vec::new();
15064 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15065
15066 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15067 while start_row < end_row {
15068 match self
15069 .snapshot(window, cx)
15070 .crease_for_buffer_row(MultiBufferRow(start_row))
15071 {
15072 Some(crease) => {
15073 let nested_start_row = crease.range().start.row + 1;
15074 let nested_end_row = crease.range().end.row;
15075
15076 if current_level < fold_at_level {
15077 stack.push((nested_start_row, nested_end_row, current_level + 1));
15078 } else if current_level == fold_at_level {
15079 to_fold.push(crease);
15080 }
15081
15082 start_row = nested_end_row + 1;
15083 }
15084 None => start_row += 1,
15085 }
15086 }
15087 }
15088
15089 self.fold_creases(to_fold, true, window, cx);
15090 }
15091
15092 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15093 if self.buffer.read(cx).is_singleton() {
15094 let mut fold_ranges = Vec::new();
15095 let snapshot = self.buffer.read(cx).snapshot(cx);
15096
15097 for row in 0..snapshot.max_row().0 {
15098 if let Some(foldable_range) = self
15099 .snapshot(window, cx)
15100 .crease_for_buffer_row(MultiBufferRow(row))
15101 {
15102 fold_ranges.push(foldable_range);
15103 }
15104 }
15105
15106 self.fold_creases(fold_ranges, true, window, cx);
15107 } else {
15108 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15109 editor
15110 .update_in(cx, |editor, _, cx| {
15111 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15112 editor.fold_buffer(buffer_id, cx);
15113 }
15114 })
15115 .ok();
15116 });
15117 }
15118 }
15119
15120 pub fn fold_function_bodies(
15121 &mut self,
15122 _: &actions::FoldFunctionBodies,
15123 window: &mut Window,
15124 cx: &mut Context<Self>,
15125 ) {
15126 let snapshot = self.buffer.read(cx).snapshot(cx);
15127
15128 let ranges = snapshot
15129 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15130 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15131 .collect::<Vec<_>>();
15132
15133 let creases = ranges
15134 .into_iter()
15135 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15136 .collect();
15137
15138 self.fold_creases(creases, true, window, cx);
15139 }
15140
15141 pub fn fold_recursive(
15142 &mut self,
15143 _: &actions::FoldRecursive,
15144 window: &mut Window,
15145 cx: &mut Context<Self>,
15146 ) {
15147 let mut to_fold = Vec::new();
15148 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15149 let selections = self.selections.all_adjusted(cx);
15150
15151 for selection in selections {
15152 let range = selection.range().sorted();
15153 let buffer_start_row = range.start.row;
15154
15155 if range.start.row != range.end.row {
15156 let mut found = false;
15157 for row in range.start.row..=range.end.row {
15158 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15159 found = true;
15160 to_fold.push(crease);
15161 }
15162 }
15163 if found {
15164 continue;
15165 }
15166 }
15167
15168 for row in (0..=range.start.row).rev() {
15169 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15170 if crease.range().end.row >= buffer_start_row {
15171 to_fold.push(crease);
15172 } else {
15173 break;
15174 }
15175 }
15176 }
15177 }
15178
15179 self.fold_creases(to_fold, true, window, cx);
15180 }
15181
15182 pub fn fold_at(
15183 &mut self,
15184 buffer_row: MultiBufferRow,
15185 window: &mut Window,
15186 cx: &mut Context<Self>,
15187 ) {
15188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15189
15190 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15191 let autoscroll = self
15192 .selections
15193 .all::<Point>(cx)
15194 .iter()
15195 .any(|selection| crease.range().overlaps(&selection.range()));
15196
15197 self.fold_creases(vec![crease], autoscroll, window, cx);
15198 }
15199 }
15200
15201 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15202 if self.is_singleton(cx) {
15203 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15204 let buffer = &display_map.buffer_snapshot;
15205 let selections = self.selections.all::<Point>(cx);
15206 let ranges = selections
15207 .iter()
15208 .map(|s| {
15209 let range = s.display_range(&display_map).sorted();
15210 let mut start = range.start.to_point(&display_map);
15211 let mut end = range.end.to_point(&display_map);
15212 start.column = 0;
15213 end.column = buffer.line_len(MultiBufferRow(end.row));
15214 start..end
15215 })
15216 .collect::<Vec<_>>();
15217
15218 self.unfold_ranges(&ranges, true, true, cx);
15219 } else {
15220 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15221 let buffer_ids = self
15222 .selections
15223 .disjoint_anchor_ranges()
15224 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15225 .collect::<HashSet<_>>();
15226 for buffer_id in buffer_ids {
15227 self.unfold_buffer(buffer_id, cx);
15228 }
15229 }
15230 }
15231
15232 pub fn unfold_recursive(
15233 &mut self,
15234 _: &UnfoldRecursive,
15235 _window: &mut Window,
15236 cx: &mut Context<Self>,
15237 ) {
15238 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15239 let selections = self.selections.all::<Point>(cx);
15240 let ranges = selections
15241 .iter()
15242 .map(|s| {
15243 let mut range = s.display_range(&display_map).sorted();
15244 *range.start.column_mut() = 0;
15245 *range.end.column_mut() = display_map.line_len(range.end.row());
15246 let start = range.start.to_point(&display_map);
15247 let end = range.end.to_point(&display_map);
15248 start..end
15249 })
15250 .collect::<Vec<_>>();
15251
15252 self.unfold_ranges(&ranges, true, true, cx);
15253 }
15254
15255 pub fn unfold_at(
15256 &mut self,
15257 buffer_row: MultiBufferRow,
15258 _window: &mut Window,
15259 cx: &mut Context<Self>,
15260 ) {
15261 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15262
15263 let intersection_range = Point::new(buffer_row.0, 0)
15264 ..Point::new(
15265 buffer_row.0,
15266 display_map.buffer_snapshot.line_len(buffer_row),
15267 );
15268
15269 let autoscroll = self
15270 .selections
15271 .all::<Point>(cx)
15272 .iter()
15273 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15274
15275 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15276 }
15277
15278 pub fn unfold_all(
15279 &mut self,
15280 _: &actions::UnfoldAll,
15281 _window: &mut Window,
15282 cx: &mut Context<Self>,
15283 ) {
15284 if self.buffer.read(cx).is_singleton() {
15285 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15286 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15287 } else {
15288 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15289 editor
15290 .update(cx, |editor, cx| {
15291 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15292 editor.unfold_buffer(buffer_id, cx);
15293 }
15294 })
15295 .ok();
15296 });
15297 }
15298 }
15299
15300 pub fn fold_selected_ranges(
15301 &mut self,
15302 _: &FoldSelectedRanges,
15303 window: &mut Window,
15304 cx: &mut Context<Self>,
15305 ) {
15306 let selections = self.selections.all_adjusted(cx);
15307 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15308 let ranges = selections
15309 .into_iter()
15310 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15311 .collect::<Vec<_>>();
15312 self.fold_creases(ranges, true, window, cx);
15313 }
15314
15315 pub fn fold_ranges<T: ToOffset + Clone>(
15316 &mut self,
15317 ranges: Vec<Range<T>>,
15318 auto_scroll: bool,
15319 window: &mut Window,
15320 cx: &mut Context<Self>,
15321 ) {
15322 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15323 let ranges = ranges
15324 .into_iter()
15325 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15326 .collect::<Vec<_>>();
15327 self.fold_creases(ranges, auto_scroll, window, cx);
15328 }
15329
15330 pub fn fold_creases<T: ToOffset + Clone>(
15331 &mut self,
15332 creases: Vec<Crease<T>>,
15333 auto_scroll: bool,
15334 _window: &mut Window,
15335 cx: &mut Context<Self>,
15336 ) {
15337 if creases.is_empty() {
15338 return;
15339 }
15340
15341 let mut buffers_affected = HashSet::default();
15342 let multi_buffer = self.buffer().read(cx);
15343 for crease in &creases {
15344 if let Some((_, buffer, _)) =
15345 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15346 {
15347 buffers_affected.insert(buffer.read(cx).remote_id());
15348 };
15349 }
15350
15351 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15352
15353 if auto_scroll {
15354 self.request_autoscroll(Autoscroll::fit(), cx);
15355 }
15356
15357 cx.notify();
15358
15359 self.scrollbar_marker_state.dirty = true;
15360 self.folds_did_change(cx);
15361 }
15362
15363 /// Removes any folds whose ranges intersect any of the given ranges.
15364 pub fn unfold_ranges<T: ToOffset + Clone>(
15365 &mut self,
15366 ranges: &[Range<T>],
15367 inclusive: bool,
15368 auto_scroll: bool,
15369 cx: &mut Context<Self>,
15370 ) {
15371 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15372 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15373 });
15374 self.folds_did_change(cx);
15375 }
15376
15377 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15378 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15379 return;
15380 }
15381 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15382 self.display_map.update(cx, |display_map, cx| {
15383 display_map.fold_buffers([buffer_id], cx)
15384 });
15385 cx.emit(EditorEvent::BufferFoldToggled {
15386 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15387 folded: true,
15388 });
15389 cx.notify();
15390 }
15391
15392 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15393 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15394 return;
15395 }
15396 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15397 self.display_map.update(cx, |display_map, cx| {
15398 display_map.unfold_buffers([buffer_id], cx);
15399 });
15400 cx.emit(EditorEvent::BufferFoldToggled {
15401 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15402 folded: false,
15403 });
15404 cx.notify();
15405 }
15406
15407 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15408 self.display_map.read(cx).is_buffer_folded(buffer)
15409 }
15410
15411 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15412 self.display_map.read(cx).folded_buffers()
15413 }
15414
15415 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15416 self.display_map.update(cx, |display_map, cx| {
15417 display_map.disable_header_for_buffer(buffer_id, cx);
15418 });
15419 cx.notify();
15420 }
15421
15422 /// Removes any folds with the given ranges.
15423 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15424 &mut self,
15425 ranges: &[Range<T>],
15426 type_id: TypeId,
15427 auto_scroll: bool,
15428 cx: &mut Context<Self>,
15429 ) {
15430 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15431 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15432 });
15433 self.folds_did_change(cx);
15434 }
15435
15436 fn remove_folds_with<T: ToOffset + Clone>(
15437 &mut self,
15438 ranges: &[Range<T>],
15439 auto_scroll: bool,
15440 cx: &mut Context<Self>,
15441 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15442 ) {
15443 if ranges.is_empty() {
15444 return;
15445 }
15446
15447 let mut buffers_affected = HashSet::default();
15448 let multi_buffer = self.buffer().read(cx);
15449 for range in ranges {
15450 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15451 buffers_affected.insert(buffer.read(cx).remote_id());
15452 };
15453 }
15454
15455 self.display_map.update(cx, update);
15456
15457 if auto_scroll {
15458 self.request_autoscroll(Autoscroll::fit(), cx);
15459 }
15460
15461 cx.notify();
15462 self.scrollbar_marker_state.dirty = true;
15463 self.active_indent_guides_state.dirty = true;
15464 }
15465
15466 pub fn update_fold_widths(
15467 &mut self,
15468 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15469 cx: &mut Context<Self>,
15470 ) -> bool {
15471 self.display_map
15472 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15473 }
15474
15475 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15476 self.display_map.read(cx).fold_placeholder.clone()
15477 }
15478
15479 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15480 self.buffer.update(cx, |buffer, cx| {
15481 buffer.set_all_diff_hunks_expanded(cx);
15482 });
15483 }
15484
15485 pub fn expand_all_diff_hunks(
15486 &mut self,
15487 _: &ExpandAllDiffHunks,
15488 _window: &mut Window,
15489 cx: &mut Context<Self>,
15490 ) {
15491 self.buffer.update(cx, |buffer, cx| {
15492 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15493 });
15494 }
15495
15496 pub fn toggle_selected_diff_hunks(
15497 &mut self,
15498 _: &ToggleSelectedDiffHunks,
15499 _window: &mut Window,
15500 cx: &mut Context<Self>,
15501 ) {
15502 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15503 self.toggle_diff_hunks_in_ranges(ranges, cx);
15504 }
15505
15506 pub fn diff_hunks_in_ranges<'a>(
15507 &'a self,
15508 ranges: &'a [Range<Anchor>],
15509 buffer: &'a MultiBufferSnapshot,
15510 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15511 ranges.iter().flat_map(move |range| {
15512 let end_excerpt_id = range.end.excerpt_id;
15513 let range = range.to_point(buffer);
15514 let mut peek_end = range.end;
15515 if range.end.row < buffer.max_row().0 {
15516 peek_end = Point::new(range.end.row + 1, 0);
15517 }
15518 buffer
15519 .diff_hunks_in_range(range.start..peek_end)
15520 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15521 })
15522 }
15523
15524 pub fn has_stageable_diff_hunks_in_ranges(
15525 &self,
15526 ranges: &[Range<Anchor>],
15527 snapshot: &MultiBufferSnapshot,
15528 ) -> bool {
15529 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15530 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15531 }
15532
15533 pub fn toggle_staged_selected_diff_hunks(
15534 &mut self,
15535 _: &::git::ToggleStaged,
15536 _: &mut Window,
15537 cx: &mut Context<Self>,
15538 ) {
15539 let snapshot = self.buffer.read(cx).snapshot(cx);
15540 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15541 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15542 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15543 }
15544
15545 pub fn set_render_diff_hunk_controls(
15546 &mut self,
15547 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15548 cx: &mut Context<Self>,
15549 ) {
15550 self.render_diff_hunk_controls = render_diff_hunk_controls;
15551 cx.notify();
15552 }
15553
15554 pub fn stage_and_next(
15555 &mut self,
15556 _: &::git::StageAndNext,
15557 window: &mut Window,
15558 cx: &mut Context<Self>,
15559 ) {
15560 self.do_stage_or_unstage_and_next(true, window, cx);
15561 }
15562
15563 pub fn unstage_and_next(
15564 &mut self,
15565 _: &::git::UnstageAndNext,
15566 window: &mut Window,
15567 cx: &mut Context<Self>,
15568 ) {
15569 self.do_stage_or_unstage_and_next(false, window, cx);
15570 }
15571
15572 pub fn stage_or_unstage_diff_hunks(
15573 &mut self,
15574 stage: bool,
15575 ranges: Vec<Range<Anchor>>,
15576 cx: &mut Context<Self>,
15577 ) {
15578 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15579 cx.spawn(async move |this, cx| {
15580 task.await?;
15581 this.update(cx, |this, cx| {
15582 let snapshot = this.buffer.read(cx).snapshot(cx);
15583 let chunk_by = this
15584 .diff_hunks_in_ranges(&ranges, &snapshot)
15585 .chunk_by(|hunk| hunk.buffer_id);
15586 for (buffer_id, hunks) in &chunk_by {
15587 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15588 }
15589 })
15590 })
15591 .detach_and_log_err(cx);
15592 }
15593
15594 fn save_buffers_for_ranges_if_needed(
15595 &mut self,
15596 ranges: &[Range<Anchor>],
15597 cx: &mut Context<Editor>,
15598 ) -> Task<Result<()>> {
15599 let multibuffer = self.buffer.read(cx);
15600 let snapshot = multibuffer.read(cx);
15601 let buffer_ids: HashSet<_> = ranges
15602 .iter()
15603 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15604 .collect();
15605 drop(snapshot);
15606
15607 let mut buffers = HashSet::default();
15608 for buffer_id in buffer_ids {
15609 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15610 let buffer = buffer_entity.read(cx);
15611 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15612 {
15613 buffers.insert(buffer_entity);
15614 }
15615 }
15616 }
15617
15618 if let Some(project) = &self.project {
15619 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15620 } else {
15621 Task::ready(Ok(()))
15622 }
15623 }
15624
15625 fn do_stage_or_unstage_and_next(
15626 &mut self,
15627 stage: bool,
15628 window: &mut Window,
15629 cx: &mut Context<Self>,
15630 ) {
15631 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15632
15633 if ranges.iter().any(|range| range.start != range.end) {
15634 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15635 return;
15636 }
15637
15638 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15639 let snapshot = self.snapshot(window, cx);
15640 let position = self.selections.newest::<Point>(cx).head();
15641 let mut row = snapshot
15642 .buffer_snapshot
15643 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15644 .find(|hunk| hunk.row_range.start.0 > position.row)
15645 .map(|hunk| hunk.row_range.start);
15646
15647 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15648 // Outside of the project diff editor, wrap around to the beginning.
15649 if !all_diff_hunks_expanded {
15650 row = row.or_else(|| {
15651 snapshot
15652 .buffer_snapshot
15653 .diff_hunks_in_range(Point::zero()..position)
15654 .find(|hunk| hunk.row_range.end.0 < position.row)
15655 .map(|hunk| hunk.row_range.start)
15656 });
15657 }
15658
15659 if let Some(row) = row {
15660 let destination = Point::new(row.0, 0);
15661 let autoscroll = Autoscroll::center();
15662
15663 self.unfold_ranges(&[destination..destination], false, false, cx);
15664 self.change_selections(Some(autoscroll), window, cx, |s| {
15665 s.select_ranges([destination..destination]);
15666 });
15667 }
15668 }
15669
15670 fn do_stage_or_unstage(
15671 &self,
15672 stage: bool,
15673 buffer_id: BufferId,
15674 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15675 cx: &mut App,
15676 ) -> Option<()> {
15677 let project = self.project.as_ref()?;
15678 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15679 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15680 let buffer_snapshot = buffer.read(cx).snapshot();
15681 let file_exists = buffer_snapshot
15682 .file()
15683 .is_some_and(|file| file.disk_state().exists());
15684 diff.update(cx, |diff, cx| {
15685 diff.stage_or_unstage_hunks(
15686 stage,
15687 &hunks
15688 .map(|hunk| buffer_diff::DiffHunk {
15689 buffer_range: hunk.buffer_range,
15690 diff_base_byte_range: hunk.diff_base_byte_range,
15691 secondary_status: hunk.secondary_status,
15692 range: Point::zero()..Point::zero(), // unused
15693 })
15694 .collect::<Vec<_>>(),
15695 &buffer_snapshot,
15696 file_exists,
15697 cx,
15698 )
15699 });
15700 None
15701 }
15702
15703 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15704 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15705 self.buffer
15706 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15707 }
15708
15709 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15710 self.buffer.update(cx, |buffer, cx| {
15711 let ranges = vec![Anchor::min()..Anchor::max()];
15712 if !buffer.all_diff_hunks_expanded()
15713 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15714 {
15715 buffer.collapse_diff_hunks(ranges, cx);
15716 true
15717 } else {
15718 false
15719 }
15720 })
15721 }
15722
15723 fn toggle_diff_hunks_in_ranges(
15724 &mut self,
15725 ranges: Vec<Range<Anchor>>,
15726 cx: &mut Context<Editor>,
15727 ) {
15728 self.buffer.update(cx, |buffer, cx| {
15729 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15730 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15731 })
15732 }
15733
15734 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15735 self.buffer.update(cx, |buffer, cx| {
15736 let snapshot = buffer.snapshot(cx);
15737 let excerpt_id = range.end.excerpt_id;
15738 let point_range = range.to_point(&snapshot);
15739 let expand = !buffer.single_hunk_is_expanded(range, cx);
15740 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15741 })
15742 }
15743
15744 pub(crate) fn apply_all_diff_hunks(
15745 &mut self,
15746 _: &ApplyAllDiffHunks,
15747 window: &mut Window,
15748 cx: &mut Context<Self>,
15749 ) {
15750 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15751
15752 let buffers = self.buffer.read(cx).all_buffers();
15753 for branch_buffer in buffers {
15754 branch_buffer.update(cx, |branch_buffer, cx| {
15755 branch_buffer.merge_into_base(Vec::new(), cx);
15756 });
15757 }
15758
15759 if let Some(project) = self.project.clone() {
15760 self.save(true, project, window, cx).detach_and_log_err(cx);
15761 }
15762 }
15763
15764 pub(crate) fn apply_selected_diff_hunks(
15765 &mut self,
15766 _: &ApplyDiffHunk,
15767 window: &mut Window,
15768 cx: &mut Context<Self>,
15769 ) {
15770 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15771 let snapshot = self.snapshot(window, cx);
15772 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15773 let mut ranges_by_buffer = HashMap::default();
15774 self.transact(window, cx, |editor, _window, cx| {
15775 for hunk in hunks {
15776 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15777 ranges_by_buffer
15778 .entry(buffer.clone())
15779 .or_insert_with(Vec::new)
15780 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15781 }
15782 }
15783
15784 for (buffer, ranges) in ranges_by_buffer {
15785 buffer.update(cx, |buffer, cx| {
15786 buffer.merge_into_base(ranges, cx);
15787 });
15788 }
15789 });
15790
15791 if let Some(project) = self.project.clone() {
15792 self.save(true, project, window, cx).detach_and_log_err(cx);
15793 }
15794 }
15795
15796 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15797 if hovered != self.gutter_hovered {
15798 self.gutter_hovered = hovered;
15799 cx.notify();
15800 }
15801 }
15802
15803 pub fn insert_blocks(
15804 &mut self,
15805 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15806 autoscroll: Option<Autoscroll>,
15807 cx: &mut Context<Self>,
15808 ) -> Vec<CustomBlockId> {
15809 let blocks = self
15810 .display_map
15811 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15812 if let Some(autoscroll) = autoscroll {
15813 self.request_autoscroll(autoscroll, cx);
15814 }
15815 cx.notify();
15816 blocks
15817 }
15818
15819 pub fn resize_blocks(
15820 &mut self,
15821 heights: HashMap<CustomBlockId, u32>,
15822 autoscroll: Option<Autoscroll>,
15823 cx: &mut Context<Self>,
15824 ) {
15825 self.display_map
15826 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15827 if let Some(autoscroll) = autoscroll {
15828 self.request_autoscroll(autoscroll, cx);
15829 }
15830 cx.notify();
15831 }
15832
15833 pub fn replace_blocks(
15834 &mut self,
15835 renderers: HashMap<CustomBlockId, RenderBlock>,
15836 autoscroll: Option<Autoscroll>,
15837 cx: &mut Context<Self>,
15838 ) {
15839 self.display_map
15840 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15841 if let Some(autoscroll) = autoscroll {
15842 self.request_autoscroll(autoscroll, cx);
15843 }
15844 cx.notify();
15845 }
15846
15847 pub fn remove_blocks(
15848 &mut self,
15849 block_ids: HashSet<CustomBlockId>,
15850 autoscroll: Option<Autoscroll>,
15851 cx: &mut Context<Self>,
15852 ) {
15853 self.display_map.update(cx, |display_map, cx| {
15854 display_map.remove_blocks(block_ids, cx)
15855 });
15856 if let Some(autoscroll) = autoscroll {
15857 self.request_autoscroll(autoscroll, cx);
15858 }
15859 cx.notify();
15860 }
15861
15862 pub fn row_for_block(
15863 &self,
15864 block_id: CustomBlockId,
15865 cx: &mut Context<Self>,
15866 ) -> Option<DisplayRow> {
15867 self.display_map
15868 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15869 }
15870
15871 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15872 self.focused_block = Some(focused_block);
15873 }
15874
15875 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15876 self.focused_block.take()
15877 }
15878
15879 pub fn insert_creases(
15880 &mut self,
15881 creases: impl IntoIterator<Item = Crease<Anchor>>,
15882 cx: &mut Context<Self>,
15883 ) -> Vec<CreaseId> {
15884 self.display_map
15885 .update(cx, |map, cx| map.insert_creases(creases, cx))
15886 }
15887
15888 pub fn remove_creases(
15889 &mut self,
15890 ids: impl IntoIterator<Item = CreaseId>,
15891 cx: &mut Context<Self>,
15892 ) {
15893 self.display_map
15894 .update(cx, |map, cx| map.remove_creases(ids, cx));
15895 }
15896
15897 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15898 self.display_map
15899 .update(cx, |map, cx| map.snapshot(cx))
15900 .longest_row()
15901 }
15902
15903 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15904 self.display_map
15905 .update(cx, |map, cx| map.snapshot(cx))
15906 .max_point()
15907 }
15908
15909 pub fn text(&self, cx: &App) -> String {
15910 self.buffer.read(cx).read(cx).text()
15911 }
15912
15913 pub fn is_empty(&self, cx: &App) -> bool {
15914 self.buffer.read(cx).read(cx).is_empty()
15915 }
15916
15917 pub fn text_option(&self, cx: &App) -> Option<String> {
15918 let text = self.text(cx);
15919 let text = text.trim();
15920
15921 if text.is_empty() {
15922 return None;
15923 }
15924
15925 Some(text.to_string())
15926 }
15927
15928 pub fn set_text(
15929 &mut self,
15930 text: impl Into<Arc<str>>,
15931 window: &mut Window,
15932 cx: &mut Context<Self>,
15933 ) {
15934 self.transact(window, cx, |this, _, cx| {
15935 this.buffer
15936 .read(cx)
15937 .as_singleton()
15938 .expect("you can only call set_text on editors for singleton buffers")
15939 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15940 });
15941 }
15942
15943 pub fn display_text(&self, cx: &mut App) -> String {
15944 self.display_map
15945 .update(cx, |map, cx| map.snapshot(cx))
15946 .text()
15947 }
15948
15949 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15950 let mut wrap_guides = smallvec::smallvec![];
15951
15952 if self.show_wrap_guides == Some(false) {
15953 return wrap_guides;
15954 }
15955
15956 let settings = self.buffer.read(cx).language_settings(cx);
15957 if settings.show_wrap_guides {
15958 match self.soft_wrap_mode(cx) {
15959 SoftWrap::Column(soft_wrap) => {
15960 wrap_guides.push((soft_wrap as usize, true));
15961 }
15962 SoftWrap::Bounded(soft_wrap) => {
15963 wrap_guides.push((soft_wrap as usize, true));
15964 }
15965 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15966 }
15967 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15968 }
15969
15970 wrap_guides
15971 }
15972
15973 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15974 let settings = self.buffer.read(cx).language_settings(cx);
15975 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15976 match mode {
15977 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15978 SoftWrap::None
15979 }
15980 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15981 language_settings::SoftWrap::PreferredLineLength => {
15982 SoftWrap::Column(settings.preferred_line_length)
15983 }
15984 language_settings::SoftWrap::Bounded => {
15985 SoftWrap::Bounded(settings.preferred_line_length)
15986 }
15987 }
15988 }
15989
15990 pub fn set_soft_wrap_mode(
15991 &mut self,
15992 mode: language_settings::SoftWrap,
15993
15994 cx: &mut Context<Self>,
15995 ) {
15996 self.soft_wrap_mode_override = Some(mode);
15997 cx.notify();
15998 }
15999
16000 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16001 self.hard_wrap = hard_wrap;
16002 cx.notify();
16003 }
16004
16005 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16006 self.text_style_refinement = Some(style);
16007 }
16008
16009 /// called by the Element so we know what style we were most recently rendered with.
16010 pub(crate) fn set_style(
16011 &mut self,
16012 style: EditorStyle,
16013 window: &mut Window,
16014 cx: &mut Context<Self>,
16015 ) {
16016 let rem_size = window.rem_size();
16017 self.display_map.update(cx, |map, cx| {
16018 map.set_font(
16019 style.text.font(),
16020 style.text.font_size.to_pixels(rem_size),
16021 cx,
16022 )
16023 });
16024 self.style = Some(style);
16025 }
16026
16027 pub fn style(&self) -> Option<&EditorStyle> {
16028 self.style.as_ref()
16029 }
16030
16031 // Called by the element. This method is not designed to be called outside of the editor
16032 // element's layout code because it does not notify when rewrapping is computed synchronously.
16033 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16034 self.display_map
16035 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16036 }
16037
16038 pub fn set_soft_wrap(&mut self) {
16039 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16040 }
16041
16042 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16043 if self.soft_wrap_mode_override.is_some() {
16044 self.soft_wrap_mode_override.take();
16045 } else {
16046 let soft_wrap = match self.soft_wrap_mode(cx) {
16047 SoftWrap::GitDiff => return,
16048 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16049 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16050 language_settings::SoftWrap::None
16051 }
16052 };
16053 self.soft_wrap_mode_override = Some(soft_wrap);
16054 }
16055 cx.notify();
16056 }
16057
16058 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16059 let Some(workspace) = self.workspace() else {
16060 return;
16061 };
16062 let fs = workspace.read(cx).app_state().fs.clone();
16063 let current_show = TabBarSettings::get_global(cx).show;
16064 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16065 setting.show = Some(!current_show);
16066 });
16067 }
16068
16069 pub fn toggle_indent_guides(
16070 &mut self,
16071 _: &ToggleIndentGuides,
16072 _: &mut Window,
16073 cx: &mut Context<Self>,
16074 ) {
16075 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16076 self.buffer
16077 .read(cx)
16078 .language_settings(cx)
16079 .indent_guides
16080 .enabled
16081 });
16082 self.show_indent_guides = Some(!currently_enabled);
16083 cx.notify();
16084 }
16085
16086 fn should_show_indent_guides(&self) -> Option<bool> {
16087 self.show_indent_guides
16088 }
16089
16090 pub fn toggle_line_numbers(
16091 &mut self,
16092 _: &ToggleLineNumbers,
16093 _: &mut Window,
16094 cx: &mut Context<Self>,
16095 ) {
16096 let mut editor_settings = EditorSettings::get_global(cx).clone();
16097 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16098 EditorSettings::override_global(editor_settings, cx);
16099 }
16100
16101 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16102 if let Some(show_line_numbers) = self.show_line_numbers {
16103 return show_line_numbers;
16104 }
16105 EditorSettings::get_global(cx).gutter.line_numbers
16106 }
16107
16108 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16109 self.use_relative_line_numbers
16110 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16111 }
16112
16113 pub fn toggle_relative_line_numbers(
16114 &mut self,
16115 _: &ToggleRelativeLineNumbers,
16116 _: &mut Window,
16117 cx: &mut Context<Self>,
16118 ) {
16119 let is_relative = self.should_use_relative_line_numbers(cx);
16120 self.set_relative_line_number(Some(!is_relative), cx)
16121 }
16122
16123 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16124 self.use_relative_line_numbers = is_relative;
16125 cx.notify();
16126 }
16127
16128 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16129 self.show_gutter = show_gutter;
16130 cx.notify();
16131 }
16132
16133 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16134 self.show_scrollbars = show_scrollbars;
16135 cx.notify();
16136 }
16137
16138 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16139 self.show_line_numbers = Some(show_line_numbers);
16140 cx.notify();
16141 }
16142
16143 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16144 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16145 cx.notify();
16146 }
16147
16148 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16149 self.show_code_actions = Some(show_code_actions);
16150 cx.notify();
16151 }
16152
16153 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16154 self.show_runnables = Some(show_runnables);
16155 cx.notify();
16156 }
16157
16158 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16159 self.show_breakpoints = Some(show_breakpoints);
16160 cx.notify();
16161 }
16162
16163 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16164 if self.display_map.read(cx).masked != masked {
16165 self.display_map.update(cx, |map, _| map.masked = masked);
16166 }
16167 cx.notify()
16168 }
16169
16170 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16171 self.show_wrap_guides = Some(show_wrap_guides);
16172 cx.notify();
16173 }
16174
16175 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16176 self.show_indent_guides = Some(show_indent_guides);
16177 cx.notify();
16178 }
16179
16180 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16181 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16182 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16183 if let Some(dir) = file.abs_path(cx).parent() {
16184 return Some(dir.to_owned());
16185 }
16186 }
16187
16188 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16189 return Some(project_path.path.to_path_buf());
16190 }
16191 }
16192
16193 None
16194 }
16195
16196 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16197 self.active_excerpt(cx)?
16198 .1
16199 .read(cx)
16200 .file()
16201 .and_then(|f| f.as_local())
16202 }
16203
16204 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16205 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16206 let buffer = buffer.read(cx);
16207 if let Some(project_path) = buffer.project_path(cx) {
16208 let project = self.project.as_ref()?.read(cx);
16209 project.absolute_path(&project_path, cx)
16210 } else {
16211 buffer
16212 .file()
16213 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16214 }
16215 })
16216 }
16217
16218 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16219 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16220 let project_path = buffer.read(cx).project_path(cx)?;
16221 let project = self.project.as_ref()?.read(cx);
16222 let entry = project.entry_for_path(&project_path, cx)?;
16223 let path = entry.path.to_path_buf();
16224 Some(path)
16225 })
16226 }
16227
16228 pub fn reveal_in_finder(
16229 &mut self,
16230 _: &RevealInFileManager,
16231 _window: &mut Window,
16232 cx: &mut Context<Self>,
16233 ) {
16234 if let Some(target) = self.target_file(cx) {
16235 cx.reveal_path(&target.abs_path(cx));
16236 }
16237 }
16238
16239 pub fn copy_path(
16240 &mut self,
16241 _: &zed_actions::workspace::CopyPath,
16242 _window: &mut Window,
16243 cx: &mut Context<Self>,
16244 ) {
16245 if let Some(path) = self.target_file_abs_path(cx) {
16246 if let Some(path) = path.to_str() {
16247 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16248 }
16249 }
16250 }
16251
16252 pub fn copy_relative_path(
16253 &mut self,
16254 _: &zed_actions::workspace::CopyRelativePath,
16255 _window: &mut Window,
16256 cx: &mut Context<Self>,
16257 ) {
16258 if let Some(path) = self.target_file_path(cx) {
16259 if let Some(path) = path.to_str() {
16260 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16261 }
16262 }
16263 }
16264
16265 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16266 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16267 buffer.read(cx).project_path(cx)
16268 } else {
16269 None
16270 }
16271 }
16272
16273 // Returns true if the editor handled a go-to-line request
16274 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16275 maybe!({
16276 let breakpoint_store = self.breakpoint_store.as_ref()?;
16277
16278 let Some((_, _, active_position)) =
16279 breakpoint_store.read(cx).active_position().cloned()
16280 else {
16281 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16282 return None;
16283 };
16284
16285 let snapshot = self
16286 .project
16287 .as_ref()?
16288 .read(cx)
16289 .buffer_for_id(active_position.buffer_id?, cx)?
16290 .read(cx)
16291 .snapshot();
16292
16293 let mut handled = false;
16294 for (id, ExcerptRange { context, .. }) in self
16295 .buffer
16296 .read(cx)
16297 .excerpts_for_buffer(active_position.buffer_id?, cx)
16298 {
16299 if context.start.cmp(&active_position, &snapshot).is_ge()
16300 || context.end.cmp(&active_position, &snapshot).is_lt()
16301 {
16302 continue;
16303 }
16304 let snapshot = self.buffer.read(cx).snapshot(cx);
16305 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
16306
16307 handled = true;
16308 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16309 self.go_to_line::<DebugCurrentRowHighlight>(
16310 multibuffer_anchor,
16311 Some(cx.theme().colors().editor_debugger_active_line_background),
16312 window,
16313 cx,
16314 );
16315
16316 cx.notify();
16317 }
16318 handled.then_some(())
16319 })
16320 .is_some()
16321 }
16322
16323 pub fn copy_file_name_without_extension(
16324 &mut self,
16325 _: &CopyFileNameWithoutExtension,
16326 _: &mut Window,
16327 cx: &mut Context<Self>,
16328 ) {
16329 if let Some(file) = self.target_file(cx) {
16330 if let Some(file_stem) = file.path().file_stem() {
16331 if let Some(name) = file_stem.to_str() {
16332 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16333 }
16334 }
16335 }
16336 }
16337
16338 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16339 if let Some(file) = self.target_file(cx) {
16340 if let Some(file_name) = file.path().file_name() {
16341 if let Some(name) = file_name.to_str() {
16342 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16343 }
16344 }
16345 }
16346 }
16347
16348 pub fn toggle_git_blame(
16349 &mut self,
16350 _: &::git::Blame,
16351 window: &mut Window,
16352 cx: &mut Context<Self>,
16353 ) {
16354 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16355
16356 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16357 self.start_git_blame(true, window, cx);
16358 }
16359
16360 cx.notify();
16361 }
16362
16363 pub fn toggle_git_blame_inline(
16364 &mut self,
16365 _: &ToggleGitBlameInline,
16366 window: &mut Window,
16367 cx: &mut Context<Self>,
16368 ) {
16369 self.toggle_git_blame_inline_internal(true, window, cx);
16370 cx.notify();
16371 }
16372
16373 pub fn open_git_blame_commit(
16374 &mut self,
16375 _: &OpenGitBlameCommit,
16376 window: &mut Window,
16377 cx: &mut Context<Self>,
16378 ) {
16379 self.open_git_blame_commit_internal(window, cx);
16380 }
16381
16382 fn open_git_blame_commit_internal(
16383 &mut self,
16384 window: &mut Window,
16385 cx: &mut Context<Self>,
16386 ) -> Option<()> {
16387 let blame = self.blame.as_ref()?;
16388 let snapshot = self.snapshot(window, cx);
16389 let cursor = self.selections.newest::<Point>(cx).head();
16390 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16391 let blame_entry = blame
16392 .update(cx, |blame, cx| {
16393 blame
16394 .blame_for_rows(
16395 &[RowInfo {
16396 buffer_id: Some(buffer.remote_id()),
16397 buffer_row: Some(point.row),
16398 ..Default::default()
16399 }],
16400 cx,
16401 )
16402 .next()
16403 })
16404 .flatten()?;
16405 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16406 let repo = blame.read(cx).repository(cx)?;
16407 let workspace = self.workspace()?.downgrade();
16408 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16409 None
16410 }
16411
16412 pub fn git_blame_inline_enabled(&self) -> bool {
16413 self.git_blame_inline_enabled
16414 }
16415
16416 pub fn toggle_selection_menu(
16417 &mut self,
16418 _: &ToggleSelectionMenu,
16419 _: &mut Window,
16420 cx: &mut Context<Self>,
16421 ) {
16422 self.show_selection_menu = self
16423 .show_selection_menu
16424 .map(|show_selections_menu| !show_selections_menu)
16425 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16426
16427 cx.notify();
16428 }
16429
16430 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16431 self.show_selection_menu
16432 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16433 }
16434
16435 fn start_git_blame(
16436 &mut self,
16437 user_triggered: bool,
16438 window: &mut Window,
16439 cx: &mut Context<Self>,
16440 ) {
16441 if let Some(project) = self.project.as_ref() {
16442 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16443 return;
16444 };
16445
16446 if buffer.read(cx).file().is_none() {
16447 return;
16448 }
16449
16450 let focused = self.focus_handle(cx).contains_focused(window, cx);
16451
16452 let project = project.clone();
16453 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16454 self.blame_subscription =
16455 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16456 self.blame = Some(blame);
16457 }
16458 }
16459
16460 fn toggle_git_blame_inline_internal(
16461 &mut self,
16462 user_triggered: bool,
16463 window: &mut Window,
16464 cx: &mut Context<Self>,
16465 ) {
16466 if self.git_blame_inline_enabled {
16467 self.git_blame_inline_enabled = false;
16468 self.show_git_blame_inline = false;
16469 self.show_git_blame_inline_delay_task.take();
16470 } else {
16471 self.git_blame_inline_enabled = true;
16472 self.start_git_blame_inline(user_triggered, window, cx);
16473 }
16474
16475 cx.notify();
16476 }
16477
16478 fn start_git_blame_inline(
16479 &mut self,
16480 user_triggered: bool,
16481 window: &mut Window,
16482 cx: &mut Context<Self>,
16483 ) {
16484 self.start_git_blame(user_triggered, window, cx);
16485
16486 if ProjectSettings::get_global(cx)
16487 .git
16488 .inline_blame_delay()
16489 .is_some()
16490 {
16491 self.start_inline_blame_timer(window, cx);
16492 } else {
16493 self.show_git_blame_inline = true
16494 }
16495 }
16496
16497 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16498 self.blame.as_ref()
16499 }
16500
16501 pub fn show_git_blame_gutter(&self) -> bool {
16502 self.show_git_blame_gutter
16503 }
16504
16505 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16506 self.show_git_blame_gutter && self.has_blame_entries(cx)
16507 }
16508
16509 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16510 self.show_git_blame_inline
16511 && (self.focus_handle.is_focused(window)
16512 || self
16513 .git_blame_inline_tooltip
16514 .as_ref()
16515 .and_then(|t| t.upgrade())
16516 .is_some())
16517 && !self.newest_selection_head_on_empty_line(cx)
16518 && self.has_blame_entries(cx)
16519 }
16520
16521 fn has_blame_entries(&self, cx: &App) -> bool {
16522 self.blame()
16523 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16524 }
16525
16526 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16527 let cursor_anchor = self.selections.newest_anchor().head();
16528
16529 let snapshot = self.buffer.read(cx).snapshot(cx);
16530 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16531
16532 snapshot.line_len(buffer_row) == 0
16533 }
16534
16535 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16536 let buffer_and_selection = maybe!({
16537 let selection = self.selections.newest::<Point>(cx);
16538 let selection_range = selection.range();
16539
16540 let multi_buffer = self.buffer().read(cx);
16541 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16542 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16543
16544 let (buffer, range, _) = if selection.reversed {
16545 buffer_ranges.first()
16546 } else {
16547 buffer_ranges.last()
16548 }?;
16549
16550 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16551 ..text::ToPoint::to_point(&range.end, &buffer).row;
16552 Some((
16553 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16554 selection,
16555 ))
16556 });
16557
16558 let Some((buffer, selection)) = buffer_and_selection else {
16559 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16560 };
16561
16562 let Some(project) = self.project.as_ref() else {
16563 return Task::ready(Err(anyhow!("editor does not have project")));
16564 };
16565
16566 project.update(cx, |project, cx| {
16567 project.get_permalink_to_line(&buffer, selection, cx)
16568 })
16569 }
16570
16571 pub fn copy_permalink_to_line(
16572 &mut self,
16573 _: &CopyPermalinkToLine,
16574 window: &mut Window,
16575 cx: &mut Context<Self>,
16576 ) {
16577 let permalink_task = self.get_permalink_to_line(cx);
16578 let workspace = self.workspace();
16579
16580 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16581 Ok(permalink) => {
16582 cx.update(|_, cx| {
16583 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16584 })
16585 .ok();
16586 }
16587 Err(err) => {
16588 let message = format!("Failed to copy permalink: {err}");
16589
16590 Err::<(), anyhow::Error>(err).log_err();
16591
16592 if let Some(workspace) = workspace {
16593 workspace
16594 .update_in(cx, |workspace, _, cx| {
16595 struct CopyPermalinkToLine;
16596
16597 workspace.show_toast(
16598 Toast::new(
16599 NotificationId::unique::<CopyPermalinkToLine>(),
16600 message,
16601 ),
16602 cx,
16603 )
16604 })
16605 .ok();
16606 }
16607 }
16608 })
16609 .detach();
16610 }
16611
16612 pub fn copy_file_location(
16613 &mut self,
16614 _: &CopyFileLocation,
16615 _: &mut Window,
16616 cx: &mut Context<Self>,
16617 ) {
16618 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16619 if let Some(file) = self.target_file(cx) {
16620 if let Some(path) = file.path().to_str() {
16621 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16622 }
16623 }
16624 }
16625
16626 pub fn open_permalink_to_line(
16627 &mut self,
16628 _: &OpenPermalinkToLine,
16629 window: &mut Window,
16630 cx: &mut Context<Self>,
16631 ) {
16632 let permalink_task = self.get_permalink_to_line(cx);
16633 let workspace = self.workspace();
16634
16635 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16636 Ok(permalink) => {
16637 cx.update(|_, cx| {
16638 cx.open_url(permalink.as_ref());
16639 })
16640 .ok();
16641 }
16642 Err(err) => {
16643 let message = format!("Failed to open permalink: {err}");
16644
16645 Err::<(), anyhow::Error>(err).log_err();
16646
16647 if let Some(workspace) = workspace {
16648 workspace
16649 .update(cx, |workspace, cx| {
16650 struct OpenPermalinkToLine;
16651
16652 workspace.show_toast(
16653 Toast::new(
16654 NotificationId::unique::<OpenPermalinkToLine>(),
16655 message,
16656 ),
16657 cx,
16658 )
16659 })
16660 .ok();
16661 }
16662 }
16663 })
16664 .detach();
16665 }
16666
16667 pub fn insert_uuid_v4(
16668 &mut self,
16669 _: &InsertUuidV4,
16670 window: &mut Window,
16671 cx: &mut Context<Self>,
16672 ) {
16673 self.insert_uuid(UuidVersion::V4, window, cx);
16674 }
16675
16676 pub fn insert_uuid_v7(
16677 &mut self,
16678 _: &InsertUuidV7,
16679 window: &mut Window,
16680 cx: &mut Context<Self>,
16681 ) {
16682 self.insert_uuid(UuidVersion::V7, window, cx);
16683 }
16684
16685 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16686 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16687 self.transact(window, cx, |this, window, cx| {
16688 let edits = this
16689 .selections
16690 .all::<Point>(cx)
16691 .into_iter()
16692 .map(|selection| {
16693 let uuid = match version {
16694 UuidVersion::V4 => uuid::Uuid::new_v4(),
16695 UuidVersion::V7 => uuid::Uuid::now_v7(),
16696 };
16697
16698 (selection.range(), uuid.to_string())
16699 });
16700 this.edit(edits, cx);
16701 this.refresh_inline_completion(true, false, window, cx);
16702 });
16703 }
16704
16705 pub fn open_selections_in_multibuffer(
16706 &mut self,
16707 _: &OpenSelectionsInMultibuffer,
16708 window: &mut Window,
16709 cx: &mut Context<Self>,
16710 ) {
16711 let multibuffer = self.buffer.read(cx);
16712
16713 let Some(buffer) = multibuffer.as_singleton() else {
16714 return;
16715 };
16716
16717 let Some(workspace) = self.workspace() else {
16718 return;
16719 };
16720
16721 let locations = self
16722 .selections
16723 .disjoint_anchors()
16724 .iter()
16725 .map(|range| Location {
16726 buffer: buffer.clone(),
16727 range: range.start.text_anchor..range.end.text_anchor,
16728 })
16729 .collect::<Vec<_>>();
16730
16731 let title = multibuffer.title(cx).to_string();
16732
16733 cx.spawn_in(window, async move |_, cx| {
16734 workspace.update_in(cx, |workspace, window, cx| {
16735 Self::open_locations_in_multibuffer(
16736 workspace,
16737 locations,
16738 format!("Selections for '{title}'"),
16739 false,
16740 MultibufferSelectionMode::All,
16741 window,
16742 cx,
16743 );
16744 })
16745 })
16746 .detach();
16747 }
16748
16749 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16750 /// last highlight added will be used.
16751 ///
16752 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16753 pub fn highlight_rows<T: 'static>(
16754 &mut self,
16755 range: Range<Anchor>,
16756 color: Hsla,
16757 should_autoscroll: bool,
16758 cx: &mut Context<Self>,
16759 ) {
16760 let snapshot = self.buffer().read(cx).snapshot(cx);
16761 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16762 let ix = row_highlights.binary_search_by(|highlight| {
16763 Ordering::Equal
16764 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16765 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16766 });
16767
16768 if let Err(mut ix) = ix {
16769 let index = post_inc(&mut self.highlight_order);
16770
16771 // If this range intersects with the preceding highlight, then merge it with
16772 // the preceding highlight. Otherwise insert a new highlight.
16773 let mut merged = false;
16774 if ix > 0 {
16775 let prev_highlight = &mut row_highlights[ix - 1];
16776 if prev_highlight
16777 .range
16778 .end
16779 .cmp(&range.start, &snapshot)
16780 .is_ge()
16781 {
16782 ix -= 1;
16783 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16784 prev_highlight.range.end = range.end;
16785 }
16786 merged = true;
16787 prev_highlight.index = index;
16788 prev_highlight.color = color;
16789 prev_highlight.should_autoscroll = should_autoscroll;
16790 }
16791 }
16792
16793 if !merged {
16794 row_highlights.insert(
16795 ix,
16796 RowHighlight {
16797 range: range.clone(),
16798 index,
16799 color,
16800 should_autoscroll,
16801 },
16802 );
16803 }
16804
16805 // If any of the following highlights intersect with this one, merge them.
16806 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16807 let highlight = &row_highlights[ix];
16808 if next_highlight
16809 .range
16810 .start
16811 .cmp(&highlight.range.end, &snapshot)
16812 .is_le()
16813 {
16814 if next_highlight
16815 .range
16816 .end
16817 .cmp(&highlight.range.end, &snapshot)
16818 .is_gt()
16819 {
16820 row_highlights[ix].range.end = next_highlight.range.end;
16821 }
16822 row_highlights.remove(ix + 1);
16823 } else {
16824 break;
16825 }
16826 }
16827 }
16828 }
16829
16830 /// Remove any highlighted row ranges of the given type that intersect the
16831 /// given ranges.
16832 pub fn remove_highlighted_rows<T: 'static>(
16833 &mut self,
16834 ranges_to_remove: Vec<Range<Anchor>>,
16835 cx: &mut Context<Self>,
16836 ) {
16837 let snapshot = self.buffer().read(cx).snapshot(cx);
16838 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16839 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16840 row_highlights.retain(|highlight| {
16841 while let Some(range_to_remove) = ranges_to_remove.peek() {
16842 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16843 Ordering::Less | Ordering::Equal => {
16844 ranges_to_remove.next();
16845 }
16846 Ordering::Greater => {
16847 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16848 Ordering::Less | Ordering::Equal => {
16849 return false;
16850 }
16851 Ordering::Greater => break,
16852 }
16853 }
16854 }
16855 }
16856
16857 true
16858 })
16859 }
16860
16861 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16862 pub fn clear_row_highlights<T: 'static>(&mut self) {
16863 self.highlighted_rows.remove(&TypeId::of::<T>());
16864 }
16865
16866 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16867 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16868 self.highlighted_rows
16869 .get(&TypeId::of::<T>())
16870 .map_or(&[] as &[_], |vec| vec.as_slice())
16871 .iter()
16872 .map(|highlight| (highlight.range.clone(), highlight.color))
16873 }
16874
16875 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16876 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16877 /// Allows to ignore certain kinds of highlights.
16878 pub fn highlighted_display_rows(
16879 &self,
16880 window: &mut Window,
16881 cx: &mut App,
16882 ) -> BTreeMap<DisplayRow, LineHighlight> {
16883 let snapshot = self.snapshot(window, cx);
16884 let mut used_highlight_orders = HashMap::default();
16885 self.highlighted_rows
16886 .iter()
16887 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16888 .fold(
16889 BTreeMap::<DisplayRow, LineHighlight>::new(),
16890 |mut unique_rows, highlight| {
16891 let start = highlight.range.start.to_display_point(&snapshot);
16892 let end = highlight.range.end.to_display_point(&snapshot);
16893 let start_row = start.row().0;
16894 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16895 && end.column() == 0
16896 {
16897 end.row().0.saturating_sub(1)
16898 } else {
16899 end.row().0
16900 };
16901 for row in start_row..=end_row {
16902 let used_index =
16903 used_highlight_orders.entry(row).or_insert(highlight.index);
16904 if highlight.index >= *used_index {
16905 *used_index = highlight.index;
16906 unique_rows.insert(DisplayRow(row), highlight.color.into());
16907 }
16908 }
16909 unique_rows
16910 },
16911 )
16912 }
16913
16914 pub fn highlighted_display_row_for_autoscroll(
16915 &self,
16916 snapshot: &DisplaySnapshot,
16917 ) -> Option<DisplayRow> {
16918 self.highlighted_rows
16919 .values()
16920 .flat_map(|highlighted_rows| highlighted_rows.iter())
16921 .filter_map(|highlight| {
16922 if highlight.should_autoscroll {
16923 Some(highlight.range.start.to_display_point(snapshot).row())
16924 } else {
16925 None
16926 }
16927 })
16928 .min()
16929 }
16930
16931 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16932 self.highlight_background::<SearchWithinRange>(
16933 ranges,
16934 |colors| colors.editor_document_highlight_read_background,
16935 cx,
16936 )
16937 }
16938
16939 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16940 self.breadcrumb_header = Some(new_header);
16941 }
16942
16943 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16944 self.clear_background_highlights::<SearchWithinRange>(cx);
16945 }
16946
16947 pub fn highlight_background<T: 'static>(
16948 &mut self,
16949 ranges: &[Range<Anchor>],
16950 color_fetcher: fn(&ThemeColors) -> Hsla,
16951 cx: &mut Context<Self>,
16952 ) {
16953 self.background_highlights
16954 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16955 self.scrollbar_marker_state.dirty = true;
16956 cx.notify();
16957 }
16958
16959 pub fn clear_background_highlights<T: 'static>(
16960 &mut self,
16961 cx: &mut Context<Self>,
16962 ) -> Option<BackgroundHighlight> {
16963 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16964 if !text_highlights.1.is_empty() {
16965 self.scrollbar_marker_state.dirty = true;
16966 cx.notify();
16967 }
16968 Some(text_highlights)
16969 }
16970
16971 pub fn highlight_gutter<T: 'static>(
16972 &mut self,
16973 ranges: &[Range<Anchor>],
16974 color_fetcher: fn(&App) -> Hsla,
16975 cx: &mut Context<Self>,
16976 ) {
16977 self.gutter_highlights
16978 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16979 cx.notify();
16980 }
16981
16982 pub fn clear_gutter_highlights<T: 'static>(
16983 &mut self,
16984 cx: &mut Context<Self>,
16985 ) -> Option<GutterHighlight> {
16986 cx.notify();
16987 self.gutter_highlights.remove(&TypeId::of::<T>())
16988 }
16989
16990 #[cfg(feature = "test-support")]
16991 pub fn all_text_background_highlights(
16992 &self,
16993 window: &mut Window,
16994 cx: &mut Context<Self>,
16995 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16996 let snapshot = self.snapshot(window, cx);
16997 let buffer = &snapshot.buffer_snapshot;
16998 let start = buffer.anchor_before(0);
16999 let end = buffer.anchor_after(buffer.len());
17000 let theme = cx.theme().colors();
17001 self.background_highlights_in_range(start..end, &snapshot, theme)
17002 }
17003
17004 #[cfg(feature = "test-support")]
17005 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17006 let snapshot = self.buffer().read(cx).snapshot(cx);
17007
17008 let highlights = self
17009 .background_highlights
17010 .get(&TypeId::of::<items::BufferSearchHighlights>());
17011
17012 if let Some((_color, ranges)) = highlights {
17013 ranges
17014 .iter()
17015 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17016 .collect_vec()
17017 } else {
17018 vec![]
17019 }
17020 }
17021
17022 fn document_highlights_for_position<'a>(
17023 &'a self,
17024 position: Anchor,
17025 buffer: &'a MultiBufferSnapshot,
17026 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17027 let read_highlights = self
17028 .background_highlights
17029 .get(&TypeId::of::<DocumentHighlightRead>())
17030 .map(|h| &h.1);
17031 let write_highlights = self
17032 .background_highlights
17033 .get(&TypeId::of::<DocumentHighlightWrite>())
17034 .map(|h| &h.1);
17035 let left_position = position.bias_left(buffer);
17036 let right_position = position.bias_right(buffer);
17037 read_highlights
17038 .into_iter()
17039 .chain(write_highlights)
17040 .flat_map(move |ranges| {
17041 let start_ix = match ranges.binary_search_by(|probe| {
17042 let cmp = probe.end.cmp(&left_position, buffer);
17043 if cmp.is_ge() {
17044 Ordering::Greater
17045 } else {
17046 Ordering::Less
17047 }
17048 }) {
17049 Ok(i) | Err(i) => i,
17050 };
17051
17052 ranges[start_ix..]
17053 .iter()
17054 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17055 })
17056 }
17057
17058 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17059 self.background_highlights
17060 .get(&TypeId::of::<T>())
17061 .map_or(false, |(_, highlights)| !highlights.is_empty())
17062 }
17063
17064 pub fn background_highlights_in_range(
17065 &self,
17066 search_range: Range<Anchor>,
17067 display_snapshot: &DisplaySnapshot,
17068 theme: &ThemeColors,
17069 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17070 let mut results = Vec::new();
17071 for (color_fetcher, ranges) in self.background_highlights.values() {
17072 let color = color_fetcher(theme);
17073 let start_ix = match ranges.binary_search_by(|probe| {
17074 let cmp = probe
17075 .end
17076 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17077 if cmp.is_gt() {
17078 Ordering::Greater
17079 } else {
17080 Ordering::Less
17081 }
17082 }) {
17083 Ok(i) | Err(i) => i,
17084 };
17085 for range in &ranges[start_ix..] {
17086 if range
17087 .start
17088 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17089 .is_ge()
17090 {
17091 break;
17092 }
17093
17094 let start = range.start.to_display_point(display_snapshot);
17095 let end = range.end.to_display_point(display_snapshot);
17096 results.push((start..end, color))
17097 }
17098 }
17099 results
17100 }
17101
17102 pub fn background_highlight_row_ranges<T: 'static>(
17103 &self,
17104 search_range: Range<Anchor>,
17105 display_snapshot: &DisplaySnapshot,
17106 count: usize,
17107 ) -> Vec<RangeInclusive<DisplayPoint>> {
17108 let mut results = Vec::new();
17109 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17110 return vec![];
17111 };
17112
17113 let start_ix = match ranges.binary_search_by(|probe| {
17114 let cmp = probe
17115 .end
17116 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17117 if cmp.is_gt() {
17118 Ordering::Greater
17119 } else {
17120 Ordering::Less
17121 }
17122 }) {
17123 Ok(i) | Err(i) => i,
17124 };
17125 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17126 if let (Some(start_display), Some(end_display)) = (start, end) {
17127 results.push(
17128 start_display.to_display_point(display_snapshot)
17129 ..=end_display.to_display_point(display_snapshot),
17130 );
17131 }
17132 };
17133 let mut start_row: Option<Point> = None;
17134 let mut end_row: Option<Point> = None;
17135 if ranges.len() > count {
17136 return Vec::new();
17137 }
17138 for range in &ranges[start_ix..] {
17139 if range
17140 .start
17141 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17142 .is_ge()
17143 {
17144 break;
17145 }
17146 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17147 if let Some(current_row) = &end_row {
17148 if end.row == current_row.row {
17149 continue;
17150 }
17151 }
17152 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17153 if start_row.is_none() {
17154 assert_eq!(end_row, None);
17155 start_row = Some(start);
17156 end_row = Some(end);
17157 continue;
17158 }
17159 if let Some(current_end) = end_row.as_mut() {
17160 if start.row > current_end.row + 1 {
17161 push_region(start_row, end_row);
17162 start_row = Some(start);
17163 end_row = Some(end);
17164 } else {
17165 // Merge two hunks.
17166 *current_end = end;
17167 }
17168 } else {
17169 unreachable!();
17170 }
17171 }
17172 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17173 push_region(start_row, end_row);
17174 results
17175 }
17176
17177 pub fn gutter_highlights_in_range(
17178 &self,
17179 search_range: Range<Anchor>,
17180 display_snapshot: &DisplaySnapshot,
17181 cx: &App,
17182 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17183 let mut results = Vec::new();
17184 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17185 let color = color_fetcher(cx);
17186 let start_ix = match ranges.binary_search_by(|probe| {
17187 let cmp = probe
17188 .end
17189 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17190 if cmp.is_gt() {
17191 Ordering::Greater
17192 } else {
17193 Ordering::Less
17194 }
17195 }) {
17196 Ok(i) | Err(i) => i,
17197 };
17198 for range in &ranges[start_ix..] {
17199 if range
17200 .start
17201 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17202 .is_ge()
17203 {
17204 break;
17205 }
17206
17207 let start = range.start.to_display_point(display_snapshot);
17208 let end = range.end.to_display_point(display_snapshot);
17209 results.push((start..end, color))
17210 }
17211 }
17212 results
17213 }
17214
17215 /// Get the text ranges corresponding to the redaction query
17216 pub fn redacted_ranges(
17217 &self,
17218 search_range: Range<Anchor>,
17219 display_snapshot: &DisplaySnapshot,
17220 cx: &App,
17221 ) -> Vec<Range<DisplayPoint>> {
17222 display_snapshot
17223 .buffer_snapshot
17224 .redacted_ranges(search_range, |file| {
17225 if let Some(file) = file {
17226 file.is_private()
17227 && EditorSettings::get(
17228 Some(SettingsLocation {
17229 worktree_id: file.worktree_id(cx),
17230 path: file.path().as_ref(),
17231 }),
17232 cx,
17233 )
17234 .redact_private_values
17235 } else {
17236 false
17237 }
17238 })
17239 .map(|range| {
17240 range.start.to_display_point(display_snapshot)
17241 ..range.end.to_display_point(display_snapshot)
17242 })
17243 .collect()
17244 }
17245
17246 pub fn highlight_text<T: 'static>(
17247 &mut self,
17248 ranges: Vec<Range<Anchor>>,
17249 style: HighlightStyle,
17250 cx: &mut Context<Self>,
17251 ) {
17252 self.display_map.update(cx, |map, _| {
17253 map.highlight_text(TypeId::of::<T>(), ranges, style)
17254 });
17255 cx.notify();
17256 }
17257
17258 pub(crate) fn highlight_inlays<T: 'static>(
17259 &mut self,
17260 highlights: Vec<InlayHighlight>,
17261 style: HighlightStyle,
17262 cx: &mut Context<Self>,
17263 ) {
17264 self.display_map.update(cx, |map, _| {
17265 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17266 });
17267 cx.notify();
17268 }
17269
17270 pub fn text_highlights<'a, T: 'static>(
17271 &'a self,
17272 cx: &'a App,
17273 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17274 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17275 }
17276
17277 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17278 let cleared = self
17279 .display_map
17280 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17281 if cleared {
17282 cx.notify();
17283 }
17284 }
17285
17286 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17287 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17288 && self.focus_handle.is_focused(window)
17289 }
17290
17291 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17292 self.show_cursor_when_unfocused = is_enabled;
17293 cx.notify();
17294 }
17295
17296 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17297 cx.notify();
17298 }
17299
17300 fn on_buffer_event(
17301 &mut self,
17302 multibuffer: &Entity<MultiBuffer>,
17303 event: &multi_buffer::Event,
17304 window: &mut Window,
17305 cx: &mut Context<Self>,
17306 ) {
17307 match event {
17308 multi_buffer::Event::Edited {
17309 singleton_buffer_edited,
17310 edited_buffer: buffer_edited,
17311 } => {
17312 self.scrollbar_marker_state.dirty = true;
17313 self.active_indent_guides_state.dirty = true;
17314 self.refresh_active_diagnostics(cx);
17315 self.refresh_code_actions(window, cx);
17316 if self.has_active_inline_completion() {
17317 self.update_visible_inline_completion(window, cx);
17318 }
17319 if let Some(buffer) = buffer_edited {
17320 let buffer_id = buffer.read(cx).remote_id();
17321 if !self.registered_buffers.contains_key(&buffer_id) {
17322 if let Some(project) = self.project.as_ref() {
17323 project.update(cx, |project, cx| {
17324 self.registered_buffers.insert(
17325 buffer_id,
17326 project.register_buffer_with_language_servers(&buffer, cx),
17327 );
17328 })
17329 }
17330 }
17331 }
17332 cx.emit(EditorEvent::BufferEdited);
17333 cx.emit(SearchEvent::MatchesInvalidated);
17334 if *singleton_buffer_edited {
17335 if let Some(project) = &self.project {
17336 #[allow(clippy::mutable_key_type)]
17337 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17338 multibuffer
17339 .all_buffers()
17340 .into_iter()
17341 .filter_map(|buffer| {
17342 buffer.update(cx, |buffer, cx| {
17343 let language = buffer.language()?;
17344 let should_discard = project.update(cx, |project, cx| {
17345 project.is_local()
17346 && !project.has_language_servers_for(buffer, cx)
17347 });
17348 should_discard.not().then_some(language.clone())
17349 })
17350 })
17351 .collect::<HashSet<_>>()
17352 });
17353 if !languages_affected.is_empty() {
17354 self.refresh_inlay_hints(
17355 InlayHintRefreshReason::BufferEdited(languages_affected),
17356 cx,
17357 );
17358 }
17359 }
17360 }
17361
17362 let Some(project) = &self.project else { return };
17363 let (telemetry, is_via_ssh) = {
17364 let project = project.read(cx);
17365 let telemetry = project.client().telemetry().clone();
17366 let is_via_ssh = project.is_via_ssh();
17367 (telemetry, is_via_ssh)
17368 };
17369 refresh_linked_ranges(self, window, cx);
17370 telemetry.log_edit_event("editor", is_via_ssh);
17371 }
17372 multi_buffer::Event::ExcerptsAdded {
17373 buffer,
17374 predecessor,
17375 excerpts,
17376 } => {
17377 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17378 let buffer_id = buffer.read(cx).remote_id();
17379 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17380 if let Some(project) = &self.project {
17381 get_uncommitted_diff_for_buffer(
17382 project,
17383 [buffer.clone()],
17384 self.buffer.clone(),
17385 cx,
17386 )
17387 .detach();
17388 }
17389 }
17390 cx.emit(EditorEvent::ExcerptsAdded {
17391 buffer: buffer.clone(),
17392 predecessor: *predecessor,
17393 excerpts: excerpts.clone(),
17394 });
17395 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17396 }
17397 multi_buffer::Event::ExcerptsRemoved { ids } => {
17398 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17399 let buffer = self.buffer.read(cx);
17400 self.registered_buffers
17401 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17402 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17403 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
17404 }
17405 multi_buffer::Event::ExcerptsEdited {
17406 excerpt_ids,
17407 buffer_ids,
17408 } => {
17409 self.display_map.update(cx, |map, cx| {
17410 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17411 });
17412 cx.emit(EditorEvent::ExcerptsEdited {
17413 ids: excerpt_ids.clone(),
17414 })
17415 }
17416 multi_buffer::Event::ExcerptsExpanded { ids } => {
17417 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17418 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17419 }
17420 multi_buffer::Event::Reparsed(buffer_id) => {
17421 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17422 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17423
17424 cx.emit(EditorEvent::Reparsed(*buffer_id));
17425 }
17426 multi_buffer::Event::DiffHunksToggled => {
17427 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17428 }
17429 multi_buffer::Event::LanguageChanged(buffer_id) => {
17430 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17431 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17432 cx.emit(EditorEvent::Reparsed(*buffer_id));
17433 cx.notify();
17434 }
17435 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17436 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17437 multi_buffer::Event::FileHandleChanged
17438 | multi_buffer::Event::Reloaded
17439 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17440 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17441 multi_buffer::Event::DiagnosticsUpdated => {
17442 self.refresh_active_diagnostics(cx);
17443 self.refresh_inline_diagnostics(true, window, cx);
17444 self.scrollbar_marker_state.dirty = true;
17445 cx.notify();
17446 }
17447 _ => {}
17448 };
17449 }
17450
17451 fn on_display_map_changed(
17452 &mut self,
17453 _: Entity<DisplayMap>,
17454 _: &mut Window,
17455 cx: &mut Context<Self>,
17456 ) {
17457 cx.notify();
17458 }
17459
17460 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17461 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17462 self.update_edit_prediction_settings(cx);
17463 self.refresh_inline_completion(true, false, window, cx);
17464 self.refresh_inlay_hints(
17465 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17466 self.selections.newest_anchor().head(),
17467 &self.buffer.read(cx).snapshot(cx),
17468 cx,
17469 )),
17470 cx,
17471 );
17472
17473 let old_cursor_shape = self.cursor_shape;
17474
17475 {
17476 let editor_settings = EditorSettings::get_global(cx);
17477 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17478 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17479 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17480 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17481 }
17482
17483 if old_cursor_shape != self.cursor_shape {
17484 cx.emit(EditorEvent::CursorShapeChanged);
17485 }
17486
17487 let project_settings = ProjectSettings::get_global(cx);
17488 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17489
17490 if self.mode.is_full() {
17491 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17492 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17493 if self.show_inline_diagnostics != show_inline_diagnostics {
17494 self.show_inline_diagnostics = show_inline_diagnostics;
17495 self.refresh_inline_diagnostics(false, window, cx);
17496 }
17497
17498 if self.git_blame_inline_enabled != inline_blame_enabled {
17499 self.toggle_git_blame_inline_internal(false, window, cx);
17500 }
17501 }
17502
17503 cx.notify();
17504 }
17505
17506 pub fn set_searchable(&mut self, searchable: bool) {
17507 self.searchable = searchable;
17508 }
17509
17510 pub fn searchable(&self) -> bool {
17511 self.searchable
17512 }
17513
17514 fn open_proposed_changes_editor(
17515 &mut self,
17516 _: &OpenProposedChangesEditor,
17517 window: &mut Window,
17518 cx: &mut Context<Self>,
17519 ) {
17520 let Some(workspace) = self.workspace() else {
17521 cx.propagate();
17522 return;
17523 };
17524
17525 let selections = self.selections.all::<usize>(cx);
17526 let multi_buffer = self.buffer.read(cx);
17527 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17528 let mut new_selections_by_buffer = HashMap::default();
17529 for selection in selections {
17530 for (buffer, range, _) in
17531 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17532 {
17533 let mut range = range.to_point(buffer);
17534 range.start.column = 0;
17535 range.end.column = buffer.line_len(range.end.row);
17536 new_selections_by_buffer
17537 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17538 .or_insert(Vec::new())
17539 .push(range)
17540 }
17541 }
17542
17543 let proposed_changes_buffers = new_selections_by_buffer
17544 .into_iter()
17545 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17546 .collect::<Vec<_>>();
17547 let proposed_changes_editor = cx.new(|cx| {
17548 ProposedChangesEditor::new(
17549 "Proposed changes",
17550 proposed_changes_buffers,
17551 self.project.clone(),
17552 window,
17553 cx,
17554 )
17555 });
17556
17557 window.defer(cx, move |window, cx| {
17558 workspace.update(cx, |workspace, cx| {
17559 workspace.active_pane().update(cx, |pane, cx| {
17560 pane.add_item(
17561 Box::new(proposed_changes_editor),
17562 true,
17563 true,
17564 None,
17565 window,
17566 cx,
17567 );
17568 });
17569 });
17570 });
17571 }
17572
17573 pub fn open_excerpts_in_split(
17574 &mut self,
17575 _: &OpenExcerptsSplit,
17576 window: &mut Window,
17577 cx: &mut Context<Self>,
17578 ) {
17579 self.open_excerpts_common(None, true, window, cx)
17580 }
17581
17582 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17583 self.open_excerpts_common(None, false, window, cx)
17584 }
17585
17586 fn open_excerpts_common(
17587 &mut self,
17588 jump_data: Option<JumpData>,
17589 split: bool,
17590 window: &mut Window,
17591 cx: &mut Context<Self>,
17592 ) {
17593 let Some(workspace) = self.workspace() else {
17594 cx.propagate();
17595 return;
17596 };
17597
17598 if self.buffer.read(cx).is_singleton() {
17599 cx.propagate();
17600 return;
17601 }
17602
17603 let mut new_selections_by_buffer = HashMap::default();
17604 match &jump_data {
17605 Some(JumpData::MultiBufferPoint {
17606 excerpt_id,
17607 position,
17608 anchor,
17609 line_offset_from_top,
17610 }) => {
17611 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17612 if let Some(buffer) = multi_buffer_snapshot
17613 .buffer_id_for_excerpt(*excerpt_id)
17614 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17615 {
17616 let buffer_snapshot = buffer.read(cx).snapshot();
17617 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17618 language::ToPoint::to_point(anchor, &buffer_snapshot)
17619 } else {
17620 buffer_snapshot.clip_point(*position, Bias::Left)
17621 };
17622 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17623 new_selections_by_buffer.insert(
17624 buffer,
17625 (
17626 vec![jump_to_offset..jump_to_offset],
17627 Some(*line_offset_from_top),
17628 ),
17629 );
17630 }
17631 }
17632 Some(JumpData::MultiBufferRow {
17633 row,
17634 line_offset_from_top,
17635 }) => {
17636 let point = MultiBufferPoint::new(row.0, 0);
17637 if let Some((buffer, buffer_point, _)) =
17638 self.buffer.read(cx).point_to_buffer_point(point, cx)
17639 {
17640 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17641 new_selections_by_buffer
17642 .entry(buffer)
17643 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17644 .0
17645 .push(buffer_offset..buffer_offset)
17646 }
17647 }
17648 None => {
17649 let selections = self.selections.all::<usize>(cx);
17650 let multi_buffer = self.buffer.read(cx);
17651 for selection in selections {
17652 for (snapshot, range, _, anchor) in multi_buffer
17653 .snapshot(cx)
17654 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17655 {
17656 if let Some(anchor) = anchor {
17657 // selection is in a deleted hunk
17658 let Some(buffer_id) = anchor.buffer_id else {
17659 continue;
17660 };
17661 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17662 continue;
17663 };
17664 let offset = text::ToOffset::to_offset(
17665 &anchor.text_anchor,
17666 &buffer_handle.read(cx).snapshot(),
17667 );
17668 let range = offset..offset;
17669 new_selections_by_buffer
17670 .entry(buffer_handle)
17671 .or_insert((Vec::new(), None))
17672 .0
17673 .push(range)
17674 } else {
17675 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17676 else {
17677 continue;
17678 };
17679 new_selections_by_buffer
17680 .entry(buffer_handle)
17681 .or_insert((Vec::new(), None))
17682 .0
17683 .push(range)
17684 }
17685 }
17686 }
17687 }
17688 }
17689
17690 new_selections_by_buffer
17691 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
17692
17693 if new_selections_by_buffer.is_empty() {
17694 return;
17695 }
17696
17697 // We defer the pane interaction because we ourselves are a workspace item
17698 // and activating a new item causes the pane to call a method on us reentrantly,
17699 // which panics if we're on the stack.
17700 window.defer(cx, move |window, cx| {
17701 workspace.update(cx, |workspace, cx| {
17702 let pane = if split {
17703 workspace.adjacent_pane(window, cx)
17704 } else {
17705 workspace.active_pane().clone()
17706 };
17707
17708 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17709 let editor = buffer
17710 .read(cx)
17711 .file()
17712 .is_none()
17713 .then(|| {
17714 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17715 // so `workspace.open_project_item` will never find them, always opening a new editor.
17716 // Instead, we try to activate the existing editor in the pane first.
17717 let (editor, pane_item_index) =
17718 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17719 let editor = item.downcast::<Editor>()?;
17720 let singleton_buffer =
17721 editor.read(cx).buffer().read(cx).as_singleton()?;
17722 if singleton_buffer == buffer {
17723 Some((editor, i))
17724 } else {
17725 None
17726 }
17727 })?;
17728 pane.update(cx, |pane, cx| {
17729 pane.activate_item(pane_item_index, true, true, window, cx)
17730 });
17731 Some(editor)
17732 })
17733 .flatten()
17734 .unwrap_or_else(|| {
17735 workspace.open_project_item::<Self>(
17736 pane.clone(),
17737 buffer,
17738 true,
17739 true,
17740 window,
17741 cx,
17742 )
17743 });
17744
17745 editor.update(cx, |editor, cx| {
17746 let autoscroll = match scroll_offset {
17747 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17748 None => Autoscroll::newest(),
17749 };
17750 let nav_history = editor.nav_history.take();
17751 editor.change_selections(Some(autoscroll), window, cx, |s| {
17752 s.select_ranges(ranges);
17753 });
17754 editor.nav_history = nav_history;
17755 });
17756 }
17757 })
17758 });
17759 }
17760
17761 // For now, don't allow opening excerpts in buffers that aren't backed by
17762 // regular project files.
17763 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
17764 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
17765 }
17766
17767 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17768 let snapshot = self.buffer.read(cx).read(cx);
17769 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17770 Some(
17771 ranges
17772 .iter()
17773 .map(move |range| {
17774 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17775 })
17776 .collect(),
17777 )
17778 }
17779
17780 fn selection_replacement_ranges(
17781 &self,
17782 range: Range<OffsetUtf16>,
17783 cx: &mut App,
17784 ) -> Vec<Range<OffsetUtf16>> {
17785 let selections = self.selections.all::<OffsetUtf16>(cx);
17786 let newest_selection = selections
17787 .iter()
17788 .max_by_key(|selection| selection.id)
17789 .unwrap();
17790 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17791 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17792 let snapshot = self.buffer.read(cx).read(cx);
17793 selections
17794 .into_iter()
17795 .map(|mut selection| {
17796 selection.start.0 =
17797 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17798 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17799 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17800 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17801 })
17802 .collect()
17803 }
17804
17805 fn report_editor_event(
17806 &self,
17807 event_type: &'static str,
17808 file_extension: Option<String>,
17809 cx: &App,
17810 ) {
17811 if cfg!(any(test, feature = "test-support")) {
17812 return;
17813 }
17814
17815 let Some(project) = &self.project else { return };
17816
17817 // If None, we are in a file without an extension
17818 let file = self
17819 .buffer
17820 .read(cx)
17821 .as_singleton()
17822 .and_then(|b| b.read(cx).file());
17823 let file_extension = file_extension.or(file
17824 .as_ref()
17825 .and_then(|file| Path::new(file.file_name(cx)).extension())
17826 .and_then(|e| e.to_str())
17827 .map(|a| a.to_string()));
17828
17829 let vim_mode = vim_enabled(cx);
17830
17831 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17832 let copilot_enabled = edit_predictions_provider
17833 == language::language_settings::EditPredictionProvider::Copilot;
17834 let copilot_enabled_for_language = self
17835 .buffer
17836 .read(cx)
17837 .language_settings(cx)
17838 .show_edit_predictions;
17839
17840 let project = project.read(cx);
17841 telemetry::event!(
17842 event_type,
17843 file_extension,
17844 vim_mode,
17845 copilot_enabled,
17846 copilot_enabled_for_language,
17847 edit_predictions_provider,
17848 is_via_ssh = project.is_via_ssh(),
17849 );
17850 }
17851
17852 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17853 /// with each line being an array of {text, highlight} objects.
17854 fn copy_highlight_json(
17855 &mut self,
17856 _: &CopyHighlightJson,
17857 window: &mut Window,
17858 cx: &mut Context<Self>,
17859 ) {
17860 #[derive(Serialize)]
17861 struct Chunk<'a> {
17862 text: String,
17863 highlight: Option<&'a str>,
17864 }
17865
17866 let snapshot = self.buffer.read(cx).snapshot(cx);
17867 let range = self
17868 .selected_text_range(false, window, cx)
17869 .and_then(|selection| {
17870 if selection.range.is_empty() {
17871 None
17872 } else {
17873 Some(selection.range)
17874 }
17875 })
17876 .unwrap_or_else(|| 0..snapshot.len());
17877
17878 let chunks = snapshot.chunks(range, true);
17879 let mut lines = Vec::new();
17880 let mut line: VecDeque<Chunk> = VecDeque::new();
17881
17882 let Some(style) = self.style.as_ref() else {
17883 return;
17884 };
17885
17886 for chunk in chunks {
17887 let highlight = chunk
17888 .syntax_highlight_id
17889 .and_then(|id| id.name(&style.syntax));
17890 let mut chunk_lines = chunk.text.split('\n').peekable();
17891 while let Some(text) = chunk_lines.next() {
17892 let mut merged_with_last_token = false;
17893 if let Some(last_token) = line.back_mut() {
17894 if last_token.highlight == highlight {
17895 last_token.text.push_str(text);
17896 merged_with_last_token = true;
17897 }
17898 }
17899
17900 if !merged_with_last_token {
17901 line.push_back(Chunk {
17902 text: text.into(),
17903 highlight,
17904 });
17905 }
17906
17907 if chunk_lines.peek().is_some() {
17908 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17909 line.pop_front();
17910 }
17911 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17912 line.pop_back();
17913 }
17914
17915 lines.push(mem::take(&mut line));
17916 }
17917 }
17918 }
17919
17920 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17921 return;
17922 };
17923 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17924 }
17925
17926 pub fn open_context_menu(
17927 &mut self,
17928 _: &OpenContextMenu,
17929 window: &mut Window,
17930 cx: &mut Context<Self>,
17931 ) {
17932 self.request_autoscroll(Autoscroll::newest(), cx);
17933 let position = self.selections.newest_display(cx).start;
17934 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17935 }
17936
17937 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17938 &self.inlay_hint_cache
17939 }
17940
17941 pub fn replay_insert_event(
17942 &mut self,
17943 text: &str,
17944 relative_utf16_range: Option<Range<isize>>,
17945 window: &mut Window,
17946 cx: &mut Context<Self>,
17947 ) {
17948 if !self.input_enabled {
17949 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17950 return;
17951 }
17952 if let Some(relative_utf16_range) = relative_utf16_range {
17953 let selections = self.selections.all::<OffsetUtf16>(cx);
17954 self.change_selections(None, window, cx, |s| {
17955 let new_ranges = selections.into_iter().map(|range| {
17956 let start = OffsetUtf16(
17957 range
17958 .head()
17959 .0
17960 .saturating_add_signed(relative_utf16_range.start),
17961 );
17962 let end = OffsetUtf16(
17963 range
17964 .head()
17965 .0
17966 .saturating_add_signed(relative_utf16_range.end),
17967 );
17968 start..end
17969 });
17970 s.select_ranges(new_ranges);
17971 });
17972 }
17973
17974 self.handle_input(text, window, cx);
17975 }
17976
17977 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17978 let Some(provider) = self.semantics_provider.as_ref() else {
17979 return false;
17980 };
17981
17982 let mut supports = false;
17983 self.buffer().update(cx, |this, cx| {
17984 this.for_each_buffer(|buffer| {
17985 supports |= provider.supports_inlay_hints(buffer, cx);
17986 });
17987 });
17988
17989 supports
17990 }
17991
17992 pub fn is_focused(&self, window: &Window) -> bool {
17993 self.focus_handle.is_focused(window)
17994 }
17995
17996 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17997 cx.emit(EditorEvent::Focused);
17998
17999 if let Some(descendant) = self
18000 .last_focused_descendant
18001 .take()
18002 .and_then(|descendant| descendant.upgrade())
18003 {
18004 window.focus(&descendant);
18005 } else {
18006 if let Some(blame) = self.blame.as_ref() {
18007 blame.update(cx, GitBlame::focus)
18008 }
18009
18010 self.blink_manager.update(cx, BlinkManager::enable);
18011 self.show_cursor_names(window, cx);
18012 self.buffer.update(cx, |buffer, cx| {
18013 buffer.finalize_last_transaction(cx);
18014 if self.leader_peer_id.is_none() {
18015 buffer.set_active_selections(
18016 &self.selections.disjoint_anchors(),
18017 self.selections.line_mode,
18018 self.cursor_shape,
18019 cx,
18020 );
18021 }
18022 });
18023 }
18024 }
18025
18026 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18027 cx.emit(EditorEvent::FocusedIn)
18028 }
18029
18030 fn handle_focus_out(
18031 &mut self,
18032 event: FocusOutEvent,
18033 _window: &mut Window,
18034 cx: &mut Context<Self>,
18035 ) {
18036 if event.blurred != self.focus_handle {
18037 self.last_focused_descendant = Some(event.blurred);
18038 }
18039 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18040 }
18041
18042 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18043 self.blink_manager.update(cx, BlinkManager::disable);
18044 self.buffer
18045 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18046
18047 if let Some(blame) = self.blame.as_ref() {
18048 blame.update(cx, GitBlame::blur)
18049 }
18050 if !self.hover_state.focused(window, cx) {
18051 hide_hover(self, cx);
18052 }
18053 if !self
18054 .context_menu
18055 .borrow()
18056 .as_ref()
18057 .is_some_and(|context_menu| context_menu.focused(window, cx))
18058 {
18059 self.hide_context_menu(window, cx);
18060 }
18061 self.discard_inline_completion(false, cx);
18062 cx.emit(EditorEvent::Blurred);
18063 cx.notify();
18064 }
18065
18066 pub fn register_action<A: Action>(
18067 &mut self,
18068 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18069 ) -> Subscription {
18070 let id = self.next_editor_action_id.post_inc();
18071 let listener = Arc::new(listener);
18072 self.editor_actions.borrow_mut().insert(
18073 id,
18074 Box::new(move |window, _| {
18075 let listener = listener.clone();
18076 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18077 let action = action.downcast_ref().unwrap();
18078 if phase == DispatchPhase::Bubble {
18079 listener(action, window, cx)
18080 }
18081 })
18082 }),
18083 );
18084
18085 let editor_actions = self.editor_actions.clone();
18086 Subscription::new(move || {
18087 editor_actions.borrow_mut().remove(&id);
18088 })
18089 }
18090
18091 pub fn file_header_size(&self) -> u32 {
18092 FILE_HEADER_HEIGHT
18093 }
18094
18095 pub fn restore(
18096 &mut self,
18097 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18098 window: &mut Window,
18099 cx: &mut Context<Self>,
18100 ) {
18101 let workspace = self.workspace();
18102 let project = self.project.as_ref();
18103 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18104 let mut tasks = Vec::new();
18105 for (buffer_id, changes) in revert_changes {
18106 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18107 buffer.update(cx, |buffer, cx| {
18108 buffer.edit(
18109 changes
18110 .into_iter()
18111 .map(|(range, text)| (range, text.to_string())),
18112 None,
18113 cx,
18114 );
18115 });
18116
18117 if let Some(project) =
18118 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18119 {
18120 project.update(cx, |project, cx| {
18121 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18122 })
18123 }
18124 }
18125 }
18126 tasks
18127 });
18128 cx.spawn_in(window, async move |_, cx| {
18129 for (buffer, task) in save_tasks {
18130 let result = task.await;
18131 if result.is_err() {
18132 let Some(path) = buffer
18133 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18134 .ok()
18135 else {
18136 continue;
18137 };
18138 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18139 let Some(task) = cx
18140 .update_window_entity(&workspace, |workspace, window, cx| {
18141 workspace
18142 .open_path_preview(path, None, false, false, false, window, cx)
18143 })
18144 .ok()
18145 else {
18146 continue;
18147 };
18148 task.await.log_err();
18149 }
18150 }
18151 }
18152 })
18153 .detach();
18154 self.change_selections(None, window, cx, |selections| selections.refresh());
18155 }
18156
18157 pub fn to_pixel_point(
18158 &self,
18159 source: multi_buffer::Anchor,
18160 editor_snapshot: &EditorSnapshot,
18161 window: &mut Window,
18162 ) -> Option<gpui::Point<Pixels>> {
18163 let source_point = source.to_display_point(editor_snapshot);
18164 self.display_to_pixel_point(source_point, editor_snapshot, window)
18165 }
18166
18167 pub fn display_to_pixel_point(
18168 &self,
18169 source: DisplayPoint,
18170 editor_snapshot: &EditorSnapshot,
18171 window: &mut Window,
18172 ) -> Option<gpui::Point<Pixels>> {
18173 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18174 let text_layout_details = self.text_layout_details(window);
18175 let scroll_top = text_layout_details
18176 .scroll_anchor
18177 .scroll_position(editor_snapshot)
18178 .y;
18179
18180 if source.row().as_f32() < scroll_top.floor() {
18181 return None;
18182 }
18183 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18184 let source_y = line_height * (source.row().as_f32() - scroll_top);
18185 Some(gpui::Point::new(source_x, source_y))
18186 }
18187
18188 pub fn has_visible_completions_menu(&self) -> bool {
18189 !self.edit_prediction_preview_is_active()
18190 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18191 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18192 })
18193 }
18194
18195 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18196 self.addons
18197 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18198 }
18199
18200 pub fn unregister_addon<T: Addon>(&mut self) {
18201 self.addons.remove(&std::any::TypeId::of::<T>());
18202 }
18203
18204 pub fn addon<T: Addon>(&self) -> Option<&T> {
18205 let type_id = std::any::TypeId::of::<T>();
18206 self.addons
18207 .get(&type_id)
18208 .and_then(|item| item.to_any().downcast_ref::<T>())
18209 }
18210
18211 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18212 let text_layout_details = self.text_layout_details(window);
18213 let style = &text_layout_details.editor_style;
18214 let font_id = window.text_system().resolve_font(&style.text.font());
18215 let font_size = style.text.font_size.to_pixels(window.rem_size());
18216 let line_height = style.text.line_height_in_pixels(window.rem_size());
18217 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18218
18219 gpui::Size::new(em_width, line_height)
18220 }
18221
18222 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18223 self.load_diff_task.clone()
18224 }
18225
18226 fn read_metadata_from_db(
18227 &mut self,
18228 item_id: u64,
18229 workspace_id: WorkspaceId,
18230 window: &mut Window,
18231 cx: &mut Context<Editor>,
18232 ) {
18233 if self.is_singleton(cx)
18234 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18235 {
18236 let buffer_snapshot = OnceCell::new();
18237
18238 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18239 if !folds.is_empty() {
18240 let snapshot =
18241 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18242 self.fold_ranges(
18243 folds
18244 .into_iter()
18245 .map(|(start, end)| {
18246 snapshot.clip_offset(start, Bias::Left)
18247 ..snapshot.clip_offset(end, Bias::Right)
18248 })
18249 .collect(),
18250 false,
18251 window,
18252 cx,
18253 );
18254 }
18255 }
18256
18257 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18258 if !selections.is_empty() {
18259 let snapshot =
18260 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18261 self.change_selections(None, window, cx, |s| {
18262 s.select_ranges(selections.into_iter().map(|(start, end)| {
18263 snapshot.clip_offset(start, Bias::Left)
18264 ..snapshot.clip_offset(end, Bias::Right)
18265 }));
18266 });
18267 }
18268 };
18269 }
18270
18271 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18272 }
18273}
18274
18275fn vim_enabled(cx: &App) -> bool {
18276 cx.global::<SettingsStore>()
18277 .raw_user_settings()
18278 .get("vim_mode")
18279 == Some(&serde_json::Value::Bool(true))
18280}
18281
18282// Consider user intent and default settings
18283fn choose_completion_range(
18284 completion: &Completion,
18285 intent: CompletionIntent,
18286 buffer: &Entity<Buffer>,
18287 cx: &mut Context<Editor>,
18288) -> Range<usize> {
18289 fn should_replace(
18290 completion: &Completion,
18291 insert_range: &Range<text::Anchor>,
18292 intent: CompletionIntent,
18293 completion_mode_setting: LspInsertMode,
18294 buffer: &Buffer,
18295 ) -> bool {
18296 // specific actions take precedence over settings
18297 match intent {
18298 CompletionIntent::CompleteWithInsert => return false,
18299 CompletionIntent::CompleteWithReplace => return true,
18300 CompletionIntent::Complete | CompletionIntent::Compose => {}
18301 }
18302
18303 match completion_mode_setting {
18304 LspInsertMode::Insert => false,
18305 LspInsertMode::Replace => true,
18306 LspInsertMode::ReplaceSubsequence => {
18307 let mut text_to_replace = buffer.chars_for_range(
18308 buffer.anchor_before(completion.replace_range.start)
18309 ..buffer.anchor_after(completion.replace_range.end),
18310 );
18311 let mut completion_text = completion.new_text.chars();
18312
18313 // is `text_to_replace` a subsequence of `completion_text`
18314 text_to_replace
18315 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18316 }
18317 LspInsertMode::ReplaceSuffix => {
18318 let range_after_cursor = insert_range.end..completion.replace_range.end;
18319
18320 let text_after_cursor = buffer
18321 .text_for_range(
18322 buffer.anchor_before(range_after_cursor.start)
18323 ..buffer.anchor_after(range_after_cursor.end),
18324 )
18325 .collect::<String>();
18326 completion.new_text.ends_with(&text_after_cursor)
18327 }
18328 }
18329 }
18330
18331 let buffer = buffer.read(cx);
18332
18333 if let CompletionSource::Lsp {
18334 insert_range: Some(insert_range),
18335 ..
18336 } = &completion.source
18337 {
18338 let completion_mode_setting =
18339 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18340 .completions
18341 .lsp_insert_mode;
18342
18343 if !should_replace(
18344 completion,
18345 &insert_range,
18346 intent,
18347 completion_mode_setting,
18348 buffer,
18349 ) {
18350 return insert_range.to_offset(buffer);
18351 }
18352 }
18353
18354 completion.replace_range.to_offset(buffer)
18355}
18356
18357fn insert_extra_newline_brackets(
18358 buffer: &MultiBufferSnapshot,
18359 range: Range<usize>,
18360 language: &language::LanguageScope,
18361) -> bool {
18362 let leading_whitespace_len = buffer
18363 .reversed_chars_at(range.start)
18364 .take_while(|c| c.is_whitespace() && *c != '\n')
18365 .map(|c| c.len_utf8())
18366 .sum::<usize>();
18367 let trailing_whitespace_len = buffer
18368 .chars_at(range.end)
18369 .take_while(|c| c.is_whitespace() && *c != '\n')
18370 .map(|c| c.len_utf8())
18371 .sum::<usize>();
18372 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18373
18374 language.brackets().any(|(pair, enabled)| {
18375 let pair_start = pair.start.trim_end();
18376 let pair_end = pair.end.trim_start();
18377
18378 enabled
18379 && pair.newline
18380 && buffer.contains_str_at(range.end, pair_end)
18381 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18382 })
18383}
18384
18385fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18386 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18387 [(buffer, range, _)] => (*buffer, range.clone()),
18388 _ => return false,
18389 };
18390 let pair = {
18391 let mut result: Option<BracketMatch> = None;
18392
18393 for pair in buffer
18394 .all_bracket_ranges(range.clone())
18395 .filter(move |pair| {
18396 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18397 })
18398 {
18399 let len = pair.close_range.end - pair.open_range.start;
18400
18401 if let Some(existing) = &result {
18402 let existing_len = existing.close_range.end - existing.open_range.start;
18403 if len > existing_len {
18404 continue;
18405 }
18406 }
18407
18408 result = Some(pair);
18409 }
18410
18411 result
18412 };
18413 let Some(pair) = pair else {
18414 return false;
18415 };
18416 pair.newline_only
18417 && buffer
18418 .chars_for_range(pair.open_range.end..range.start)
18419 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18420 .all(|c| c.is_whitespace() && c != '\n')
18421}
18422
18423fn get_uncommitted_diff_for_buffer(
18424 project: &Entity<Project>,
18425 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18426 buffer: Entity<MultiBuffer>,
18427 cx: &mut App,
18428) -> Task<()> {
18429 let mut tasks = Vec::new();
18430 project.update(cx, |project, cx| {
18431 for buffer in buffers {
18432 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18433 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18434 }
18435 }
18436 });
18437 cx.spawn(async move |cx| {
18438 let diffs = future::join_all(tasks).await;
18439 buffer
18440 .update(cx, |buffer, cx| {
18441 for diff in diffs.into_iter().flatten() {
18442 buffer.add_diff(diff, cx);
18443 }
18444 })
18445 .ok();
18446 })
18447}
18448
18449fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18450 let tab_size = tab_size.get() as usize;
18451 let mut width = offset;
18452
18453 for ch in text.chars() {
18454 width += if ch == '\t' {
18455 tab_size - (width % tab_size)
18456 } else {
18457 1
18458 };
18459 }
18460
18461 width - offset
18462}
18463
18464#[cfg(test)]
18465mod tests {
18466 use super::*;
18467
18468 #[test]
18469 fn test_string_size_with_expanded_tabs() {
18470 let nz = |val| NonZeroU32::new(val).unwrap();
18471 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18472 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18473 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18474 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18475 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18476 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18477 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18478 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18479 }
18480}
18481
18482/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18483struct WordBreakingTokenizer<'a> {
18484 input: &'a str,
18485}
18486
18487impl<'a> WordBreakingTokenizer<'a> {
18488 fn new(input: &'a str) -> Self {
18489 Self { input }
18490 }
18491}
18492
18493fn is_char_ideographic(ch: char) -> bool {
18494 use unicode_script::Script::*;
18495 use unicode_script::UnicodeScript;
18496 matches!(ch.script(), Han | Tangut | Yi)
18497}
18498
18499fn is_grapheme_ideographic(text: &str) -> bool {
18500 text.chars().any(is_char_ideographic)
18501}
18502
18503fn is_grapheme_whitespace(text: &str) -> bool {
18504 text.chars().any(|x| x.is_whitespace())
18505}
18506
18507fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18508 text.chars().next().map_or(false, |ch| {
18509 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18510 })
18511}
18512
18513#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18514enum WordBreakToken<'a> {
18515 Word { token: &'a str, grapheme_len: usize },
18516 InlineWhitespace { token: &'a str, grapheme_len: usize },
18517 Newline,
18518}
18519
18520impl<'a> Iterator for WordBreakingTokenizer<'a> {
18521 /// Yields a span, the count of graphemes in the token, and whether it was
18522 /// whitespace. Note that it also breaks at word boundaries.
18523 type Item = WordBreakToken<'a>;
18524
18525 fn next(&mut self) -> Option<Self::Item> {
18526 use unicode_segmentation::UnicodeSegmentation;
18527 if self.input.is_empty() {
18528 return None;
18529 }
18530
18531 let mut iter = self.input.graphemes(true).peekable();
18532 let mut offset = 0;
18533 let mut grapheme_len = 0;
18534 if let Some(first_grapheme) = iter.next() {
18535 let is_newline = first_grapheme == "\n";
18536 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18537 offset += first_grapheme.len();
18538 grapheme_len += 1;
18539 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18540 if let Some(grapheme) = iter.peek().copied() {
18541 if should_stay_with_preceding_ideograph(grapheme) {
18542 offset += grapheme.len();
18543 grapheme_len += 1;
18544 }
18545 }
18546 } else {
18547 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18548 let mut next_word_bound = words.peek().copied();
18549 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18550 next_word_bound = words.next();
18551 }
18552 while let Some(grapheme) = iter.peek().copied() {
18553 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18554 break;
18555 };
18556 if is_grapheme_whitespace(grapheme) != is_whitespace
18557 || (grapheme == "\n") != is_newline
18558 {
18559 break;
18560 };
18561 offset += grapheme.len();
18562 grapheme_len += 1;
18563 iter.next();
18564 }
18565 }
18566 let token = &self.input[..offset];
18567 self.input = &self.input[offset..];
18568 if token == "\n" {
18569 Some(WordBreakToken::Newline)
18570 } else if is_whitespace {
18571 Some(WordBreakToken::InlineWhitespace {
18572 token,
18573 grapheme_len,
18574 })
18575 } else {
18576 Some(WordBreakToken::Word {
18577 token,
18578 grapheme_len,
18579 })
18580 }
18581 } else {
18582 None
18583 }
18584 }
18585}
18586
18587#[test]
18588fn test_word_breaking_tokenizer() {
18589 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18590 ("", &[]),
18591 (" ", &[whitespace(" ", 2)]),
18592 ("Ʒ", &[word("Ʒ", 1)]),
18593 ("Ǽ", &[word("Ǽ", 1)]),
18594 ("⋑", &[word("⋑", 1)]),
18595 ("⋑⋑", &[word("⋑⋑", 2)]),
18596 (
18597 "原理,进而",
18598 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18599 ),
18600 (
18601 "hello world",
18602 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18603 ),
18604 (
18605 "hello, world",
18606 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18607 ),
18608 (
18609 " hello world",
18610 &[
18611 whitespace(" ", 2),
18612 word("hello", 5),
18613 whitespace(" ", 1),
18614 word("world", 5),
18615 ],
18616 ),
18617 (
18618 "这是什么 \n 钢笔",
18619 &[
18620 word("这", 1),
18621 word("是", 1),
18622 word("什", 1),
18623 word("么", 1),
18624 whitespace(" ", 1),
18625 newline(),
18626 whitespace(" ", 1),
18627 word("钢", 1),
18628 word("笔", 1),
18629 ],
18630 ),
18631 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
18632 ];
18633
18634 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18635 WordBreakToken::Word {
18636 token,
18637 grapheme_len,
18638 }
18639 }
18640
18641 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18642 WordBreakToken::InlineWhitespace {
18643 token,
18644 grapheme_len,
18645 }
18646 }
18647
18648 fn newline() -> WordBreakToken<'static> {
18649 WordBreakToken::Newline
18650 }
18651
18652 for (input, result) in tests {
18653 assert_eq!(
18654 WordBreakingTokenizer::new(input)
18655 .collect::<Vec<_>>()
18656 .as_slice(),
18657 *result,
18658 );
18659 }
18660}
18661
18662fn wrap_with_prefix(
18663 line_prefix: String,
18664 unwrapped_text: String,
18665 wrap_column: usize,
18666 tab_size: NonZeroU32,
18667 preserve_existing_whitespace: bool,
18668) -> String {
18669 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18670 let mut wrapped_text = String::new();
18671 let mut current_line = line_prefix.clone();
18672
18673 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18674 let mut current_line_len = line_prefix_len;
18675 let mut in_whitespace = false;
18676 for token in tokenizer {
18677 let have_preceding_whitespace = in_whitespace;
18678 match token {
18679 WordBreakToken::Word {
18680 token,
18681 grapheme_len,
18682 } => {
18683 in_whitespace = false;
18684 if current_line_len + grapheme_len > wrap_column
18685 && current_line_len != line_prefix_len
18686 {
18687 wrapped_text.push_str(current_line.trim_end());
18688 wrapped_text.push('\n');
18689 current_line.truncate(line_prefix.len());
18690 current_line_len = line_prefix_len;
18691 }
18692 current_line.push_str(token);
18693 current_line_len += grapheme_len;
18694 }
18695 WordBreakToken::InlineWhitespace {
18696 mut token,
18697 mut grapheme_len,
18698 } => {
18699 in_whitespace = true;
18700 if have_preceding_whitespace && !preserve_existing_whitespace {
18701 continue;
18702 }
18703 if !preserve_existing_whitespace {
18704 token = " ";
18705 grapheme_len = 1;
18706 }
18707 if current_line_len + grapheme_len > wrap_column {
18708 wrapped_text.push_str(current_line.trim_end());
18709 wrapped_text.push('\n');
18710 current_line.truncate(line_prefix.len());
18711 current_line_len = line_prefix_len;
18712 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18713 current_line.push_str(token);
18714 current_line_len += grapheme_len;
18715 }
18716 }
18717 WordBreakToken::Newline => {
18718 in_whitespace = true;
18719 if preserve_existing_whitespace {
18720 wrapped_text.push_str(current_line.trim_end());
18721 wrapped_text.push('\n');
18722 current_line.truncate(line_prefix.len());
18723 current_line_len = line_prefix_len;
18724 } else if have_preceding_whitespace {
18725 continue;
18726 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18727 {
18728 wrapped_text.push_str(current_line.trim_end());
18729 wrapped_text.push('\n');
18730 current_line.truncate(line_prefix.len());
18731 current_line_len = line_prefix_len;
18732 } else if current_line_len != line_prefix_len {
18733 current_line.push(' ');
18734 current_line_len += 1;
18735 }
18736 }
18737 }
18738 }
18739
18740 if !current_line.is_empty() {
18741 wrapped_text.push_str(¤t_line);
18742 }
18743 wrapped_text
18744}
18745
18746#[test]
18747fn test_wrap_with_prefix() {
18748 assert_eq!(
18749 wrap_with_prefix(
18750 "# ".to_string(),
18751 "abcdefg".to_string(),
18752 4,
18753 NonZeroU32::new(4).unwrap(),
18754 false,
18755 ),
18756 "# abcdefg"
18757 );
18758 assert_eq!(
18759 wrap_with_prefix(
18760 "".to_string(),
18761 "\thello world".to_string(),
18762 8,
18763 NonZeroU32::new(4).unwrap(),
18764 false,
18765 ),
18766 "hello\nworld"
18767 );
18768 assert_eq!(
18769 wrap_with_prefix(
18770 "// ".to_string(),
18771 "xx \nyy zz aa bb cc".to_string(),
18772 12,
18773 NonZeroU32::new(4).unwrap(),
18774 false,
18775 ),
18776 "// xx yy zz\n// aa bb cc"
18777 );
18778 assert_eq!(
18779 wrap_with_prefix(
18780 String::new(),
18781 "这是什么 \n 钢笔".to_string(),
18782 3,
18783 NonZeroU32::new(4).unwrap(),
18784 false,
18785 ),
18786 "这是什\n么 钢\n笔"
18787 );
18788}
18789
18790pub trait CollaborationHub {
18791 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18792 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18793 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18794}
18795
18796impl CollaborationHub for Entity<Project> {
18797 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18798 self.read(cx).collaborators()
18799 }
18800
18801 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18802 self.read(cx).user_store().read(cx).participant_indices()
18803 }
18804
18805 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18806 let this = self.read(cx);
18807 let user_ids = this.collaborators().values().map(|c| c.user_id);
18808 this.user_store().read_with(cx, |user_store, cx| {
18809 user_store.participant_names(user_ids, cx)
18810 })
18811 }
18812}
18813
18814pub trait SemanticsProvider {
18815 fn hover(
18816 &self,
18817 buffer: &Entity<Buffer>,
18818 position: text::Anchor,
18819 cx: &mut App,
18820 ) -> Option<Task<Vec<project::Hover>>>;
18821
18822 fn inlay_hints(
18823 &self,
18824 buffer_handle: Entity<Buffer>,
18825 range: Range<text::Anchor>,
18826 cx: &mut App,
18827 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18828
18829 fn resolve_inlay_hint(
18830 &self,
18831 hint: InlayHint,
18832 buffer_handle: Entity<Buffer>,
18833 server_id: LanguageServerId,
18834 cx: &mut App,
18835 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18836
18837 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18838
18839 fn document_highlights(
18840 &self,
18841 buffer: &Entity<Buffer>,
18842 position: text::Anchor,
18843 cx: &mut App,
18844 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18845
18846 fn definitions(
18847 &self,
18848 buffer: &Entity<Buffer>,
18849 position: text::Anchor,
18850 kind: GotoDefinitionKind,
18851 cx: &mut App,
18852 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18853
18854 fn range_for_rename(
18855 &self,
18856 buffer: &Entity<Buffer>,
18857 position: text::Anchor,
18858 cx: &mut App,
18859 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18860
18861 fn perform_rename(
18862 &self,
18863 buffer: &Entity<Buffer>,
18864 position: text::Anchor,
18865 new_name: String,
18866 cx: &mut App,
18867 ) -> Option<Task<Result<ProjectTransaction>>>;
18868}
18869
18870pub trait CompletionProvider {
18871 fn completions(
18872 &self,
18873 excerpt_id: ExcerptId,
18874 buffer: &Entity<Buffer>,
18875 buffer_position: text::Anchor,
18876 trigger: CompletionContext,
18877 window: &mut Window,
18878 cx: &mut Context<Editor>,
18879 ) -> Task<Result<Option<Vec<Completion>>>>;
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
18889 fn apply_additional_edits_for_completion(
18890 &self,
18891 _buffer: Entity<Buffer>,
18892 _completions: Rc<RefCell<Box<[Completion]>>>,
18893 _completion_index: usize,
18894 _push_to_history: bool,
18895 _cx: &mut Context<Editor>,
18896 ) -> Task<Result<Option<language::Transaction>>> {
18897 Task::ready(Ok(None))
18898 }
18899
18900 fn is_completion_trigger(
18901 &self,
18902 buffer: &Entity<Buffer>,
18903 position: language::Anchor,
18904 text: &str,
18905 trigger_in_words: bool,
18906 cx: &mut Context<Editor>,
18907 ) -> bool;
18908
18909 fn sort_completions(&self) -> bool {
18910 true
18911 }
18912
18913 fn filter_completions(&self) -> bool {
18914 true
18915 }
18916}
18917
18918pub trait CodeActionProvider {
18919 fn id(&self) -> Arc<str>;
18920
18921 fn code_actions(
18922 &self,
18923 buffer: &Entity<Buffer>,
18924 range: Range<text::Anchor>,
18925 window: &mut Window,
18926 cx: &mut App,
18927 ) -> Task<Result<Vec<CodeAction>>>;
18928
18929 fn apply_code_action(
18930 &self,
18931 buffer_handle: Entity<Buffer>,
18932 action: CodeAction,
18933 excerpt_id: ExcerptId,
18934 push_to_history: bool,
18935 window: &mut Window,
18936 cx: &mut App,
18937 ) -> Task<Result<ProjectTransaction>>;
18938}
18939
18940impl CodeActionProvider for Entity<Project> {
18941 fn id(&self) -> Arc<str> {
18942 "project".into()
18943 }
18944
18945 fn code_actions(
18946 &self,
18947 buffer: &Entity<Buffer>,
18948 range: Range<text::Anchor>,
18949 _window: &mut Window,
18950 cx: &mut App,
18951 ) -> Task<Result<Vec<CodeAction>>> {
18952 self.update(cx, |project, cx| {
18953 let code_lens = project.code_lens(buffer, range.clone(), cx);
18954 let code_actions = project.code_actions(buffer, range, None, cx);
18955 cx.background_spawn(async move {
18956 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18957 Ok(code_lens
18958 .context("code lens fetch")?
18959 .into_iter()
18960 .chain(code_actions.context("code action fetch")?)
18961 .collect())
18962 })
18963 })
18964 }
18965
18966 fn apply_code_action(
18967 &self,
18968 buffer_handle: Entity<Buffer>,
18969 action: CodeAction,
18970 _excerpt_id: ExcerptId,
18971 push_to_history: bool,
18972 _window: &mut Window,
18973 cx: &mut App,
18974 ) -> Task<Result<ProjectTransaction>> {
18975 self.update(cx, |project, cx| {
18976 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18977 })
18978 }
18979}
18980
18981fn snippet_completions(
18982 project: &Project,
18983 buffer: &Entity<Buffer>,
18984 buffer_position: text::Anchor,
18985 cx: &mut App,
18986) -> Task<Result<Vec<Completion>>> {
18987 let languages = buffer.read(cx).languages_at(buffer_position);
18988 let snippet_store = project.snippets().read(cx);
18989
18990 let scopes: Vec<_> = languages
18991 .iter()
18992 .filter_map(|language| {
18993 let language_name = language.lsp_id();
18994 let snippets = snippet_store.snippets_for(Some(language_name), cx);
18995
18996 if snippets.is_empty() {
18997 None
18998 } else {
18999 Some((language.default_scope(), snippets))
19000 }
19001 })
19002 .collect();
19003
19004 if scopes.is_empty() {
19005 return Task::ready(Ok(vec![]));
19006 }
19007
19008 let snapshot = buffer.read(cx).text_snapshot();
19009 let chars: String = snapshot
19010 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19011 .collect();
19012 let executor = cx.background_executor().clone();
19013
19014 cx.background_spawn(async move {
19015 let mut all_results: Vec<Completion> = Vec::new();
19016 for (scope, snippets) in scopes.into_iter() {
19017 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19018 let mut last_word = chars
19019 .chars()
19020 .take_while(|c| classifier.is_word(*c))
19021 .collect::<String>();
19022 last_word = last_word.chars().rev().collect();
19023
19024 if last_word.is_empty() {
19025 return Ok(vec![]);
19026 }
19027
19028 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19029 let to_lsp = |point: &text::Anchor| {
19030 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19031 point_to_lsp(end)
19032 };
19033 let lsp_end = to_lsp(&buffer_position);
19034
19035 let candidates = snippets
19036 .iter()
19037 .enumerate()
19038 .flat_map(|(ix, snippet)| {
19039 snippet
19040 .prefix
19041 .iter()
19042 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19043 })
19044 .collect::<Vec<StringMatchCandidate>>();
19045
19046 let mut matches = fuzzy::match_strings(
19047 &candidates,
19048 &last_word,
19049 last_word.chars().any(|c| c.is_uppercase()),
19050 100,
19051 &Default::default(),
19052 executor.clone(),
19053 )
19054 .await;
19055
19056 // Remove all candidates where the query's start does not match the start of any word in the candidate
19057 if let Some(query_start) = last_word.chars().next() {
19058 matches.retain(|string_match| {
19059 split_words(&string_match.string).any(|word| {
19060 // Check that the first codepoint of the word as lowercase matches the first
19061 // codepoint of the query as lowercase
19062 word.chars()
19063 .flat_map(|codepoint| codepoint.to_lowercase())
19064 .zip(query_start.to_lowercase())
19065 .all(|(word_cp, query_cp)| word_cp == query_cp)
19066 })
19067 });
19068 }
19069
19070 let matched_strings = matches
19071 .into_iter()
19072 .map(|m| m.string)
19073 .collect::<HashSet<_>>();
19074
19075 let mut result: Vec<Completion> = snippets
19076 .iter()
19077 .filter_map(|snippet| {
19078 let matching_prefix = snippet
19079 .prefix
19080 .iter()
19081 .find(|prefix| matched_strings.contains(*prefix))?;
19082 let start = as_offset - last_word.len();
19083 let start = snapshot.anchor_before(start);
19084 let range = start..buffer_position;
19085 let lsp_start = to_lsp(&start);
19086 let lsp_range = lsp::Range {
19087 start: lsp_start,
19088 end: lsp_end,
19089 };
19090 Some(Completion {
19091 replace_range: range,
19092 new_text: snippet.body.clone(),
19093 source: CompletionSource::Lsp {
19094 insert_range: None,
19095 server_id: LanguageServerId(usize::MAX),
19096 resolved: true,
19097 lsp_completion: Box::new(lsp::CompletionItem {
19098 label: snippet.prefix.first().unwrap().clone(),
19099 kind: Some(CompletionItemKind::SNIPPET),
19100 label_details: snippet.description.as_ref().map(|description| {
19101 lsp::CompletionItemLabelDetails {
19102 detail: Some(description.clone()),
19103 description: None,
19104 }
19105 }),
19106 insert_text_format: Some(InsertTextFormat::SNIPPET),
19107 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19108 lsp::InsertReplaceEdit {
19109 new_text: snippet.body.clone(),
19110 insert: lsp_range,
19111 replace: lsp_range,
19112 },
19113 )),
19114 filter_text: Some(snippet.body.clone()),
19115 sort_text: Some(char::MAX.to_string()),
19116 ..lsp::CompletionItem::default()
19117 }),
19118 lsp_defaults: None,
19119 },
19120 label: CodeLabel {
19121 text: matching_prefix.clone(),
19122 runs: Vec::new(),
19123 filter_range: 0..matching_prefix.len(),
19124 },
19125 icon_path: None,
19126 documentation: snippet.description.clone().map(|description| {
19127 CompletionDocumentation::SingleLine(description.into())
19128 }),
19129 insert_text_mode: None,
19130 confirm: None,
19131 })
19132 })
19133 .collect();
19134
19135 all_results.append(&mut result);
19136 }
19137
19138 Ok(all_results)
19139 })
19140}
19141
19142impl CompletionProvider for Entity<Project> {
19143 fn completions(
19144 &self,
19145 _excerpt_id: ExcerptId,
19146 buffer: &Entity<Buffer>,
19147 buffer_position: text::Anchor,
19148 options: CompletionContext,
19149 _window: &mut Window,
19150 cx: &mut Context<Editor>,
19151 ) -> Task<Result<Option<Vec<Completion>>>> {
19152 self.update(cx, |project, cx| {
19153 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19154 let project_completions = project.completions(buffer, buffer_position, options, cx);
19155 cx.background_spawn(async move {
19156 let snippets_completions = snippets.await?;
19157 match project_completions.await? {
19158 Some(mut completions) => {
19159 completions.extend(snippets_completions);
19160 Ok(Some(completions))
19161 }
19162 None => {
19163 if snippets_completions.is_empty() {
19164 Ok(None)
19165 } else {
19166 Ok(Some(snippets_completions))
19167 }
19168 }
19169 }
19170 })
19171 })
19172 }
19173
19174 fn resolve_completions(
19175 &self,
19176 buffer: Entity<Buffer>,
19177 completion_indices: Vec<usize>,
19178 completions: Rc<RefCell<Box<[Completion]>>>,
19179 cx: &mut Context<Editor>,
19180 ) -> Task<Result<bool>> {
19181 self.update(cx, |project, cx| {
19182 project.lsp_store().update(cx, |lsp_store, cx| {
19183 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19184 })
19185 })
19186 }
19187
19188 fn apply_additional_edits_for_completion(
19189 &self,
19190 buffer: Entity<Buffer>,
19191 completions: Rc<RefCell<Box<[Completion]>>>,
19192 completion_index: usize,
19193 push_to_history: bool,
19194 cx: &mut Context<Editor>,
19195 ) -> Task<Result<Option<language::Transaction>>> {
19196 self.update(cx, |project, cx| {
19197 project.lsp_store().update(cx, |lsp_store, cx| {
19198 lsp_store.apply_additional_edits_for_completion(
19199 buffer,
19200 completions,
19201 completion_index,
19202 push_to_history,
19203 cx,
19204 )
19205 })
19206 })
19207 }
19208
19209 fn is_completion_trigger(
19210 &self,
19211 buffer: &Entity<Buffer>,
19212 position: language::Anchor,
19213 text: &str,
19214 trigger_in_words: bool,
19215 cx: &mut Context<Editor>,
19216 ) -> bool {
19217 let mut chars = text.chars();
19218 let char = if let Some(char) = chars.next() {
19219 char
19220 } else {
19221 return false;
19222 };
19223 if chars.next().is_some() {
19224 return false;
19225 }
19226
19227 let buffer = buffer.read(cx);
19228 let snapshot = buffer.snapshot();
19229 if !snapshot.settings_at(position, cx).show_completions_on_input {
19230 return false;
19231 }
19232 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19233 if trigger_in_words && classifier.is_word(char) {
19234 return true;
19235 }
19236
19237 buffer.completion_triggers().contains(text)
19238 }
19239}
19240
19241impl SemanticsProvider for Entity<Project> {
19242 fn hover(
19243 &self,
19244 buffer: &Entity<Buffer>,
19245 position: text::Anchor,
19246 cx: &mut App,
19247 ) -> Option<Task<Vec<project::Hover>>> {
19248 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19249 }
19250
19251 fn document_highlights(
19252 &self,
19253 buffer: &Entity<Buffer>,
19254 position: text::Anchor,
19255 cx: &mut App,
19256 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19257 Some(self.update(cx, |project, cx| {
19258 project.document_highlights(buffer, position, cx)
19259 }))
19260 }
19261
19262 fn definitions(
19263 &self,
19264 buffer: &Entity<Buffer>,
19265 position: text::Anchor,
19266 kind: GotoDefinitionKind,
19267 cx: &mut App,
19268 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19269 Some(self.update(cx, |project, cx| match kind {
19270 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19271 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19272 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19273 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19274 }))
19275 }
19276
19277 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19278 // TODO: make this work for remote projects
19279 self.update(cx, |this, cx| {
19280 buffer.update(cx, |buffer, cx| {
19281 this.any_language_server_supports_inlay_hints(buffer, cx)
19282 })
19283 })
19284 }
19285
19286 fn inlay_hints(
19287 &self,
19288 buffer_handle: Entity<Buffer>,
19289 range: Range<text::Anchor>,
19290 cx: &mut App,
19291 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19292 Some(self.update(cx, |project, cx| {
19293 project.inlay_hints(buffer_handle, range, cx)
19294 }))
19295 }
19296
19297 fn resolve_inlay_hint(
19298 &self,
19299 hint: InlayHint,
19300 buffer_handle: Entity<Buffer>,
19301 server_id: LanguageServerId,
19302 cx: &mut App,
19303 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19304 Some(self.update(cx, |project, cx| {
19305 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19306 }))
19307 }
19308
19309 fn range_for_rename(
19310 &self,
19311 buffer: &Entity<Buffer>,
19312 position: text::Anchor,
19313 cx: &mut App,
19314 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19315 Some(self.update(cx, |project, cx| {
19316 let buffer = buffer.clone();
19317 let task = project.prepare_rename(buffer.clone(), position, cx);
19318 cx.spawn(async move |_, cx| {
19319 Ok(match task.await? {
19320 PrepareRenameResponse::Success(range) => Some(range),
19321 PrepareRenameResponse::InvalidPosition => None,
19322 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19323 // Fallback on using TreeSitter info to determine identifier range
19324 buffer.update(cx, |buffer, _| {
19325 let snapshot = buffer.snapshot();
19326 let (range, kind) = snapshot.surrounding_word(position);
19327 if kind != Some(CharKind::Word) {
19328 return None;
19329 }
19330 Some(
19331 snapshot.anchor_before(range.start)
19332 ..snapshot.anchor_after(range.end),
19333 )
19334 })?
19335 }
19336 })
19337 })
19338 }))
19339 }
19340
19341 fn perform_rename(
19342 &self,
19343 buffer: &Entity<Buffer>,
19344 position: text::Anchor,
19345 new_name: String,
19346 cx: &mut App,
19347 ) -> Option<Task<Result<ProjectTransaction>>> {
19348 Some(self.update(cx, |project, cx| {
19349 project.perform_rename(buffer.clone(), position, new_name, cx)
19350 }))
19351 }
19352}
19353
19354fn inlay_hint_settings(
19355 location: Anchor,
19356 snapshot: &MultiBufferSnapshot,
19357 cx: &mut Context<Editor>,
19358) -> InlayHintSettings {
19359 let file = snapshot.file_at(location);
19360 let language = snapshot.language_at(location).map(|l| l.name());
19361 language_settings(language, file, cx).inlay_hints
19362}
19363
19364fn consume_contiguous_rows(
19365 contiguous_row_selections: &mut Vec<Selection<Point>>,
19366 selection: &Selection<Point>,
19367 display_map: &DisplaySnapshot,
19368 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19369) -> (MultiBufferRow, MultiBufferRow) {
19370 contiguous_row_selections.push(selection.clone());
19371 let start_row = MultiBufferRow(selection.start.row);
19372 let mut end_row = ending_row(selection, display_map);
19373
19374 while let Some(next_selection) = selections.peek() {
19375 if next_selection.start.row <= end_row.0 {
19376 end_row = ending_row(next_selection, display_map);
19377 contiguous_row_selections.push(selections.next().unwrap().clone());
19378 } else {
19379 break;
19380 }
19381 }
19382 (start_row, end_row)
19383}
19384
19385fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19386 if next_selection.end.column > 0 || next_selection.is_empty() {
19387 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19388 } else {
19389 MultiBufferRow(next_selection.end.row)
19390 }
19391}
19392
19393impl EditorSnapshot {
19394 pub fn remote_selections_in_range<'a>(
19395 &'a self,
19396 range: &'a Range<Anchor>,
19397 collaboration_hub: &dyn CollaborationHub,
19398 cx: &'a App,
19399 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19400 let participant_names = collaboration_hub.user_names(cx);
19401 let participant_indices = collaboration_hub.user_participant_indices(cx);
19402 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19403 let collaborators_by_replica_id = collaborators_by_peer_id
19404 .iter()
19405 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19406 .collect::<HashMap<_, _>>();
19407 self.buffer_snapshot
19408 .selections_in_range(range, false)
19409 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19410 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19411 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19412 let user_name = participant_names.get(&collaborator.user_id).cloned();
19413 Some(RemoteSelection {
19414 replica_id,
19415 selection,
19416 cursor_shape,
19417 line_mode,
19418 participant_index,
19419 peer_id: collaborator.peer_id,
19420 user_name,
19421 })
19422 })
19423 }
19424
19425 pub fn hunks_for_ranges(
19426 &self,
19427 ranges: impl IntoIterator<Item = Range<Point>>,
19428 ) -> Vec<MultiBufferDiffHunk> {
19429 let mut hunks = Vec::new();
19430 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19431 HashMap::default();
19432 for query_range in ranges {
19433 let query_rows =
19434 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19435 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19436 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19437 ) {
19438 // Include deleted hunks that are adjacent to the query range, because
19439 // otherwise they would be missed.
19440 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19441 if hunk.status().is_deleted() {
19442 intersects_range |= hunk.row_range.start == query_rows.end;
19443 intersects_range |= hunk.row_range.end == query_rows.start;
19444 }
19445 if intersects_range {
19446 if !processed_buffer_rows
19447 .entry(hunk.buffer_id)
19448 .or_default()
19449 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19450 {
19451 continue;
19452 }
19453 hunks.push(hunk);
19454 }
19455 }
19456 }
19457
19458 hunks
19459 }
19460
19461 fn display_diff_hunks_for_rows<'a>(
19462 &'a self,
19463 display_rows: Range<DisplayRow>,
19464 folded_buffers: &'a HashSet<BufferId>,
19465 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19466 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19467 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19468
19469 self.buffer_snapshot
19470 .diff_hunks_in_range(buffer_start..buffer_end)
19471 .filter_map(|hunk| {
19472 if folded_buffers.contains(&hunk.buffer_id) {
19473 return None;
19474 }
19475
19476 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19477 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19478
19479 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19480 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19481
19482 let display_hunk = if hunk_display_start.column() != 0 {
19483 DisplayDiffHunk::Folded {
19484 display_row: hunk_display_start.row(),
19485 }
19486 } else {
19487 let mut end_row = hunk_display_end.row();
19488 if hunk_display_end.column() > 0 {
19489 end_row.0 += 1;
19490 }
19491 let is_created_file = hunk.is_created_file();
19492 DisplayDiffHunk::Unfolded {
19493 status: hunk.status(),
19494 diff_base_byte_range: hunk.diff_base_byte_range,
19495 display_row_range: hunk_display_start.row()..end_row,
19496 multi_buffer_range: Anchor::range_in_buffer(
19497 hunk.excerpt_id,
19498 hunk.buffer_id,
19499 hunk.buffer_range,
19500 ),
19501 is_created_file,
19502 }
19503 };
19504
19505 Some(display_hunk)
19506 })
19507 }
19508
19509 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19510 self.display_snapshot.buffer_snapshot.language_at(position)
19511 }
19512
19513 pub fn is_focused(&self) -> bool {
19514 self.is_focused
19515 }
19516
19517 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19518 self.placeholder_text.as_ref()
19519 }
19520
19521 pub fn scroll_position(&self) -> gpui::Point<f32> {
19522 self.scroll_anchor.scroll_position(&self.display_snapshot)
19523 }
19524
19525 fn gutter_dimensions(
19526 &self,
19527 font_id: FontId,
19528 font_size: Pixels,
19529 max_line_number_width: Pixels,
19530 cx: &App,
19531 ) -> Option<GutterDimensions> {
19532 if !self.show_gutter {
19533 return None;
19534 }
19535
19536 let descent = cx.text_system().descent(font_id, font_size);
19537 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19538 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19539
19540 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19541 matches!(
19542 ProjectSettings::get_global(cx).git.git_gutter,
19543 Some(GitGutterSetting::TrackedFiles)
19544 )
19545 });
19546 let gutter_settings = EditorSettings::get_global(cx).gutter;
19547 let show_line_numbers = self
19548 .show_line_numbers
19549 .unwrap_or(gutter_settings.line_numbers);
19550 let line_gutter_width = if show_line_numbers {
19551 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19552 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19553 max_line_number_width.max(min_width_for_number_on_gutter)
19554 } else {
19555 0.0.into()
19556 };
19557
19558 let show_code_actions = self
19559 .show_code_actions
19560 .unwrap_or(gutter_settings.code_actions);
19561
19562 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19563 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19564
19565 let git_blame_entries_width =
19566 self.git_blame_gutter_max_author_length
19567 .map(|max_author_length| {
19568 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19569 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19570
19571 /// The number of characters to dedicate to gaps and margins.
19572 const SPACING_WIDTH: usize = 4;
19573
19574 let max_char_count = max_author_length.min(renderer.max_author_length())
19575 + ::git::SHORT_SHA_LENGTH
19576 + MAX_RELATIVE_TIMESTAMP.len()
19577 + SPACING_WIDTH;
19578
19579 em_advance * max_char_count
19580 });
19581
19582 let is_singleton = self.buffer_snapshot.is_singleton();
19583
19584 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19585 left_padding += if !is_singleton {
19586 em_width * 4.0
19587 } else if show_code_actions || show_runnables || show_breakpoints {
19588 em_width * 3.0
19589 } else if show_git_gutter && show_line_numbers {
19590 em_width * 2.0
19591 } else if show_git_gutter || show_line_numbers {
19592 em_width
19593 } else {
19594 px(0.)
19595 };
19596
19597 let shows_folds = is_singleton && gutter_settings.folds;
19598
19599 let right_padding = if shows_folds && show_line_numbers {
19600 em_width * 4.0
19601 } else if shows_folds || (!is_singleton && show_line_numbers) {
19602 em_width * 3.0
19603 } else if show_line_numbers {
19604 em_width
19605 } else {
19606 px(0.)
19607 };
19608
19609 Some(GutterDimensions {
19610 left_padding,
19611 right_padding,
19612 width: line_gutter_width + left_padding + right_padding,
19613 margin: -descent,
19614 git_blame_entries_width,
19615 })
19616 }
19617
19618 pub fn render_crease_toggle(
19619 &self,
19620 buffer_row: MultiBufferRow,
19621 row_contains_cursor: bool,
19622 editor: Entity<Editor>,
19623 window: &mut Window,
19624 cx: &mut App,
19625 ) -> Option<AnyElement> {
19626 let folded = self.is_line_folded(buffer_row);
19627 let mut is_foldable = false;
19628
19629 if let Some(crease) = self
19630 .crease_snapshot
19631 .query_row(buffer_row, &self.buffer_snapshot)
19632 {
19633 is_foldable = true;
19634 match crease {
19635 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
19636 if let Some(render_toggle) = render_toggle {
19637 let toggle_callback =
19638 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
19639 if folded {
19640 editor.update(cx, |editor, cx| {
19641 editor.fold_at(buffer_row, window, cx)
19642 });
19643 } else {
19644 editor.update(cx, |editor, cx| {
19645 editor.unfold_at(buffer_row, window, cx)
19646 });
19647 }
19648 });
19649 return Some((render_toggle)(
19650 buffer_row,
19651 folded,
19652 toggle_callback,
19653 window,
19654 cx,
19655 ));
19656 }
19657 }
19658 }
19659 }
19660
19661 is_foldable |= self.starts_indent(buffer_row);
19662
19663 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19664 Some(
19665 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19666 .toggle_state(folded)
19667 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19668 if folded {
19669 this.unfold_at(buffer_row, window, cx);
19670 } else {
19671 this.fold_at(buffer_row, window, cx);
19672 }
19673 }))
19674 .into_any_element(),
19675 )
19676 } else {
19677 None
19678 }
19679 }
19680
19681 pub fn render_crease_trailer(
19682 &self,
19683 buffer_row: MultiBufferRow,
19684 window: &mut Window,
19685 cx: &mut App,
19686 ) -> Option<AnyElement> {
19687 let folded = self.is_line_folded(buffer_row);
19688 if let Crease::Inline { render_trailer, .. } = self
19689 .crease_snapshot
19690 .query_row(buffer_row, &self.buffer_snapshot)?
19691 {
19692 let render_trailer = render_trailer.as_ref()?;
19693 Some(render_trailer(buffer_row, folded, window, cx))
19694 } else {
19695 None
19696 }
19697 }
19698}
19699
19700impl Deref for EditorSnapshot {
19701 type Target = DisplaySnapshot;
19702
19703 fn deref(&self) -> &Self::Target {
19704 &self.display_snapshot
19705 }
19706}
19707
19708#[derive(Clone, Debug, PartialEq, Eq)]
19709pub enum EditorEvent {
19710 InputIgnored {
19711 text: Arc<str>,
19712 },
19713 InputHandled {
19714 utf16_range_to_replace: Option<Range<isize>>,
19715 text: Arc<str>,
19716 },
19717 ExcerptsAdded {
19718 buffer: Entity<Buffer>,
19719 predecessor: ExcerptId,
19720 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19721 },
19722 ExcerptsRemoved {
19723 ids: Vec<ExcerptId>,
19724 },
19725 BufferFoldToggled {
19726 ids: Vec<ExcerptId>,
19727 folded: bool,
19728 },
19729 ExcerptsEdited {
19730 ids: Vec<ExcerptId>,
19731 },
19732 ExcerptsExpanded {
19733 ids: Vec<ExcerptId>,
19734 },
19735 BufferEdited,
19736 Edited {
19737 transaction_id: clock::Lamport,
19738 },
19739 Reparsed(BufferId),
19740 Focused,
19741 FocusedIn,
19742 Blurred,
19743 DirtyChanged,
19744 Saved,
19745 TitleChanged,
19746 DiffBaseChanged,
19747 SelectionsChanged {
19748 local: bool,
19749 },
19750 ScrollPositionChanged {
19751 local: bool,
19752 autoscroll: bool,
19753 },
19754 Closed,
19755 TransactionUndone {
19756 transaction_id: clock::Lamport,
19757 },
19758 TransactionBegun {
19759 transaction_id: clock::Lamport,
19760 },
19761 Reloaded,
19762 CursorShapeChanged,
19763 PushedToNavHistory {
19764 anchor: Anchor,
19765 is_deactivate: bool,
19766 },
19767}
19768
19769impl EventEmitter<EditorEvent> for Editor {}
19770
19771impl Focusable for Editor {
19772 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19773 self.focus_handle.clone()
19774 }
19775}
19776
19777impl Render for Editor {
19778 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19779 let settings = ThemeSettings::get_global(cx);
19780
19781 let mut text_style = match self.mode {
19782 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19783 color: cx.theme().colors().editor_foreground,
19784 font_family: settings.ui_font.family.clone(),
19785 font_features: settings.ui_font.features.clone(),
19786 font_fallbacks: settings.ui_font.fallbacks.clone(),
19787 font_size: rems(0.875).into(),
19788 font_weight: settings.ui_font.weight,
19789 line_height: relative(settings.buffer_line_height.value()),
19790 ..Default::default()
19791 },
19792 EditorMode::Full { .. } => TextStyle {
19793 color: cx.theme().colors().editor_foreground,
19794 font_family: settings.buffer_font.family.clone(),
19795 font_features: settings.buffer_font.features.clone(),
19796 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19797 font_size: settings.buffer_font_size(cx).into(),
19798 font_weight: settings.buffer_font.weight,
19799 line_height: relative(settings.buffer_line_height.value()),
19800 ..Default::default()
19801 },
19802 };
19803 if let Some(text_style_refinement) = &self.text_style_refinement {
19804 text_style.refine(text_style_refinement)
19805 }
19806
19807 let background = match self.mode {
19808 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19809 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19810 EditorMode::Full { .. } => cx.theme().colors().editor_background,
19811 };
19812
19813 EditorElement::new(
19814 &cx.entity(),
19815 EditorStyle {
19816 background,
19817 local_player: cx.theme().players().local(),
19818 text: text_style,
19819 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19820 syntax: cx.theme().syntax().clone(),
19821 status: cx.theme().status().clone(),
19822 inlay_hints_style: make_inlay_hints_style(cx),
19823 inline_completion_styles: make_suggestion_styles(cx),
19824 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19825 },
19826 )
19827 }
19828}
19829
19830impl EntityInputHandler for Editor {
19831 fn text_for_range(
19832 &mut self,
19833 range_utf16: Range<usize>,
19834 adjusted_range: &mut Option<Range<usize>>,
19835 _: &mut Window,
19836 cx: &mut Context<Self>,
19837 ) -> Option<String> {
19838 let snapshot = self.buffer.read(cx).read(cx);
19839 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19840 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19841 if (start.0..end.0) != range_utf16 {
19842 adjusted_range.replace(start.0..end.0);
19843 }
19844 Some(snapshot.text_for_range(start..end).collect())
19845 }
19846
19847 fn selected_text_range(
19848 &mut self,
19849 ignore_disabled_input: bool,
19850 _: &mut Window,
19851 cx: &mut Context<Self>,
19852 ) -> Option<UTF16Selection> {
19853 // Prevent the IME menu from appearing when holding down an alphabetic key
19854 // while input is disabled.
19855 if !ignore_disabled_input && !self.input_enabled {
19856 return None;
19857 }
19858
19859 let selection = self.selections.newest::<OffsetUtf16>(cx);
19860 let range = selection.range();
19861
19862 Some(UTF16Selection {
19863 range: range.start.0..range.end.0,
19864 reversed: selection.reversed,
19865 })
19866 }
19867
19868 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19869 let snapshot = self.buffer.read(cx).read(cx);
19870 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19871 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19872 }
19873
19874 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19875 self.clear_highlights::<InputComposition>(cx);
19876 self.ime_transaction.take();
19877 }
19878
19879 fn replace_text_in_range(
19880 &mut self,
19881 range_utf16: Option<Range<usize>>,
19882 text: &str,
19883 window: &mut Window,
19884 cx: &mut Context<Self>,
19885 ) {
19886 if !self.input_enabled {
19887 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19888 return;
19889 }
19890
19891 self.transact(window, cx, |this, window, cx| {
19892 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19893 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19894 Some(this.selection_replacement_ranges(range_utf16, cx))
19895 } else {
19896 this.marked_text_ranges(cx)
19897 };
19898
19899 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19900 let newest_selection_id = this.selections.newest_anchor().id;
19901 this.selections
19902 .all::<OffsetUtf16>(cx)
19903 .iter()
19904 .zip(ranges_to_replace.iter())
19905 .find_map(|(selection, range)| {
19906 if selection.id == newest_selection_id {
19907 Some(
19908 (range.start.0 as isize - selection.head().0 as isize)
19909 ..(range.end.0 as isize - selection.head().0 as isize),
19910 )
19911 } else {
19912 None
19913 }
19914 })
19915 });
19916
19917 cx.emit(EditorEvent::InputHandled {
19918 utf16_range_to_replace: range_to_replace,
19919 text: text.into(),
19920 });
19921
19922 if let Some(new_selected_ranges) = new_selected_ranges {
19923 this.change_selections(None, window, cx, |selections| {
19924 selections.select_ranges(new_selected_ranges)
19925 });
19926 this.backspace(&Default::default(), window, cx);
19927 }
19928
19929 this.handle_input(text, window, cx);
19930 });
19931
19932 if let Some(transaction) = self.ime_transaction {
19933 self.buffer.update(cx, |buffer, cx| {
19934 buffer.group_until_transaction(transaction, cx);
19935 });
19936 }
19937
19938 self.unmark_text(window, cx);
19939 }
19940
19941 fn replace_and_mark_text_in_range(
19942 &mut self,
19943 range_utf16: Option<Range<usize>>,
19944 text: &str,
19945 new_selected_range_utf16: Option<Range<usize>>,
19946 window: &mut Window,
19947 cx: &mut Context<Self>,
19948 ) {
19949 if !self.input_enabled {
19950 return;
19951 }
19952
19953 let transaction = self.transact(window, cx, |this, window, cx| {
19954 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19955 let snapshot = this.buffer.read(cx).read(cx);
19956 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19957 for marked_range in &mut marked_ranges {
19958 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19959 marked_range.start.0 += relative_range_utf16.start;
19960 marked_range.start =
19961 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19962 marked_range.end =
19963 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19964 }
19965 }
19966 Some(marked_ranges)
19967 } else if let Some(range_utf16) = range_utf16 {
19968 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19969 Some(this.selection_replacement_ranges(range_utf16, cx))
19970 } else {
19971 None
19972 };
19973
19974 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19975 let newest_selection_id = this.selections.newest_anchor().id;
19976 this.selections
19977 .all::<OffsetUtf16>(cx)
19978 .iter()
19979 .zip(ranges_to_replace.iter())
19980 .find_map(|(selection, range)| {
19981 if selection.id == newest_selection_id {
19982 Some(
19983 (range.start.0 as isize - selection.head().0 as isize)
19984 ..(range.end.0 as isize - selection.head().0 as isize),
19985 )
19986 } else {
19987 None
19988 }
19989 })
19990 });
19991
19992 cx.emit(EditorEvent::InputHandled {
19993 utf16_range_to_replace: range_to_replace,
19994 text: text.into(),
19995 });
19996
19997 if let Some(ranges) = ranges_to_replace {
19998 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19999 }
20000
20001 let marked_ranges = {
20002 let snapshot = this.buffer.read(cx).read(cx);
20003 this.selections
20004 .disjoint_anchors()
20005 .iter()
20006 .map(|selection| {
20007 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20008 })
20009 .collect::<Vec<_>>()
20010 };
20011
20012 if text.is_empty() {
20013 this.unmark_text(window, cx);
20014 } else {
20015 this.highlight_text::<InputComposition>(
20016 marked_ranges.clone(),
20017 HighlightStyle {
20018 underline: Some(UnderlineStyle {
20019 thickness: px(1.),
20020 color: None,
20021 wavy: false,
20022 }),
20023 ..Default::default()
20024 },
20025 cx,
20026 );
20027 }
20028
20029 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20030 let use_autoclose = this.use_autoclose;
20031 let use_auto_surround = this.use_auto_surround;
20032 this.set_use_autoclose(false);
20033 this.set_use_auto_surround(false);
20034 this.handle_input(text, window, cx);
20035 this.set_use_autoclose(use_autoclose);
20036 this.set_use_auto_surround(use_auto_surround);
20037
20038 if let Some(new_selected_range) = new_selected_range_utf16 {
20039 let snapshot = this.buffer.read(cx).read(cx);
20040 let new_selected_ranges = marked_ranges
20041 .into_iter()
20042 .map(|marked_range| {
20043 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20044 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20045 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20046 snapshot.clip_offset_utf16(new_start, Bias::Left)
20047 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20048 })
20049 .collect::<Vec<_>>();
20050
20051 drop(snapshot);
20052 this.change_selections(None, window, cx, |selections| {
20053 selections.select_ranges(new_selected_ranges)
20054 });
20055 }
20056 });
20057
20058 self.ime_transaction = self.ime_transaction.or(transaction);
20059 if let Some(transaction) = self.ime_transaction {
20060 self.buffer.update(cx, |buffer, cx| {
20061 buffer.group_until_transaction(transaction, cx);
20062 });
20063 }
20064
20065 if self.text_highlights::<InputComposition>(cx).is_none() {
20066 self.ime_transaction.take();
20067 }
20068 }
20069
20070 fn bounds_for_range(
20071 &mut self,
20072 range_utf16: Range<usize>,
20073 element_bounds: gpui::Bounds<Pixels>,
20074 window: &mut Window,
20075 cx: &mut Context<Self>,
20076 ) -> Option<gpui::Bounds<Pixels>> {
20077 let text_layout_details = self.text_layout_details(window);
20078 let gpui::Size {
20079 width: em_width,
20080 height: line_height,
20081 } = self.character_size(window);
20082
20083 let snapshot = self.snapshot(window, cx);
20084 let scroll_position = snapshot.scroll_position();
20085 let scroll_left = scroll_position.x * em_width;
20086
20087 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20088 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20089 + self.gutter_dimensions.width
20090 + self.gutter_dimensions.margin;
20091 let y = line_height * (start.row().as_f32() - scroll_position.y);
20092
20093 Some(Bounds {
20094 origin: element_bounds.origin + point(x, y),
20095 size: size(em_width, line_height),
20096 })
20097 }
20098
20099 fn character_index_for_point(
20100 &mut self,
20101 point: gpui::Point<Pixels>,
20102 _window: &mut Window,
20103 _cx: &mut Context<Self>,
20104 ) -> Option<usize> {
20105 let position_map = self.last_position_map.as_ref()?;
20106 if !position_map.text_hitbox.contains(&point) {
20107 return None;
20108 }
20109 let display_point = position_map.point_for_position(point).previous_valid;
20110 let anchor = position_map
20111 .snapshot
20112 .display_point_to_anchor(display_point, Bias::Left);
20113 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20114 Some(utf16_offset.0)
20115 }
20116}
20117
20118trait SelectionExt {
20119 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20120 fn spanned_rows(
20121 &self,
20122 include_end_if_at_line_start: bool,
20123 map: &DisplaySnapshot,
20124 ) -> Range<MultiBufferRow>;
20125}
20126
20127impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20128 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20129 let start = self
20130 .start
20131 .to_point(&map.buffer_snapshot)
20132 .to_display_point(map);
20133 let end = self
20134 .end
20135 .to_point(&map.buffer_snapshot)
20136 .to_display_point(map);
20137 if self.reversed {
20138 end..start
20139 } else {
20140 start..end
20141 }
20142 }
20143
20144 fn spanned_rows(
20145 &self,
20146 include_end_if_at_line_start: bool,
20147 map: &DisplaySnapshot,
20148 ) -> Range<MultiBufferRow> {
20149 let start = self.start.to_point(&map.buffer_snapshot);
20150 let mut end = self.end.to_point(&map.buffer_snapshot);
20151 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20152 end.row -= 1;
20153 }
20154
20155 let buffer_start = map.prev_line_boundary(start).0;
20156 let buffer_end = map.next_line_boundary(end).0;
20157 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20158 }
20159}
20160
20161impl<T: InvalidationRegion> InvalidationStack<T> {
20162 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20163 where
20164 S: Clone + ToOffset,
20165 {
20166 while let Some(region) = self.last() {
20167 let all_selections_inside_invalidation_ranges =
20168 if selections.len() == region.ranges().len() {
20169 selections
20170 .iter()
20171 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20172 .all(|(selection, invalidation_range)| {
20173 let head = selection.head().to_offset(buffer);
20174 invalidation_range.start <= head && invalidation_range.end >= head
20175 })
20176 } else {
20177 false
20178 };
20179
20180 if all_selections_inside_invalidation_ranges {
20181 break;
20182 } else {
20183 self.pop();
20184 }
20185 }
20186 }
20187}
20188
20189impl<T> Default for InvalidationStack<T> {
20190 fn default() -> Self {
20191 Self(Default::default())
20192 }
20193}
20194
20195impl<T> Deref for InvalidationStack<T> {
20196 type Target = Vec<T>;
20197
20198 fn deref(&self) -> &Self::Target {
20199 &self.0
20200 }
20201}
20202
20203impl<T> DerefMut for InvalidationStack<T> {
20204 fn deref_mut(&mut self) -> &mut Self::Target {
20205 &mut self.0
20206 }
20207}
20208
20209impl InvalidationRegion for SnippetState {
20210 fn ranges(&self) -> &[Range<Anchor>] {
20211 &self.ranges[self.active_index]
20212 }
20213}
20214
20215fn inline_completion_edit_text(
20216 current_snapshot: &BufferSnapshot,
20217 edits: &[(Range<Anchor>, String)],
20218 edit_preview: &EditPreview,
20219 include_deletions: bool,
20220 cx: &App,
20221) -> HighlightedText {
20222 let edits = edits
20223 .iter()
20224 .map(|(anchor, text)| {
20225 (
20226 anchor.start.text_anchor..anchor.end.text_anchor,
20227 text.clone(),
20228 )
20229 })
20230 .collect::<Vec<_>>();
20231
20232 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20233}
20234
20235pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20236 match severity {
20237 DiagnosticSeverity::ERROR => colors.error,
20238 DiagnosticSeverity::WARNING => colors.warning,
20239 DiagnosticSeverity::INFORMATION => colors.info,
20240 DiagnosticSeverity::HINT => colors.info,
20241 _ => colors.ignored,
20242 }
20243}
20244
20245pub fn styled_runs_for_code_label<'a>(
20246 label: &'a CodeLabel,
20247 syntax_theme: &'a theme::SyntaxTheme,
20248) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20249 let fade_out = HighlightStyle {
20250 fade_out: Some(0.35),
20251 ..Default::default()
20252 };
20253
20254 let mut prev_end = label.filter_range.end;
20255 label
20256 .runs
20257 .iter()
20258 .enumerate()
20259 .flat_map(move |(ix, (range, highlight_id))| {
20260 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20261 style
20262 } else {
20263 return Default::default();
20264 };
20265 let mut muted_style = style;
20266 muted_style.highlight(fade_out);
20267
20268 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20269 if range.start >= label.filter_range.end {
20270 if range.start > prev_end {
20271 runs.push((prev_end..range.start, fade_out));
20272 }
20273 runs.push((range.clone(), muted_style));
20274 } else if range.end <= label.filter_range.end {
20275 runs.push((range.clone(), style));
20276 } else {
20277 runs.push((range.start..label.filter_range.end, style));
20278 runs.push((label.filter_range.end..range.end, muted_style));
20279 }
20280 prev_end = cmp::max(prev_end, range.end);
20281
20282 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20283 runs.push((prev_end..label.text.len(), fade_out));
20284 }
20285
20286 runs
20287 })
20288}
20289
20290pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20291 let mut prev_index = 0;
20292 let mut prev_codepoint: Option<char> = None;
20293 text.char_indices()
20294 .chain([(text.len(), '\0')])
20295 .filter_map(move |(index, codepoint)| {
20296 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20297 let is_boundary = index == text.len()
20298 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20299 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20300 if is_boundary {
20301 let chunk = &text[prev_index..index];
20302 prev_index = index;
20303 Some(chunk)
20304 } else {
20305 None
20306 }
20307 })
20308}
20309
20310pub trait RangeToAnchorExt: Sized {
20311 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20312
20313 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20314 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20315 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20316 }
20317}
20318
20319impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20320 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20321 let start_offset = self.start.to_offset(snapshot);
20322 let end_offset = self.end.to_offset(snapshot);
20323 if start_offset == end_offset {
20324 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20325 } else {
20326 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20327 }
20328 }
20329}
20330
20331pub trait RowExt {
20332 fn as_f32(&self) -> f32;
20333
20334 fn next_row(&self) -> Self;
20335
20336 fn previous_row(&self) -> Self;
20337
20338 fn minus(&self, other: Self) -> u32;
20339}
20340
20341impl RowExt for DisplayRow {
20342 fn as_f32(&self) -> f32 {
20343 self.0 as f32
20344 }
20345
20346 fn next_row(&self) -> Self {
20347 Self(self.0 + 1)
20348 }
20349
20350 fn previous_row(&self) -> Self {
20351 Self(self.0.saturating_sub(1))
20352 }
20353
20354 fn minus(&self, other: Self) -> u32 {
20355 self.0 - other.0
20356 }
20357}
20358
20359impl RowExt for MultiBufferRow {
20360 fn as_f32(&self) -> f32 {
20361 self.0 as f32
20362 }
20363
20364 fn next_row(&self) -> Self {
20365 Self(self.0 + 1)
20366 }
20367
20368 fn previous_row(&self) -> Self {
20369 Self(self.0.saturating_sub(1))
20370 }
20371
20372 fn minus(&self, other: Self) -> u32 {
20373 self.0 - other.0
20374 }
20375}
20376
20377trait RowRangeExt {
20378 type Row;
20379
20380 fn len(&self) -> usize;
20381
20382 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20383}
20384
20385impl RowRangeExt for Range<MultiBufferRow> {
20386 type Row = MultiBufferRow;
20387
20388 fn len(&self) -> usize {
20389 (self.end.0 - self.start.0) as usize
20390 }
20391
20392 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20393 (self.start.0..self.end.0).map(MultiBufferRow)
20394 }
20395}
20396
20397impl RowRangeExt for Range<DisplayRow> {
20398 type Row = DisplayRow;
20399
20400 fn len(&self) -> usize {
20401 (self.end.0 - self.start.0) as usize
20402 }
20403
20404 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20405 (self.start.0..self.end.0).map(DisplayRow)
20406 }
20407}
20408
20409/// If select range has more than one line, we
20410/// just point the cursor to range.start.
20411fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20412 if range.start.row == range.end.row {
20413 range
20414 } else {
20415 range.start..range.start
20416 }
20417}
20418pub struct KillRing(ClipboardItem);
20419impl Global for KillRing {}
20420
20421const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20422
20423enum BreakpointPromptEditAction {
20424 Log,
20425 Condition,
20426 HitCondition,
20427}
20428
20429struct BreakpointPromptEditor {
20430 pub(crate) prompt: Entity<Editor>,
20431 editor: WeakEntity<Editor>,
20432 breakpoint_anchor: Anchor,
20433 breakpoint: Breakpoint,
20434 edit_action: BreakpointPromptEditAction,
20435 block_ids: HashSet<CustomBlockId>,
20436 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20437 _subscriptions: Vec<Subscription>,
20438}
20439
20440impl BreakpointPromptEditor {
20441 const MAX_LINES: u8 = 4;
20442
20443 fn new(
20444 editor: WeakEntity<Editor>,
20445 breakpoint_anchor: Anchor,
20446 breakpoint: Breakpoint,
20447 edit_action: BreakpointPromptEditAction,
20448 window: &mut Window,
20449 cx: &mut Context<Self>,
20450 ) -> Self {
20451 let base_text = match edit_action {
20452 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20453 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20454 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20455 }
20456 .map(|msg| msg.to_string())
20457 .unwrap_or_default();
20458
20459 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20460 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20461
20462 let prompt = cx.new(|cx| {
20463 let mut prompt = Editor::new(
20464 EditorMode::AutoHeight {
20465 max_lines: Self::MAX_LINES as usize,
20466 },
20467 buffer,
20468 None,
20469 window,
20470 cx,
20471 );
20472 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20473 prompt.set_show_cursor_when_unfocused(false, cx);
20474 prompt.set_placeholder_text(
20475 match edit_action {
20476 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20477 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20478 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20479 },
20480 cx,
20481 );
20482
20483 prompt
20484 });
20485
20486 Self {
20487 prompt,
20488 editor,
20489 breakpoint_anchor,
20490 breakpoint,
20491 edit_action,
20492 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20493 block_ids: Default::default(),
20494 _subscriptions: vec![],
20495 }
20496 }
20497
20498 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20499 self.block_ids.extend(block_ids)
20500 }
20501
20502 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20503 if let Some(editor) = self.editor.upgrade() {
20504 let message = self
20505 .prompt
20506 .read(cx)
20507 .buffer
20508 .read(cx)
20509 .as_singleton()
20510 .expect("A multi buffer in breakpoint prompt isn't possible")
20511 .read(cx)
20512 .as_rope()
20513 .to_string();
20514
20515 editor.update(cx, |editor, cx| {
20516 editor.edit_breakpoint_at_anchor(
20517 self.breakpoint_anchor,
20518 self.breakpoint.clone(),
20519 match self.edit_action {
20520 BreakpointPromptEditAction::Log => {
20521 BreakpointEditAction::EditLogMessage(message.into())
20522 }
20523 BreakpointPromptEditAction::Condition => {
20524 BreakpointEditAction::EditCondition(message.into())
20525 }
20526 BreakpointPromptEditAction::HitCondition => {
20527 BreakpointEditAction::EditHitCondition(message.into())
20528 }
20529 },
20530 cx,
20531 );
20532
20533 editor.remove_blocks(self.block_ids.clone(), None, cx);
20534 cx.focus_self(window);
20535 });
20536 }
20537 }
20538
20539 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20540 self.editor
20541 .update(cx, |editor, cx| {
20542 editor.remove_blocks(self.block_ids.clone(), None, cx);
20543 window.focus(&editor.focus_handle);
20544 })
20545 .log_err();
20546 }
20547
20548 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20549 let settings = ThemeSettings::get_global(cx);
20550 let text_style = TextStyle {
20551 color: if self.prompt.read(cx).read_only(cx) {
20552 cx.theme().colors().text_disabled
20553 } else {
20554 cx.theme().colors().text
20555 },
20556 font_family: settings.buffer_font.family.clone(),
20557 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20558 font_size: settings.buffer_font_size(cx).into(),
20559 font_weight: settings.buffer_font.weight,
20560 line_height: relative(settings.buffer_line_height.value()),
20561 ..Default::default()
20562 };
20563 EditorElement::new(
20564 &self.prompt,
20565 EditorStyle {
20566 background: cx.theme().colors().editor_background,
20567 local_player: cx.theme().players().local(),
20568 text: text_style,
20569 ..Default::default()
20570 },
20571 )
20572 }
20573}
20574
20575impl Render for BreakpointPromptEditor {
20576 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20577 let gutter_dimensions = *self.gutter_dimensions.lock();
20578 h_flex()
20579 .key_context("Editor")
20580 .bg(cx.theme().colors().editor_background)
20581 .border_y_1()
20582 .border_color(cx.theme().status().info_border)
20583 .size_full()
20584 .py(window.line_height() / 2.5)
20585 .on_action(cx.listener(Self::confirm))
20586 .on_action(cx.listener(Self::cancel))
20587 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20588 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20589 }
20590}
20591
20592impl Focusable for BreakpointPromptEditor {
20593 fn focus_handle(&self, cx: &App) -> FocusHandle {
20594 self.prompt.focus_handle(cx)
20595 }
20596}
20597
20598fn all_edits_insertions_or_deletions(
20599 edits: &Vec<(Range<Anchor>, String)>,
20600 snapshot: &MultiBufferSnapshot,
20601) -> bool {
20602 let mut all_insertions = true;
20603 let mut all_deletions = true;
20604
20605 for (range, new_text) in edits.iter() {
20606 let range_is_empty = range.to_offset(&snapshot).is_empty();
20607 let text_is_empty = new_text.is_empty();
20608
20609 if range_is_empty != text_is_empty {
20610 if range_is_empty {
20611 all_deletions = false;
20612 } else {
20613 all_insertions = false;
20614 }
20615 } else {
20616 return false;
20617 }
20618
20619 if !all_insertions && !all_deletions {
20620 return false;
20621 }
20622 }
20623 all_insertions || all_deletions
20624}
20625
20626struct MissingEditPredictionKeybindingTooltip;
20627
20628impl Render for MissingEditPredictionKeybindingTooltip {
20629 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20630 ui::tooltip_container(window, cx, |container, _, cx| {
20631 container
20632 .flex_shrink_0()
20633 .max_w_80()
20634 .min_h(rems_from_px(124.))
20635 .justify_between()
20636 .child(
20637 v_flex()
20638 .flex_1()
20639 .text_ui_sm(cx)
20640 .child(Label::new("Conflict with Accept Keybinding"))
20641 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20642 )
20643 .child(
20644 h_flex()
20645 .pb_1()
20646 .gap_1()
20647 .items_end()
20648 .w_full()
20649 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20650 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20651 }))
20652 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20653 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20654 })),
20655 )
20656 })
20657 }
20658}
20659
20660#[derive(Debug, Clone, Copy, PartialEq)]
20661pub struct LineHighlight {
20662 pub background: Background,
20663 pub border: Option<gpui::Hsla>,
20664}
20665
20666impl From<Hsla> for LineHighlight {
20667 fn from(hsla: Hsla) -> Self {
20668 Self {
20669 background: hsla.into(),
20670 border: None,
20671 }
20672 }
20673}
20674
20675impl From<Background> for LineHighlight {
20676 fn from(background: Background) -> Self {
20677 Self {
20678 background,
20679 border: None,
20680 }
20681 }
20682}
20683
20684fn render_diff_hunk_controls(
20685 row: u32,
20686 status: &DiffHunkStatus,
20687 hunk_range: Range<Anchor>,
20688 is_created_file: bool,
20689 line_height: Pixels,
20690 editor: &Entity<Editor>,
20691 _window: &mut Window,
20692 cx: &mut App,
20693) -> AnyElement {
20694 h_flex()
20695 .h(line_height)
20696 .mr_1()
20697 .gap_1()
20698 .px_0p5()
20699 .pb_1()
20700 .border_x_1()
20701 .border_b_1()
20702 .border_color(cx.theme().colors().border_variant)
20703 .rounded_b_lg()
20704 .bg(cx.theme().colors().editor_background)
20705 .gap_1()
20706 .occlude()
20707 .shadow_md()
20708 .child(if status.has_secondary_hunk() {
20709 Button::new(("stage", row as u64), "Stage")
20710 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20711 .tooltip({
20712 let focus_handle = editor.focus_handle(cx);
20713 move |window, cx| {
20714 Tooltip::for_action_in(
20715 "Stage Hunk",
20716 &::git::ToggleStaged,
20717 &focus_handle,
20718 window,
20719 cx,
20720 )
20721 }
20722 })
20723 .on_click({
20724 let editor = editor.clone();
20725 move |_event, _window, cx| {
20726 editor.update(cx, |editor, cx| {
20727 editor.stage_or_unstage_diff_hunks(
20728 true,
20729 vec![hunk_range.start..hunk_range.start],
20730 cx,
20731 );
20732 });
20733 }
20734 })
20735 } else {
20736 Button::new(("unstage", row as u64), "Unstage")
20737 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20738 .tooltip({
20739 let focus_handle = editor.focus_handle(cx);
20740 move |window, cx| {
20741 Tooltip::for_action_in(
20742 "Unstage Hunk",
20743 &::git::ToggleStaged,
20744 &focus_handle,
20745 window,
20746 cx,
20747 )
20748 }
20749 })
20750 .on_click({
20751 let editor = editor.clone();
20752 move |_event, _window, cx| {
20753 editor.update(cx, |editor, cx| {
20754 editor.stage_or_unstage_diff_hunks(
20755 false,
20756 vec![hunk_range.start..hunk_range.start],
20757 cx,
20758 );
20759 });
20760 }
20761 })
20762 })
20763 .child(
20764 Button::new(("restore", row as u64), "Restore")
20765 .tooltip({
20766 let focus_handle = editor.focus_handle(cx);
20767 move |window, cx| {
20768 Tooltip::for_action_in(
20769 "Restore Hunk",
20770 &::git::Restore,
20771 &focus_handle,
20772 window,
20773 cx,
20774 )
20775 }
20776 })
20777 .on_click({
20778 let editor = editor.clone();
20779 move |_event, window, cx| {
20780 editor.update(cx, |editor, cx| {
20781 let snapshot = editor.snapshot(window, cx);
20782 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20783 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20784 });
20785 }
20786 })
20787 .disabled(is_created_file),
20788 )
20789 .when(
20790 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20791 |el| {
20792 el.child(
20793 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20794 .shape(IconButtonShape::Square)
20795 .icon_size(IconSize::Small)
20796 // .disabled(!has_multiple_hunks)
20797 .tooltip({
20798 let focus_handle = editor.focus_handle(cx);
20799 move |window, cx| {
20800 Tooltip::for_action_in(
20801 "Next Hunk",
20802 &GoToHunk,
20803 &focus_handle,
20804 window,
20805 cx,
20806 )
20807 }
20808 })
20809 .on_click({
20810 let editor = editor.clone();
20811 move |_event, window, cx| {
20812 editor.update(cx, |editor, cx| {
20813 let snapshot = editor.snapshot(window, cx);
20814 let position =
20815 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20816 editor.go_to_hunk_before_or_after_position(
20817 &snapshot,
20818 position,
20819 Direction::Next,
20820 window,
20821 cx,
20822 );
20823 editor.expand_selected_diff_hunks(cx);
20824 });
20825 }
20826 }),
20827 )
20828 .child(
20829 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20830 .shape(IconButtonShape::Square)
20831 .icon_size(IconSize::Small)
20832 // .disabled(!has_multiple_hunks)
20833 .tooltip({
20834 let focus_handle = editor.focus_handle(cx);
20835 move |window, cx| {
20836 Tooltip::for_action_in(
20837 "Previous Hunk",
20838 &GoToPreviousHunk,
20839 &focus_handle,
20840 window,
20841 cx,
20842 )
20843 }
20844 })
20845 .on_click({
20846 let editor = editor.clone();
20847 move |_event, window, cx| {
20848 editor.update(cx, |editor, cx| {
20849 let snapshot = editor.snapshot(window, cx);
20850 let point =
20851 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20852 editor.go_to_hunk_before_or_after_position(
20853 &snapshot,
20854 point,
20855 Direction::Prev,
20856 window,
20857 cx,
20858 );
20859 editor.expand_selected_diff_hunks(cx);
20860 });
20861 }
20862 }),
20863 )
20864 },
20865 )
20866 .into_any_element()
20867}