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);
218const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
219
220pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
221pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
222pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
223
224pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
225pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
226pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
227
228pub type RenderDiffHunkControlsFn = Arc<
229 dyn Fn(
230 u32,
231 &DiffHunkStatus,
232 Range<Anchor>,
233 bool,
234 Pixels,
235 &Entity<Editor>,
236 &mut Window,
237 &mut App,
238 ) -> AnyElement,
239>;
240
241const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
242 alt: true,
243 shift: true,
244 control: false,
245 platform: false,
246 function: false,
247};
248
249#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
250pub enum InlayId {
251 InlineCompletion(usize),
252 Hint(usize),
253}
254
255impl InlayId {
256 fn id(&self) -> usize {
257 match self {
258 Self::InlineCompletion(id) => *id,
259 Self::Hint(id) => *id,
260 }
261 }
262}
263
264pub enum DebugCurrentRowHighlight {}
265enum DocumentHighlightRead {}
266enum DocumentHighlightWrite {}
267enum InputComposition {}
268enum SelectedTextHighlight {}
269
270#[derive(Debug, Copy, Clone, PartialEq, Eq)]
271pub enum Navigated {
272 Yes,
273 No,
274}
275
276impl Navigated {
277 pub fn from_bool(yes: bool) -> Navigated {
278 if yes { Navigated::Yes } else { Navigated::No }
279 }
280}
281
282#[derive(Debug, Clone, PartialEq, Eq)]
283enum DisplayDiffHunk {
284 Folded {
285 display_row: DisplayRow,
286 },
287 Unfolded {
288 is_created_file: bool,
289 diff_base_byte_range: Range<usize>,
290 display_row_range: Range<DisplayRow>,
291 multi_buffer_range: Range<Anchor>,
292 status: DiffHunkStatus,
293 },
294}
295
296pub enum HideMouseCursorOrigin {
297 TypingAction,
298 MovementAction,
299}
300
301pub fn init_settings(cx: &mut App) {
302 EditorSettings::register(cx);
303}
304
305pub fn init(cx: &mut App) {
306 init_settings(cx);
307
308 cx.set_global(GlobalBlameRenderer(Arc::new(())));
309
310 workspace::register_project_item::<Editor>(cx);
311 workspace::FollowableViewRegistry::register::<Editor>(cx);
312 workspace::register_serializable_item::<Editor>(cx);
313
314 cx.observe_new(
315 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
316 workspace.register_action(Editor::new_file);
317 workspace.register_action(Editor::new_file_vertical);
318 workspace.register_action(Editor::new_file_horizontal);
319 workspace.register_action(Editor::cancel_language_server_work);
320 },
321 )
322 .detach();
323
324 cx.on_action(move |_: &workspace::NewFile, cx| {
325 let app_state = workspace::AppState::global(cx);
326 if let Some(app_state) = app_state.upgrade() {
327 workspace::open_new(
328 Default::default(),
329 app_state,
330 cx,
331 |workspace, window, cx| {
332 Editor::new_file(workspace, &Default::default(), window, cx)
333 },
334 )
335 .detach();
336 }
337 });
338 cx.on_action(move |_: &workspace::NewWindow, cx| {
339 let app_state = workspace::AppState::global(cx);
340 if let Some(app_state) = app_state.upgrade() {
341 workspace::open_new(
342 Default::default(),
343 app_state,
344 cx,
345 |workspace, window, cx| {
346 cx.activate(true);
347 Editor::new_file(workspace, &Default::default(), window, cx)
348 },
349 )
350 .detach();
351 }
352 });
353}
354
355pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
356 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
357}
358
359pub trait DiagnosticRenderer {
360 fn render_group(
361 &self,
362 diagnostic_group: Vec<DiagnosticEntry<Point>>,
363 buffer_id: BufferId,
364 snapshot: EditorSnapshot,
365 editor: WeakEntity<Editor>,
366 cx: &mut App,
367 ) -> Vec<BlockProperties<Anchor>>;
368}
369
370pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
371
372impl gpui::Global for GlobalDiagnosticRenderer {}
373pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
374 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
375}
376
377pub struct SearchWithinRange;
378
379trait InvalidationRegion {
380 fn ranges(&self) -> &[Range<Anchor>];
381}
382
383#[derive(Clone, Debug, PartialEq)]
384pub enum SelectPhase {
385 Begin {
386 position: DisplayPoint,
387 add: bool,
388 click_count: usize,
389 },
390 BeginColumnar {
391 position: DisplayPoint,
392 reset: bool,
393 goal_column: u32,
394 },
395 Extend {
396 position: DisplayPoint,
397 click_count: usize,
398 },
399 Update {
400 position: DisplayPoint,
401 goal_column: u32,
402 scroll_delta: gpui::Point<f32>,
403 },
404 End,
405}
406
407#[derive(Clone, Debug)]
408pub enum SelectMode {
409 Character,
410 Word(Range<Anchor>),
411 Line(Range<Anchor>),
412 All,
413}
414
415#[derive(Copy, Clone, PartialEq, Eq, Debug)]
416pub enum EditorMode {
417 SingleLine {
418 auto_width: bool,
419 },
420 AutoHeight {
421 max_lines: usize,
422 },
423 Full {
424 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
425 scale_ui_elements_with_buffer_font_size: bool,
426 /// When set to `true`, the editor will render a background for the active line.
427 show_active_line_background: bool,
428 },
429}
430
431impl EditorMode {
432 pub fn full() -> Self {
433 Self::Full {
434 scale_ui_elements_with_buffer_font_size: true,
435 show_active_line_background: true,
436 }
437 }
438
439 pub fn is_full(&self) -> bool {
440 matches!(self, Self::Full { .. })
441 }
442}
443
444#[derive(Copy, Clone, Debug)]
445pub enum SoftWrap {
446 /// Prefer not to wrap at all.
447 ///
448 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
449 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
450 GitDiff,
451 /// Prefer a single line generally, unless an overly long line is encountered.
452 None,
453 /// Soft wrap lines that exceed the editor width.
454 EditorWidth,
455 /// Soft wrap lines at the preferred line length.
456 Column(u32),
457 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
458 Bounded(u32),
459}
460
461#[derive(Clone)]
462pub struct EditorStyle {
463 pub background: Hsla,
464 pub local_player: PlayerColor,
465 pub text: TextStyle,
466 pub scrollbar_width: Pixels,
467 pub syntax: Arc<SyntaxTheme>,
468 pub status: StatusColors,
469 pub inlay_hints_style: HighlightStyle,
470 pub inline_completion_styles: InlineCompletionStyles,
471 pub unnecessary_code_fade: f32,
472}
473
474impl Default for EditorStyle {
475 fn default() -> Self {
476 Self {
477 background: Hsla::default(),
478 local_player: PlayerColor::default(),
479 text: TextStyle::default(),
480 scrollbar_width: Pixels::default(),
481 syntax: Default::default(),
482 // HACK: Status colors don't have a real default.
483 // We should look into removing the status colors from the editor
484 // style and retrieve them directly from the theme.
485 status: StatusColors::dark(),
486 inlay_hints_style: HighlightStyle::default(),
487 inline_completion_styles: InlineCompletionStyles {
488 insertion: HighlightStyle::default(),
489 whitespace: HighlightStyle::default(),
490 },
491 unnecessary_code_fade: Default::default(),
492 }
493 }
494}
495
496pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
497 let show_background = language_settings::language_settings(None, None, cx)
498 .inlay_hints
499 .show_background;
500
501 HighlightStyle {
502 color: Some(cx.theme().status().hint),
503 background_color: show_background.then(|| cx.theme().status().hint_background),
504 ..HighlightStyle::default()
505 }
506}
507
508pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
509 InlineCompletionStyles {
510 insertion: HighlightStyle {
511 color: Some(cx.theme().status().predictive),
512 ..HighlightStyle::default()
513 },
514 whitespace: HighlightStyle {
515 background_color: Some(cx.theme().status().created_background),
516 ..HighlightStyle::default()
517 },
518 }
519}
520
521type CompletionId = usize;
522
523pub(crate) enum EditDisplayMode {
524 TabAccept,
525 DiffPopover,
526 Inline,
527}
528
529enum InlineCompletion {
530 Edit {
531 edits: Vec<(Range<Anchor>, String)>,
532 edit_preview: Option<EditPreview>,
533 display_mode: EditDisplayMode,
534 snapshot: BufferSnapshot,
535 },
536 Move {
537 target: Anchor,
538 snapshot: BufferSnapshot,
539 },
540}
541
542struct InlineCompletionState {
543 inlay_ids: Vec<InlayId>,
544 completion: InlineCompletion,
545 completion_id: Option<SharedString>,
546 invalidation_range: Range<Anchor>,
547}
548
549enum EditPredictionSettings {
550 Disabled,
551 Enabled {
552 show_in_menu: bool,
553 preview_requires_modifier: bool,
554 },
555}
556
557enum InlineCompletionHighlight {}
558
559#[derive(Debug, Clone)]
560struct InlineDiagnostic {
561 message: SharedString,
562 group_id: usize,
563 is_primary: bool,
564 start: Point,
565 severity: DiagnosticSeverity,
566}
567
568pub enum MenuInlineCompletionsPolicy {
569 Never,
570 ByProvider,
571}
572
573pub enum EditPredictionPreview {
574 /// Modifier is not pressed
575 Inactive { released_too_fast: bool },
576 /// Modifier pressed
577 Active {
578 since: Instant,
579 previous_scroll_position: Option<ScrollAnchor>,
580 },
581}
582
583impl EditPredictionPreview {
584 pub fn released_too_fast(&self) -> bool {
585 match self {
586 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
587 EditPredictionPreview::Active { .. } => false,
588 }
589 }
590
591 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
592 if let EditPredictionPreview::Active {
593 previous_scroll_position,
594 ..
595 } = self
596 {
597 *previous_scroll_position = scroll_position;
598 }
599 }
600}
601
602pub struct ContextMenuOptions {
603 pub min_entries_visible: usize,
604 pub max_entries_visible: usize,
605 pub placement: Option<ContextMenuPlacement>,
606}
607
608#[derive(Debug, Clone, PartialEq, Eq)]
609pub enum ContextMenuPlacement {
610 Above,
611 Below,
612}
613
614#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
615struct EditorActionId(usize);
616
617impl EditorActionId {
618 pub fn post_inc(&mut self) -> Self {
619 let answer = self.0;
620
621 *self = Self(answer + 1);
622
623 Self(answer)
624 }
625}
626
627// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
628// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
629
630type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
631type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
632
633#[derive(Default)]
634struct ScrollbarMarkerState {
635 scrollbar_size: Size<Pixels>,
636 dirty: bool,
637 markers: Arc<[PaintQuad]>,
638 pending_refresh: Option<Task<Result<()>>>,
639}
640
641impl ScrollbarMarkerState {
642 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
643 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
644 }
645}
646
647#[derive(Clone, Debug)]
648struct RunnableTasks {
649 templates: Vec<(TaskSourceKind, TaskTemplate)>,
650 offset: multi_buffer::Anchor,
651 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
652 column: u32,
653 // Values of all named captures, including those starting with '_'
654 extra_variables: HashMap<String, String>,
655 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
656 context_range: Range<BufferOffset>,
657}
658
659impl RunnableTasks {
660 fn resolve<'a>(
661 &'a self,
662 cx: &'a task::TaskContext,
663 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
664 self.templates.iter().filter_map(|(kind, template)| {
665 template
666 .resolve_task(&kind.to_id_base(), cx)
667 .map(|task| (kind.clone(), task))
668 })
669 }
670}
671
672#[derive(Clone)]
673struct ResolvedTasks {
674 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
675 position: Anchor,
676}
677
678#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
679struct BufferOffset(usize);
680
681// Addons allow storing per-editor state in other crates (e.g. Vim)
682pub trait Addon: 'static {
683 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
684
685 fn render_buffer_header_controls(
686 &self,
687 _: &ExcerptInfo,
688 _: &Window,
689 _: &App,
690 ) -> Option<AnyElement> {
691 None
692 }
693
694 fn to_any(&self) -> &dyn std::any::Any;
695}
696
697/// A set of caret positions, registered when the editor was edited.
698pub struct ChangeList {
699 changes: Vec<Vec<Anchor>>,
700 /// Currently "selected" change.
701 position: Option<usize>,
702}
703
704impl ChangeList {
705 pub fn new() -> Self {
706 Self {
707 changes: Vec::new(),
708 position: None,
709 }
710 }
711
712 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
713 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
714 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
715 if self.changes.is_empty() {
716 return None;
717 }
718
719 let prev = self.position.unwrap_or(self.changes.len());
720 let next = if direction == Direction::Prev {
721 prev.saturating_sub(count)
722 } else {
723 (prev + count).min(self.changes.len() - 1)
724 };
725 self.position = Some(next);
726 self.changes.get(next).map(|anchors| anchors.as_slice())
727 }
728
729 /// Adds a new change to the list, resetting the change list position.
730 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
731 self.position.take();
732 if pop_state {
733 self.changes.pop();
734 }
735 self.changes.push(new_positions.clone());
736 }
737
738 pub fn last(&self) -> Option<&[Anchor]> {
739 self.changes.last().map(|anchors| anchors.as_slice())
740 }
741}
742
743/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
744///
745/// See the [module level documentation](self) for more information.
746pub struct Editor {
747 focus_handle: FocusHandle,
748 last_focused_descendant: Option<WeakFocusHandle>,
749 /// The text buffer being edited
750 buffer: Entity<MultiBuffer>,
751 /// Map of how text in the buffer should be displayed.
752 /// Handles soft wraps, folds, fake inlay text insertions, etc.
753 pub display_map: Entity<DisplayMap>,
754 pub selections: SelectionsCollection,
755 pub scroll_manager: ScrollManager,
756 /// When inline assist editors are linked, they all render cursors because
757 /// typing enters text into each of them, even the ones that aren't focused.
758 pub(crate) show_cursor_when_unfocused: bool,
759 columnar_selection_tail: Option<Anchor>,
760 add_selections_state: Option<AddSelectionsState>,
761 select_next_state: Option<SelectNextState>,
762 select_prev_state: Option<SelectNextState>,
763 selection_history: SelectionHistory,
764 autoclose_regions: Vec<AutocloseRegion>,
765 snippet_stack: InvalidationStack<SnippetState>,
766 select_syntax_node_history: SelectSyntaxNodeHistory,
767 ime_transaction: Option<TransactionId>,
768 active_diagnostics: ActiveDiagnostic,
769 show_inline_diagnostics: bool,
770 inline_diagnostics_update: Task<()>,
771 inline_diagnostics_enabled: bool,
772 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
773 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
774 hard_wrap: Option<usize>,
775
776 // TODO: make this a access method
777 pub project: Option<Entity<Project>>,
778 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
779 completion_provider: Option<Box<dyn CompletionProvider>>,
780 collaboration_hub: Option<Box<dyn CollaborationHub>>,
781 blink_manager: Entity<BlinkManager>,
782 show_cursor_names: bool,
783 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
784 pub show_local_selections: bool,
785 mode: EditorMode,
786 show_breadcrumbs: bool,
787 show_gutter: bool,
788 show_scrollbars: bool,
789 show_line_numbers: Option<bool>,
790 use_relative_line_numbers: Option<bool>,
791 show_git_diff_gutter: Option<bool>,
792 show_code_actions: Option<bool>,
793 show_runnables: Option<bool>,
794 show_breakpoints: Option<bool>,
795 show_wrap_guides: Option<bool>,
796 show_indent_guides: Option<bool>,
797 placeholder_text: Option<Arc<str>>,
798 highlight_order: usize,
799 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
800 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
801 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
802 scrollbar_marker_state: ScrollbarMarkerState,
803 active_indent_guides_state: ActiveIndentGuidesState,
804 nav_history: Option<ItemNavHistory>,
805 context_menu: RefCell<Option<CodeContextMenu>>,
806 context_menu_options: Option<ContextMenuOptions>,
807 mouse_context_menu: Option<MouseContextMenu>,
808 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
809 signature_help_state: SignatureHelpState,
810 auto_signature_help: Option<bool>,
811 find_all_references_task_sources: Vec<Anchor>,
812 next_completion_id: CompletionId,
813 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
814 code_actions_task: Option<Task<Result<()>>>,
815 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
816 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
817 document_highlights_task: Option<Task<()>>,
818 linked_editing_range_task: Option<Task<Option<()>>>,
819 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
820 pending_rename: Option<RenameState>,
821 searchable: bool,
822 cursor_shape: CursorShape,
823 current_line_highlight: Option<CurrentLineHighlight>,
824 collapse_matches: bool,
825 autoindent_mode: Option<AutoindentMode>,
826 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
827 input_enabled: bool,
828 use_modal_editing: bool,
829 read_only: bool,
830 leader_peer_id: Option<PeerId>,
831 remote_id: Option<ViewId>,
832 hover_state: HoverState,
833 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
834 gutter_hovered: bool,
835 hovered_link_state: Option<HoveredLinkState>,
836 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
837 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
838 active_inline_completion: Option<InlineCompletionState>,
839 /// Used to prevent flickering as the user types while the menu is open
840 stale_inline_completion_in_menu: Option<InlineCompletionState>,
841 edit_prediction_settings: EditPredictionSettings,
842 inline_completions_hidden_for_vim_mode: bool,
843 show_inline_completions_override: Option<bool>,
844 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
845 edit_prediction_preview: EditPredictionPreview,
846 edit_prediction_indent_conflict: bool,
847 edit_prediction_requires_modifier_in_indent_conflict: bool,
848 inlay_hint_cache: InlayHintCache,
849 next_inlay_id: usize,
850 _subscriptions: Vec<Subscription>,
851 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
852 gutter_dimensions: GutterDimensions,
853 style: Option<EditorStyle>,
854 text_style_refinement: Option<TextStyleRefinement>,
855 next_editor_action_id: EditorActionId,
856 editor_actions:
857 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
858 use_autoclose: bool,
859 use_auto_surround: bool,
860 auto_replace_emoji_shortcode: bool,
861 jsx_tag_auto_close_enabled_in_any_buffer: bool,
862 show_git_blame_gutter: bool,
863 show_git_blame_inline: bool,
864 show_git_blame_inline_delay_task: Option<Task<()>>,
865 pub git_blame_inline_tooltip: Option<AnyWeakEntity>,
866 git_blame_inline_enabled: bool,
867 render_diff_hunk_controls: RenderDiffHunkControlsFn,
868 serialize_dirty_buffers: bool,
869 show_selection_menu: Option<bool>,
870 blame: Option<Entity<GitBlame>>,
871 blame_subscription: Option<Subscription>,
872 custom_context_menu: Option<
873 Box<
874 dyn 'static
875 + Fn(
876 &mut Self,
877 DisplayPoint,
878 &mut Window,
879 &mut Context<Self>,
880 ) -> Option<Entity<ui::ContextMenu>>,
881 >,
882 >,
883 last_bounds: Option<Bounds<Pixels>>,
884 last_position_map: Option<Rc<PositionMap>>,
885 expect_bounds_change: Option<Bounds<Pixels>>,
886 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
887 tasks_update_task: Option<Task<()>>,
888 breakpoint_store: Option<Entity<BreakpointStore>>,
889 /// Allow's a user to create a breakpoint by selecting this indicator
890 /// It should be None while a user is not hovering over the gutter
891 /// Otherwise it represents the point that the breakpoint will be shown
892 gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
893 in_project_search: bool,
894 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
895 breadcrumb_header: Option<String>,
896 focused_block: Option<FocusedBlock>,
897 next_scroll_position: NextScrollCursorCenterTopBottom,
898 addons: HashMap<TypeId, Box<dyn Addon>>,
899 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
900 load_diff_task: Option<Shared<Task<()>>>,
901 selection_mark_mode: bool,
902 toggle_fold_multiple_buffers: Task<()>,
903 _scroll_cursor_center_top_bottom_task: Task<()>,
904 serialize_selections: Task<()>,
905 serialize_folds: Task<()>,
906 mouse_cursor_hidden: bool,
907 hide_mouse_mode: HideMouseMode,
908 pub change_list: ChangeList,
909}
910
911#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
912enum NextScrollCursorCenterTopBottom {
913 #[default]
914 Center,
915 Top,
916 Bottom,
917}
918
919impl NextScrollCursorCenterTopBottom {
920 fn next(&self) -> Self {
921 match self {
922 Self::Center => Self::Top,
923 Self::Top => Self::Bottom,
924 Self::Bottom => Self::Center,
925 }
926 }
927}
928
929#[derive(Clone)]
930pub struct EditorSnapshot {
931 pub mode: EditorMode,
932 show_gutter: bool,
933 show_line_numbers: Option<bool>,
934 show_git_diff_gutter: Option<bool>,
935 show_code_actions: Option<bool>,
936 show_runnables: Option<bool>,
937 show_breakpoints: Option<bool>,
938 git_blame_gutter_max_author_length: Option<usize>,
939 pub display_snapshot: DisplaySnapshot,
940 pub placeholder_text: Option<Arc<str>>,
941 is_focused: bool,
942 scroll_anchor: ScrollAnchor,
943 ongoing_scroll: OngoingScroll,
944 current_line_highlight: CurrentLineHighlight,
945 gutter_hovered: bool,
946}
947
948#[derive(Default, Debug, Clone, Copy)]
949pub struct GutterDimensions {
950 pub left_padding: Pixels,
951 pub right_padding: Pixels,
952 pub width: Pixels,
953 pub margin: Pixels,
954 pub git_blame_entries_width: Option<Pixels>,
955}
956
957impl GutterDimensions {
958 /// The full width of the space taken up by the gutter.
959 pub fn full_width(&self) -> Pixels {
960 self.margin + self.width
961 }
962
963 /// The width of the space reserved for the fold indicators,
964 /// use alongside 'justify_end' and `gutter_width` to
965 /// right align content with the line numbers
966 pub fn fold_area_width(&self) -> Pixels {
967 self.margin + self.right_padding
968 }
969}
970
971#[derive(Debug)]
972pub struct RemoteSelection {
973 pub replica_id: ReplicaId,
974 pub selection: Selection<Anchor>,
975 pub cursor_shape: CursorShape,
976 pub peer_id: PeerId,
977 pub line_mode: bool,
978 pub participant_index: Option<ParticipantIndex>,
979 pub user_name: Option<SharedString>,
980}
981
982#[derive(Clone, Debug)]
983struct SelectionHistoryEntry {
984 selections: Arc<[Selection<Anchor>]>,
985 select_next_state: Option<SelectNextState>,
986 select_prev_state: Option<SelectNextState>,
987 add_selections_state: Option<AddSelectionsState>,
988}
989
990enum SelectionHistoryMode {
991 Normal,
992 Undoing,
993 Redoing,
994}
995
996#[derive(Clone, PartialEq, Eq, Hash)]
997struct HoveredCursor {
998 replica_id: u16,
999 selection_id: usize,
1000}
1001
1002impl Default for SelectionHistoryMode {
1003 fn default() -> Self {
1004 Self::Normal
1005 }
1006}
1007
1008#[derive(Default)]
1009struct SelectionHistory {
1010 #[allow(clippy::type_complexity)]
1011 selections_by_transaction:
1012 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1013 mode: SelectionHistoryMode,
1014 undo_stack: VecDeque<SelectionHistoryEntry>,
1015 redo_stack: VecDeque<SelectionHistoryEntry>,
1016}
1017
1018impl SelectionHistory {
1019 fn insert_transaction(
1020 &mut self,
1021 transaction_id: TransactionId,
1022 selections: Arc<[Selection<Anchor>]>,
1023 ) {
1024 self.selections_by_transaction
1025 .insert(transaction_id, (selections, None));
1026 }
1027
1028 #[allow(clippy::type_complexity)]
1029 fn transaction(
1030 &self,
1031 transaction_id: TransactionId,
1032 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1033 self.selections_by_transaction.get(&transaction_id)
1034 }
1035
1036 #[allow(clippy::type_complexity)]
1037 fn transaction_mut(
1038 &mut self,
1039 transaction_id: TransactionId,
1040 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1041 self.selections_by_transaction.get_mut(&transaction_id)
1042 }
1043
1044 fn push(&mut self, entry: SelectionHistoryEntry) {
1045 if !entry.selections.is_empty() {
1046 match self.mode {
1047 SelectionHistoryMode::Normal => {
1048 self.push_undo(entry);
1049 self.redo_stack.clear();
1050 }
1051 SelectionHistoryMode::Undoing => self.push_redo(entry),
1052 SelectionHistoryMode::Redoing => self.push_undo(entry),
1053 }
1054 }
1055 }
1056
1057 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1058 if self
1059 .undo_stack
1060 .back()
1061 .map_or(true, |e| e.selections != entry.selections)
1062 {
1063 self.undo_stack.push_back(entry);
1064 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1065 self.undo_stack.pop_front();
1066 }
1067 }
1068 }
1069
1070 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1071 if self
1072 .redo_stack
1073 .back()
1074 .map_or(true, |e| e.selections != entry.selections)
1075 {
1076 self.redo_stack.push_back(entry);
1077 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1078 self.redo_stack.pop_front();
1079 }
1080 }
1081 }
1082}
1083
1084struct RowHighlight {
1085 index: usize,
1086 range: Range<Anchor>,
1087 color: Hsla,
1088 should_autoscroll: bool,
1089}
1090
1091#[derive(Clone, Debug)]
1092struct AddSelectionsState {
1093 above: bool,
1094 stack: Vec<usize>,
1095}
1096
1097#[derive(Clone)]
1098struct SelectNextState {
1099 query: AhoCorasick,
1100 wordwise: bool,
1101 done: bool,
1102}
1103
1104impl std::fmt::Debug for SelectNextState {
1105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1106 f.debug_struct(std::any::type_name::<Self>())
1107 .field("wordwise", &self.wordwise)
1108 .field("done", &self.done)
1109 .finish()
1110 }
1111}
1112
1113#[derive(Debug)]
1114struct AutocloseRegion {
1115 selection_id: usize,
1116 range: Range<Anchor>,
1117 pair: BracketPair,
1118}
1119
1120#[derive(Debug)]
1121struct SnippetState {
1122 ranges: Vec<Vec<Range<Anchor>>>,
1123 active_index: usize,
1124 choices: Vec<Option<Vec<String>>>,
1125}
1126
1127#[doc(hidden)]
1128pub struct RenameState {
1129 pub range: Range<Anchor>,
1130 pub old_name: Arc<str>,
1131 pub editor: Entity<Editor>,
1132 block_id: CustomBlockId,
1133}
1134
1135struct InvalidationStack<T>(Vec<T>);
1136
1137struct RegisteredInlineCompletionProvider {
1138 provider: Arc<dyn InlineCompletionProviderHandle>,
1139 _subscription: Subscription,
1140}
1141
1142#[derive(Debug, PartialEq, Eq)]
1143pub struct ActiveDiagnosticGroup {
1144 pub active_range: Range<Anchor>,
1145 pub active_message: String,
1146 pub group_id: usize,
1147 pub blocks: HashSet<CustomBlockId>,
1148}
1149
1150#[derive(Debug, PartialEq, Eq)]
1151#[allow(clippy::large_enum_variant)]
1152pub(crate) enum ActiveDiagnostic {
1153 None,
1154 All,
1155 Group(ActiveDiagnosticGroup),
1156}
1157
1158#[derive(Serialize, Deserialize, Clone, Debug)]
1159pub struct ClipboardSelection {
1160 /// The number of bytes in this selection.
1161 pub len: usize,
1162 /// Whether this was a full-line selection.
1163 pub is_entire_line: bool,
1164 /// The indentation of the first line when this content was originally copied.
1165 pub first_line_indent: u32,
1166}
1167
1168// selections, scroll behavior, was newest selection reversed
1169type SelectSyntaxNodeHistoryState = (
1170 Box<[Selection<usize>]>,
1171 SelectSyntaxNodeScrollBehavior,
1172 bool,
1173);
1174
1175#[derive(Default)]
1176struct SelectSyntaxNodeHistory {
1177 stack: Vec<SelectSyntaxNodeHistoryState>,
1178 // disable temporarily to allow changing selections without losing the stack
1179 pub disable_clearing: bool,
1180}
1181
1182impl SelectSyntaxNodeHistory {
1183 pub fn try_clear(&mut self) {
1184 if !self.disable_clearing {
1185 self.stack.clear();
1186 }
1187 }
1188
1189 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1190 self.stack.push(selection);
1191 }
1192
1193 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1194 self.stack.pop()
1195 }
1196}
1197
1198enum SelectSyntaxNodeScrollBehavior {
1199 CursorTop,
1200 FitSelection,
1201 CursorBottom,
1202}
1203
1204#[derive(Debug)]
1205pub(crate) struct NavigationData {
1206 cursor_anchor: Anchor,
1207 cursor_position: Point,
1208 scroll_anchor: ScrollAnchor,
1209 scroll_top_row: u32,
1210}
1211
1212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1213pub enum GotoDefinitionKind {
1214 Symbol,
1215 Declaration,
1216 Type,
1217 Implementation,
1218}
1219
1220#[derive(Debug, Clone)]
1221enum InlayHintRefreshReason {
1222 ModifiersChanged(bool),
1223 Toggle(bool),
1224 SettingsChange(InlayHintSettings),
1225 NewLinesShown,
1226 BufferEdited(HashSet<Arc<Language>>),
1227 RefreshRequested,
1228 ExcerptsRemoved(Vec<ExcerptId>),
1229}
1230
1231impl InlayHintRefreshReason {
1232 fn description(&self) -> &'static str {
1233 match self {
1234 Self::ModifiersChanged(_) => "modifiers changed",
1235 Self::Toggle(_) => "toggle",
1236 Self::SettingsChange(_) => "settings change",
1237 Self::NewLinesShown => "new lines shown",
1238 Self::BufferEdited(_) => "buffer edited",
1239 Self::RefreshRequested => "refresh requested",
1240 Self::ExcerptsRemoved(_) => "excerpts removed",
1241 }
1242 }
1243}
1244
1245pub enum FormatTarget {
1246 Buffers,
1247 Ranges(Vec<Range<MultiBufferPoint>>),
1248}
1249
1250pub(crate) struct FocusedBlock {
1251 id: BlockId,
1252 focus_handle: WeakFocusHandle,
1253}
1254
1255#[derive(Clone)]
1256enum JumpData {
1257 MultiBufferRow {
1258 row: MultiBufferRow,
1259 line_offset_from_top: u32,
1260 },
1261 MultiBufferPoint {
1262 excerpt_id: ExcerptId,
1263 position: Point,
1264 anchor: text::Anchor,
1265 line_offset_from_top: u32,
1266 },
1267}
1268
1269pub enum MultibufferSelectionMode {
1270 First,
1271 All,
1272}
1273
1274#[derive(Clone, Copy, Debug, Default)]
1275pub struct RewrapOptions {
1276 pub override_language_settings: bool,
1277 pub preserve_existing_whitespace: bool,
1278}
1279
1280impl Editor {
1281 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1282 let buffer = cx.new(|cx| Buffer::local("", cx));
1283 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1284 Self::new(
1285 EditorMode::SingleLine { auto_width: false },
1286 buffer,
1287 None,
1288 window,
1289 cx,
1290 )
1291 }
1292
1293 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1294 let buffer = cx.new(|cx| Buffer::local("", cx));
1295 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1296 Self::new(EditorMode::full(), buffer, None, window, cx)
1297 }
1298
1299 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1300 let buffer = cx.new(|cx| Buffer::local("", cx));
1301 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1302 Self::new(
1303 EditorMode::SingleLine { auto_width: true },
1304 buffer,
1305 None,
1306 window,
1307 cx,
1308 )
1309 }
1310
1311 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1312 let buffer = cx.new(|cx| Buffer::local("", cx));
1313 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1314 Self::new(
1315 EditorMode::AutoHeight { max_lines },
1316 buffer,
1317 None,
1318 window,
1319 cx,
1320 )
1321 }
1322
1323 pub fn for_buffer(
1324 buffer: Entity<Buffer>,
1325 project: Option<Entity<Project>>,
1326 window: &mut Window,
1327 cx: &mut Context<Self>,
1328 ) -> Self {
1329 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1330 Self::new(EditorMode::full(), buffer, project, window, cx)
1331 }
1332
1333 pub fn for_multibuffer(
1334 buffer: Entity<MultiBuffer>,
1335 project: Option<Entity<Project>>,
1336 window: &mut Window,
1337 cx: &mut Context<Self>,
1338 ) -> Self {
1339 Self::new(EditorMode::full(), buffer, project, window, cx)
1340 }
1341
1342 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1343 let mut clone = Self::new(
1344 self.mode,
1345 self.buffer.clone(),
1346 self.project.clone(),
1347 window,
1348 cx,
1349 );
1350 self.display_map.update(cx, |display_map, cx| {
1351 let snapshot = display_map.snapshot(cx);
1352 clone.display_map.update(cx, |display_map, cx| {
1353 display_map.set_state(&snapshot, cx);
1354 });
1355 });
1356 clone.folds_did_change(cx);
1357 clone.selections.clone_state(&self.selections);
1358 clone.scroll_manager.clone_state(&self.scroll_manager);
1359 clone.searchable = self.searchable;
1360 clone.read_only = self.read_only;
1361 clone
1362 }
1363
1364 pub fn new(
1365 mode: EditorMode,
1366 buffer: Entity<MultiBuffer>,
1367 project: Option<Entity<Project>>,
1368 window: &mut Window,
1369 cx: &mut Context<Self>,
1370 ) -> Self {
1371 let style = window.text_style();
1372 let font_size = style.font_size.to_pixels(window.rem_size());
1373 let editor = cx.entity().downgrade();
1374 let fold_placeholder = FoldPlaceholder {
1375 constrain_width: true,
1376 render: Arc::new(move |fold_id, fold_range, cx| {
1377 let editor = editor.clone();
1378 div()
1379 .id(fold_id)
1380 .bg(cx.theme().colors().ghost_element_background)
1381 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1382 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1383 .rounded_xs()
1384 .size_full()
1385 .cursor_pointer()
1386 .child("⋯")
1387 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1388 .on_click(move |_, _window, cx| {
1389 editor
1390 .update(cx, |editor, cx| {
1391 editor.unfold_ranges(
1392 &[fold_range.start..fold_range.end],
1393 true,
1394 false,
1395 cx,
1396 );
1397 cx.stop_propagation();
1398 })
1399 .ok();
1400 })
1401 .into_any()
1402 }),
1403 merge_adjacent: true,
1404 ..Default::default()
1405 };
1406 let display_map = cx.new(|cx| {
1407 DisplayMap::new(
1408 buffer.clone(),
1409 style.font(),
1410 font_size,
1411 None,
1412 FILE_HEADER_HEIGHT,
1413 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1414 fold_placeholder,
1415 cx,
1416 )
1417 });
1418
1419 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1420
1421 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1422
1423 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1424 .then(|| language_settings::SoftWrap::None);
1425
1426 let mut project_subscriptions = Vec::new();
1427 if mode.is_full() {
1428 if let Some(project) = project.as_ref() {
1429 project_subscriptions.push(cx.subscribe_in(
1430 project,
1431 window,
1432 |editor, _, event, window, cx| match event {
1433 project::Event::RefreshCodeLens => {
1434 // we always query lens with actions, without storing them, always refreshing them
1435 }
1436 project::Event::RefreshInlayHints => {
1437 editor
1438 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1439 }
1440 project::Event::SnippetEdit(id, snippet_edits) => {
1441 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1442 let focus_handle = editor.focus_handle(cx);
1443 if focus_handle.is_focused(window) {
1444 let snapshot = buffer.read(cx).snapshot();
1445 for (range, snippet) in snippet_edits {
1446 let editor_range =
1447 language::range_from_lsp(*range).to_offset(&snapshot);
1448 editor
1449 .insert_snippet(
1450 &[editor_range],
1451 snippet.clone(),
1452 window,
1453 cx,
1454 )
1455 .ok();
1456 }
1457 }
1458 }
1459 }
1460 _ => {}
1461 },
1462 ));
1463 if let Some(task_inventory) = project
1464 .read(cx)
1465 .task_store()
1466 .read(cx)
1467 .task_inventory()
1468 .cloned()
1469 {
1470 project_subscriptions.push(cx.observe_in(
1471 &task_inventory,
1472 window,
1473 |editor, _, window, cx| {
1474 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1475 },
1476 ));
1477 };
1478
1479 project_subscriptions.push(cx.subscribe_in(
1480 &project.read(cx).breakpoint_store(),
1481 window,
1482 |editor, _, event, window, cx| match event {
1483 BreakpointStoreEvent::ActiveDebugLineChanged => {
1484 if editor.go_to_active_debug_line(window, cx) {
1485 cx.stop_propagation();
1486 }
1487 }
1488 _ => {}
1489 },
1490 ));
1491 }
1492 }
1493
1494 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1495
1496 let inlay_hint_settings =
1497 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1498 let focus_handle = cx.focus_handle();
1499 cx.on_focus(&focus_handle, window, Self::handle_focus)
1500 .detach();
1501 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1502 .detach();
1503 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1504 .detach();
1505 cx.on_blur(&focus_handle, window, Self::handle_blur)
1506 .detach();
1507
1508 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1509 Some(false)
1510 } else {
1511 None
1512 };
1513
1514 let breakpoint_store = match (mode, project.as_ref()) {
1515 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1516 _ => None,
1517 };
1518
1519 let mut code_action_providers = Vec::new();
1520 let mut load_uncommitted_diff = None;
1521 if let Some(project) = project.clone() {
1522 load_uncommitted_diff = Some(
1523 get_uncommitted_diff_for_buffer(
1524 &project,
1525 buffer.read(cx).all_buffers(),
1526 buffer.clone(),
1527 cx,
1528 )
1529 .shared(),
1530 );
1531 code_action_providers.push(Rc::new(project) as Rc<_>);
1532 }
1533
1534 let mut this = Self {
1535 focus_handle,
1536 show_cursor_when_unfocused: false,
1537 last_focused_descendant: None,
1538 buffer: buffer.clone(),
1539 display_map: display_map.clone(),
1540 selections,
1541 scroll_manager: ScrollManager::new(cx),
1542 columnar_selection_tail: None,
1543 add_selections_state: None,
1544 select_next_state: None,
1545 select_prev_state: None,
1546 selection_history: Default::default(),
1547 autoclose_regions: Default::default(),
1548 snippet_stack: Default::default(),
1549 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1550 ime_transaction: Default::default(),
1551 active_diagnostics: ActiveDiagnostic::None,
1552 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1553 inline_diagnostics_update: Task::ready(()),
1554 inline_diagnostics: Vec::new(),
1555 soft_wrap_mode_override,
1556 hard_wrap: None,
1557 completion_provider: project.clone().map(|project| Box::new(project) as _),
1558 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1559 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1560 project,
1561 blink_manager: blink_manager.clone(),
1562 show_local_selections: true,
1563 show_scrollbars: true,
1564 mode,
1565 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1566 show_gutter: mode.is_full(),
1567 show_line_numbers: None,
1568 use_relative_line_numbers: None,
1569 show_git_diff_gutter: None,
1570 show_code_actions: None,
1571 show_runnables: None,
1572 show_breakpoints: None,
1573 show_wrap_guides: None,
1574 show_indent_guides,
1575 placeholder_text: None,
1576 highlight_order: 0,
1577 highlighted_rows: HashMap::default(),
1578 background_highlights: Default::default(),
1579 gutter_highlights: TreeMap::default(),
1580 scrollbar_marker_state: ScrollbarMarkerState::default(),
1581 active_indent_guides_state: ActiveIndentGuidesState::default(),
1582 nav_history: None,
1583 context_menu: RefCell::new(None),
1584 context_menu_options: None,
1585 mouse_context_menu: None,
1586 completion_tasks: Default::default(),
1587 signature_help_state: SignatureHelpState::default(),
1588 auto_signature_help: None,
1589 find_all_references_task_sources: Vec::new(),
1590 next_completion_id: 0,
1591 next_inlay_id: 0,
1592 code_action_providers,
1593 available_code_actions: Default::default(),
1594 code_actions_task: Default::default(),
1595 quick_selection_highlight_task: Default::default(),
1596 debounced_selection_highlight_task: Default::default(),
1597 document_highlights_task: Default::default(),
1598 linked_editing_range_task: Default::default(),
1599 pending_rename: Default::default(),
1600 searchable: true,
1601 cursor_shape: EditorSettings::get_global(cx)
1602 .cursor_shape
1603 .unwrap_or_default(),
1604 current_line_highlight: None,
1605 autoindent_mode: Some(AutoindentMode::EachLine),
1606 collapse_matches: false,
1607 workspace: None,
1608 input_enabled: true,
1609 use_modal_editing: mode.is_full(),
1610 read_only: false,
1611 use_autoclose: true,
1612 use_auto_surround: true,
1613 auto_replace_emoji_shortcode: false,
1614 jsx_tag_auto_close_enabled_in_any_buffer: false,
1615 leader_peer_id: None,
1616 remote_id: None,
1617 hover_state: Default::default(),
1618 pending_mouse_down: None,
1619 hovered_link_state: Default::default(),
1620 edit_prediction_provider: None,
1621 active_inline_completion: None,
1622 stale_inline_completion_in_menu: None,
1623 edit_prediction_preview: EditPredictionPreview::Inactive {
1624 released_too_fast: false,
1625 },
1626 inline_diagnostics_enabled: mode.is_full(),
1627 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1628
1629 gutter_hovered: false,
1630 pixel_position_of_newest_cursor: None,
1631 last_bounds: None,
1632 last_position_map: None,
1633 expect_bounds_change: None,
1634 gutter_dimensions: GutterDimensions::default(),
1635 style: None,
1636 show_cursor_names: false,
1637 hovered_cursors: Default::default(),
1638 next_editor_action_id: EditorActionId::default(),
1639 editor_actions: Rc::default(),
1640 inline_completions_hidden_for_vim_mode: false,
1641 show_inline_completions_override: None,
1642 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1643 edit_prediction_settings: EditPredictionSettings::Disabled,
1644 edit_prediction_indent_conflict: false,
1645 edit_prediction_requires_modifier_in_indent_conflict: true,
1646 custom_context_menu: None,
1647 show_git_blame_gutter: false,
1648 show_git_blame_inline: false,
1649 show_selection_menu: None,
1650 show_git_blame_inline_delay_task: None,
1651 git_blame_inline_tooltip: None,
1652 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1653 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1654 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1655 .session
1656 .restore_unsaved_buffers,
1657 blame: None,
1658 blame_subscription: None,
1659 tasks: Default::default(),
1660
1661 breakpoint_store,
1662 gutter_breakpoint_indicator: (None, None),
1663 _subscriptions: vec![
1664 cx.observe(&buffer, Self::on_buffer_changed),
1665 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1666 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1667 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1668 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1669 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1670 cx.observe_window_activation(window, |editor, window, cx| {
1671 let active = window.is_window_active();
1672 editor.blink_manager.update(cx, |blink_manager, cx| {
1673 if active {
1674 blink_manager.enable(cx);
1675 } else {
1676 blink_manager.disable(cx);
1677 }
1678 });
1679 }),
1680 ],
1681 tasks_update_task: None,
1682 linked_edit_ranges: Default::default(),
1683 in_project_search: false,
1684 previous_search_ranges: None,
1685 breadcrumb_header: None,
1686 focused_block: None,
1687 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1688 addons: HashMap::default(),
1689 registered_buffers: HashMap::default(),
1690 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1691 selection_mark_mode: false,
1692 toggle_fold_multiple_buffers: Task::ready(()),
1693 serialize_selections: Task::ready(()),
1694 serialize_folds: Task::ready(()),
1695 text_style_refinement: None,
1696 load_diff_task: load_uncommitted_diff,
1697 mouse_cursor_hidden: false,
1698 hide_mouse_mode: EditorSettings::get_global(cx)
1699 .hide_mouse
1700 .unwrap_or_default(),
1701 change_list: ChangeList::new(),
1702 };
1703 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1704 this._subscriptions
1705 .push(cx.observe(breakpoints, |_, _, cx| {
1706 cx.notify();
1707 }));
1708 }
1709 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1710 this._subscriptions.extend(project_subscriptions);
1711
1712 this._subscriptions.push(cx.subscribe_in(
1713 &cx.entity(),
1714 window,
1715 |editor, _, e: &EditorEvent, window, cx| match e {
1716 EditorEvent::ScrollPositionChanged { local, .. } => {
1717 if *local {
1718 let new_anchor = editor.scroll_manager.anchor();
1719 let snapshot = editor.snapshot(window, cx);
1720 editor.update_restoration_data(cx, move |data| {
1721 data.scroll_position = (
1722 new_anchor.top_row(&snapshot.buffer_snapshot),
1723 new_anchor.offset,
1724 );
1725 });
1726 }
1727 }
1728 EditorEvent::Edited { .. } => {
1729 if !vim_enabled(cx) {
1730 let (map, selections) = editor.selections.all_adjusted_display(cx);
1731 let pop_state = editor
1732 .change_list
1733 .last()
1734 .map(|previous| {
1735 previous.len() == selections.len()
1736 && previous.iter().enumerate().all(|(ix, p)| {
1737 p.to_display_point(&map).row()
1738 == selections[ix].head().row()
1739 })
1740 })
1741 .unwrap_or(false);
1742 let new_positions = selections
1743 .into_iter()
1744 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1745 .collect();
1746 editor
1747 .change_list
1748 .push_to_change_list(pop_state, new_positions);
1749 }
1750 }
1751 _ => (),
1752 },
1753 ));
1754
1755 this.end_selection(window, cx);
1756 this.scroll_manager.show_scrollbars(window, cx);
1757 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1758
1759 if mode.is_full() {
1760 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1761 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1762
1763 if this.git_blame_inline_enabled {
1764 this.git_blame_inline_enabled = true;
1765 this.start_git_blame_inline(false, window, cx);
1766 }
1767
1768 this.go_to_active_debug_line(window, cx);
1769
1770 if let Some(buffer) = buffer.read(cx).as_singleton() {
1771 if let Some(project) = this.project.as_ref() {
1772 let handle = project.update(cx, |project, cx| {
1773 project.register_buffer_with_language_servers(&buffer, cx)
1774 });
1775 this.registered_buffers
1776 .insert(buffer.read(cx).remote_id(), handle);
1777 }
1778 }
1779 }
1780
1781 this.report_editor_event("Editor Opened", None, cx);
1782 this
1783 }
1784
1785 pub fn deploy_mouse_context_menu(
1786 &mut self,
1787 position: gpui::Point<Pixels>,
1788 context_menu: Entity<ContextMenu>,
1789 window: &mut Window,
1790 cx: &mut Context<Self>,
1791 ) {
1792 self.mouse_context_menu = Some(MouseContextMenu::new(
1793 self,
1794 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1795 context_menu,
1796 window,
1797 cx,
1798 ));
1799 }
1800
1801 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1802 self.mouse_context_menu
1803 .as_ref()
1804 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1805 }
1806
1807 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1808 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1809 }
1810
1811 fn key_context_internal(
1812 &self,
1813 has_active_edit_prediction: bool,
1814 window: &Window,
1815 cx: &App,
1816 ) -> KeyContext {
1817 let mut key_context = KeyContext::new_with_defaults();
1818 key_context.add("Editor");
1819 let mode = match self.mode {
1820 EditorMode::SingleLine { .. } => "single_line",
1821 EditorMode::AutoHeight { .. } => "auto_height",
1822 EditorMode::Full { .. } => "full",
1823 };
1824
1825 if EditorSettings::jupyter_enabled(cx) {
1826 key_context.add("jupyter");
1827 }
1828
1829 key_context.set("mode", mode);
1830 if self.pending_rename.is_some() {
1831 key_context.add("renaming");
1832 }
1833
1834 match self.context_menu.borrow().as_ref() {
1835 Some(CodeContextMenu::Completions(_)) => {
1836 key_context.add("menu");
1837 key_context.add("showing_completions");
1838 }
1839 Some(CodeContextMenu::CodeActions(_)) => {
1840 key_context.add("menu");
1841 key_context.add("showing_code_actions")
1842 }
1843 None => {}
1844 }
1845
1846 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1847 if !self.focus_handle(cx).contains_focused(window, cx)
1848 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1849 {
1850 for addon in self.addons.values() {
1851 addon.extend_key_context(&mut key_context, cx)
1852 }
1853 }
1854
1855 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1856 if let Some(extension) = singleton_buffer
1857 .read(cx)
1858 .file()
1859 .and_then(|file| file.path().extension()?.to_str())
1860 {
1861 key_context.set("extension", extension.to_string());
1862 }
1863 } else {
1864 key_context.add("multibuffer");
1865 }
1866
1867 if has_active_edit_prediction {
1868 if self.edit_prediction_in_conflict() {
1869 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1870 } else {
1871 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1872 key_context.add("copilot_suggestion");
1873 }
1874 }
1875
1876 if self.selection_mark_mode {
1877 key_context.add("selection_mode");
1878 }
1879
1880 key_context
1881 }
1882
1883 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1884 self.mouse_cursor_hidden = match origin {
1885 HideMouseCursorOrigin::TypingAction => {
1886 matches!(
1887 self.hide_mouse_mode,
1888 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1889 )
1890 }
1891 HideMouseCursorOrigin::MovementAction => {
1892 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
1893 }
1894 };
1895 }
1896
1897 pub fn edit_prediction_in_conflict(&self) -> bool {
1898 if !self.show_edit_predictions_in_menu() {
1899 return false;
1900 }
1901
1902 let showing_completions = self
1903 .context_menu
1904 .borrow()
1905 .as_ref()
1906 .map_or(false, |context| {
1907 matches!(context, CodeContextMenu::Completions(_))
1908 });
1909
1910 showing_completions
1911 || self.edit_prediction_requires_modifier()
1912 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1913 // bindings to insert tab characters.
1914 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1915 }
1916
1917 pub fn accept_edit_prediction_keybind(
1918 &self,
1919 window: &Window,
1920 cx: &App,
1921 ) -> AcceptEditPredictionBinding {
1922 let key_context = self.key_context_internal(true, window, cx);
1923 let in_conflict = self.edit_prediction_in_conflict();
1924
1925 AcceptEditPredictionBinding(
1926 window
1927 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1928 .into_iter()
1929 .filter(|binding| {
1930 !in_conflict
1931 || binding
1932 .keystrokes()
1933 .first()
1934 .map_or(false, |keystroke| keystroke.modifiers.modified())
1935 })
1936 .rev()
1937 .min_by_key(|binding| {
1938 binding
1939 .keystrokes()
1940 .first()
1941 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1942 }),
1943 )
1944 }
1945
1946 pub fn new_file(
1947 workspace: &mut Workspace,
1948 _: &workspace::NewFile,
1949 window: &mut Window,
1950 cx: &mut Context<Workspace>,
1951 ) {
1952 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1953 "Failed to create buffer",
1954 window,
1955 cx,
1956 |e, _, _| match e.error_code() {
1957 ErrorCode::RemoteUpgradeRequired => Some(format!(
1958 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1959 e.error_tag("required").unwrap_or("the latest version")
1960 )),
1961 _ => None,
1962 },
1963 );
1964 }
1965
1966 pub fn new_in_workspace(
1967 workspace: &mut Workspace,
1968 window: &mut Window,
1969 cx: &mut Context<Workspace>,
1970 ) -> Task<Result<Entity<Editor>>> {
1971 let project = workspace.project().clone();
1972 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1973
1974 cx.spawn_in(window, async move |workspace, cx| {
1975 let buffer = create.await?;
1976 workspace.update_in(cx, |workspace, window, cx| {
1977 let editor =
1978 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1979 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1980 editor
1981 })
1982 })
1983 }
1984
1985 fn new_file_vertical(
1986 workspace: &mut Workspace,
1987 _: &workspace::NewFileSplitVertical,
1988 window: &mut Window,
1989 cx: &mut Context<Workspace>,
1990 ) {
1991 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1992 }
1993
1994 fn new_file_horizontal(
1995 workspace: &mut Workspace,
1996 _: &workspace::NewFileSplitHorizontal,
1997 window: &mut Window,
1998 cx: &mut Context<Workspace>,
1999 ) {
2000 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2001 }
2002
2003 fn new_file_in_direction(
2004 workspace: &mut Workspace,
2005 direction: SplitDirection,
2006 window: &mut Window,
2007 cx: &mut Context<Workspace>,
2008 ) {
2009 let project = workspace.project().clone();
2010 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2011
2012 cx.spawn_in(window, async move |workspace, cx| {
2013 let buffer = create.await?;
2014 workspace.update_in(cx, move |workspace, window, cx| {
2015 workspace.split_item(
2016 direction,
2017 Box::new(
2018 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2019 ),
2020 window,
2021 cx,
2022 )
2023 })?;
2024 anyhow::Ok(())
2025 })
2026 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2027 match e.error_code() {
2028 ErrorCode::RemoteUpgradeRequired => Some(format!(
2029 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2030 e.error_tag("required").unwrap_or("the latest version")
2031 )),
2032 _ => None,
2033 }
2034 });
2035 }
2036
2037 pub fn leader_peer_id(&self) -> Option<PeerId> {
2038 self.leader_peer_id
2039 }
2040
2041 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2042 &self.buffer
2043 }
2044
2045 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2046 self.workspace.as_ref()?.0.upgrade()
2047 }
2048
2049 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2050 self.buffer().read(cx).title(cx)
2051 }
2052
2053 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2054 let git_blame_gutter_max_author_length = self
2055 .render_git_blame_gutter(cx)
2056 .then(|| {
2057 if let Some(blame) = self.blame.as_ref() {
2058 let max_author_length =
2059 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2060 Some(max_author_length)
2061 } else {
2062 None
2063 }
2064 })
2065 .flatten();
2066
2067 EditorSnapshot {
2068 mode: self.mode,
2069 show_gutter: self.show_gutter,
2070 show_line_numbers: self.show_line_numbers,
2071 show_git_diff_gutter: self.show_git_diff_gutter,
2072 show_code_actions: self.show_code_actions,
2073 show_runnables: self.show_runnables,
2074 show_breakpoints: self.show_breakpoints,
2075 git_blame_gutter_max_author_length,
2076 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2077 scroll_anchor: self.scroll_manager.anchor(),
2078 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2079 placeholder_text: self.placeholder_text.clone(),
2080 is_focused: self.focus_handle.is_focused(window),
2081 current_line_highlight: self
2082 .current_line_highlight
2083 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2084 gutter_hovered: self.gutter_hovered,
2085 }
2086 }
2087
2088 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2089 self.buffer.read(cx).language_at(point, cx)
2090 }
2091
2092 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2093 self.buffer.read(cx).read(cx).file_at(point).cloned()
2094 }
2095
2096 pub fn active_excerpt(
2097 &self,
2098 cx: &App,
2099 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2100 self.buffer
2101 .read(cx)
2102 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2103 }
2104
2105 pub fn mode(&self) -> EditorMode {
2106 self.mode
2107 }
2108
2109 pub fn set_mode(&mut self, mode: EditorMode) {
2110 self.mode = mode;
2111 }
2112
2113 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2114 self.collaboration_hub.as_deref()
2115 }
2116
2117 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2118 self.collaboration_hub = Some(hub);
2119 }
2120
2121 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2122 self.in_project_search = in_project_search;
2123 }
2124
2125 pub fn set_custom_context_menu(
2126 &mut self,
2127 f: impl 'static
2128 + Fn(
2129 &mut Self,
2130 DisplayPoint,
2131 &mut Window,
2132 &mut Context<Self>,
2133 ) -> Option<Entity<ui::ContextMenu>>,
2134 ) {
2135 self.custom_context_menu = Some(Box::new(f))
2136 }
2137
2138 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2139 self.completion_provider = provider;
2140 }
2141
2142 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2143 self.semantics_provider.clone()
2144 }
2145
2146 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2147 self.semantics_provider = provider;
2148 }
2149
2150 pub fn set_edit_prediction_provider<T>(
2151 &mut self,
2152 provider: Option<Entity<T>>,
2153 window: &mut Window,
2154 cx: &mut Context<Self>,
2155 ) where
2156 T: EditPredictionProvider,
2157 {
2158 self.edit_prediction_provider =
2159 provider.map(|provider| RegisteredInlineCompletionProvider {
2160 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2161 if this.focus_handle.is_focused(window) {
2162 this.update_visible_inline_completion(window, cx);
2163 }
2164 }),
2165 provider: Arc::new(provider),
2166 });
2167 self.update_edit_prediction_settings(cx);
2168 self.refresh_inline_completion(false, false, window, cx);
2169 }
2170
2171 pub fn placeholder_text(&self) -> Option<&str> {
2172 self.placeholder_text.as_deref()
2173 }
2174
2175 pub fn set_placeholder_text(
2176 &mut self,
2177 placeholder_text: impl Into<Arc<str>>,
2178 cx: &mut Context<Self>,
2179 ) {
2180 let placeholder_text = Some(placeholder_text.into());
2181 if self.placeholder_text != placeholder_text {
2182 self.placeholder_text = placeholder_text;
2183 cx.notify();
2184 }
2185 }
2186
2187 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2188 self.cursor_shape = cursor_shape;
2189
2190 // Disrupt blink for immediate user feedback that the cursor shape has changed
2191 self.blink_manager.update(cx, BlinkManager::show_cursor);
2192
2193 cx.notify();
2194 }
2195
2196 pub fn set_current_line_highlight(
2197 &mut self,
2198 current_line_highlight: Option<CurrentLineHighlight>,
2199 ) {
2200 self.current_line_highlight = current_line_highlight;
2201 }
2202
2203 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2204 self.collapse_matches = collapse_matches;
2205 }
2206
2207 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2208 let buffers = self.buffer.read(cx).all_buffers();
2209 let Some(project) = self.project.as_ref() else {
2210 return;
2211 };
2212 project.update(cx, |project, cx| {
2213 for buffer in buffers {
2214 self.registered_buffers
2215 .entry(buffer.read(cx).remote_id())
2216 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2217 }
2218 })
2219 }
2220
2221 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2222 if self.collapse_matches {
2223 return range.start..range.start;
2224 }
2225 range.clone()
2226 }
2227
2228 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2229 if self.display_map.read(cx).clip_at_line_ends != clip {
2230 self.display_map
2231 .update(cx, |map, _| map.clip_at_line_ends = clip);
2232 }
2233 }
2234
2235 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2236 self.input_enabled = input_enabled;
2237 }
2238
2239 pub fn set_inline_completions_hidden_for_vim_mode(
2240 &mut self,
2241 hidden: bool,
2242 window: &mut Window,
2243 cx: &mut Context<Self>,
2244 ) {
2245 if hidden != self.inline_completions_hidden_for_vim_mode {
2246 self.inline_completions_hidden_for_vim_mode = hidden;
2247 if hidden {
2248 self.update_visible_inline_completion(window, cx);
2249 } else {
2250 self.refresh_inline_completion(true, false, window, cx);
2251 }
2252 }
2253 }
2254
2255 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2256 self.menu_inline_completions_policy = value;
2257 }
2258
2259 pub fn set_autoindent(&mut self, autoindent: bool) {
2260 if autoindent {
2261 self.autoindent_mode = Some(AutoindentMode::EachLine);
2262 } else {
2263 self.autoindent_mode = None;
2264 }
2265 }
2266
2267 pub fn read_only(&self, cx: &App) -> bool {
2268 self.read_only || self.buffer.read(cx).read_only()
2269 }
2270
2271 pub fn set_read_only(&mut self, read_only: bool) {
2272 self.read_only = read_only;
2273 }
2274
2275 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2276 self.use_autoclose = autoclose;
2277 }
2278
2279 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2280 self.use_auto_surround = auto_surround;
2281 }
2282
2283 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2284 self.auto_replace_emoji_shortcode = auto_replace;
2285 }
2286
2287 pub fn toggle_edit_predictions(
2288 &mut self,
2289 _: &ToggleEditPrediction,
2290 window: &mut Window,
2291 cx: &mut Context<Self>,
2292 ) {
2293 if self.show_inline_completions_override.is_some() {
2294 self.set_show_edit_predictions(None, window, cx);
2295 } else {
2296 let show_edit_predictions = !self.edit_predictions_enabled();
2297 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2298 }
2299 }
2300
2301 pub fn set_show_edit_predictions(
2302 &mut self,
2303 show_edit_predictions: Option<bool>,
2304 window: &mut Window,
2305 cx: &mut Context<Self>,
2306 ) {
2307 self.show_inline_completions_override = show_edit_predictions;
2308 self.update_edit_prediction_settings(cx);
2309
2310 if let Some(false) = show_edit_predictions {
2311 self.discard_inline_completion(false, cx);
2312 } else {
2313 self.refresh_inline_completion(false, true, window, cx);
2314 }
2315 }
2316
2317 fn inline_completions_disabled_in_scope(
2318 &self,
2319 buffer: &Entity<Buffer>,
2320 buffer_position: language::Anchor,
2321 cx: &App,
2322 ) -> bool {
2323 let snapshot = buffer.read(cx).snapshot();
2324 let settings = snapshot.settings_at(buffer_position, cx);
2325
2326 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2327 return false;
2328 };
2329
2330 scope.override_name().map_or(false, |scope_name| {
2331 settings
2332 .edit_predictions_disabled_in
2333 .iter()
2334 .any(|s| s == scope_name)
2335 })
2336 }
2337
2338 pub fn set_use_modal_editing(&mut self, to: bool) {
2339 self.use_modal_editing = to;
2340 }
2341
2342 pub fn use_modal_editing(&self) -> bool {
2343 self.use_modal_editing
2344 }
2345
2346 fn selections_did_change(
2347 &mut self,
2348 local: bool,
2349 old_cursor_position: &Anchor,
2350 show_completions: bool,
2351 window: &mut Window,
2352 cx: &mut Context<Self>,
2353 ) {
2354 window.invalidate_character_coordinates();
2355
2356 // Copy selections to primary selection buffer
2357 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2358 if local {
2359 let selections = self.selections.all::<usize>(cx);
2360 let buffer_handle = self.buffer.read(cx).read(cx);
2361
2362 let mut text = String::new();
2363 for (index, selection) in selections.iter().enumerate() {
2364 let text_for_selection = buffer_handle
2365 .text_for_range(selection.start..selection.end)
2366 .collect::<String>();
2367
2368 text.push_str(&text_for_selection);
2369 if index != selections.len() - 1 {
2370 text.push('\n');
2371 }
2372 }
2373
2374 if !text.is_empty() {
2375 cx.write_to_primary(ClipboardItem::new_string(text));
2376 }
2377 }
2378
2379 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2380 self.buffer.update(cx, |buffer, cx| {
2381 buffer.set_active_selections(
2382 &self.selections.disjoint_anchors(),
2383 self.selections.line_mode,
2384 self.cursor_shape,
2385 cx,
2386 )
2387 });
2388 }
2389 let display_map = self
2390 .display_map
2391 .update(cx, |display_map, cx| display_map.snapshot(cx));
2392 let buffer = &display_map.buffer_snapshot;
2393 self.add_selections_state = None;
2394 self.select_next_state = None;
2395 self.select_prev_state = None;
2396 self.select_syntax_node_history.try_clear();
2397 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2398 self.snippet_stack
2399 .invalidate(&self.selections.disjoint_anchors(), buffer);
2400 self.take_rename(false, window, cx);
2401
2402 let new_cursor_position = self.selections.newest_anchor().head();
2403
2404 self.push_to_nav_history(
2405 *old_cursor_position,
2406 Some(new_cursor_position.to_point(buffer)),
2407 false,
2408 cx,
2409 );
2410
2411 if local {
2412 let new_cursor_position = self.selections.newest_anchor().head();
2413 let mut context_menu = self.context_menu.borrow_mut();
2414 let completion_menu = match context_menu.as_ref() {
2415 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2416 _ => {
2417 *context_menu = None;
2418 None
2419 }
2420 };
2421 if let Some(buffer_id) = new_cursor_position.buffer_id {
2422 if !self.registered_buffers.contains_key(&buffer_id) {
2423 if let Some(project) = self.project.as_ref() {
2424 project.update(cx, |project, cx| {
2425 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2426 return;
2427 };
2428 self.registered_buffers.insert(
2429 buffer_id,
2430 project.register_buffer_with_language_servers(&buffer, cx),
2431 );
2432 })
2433 }
2434 }
2435 }
2436
2437 if let Some(completion_menu) = completion_menu {
2438 let cursor_position = new_cursor_position.to_offset(buffer);
2439 let (word_range, kind) =
2440 buffer.surrounding_word(completion_menu.initial_position, true);
2441 if kind == Some(CharKind::Word)
2442 && word_range.to_inclusive().contains(&cursor_position)
2443 {
2444 let mut completion_menu = completion_menu.clone();
2445 drop(context_menu);
2446
2447 let query = Self::completion_query(buffer, cursor_position);
2448 cx.spawn(async move |this, cx| {
2449 completion_menu
2450 .filter(query.as_deref(), cx.background_executor().clone())
2451 .await;
2452
2453 this.update(cx, |this, cx| {
2454 let mut context_menu = this.context_menu.borrow_mut();
2455 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2456 else {
2457 return;
2458 };
2459
2460 if menu.id > completion_menu.id {
2461 return;
2462 }
2463
2464 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2465 drop(context_menu);
2466 cx.notify();
2467 })
2468 })
2469 .detach();
2470
2471 if show_completions {
2472 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2473 }
2474 } else {
2475 drop(context_menu);
2476 self.hide_context_menu(window, cx);
2477 }
2478 } else {
2479 drop(context_menu);
2480 }
2481
2482 hide_hover(self, cx);
2483
2484 if old_cursor_position.to_display_point(&display_map).row()
2485 != new_cursor_position.to_display_point(&display_map).row()
2486 {
2487 self.available_code_actions.take();
2488 }
2489 self.refresh_code_actions(window, cx);
2490 self.refresh_document_highlights(cx);
2491 self.refresh_selected_text_highlights(window, cx);
2492 refresh_matching_bracket_highlights(self, window, cx);
2493 self.update_visible_inline_completion(window, cx);
2494 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2495 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2496 if self.git_blame_inline_enabled {
2497 self.start_inline_blame_timer(window, cx);
2498 }
2499 }
2500
2501 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2502 cx.emit(EditorEvent::SelectionsChanged { local });
2503
2504 let selections = &self.selections.disjoint;
2505 if selections.len() == 1 {
2506 cx.emit(SearchEvent::ActiveMatchChanged)
2507 }
2508 if local {
2509 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2510 let inmemory_selections = selections
2511 .iter()
2512 .map(|s| {
2513 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2514 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2515 })
2516 .collect();
2517 self.update_restoration_data(cx, |data| {
2518 data.selections = inmemory_selections;
2519 });
2520
2521 if WorkspaceSettings::get(None, cx).restore_on_startup
2522 != RestoreOnStartupBehavior::None
2523 {
2524 if let Some(workspace_id) =
2525 self.workspace.as_ref().and_then(|workspace| workspace.1)
2526 {
2527 let snapshot = self.buffer().read(cx).snapshot(cx);
2528 let selections = selections.clone();
2529 let background_executor = cx.background_executor().clone();
2530 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2531 self.serialize_selections = cx.background_spawn(async move {
2532 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2533 let db_selections = selections
2534 .iter()
2535 .map(|selection| {
2536 (
2537 selection.start.to_offset(&snapshot),
2538 selection.end.to_offset(&snapshot),
2539 )
2540 })
2541 .collect();
2542
2543 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2544 .await
2545 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2546 .log_err();
2547 });
2548 }
2549 }
2550 }
2551 }
2552
2553 cx.notify();
2554 }
2555
2556 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2557 use text::ToOffset as _;
2558 use text::ToPoint as _;
2559
2560 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2561 return;
2562 }
2563
2564 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2565 return;
2566 };
2567
2568 let snapshot = singleton.read(cx).snapshot();
2569 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2570 let display_snapshot = display_map.snapshot(cx);
2571
2572 display_snapshot
2573 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2574 .map(|fold| {
2575 fold.range.start.text_anchor.to_point(&snapshot)
2576 ..fold.range.end.text_anchor.to_point(&snapshot)
2577 })
2578 .collect()
2579 });
2580 self.update_restoration_data(cx, |data| {
2581 data.folds = inmemory_folds;
2582 });
2583
2584 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2585 return;
2586 };
2587 let background_executor = cx.background_executor().clone();
2588 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2589 let db_folds = self.display_map.update(cx, |display_map, cx| {
2590 display_map
2591 .snapshot(cx)
2592 .folds_in_range(0..snapshot.len())
2593 .map(|fold| {
2594 (
2595 fold.range.start.text_anchor.to_offset(&snapshot),
2596 fold.range.end.text_anchor.to_offset(&snapshot),
2597 )
2598 })
2599 .collect()
2600 });
2601 self.serialize_folds = cx.background_spawn(async move {
2602 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2603 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2604 .await
2605 .with_context(|| {
2606 format!(
2607 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2608 )
2609 })
2610 .log_err();
2611 });
2612 }
2613
2614 pub fn sync_selections(
2615 &mut self,
2616 other: Entity<Editor>,
2617 cx: &mut Context<Self>,
2618 ) -> gpui::Subscription {
2619 let other_selections = other.read(cx).selections.disjoint.to_vec();
2620 self.selections.change_with(cx, |selections| {
2621 selections.select_anchors(other_selections);
2622 });
2623
2624 let other_subscription =
2625 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2626 EditorEvent::SelectionsChanged { local: true } => {
2627 let other_selections = other.read(cx).selections.disjoint.to_vec();
2628 if other_selections.is_empty() {
2629 return;
2630 }
2631 this.selections.change_with(cx, |selections| {
2632 selections.select_anchors(other_selections);
2633 });
2634 }
2635 _ => {}
2636 });
2637
2638 let this_subscription =
2639 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2640 EditorEvent::SelectionsChanged { local: true } => {
2641 let these_selections = this.selections.disjoint.to_vec();
2642 if these_selections.is_empty() {
2643 return;
2644 }
2645 other.update(cx, |other_editor, cx| {
2646 other_editor.selections.change_with(cx, |selections| {
2647 selections.select_anchors(these_selections);
2648 })
2649 });
2650 }
2651 _ => {}
2652 });
2653
2654 Subscription::join(other_subscription, this_subscription)
2655 }
2656
2657 pub fn change_selections<R>(
2658 &mut self,
2659 autoscroll: Option<Autoscroll>,
2660 window: &mut Window,
2661 cx: &mut Context<Self>,
2662 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2663 ) -> R {
2664 self.change_selections_inner(autoscroll, true, window, cx, change)
2665 }
2666
2667 fn change_selections_inner<R>(
2668 &mut self,
2669 autoscroll: Option<Autoscroll>,
2670 request_completions: bool,
2671 window: &mut Window,
2672 cx: &mut Context<Self>,
2673 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2674 ) -> R {
2675 let old_cursor_position = self.selections.newest_anchor().head();
2676 self.push_to_selection_history();
2677
2678 let (changed, result) = self.selections.change_with(cx, change);
2679
2680 if changed {
2681 if let Some(autoscroll) = autoscroll {
2682 self.request_autoscroll(autoscroll, cx);
2683 }
2684 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2685
2686 if self.should_open_signature_help_automatically(
2687 &old_cursor_position,
2688 self.signature_help_state.backspace_pressed(),
2689 cx,
2690 ) {
2691 self.show_signature_help(&ShowSignatureHelp, window, cx);
2692 }
2693 self.signature_help_state.set_backspace_pressed(false);
2694 }
2695
2696 result
2697 }
2698
2699 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2700 where
2701 I: IntoIterator<Item = (Range<S>, T)>,
2702 S: ToOffset,
2703 T: Into<Arc<str>>,
2704 {
2705 if self.read_only(cx) {
2706 return;
2707 }
2708
2709 self.buffer
2710 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2711 }
2712
2713 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2714 where
2715 I: IntoIterator<Item = (Range<S>, T)>,
2716 S: ToOffset,
2717 T: Into<Arc<str>>,
2718 {
2719 if self.read_only(cx) {
2720 return;
2721 }
2722
2723 self.buffer.update(cx, |buffer, cx| {
2724 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2725 });
2726 }
2727
2728 pub fn edit_with_block_indent<I, S, T>(
2729 &mut self,
2730 edits: I,
2731 original_indent_columns: Vec<Option<u32>>,
2732 cx: &mut Context<Self>,
2733 ) where
2734 I: IntoIterator<Item = (Range<S>, T)>,
2735 S: ToOffset,
2736 T: Into<Arc<str>>,
2737 {
2738 if self.read_only(cx) {
2739 return;
2740 }
2741
2742 self.buffer.update(cx, |buffer, cx| {
2743 buffer.edit(
2744 edits,
2745 Some(AutoindentMode::Block {
2746 original_indent_columns,
2747 }),
2748 cx,
2749 )
2750 });
2751 }
2752
2753 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2754 self.hide_context_menu(window, cx);
2755
2756 match phase {
2757 SelectPhase::Begin {
2758 position,
2759 add,
2760 click_count,
2761 } => self.begin_selection(position, add, click_count, window, cx),
2762 SelectPhase::BeginColumnar {
2763 position,
2764 goal_column,
2765 reset,
2766 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2767 SelectPhase::Extend {
2768 position,
2769 click_count,
2770 } => self.extend_selection(position, click_count, window, cx),
2771 SelectPhase::Update {
2772 position,
2773 goal_column,
2774 scroll_delta,
2775 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2776 SelectPhase::End => self.end_selection(window, cx),
2777 }
2778 }
2779
2780 fn extend_selection(
2781 &mut self,
2782 position: DisplayPoint,
2783 click_count: usize,
2784 window: &mut Window,
2785 cx: &mut Context<Self>,
2786 ) {
2787 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2788 let tail = self.selections.newest::<usize>(cx).tail();
2789 self.begin_selection(position, false, click_count, window, cx);
2790
2791 let position = position.to_offset(&display_map, Bias::Left);
2792 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2793
2794 let mut pending_selection = self
2795 .selections
2796 .pending_anchor()
2797 .expect("extend_selection not called with pending selection");
2798 if position >= tail {
2799 pending_selection.start = tail_anchor;
2800 } else {
2801 pending_selection.end = tail_anchor;
2802 pending_selection.reversed = true;
2803 }
2804
2805 let mut pending_mode = self.selections.pending_mode().unwrap();
2806 match &mut pending_mode {
2807 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2808 _ => {}
2809 }
2810
2811 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2812 s.set_pending(pending_selection, pending_mode)
2813 });
2814 }
2815
2816 fn begin_selection(
2817 &mut self,
2818 position: DisplayPoint,
2819 add: bool,
2820 click_count: usize,
2821 window: &mut Window,
2822 cx: &mut Context<Self>,
2823 ) {
2824 if !self.focus_handle.is_focused(window) {
2825 self.last_focused_descendant = None;
2826 window.focus(&self.focus_handle);
2827 }
2828
2829 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2830 let buffer = &display_map.buffer_snapshot;
2831 let newest_selection = self.selections.newest_anchor().clone();
2832 let position = display_map.clip_point(position, Bias::Left);
2833
2834 let start;
2835 let end;
2836 let mode;
2837 let mut auto_scroll;
2838 match click_count {
2839 1 => {
2840 start = buffer.anchor_before(position.to_point(&display_map));
2841 end = start;
2842 mode = SelectMode::Character;
2843 auto_scroll = true;
2844 }
2845 2 => {
2846 let range = movement::surrounding_word(&display_map, position);
2847 start = buffer.anchor_before(range.start.to_point(&display_map));
2848 end = buffer.anchor_before(range.end.to_point(&display_map));
2849 mode = SelectMode::Word(start..end);
2850 auto_scroll = true;
2851 }
2852 3 => {
2853 let position = display_map
2854 .clip_point(position, Bias::Left)
2855 .to_point(&display_map);
2856 let line_start = display_map.prev_line_boundary(position).0;
2857 let next_line_start = buffer.clip_point(
2858 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2859 Bias::Left,
2860 );
2861 start = buffer.anchor_before(line_start);
2862 end = buffer.anchor_before(next_line_start);
2863 mode = SelectMode::Line(start..end);
2864 auto_scroll = true;
2865 }
2866 _ => {
2867 start = buffer.anchor_before(0);
2868 end = buffer.anchor_before(buffer.len());
2869 mode = SelectMode::All;
2870 auto_scroll = false;
2871 }
2872 }
2873 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2874
2875 let point_to_delete: Option<usize> = {
2876 let selected_points: Vec<Selection<Point>> =
2877 self.selections.disjoint_in_range(start..end, cx);
2878
2879 if !add || click_count > 1 {
2880 None
2881 } else if !selected_points.is_empty() {
2882 Some(selected_points[0].id)
2883 } else {
2884 let clicked_point_already_selected =
2885 self.selections.disjoint.iter().find(|selection| {
2886 selection.start.to_point(buffer) == start.to_point(buffer)
2887 || selection.end.to_point(buffer) == end.to_point(buffer)
2888 });
2889
2890 clicked_point_already_selected.map(|selection| selection.id)
2891 }
2892 };
2893
2894 let selections_count = self.selections.count();
2895
2896 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2897 if let Some(point_to_delete) = point_to_delete {
2898 s.delete(point_to_delete);
2899
2900 if selections_count == 1 {
2901 s.set_pending_anchor_range(start..end, mode);
2902 }
2903 } else {
2904 if !add {
2905 s.clear_disjoint();
2906 } else if click_count > 1 {
2907 s.delete(newest_selection.id)
2908 }
2909
2910 s.set_pending_anchor_range(start..end, mode);
2911 }
2912 });
2913 }
2914
2915 fn begin_columnar_selection(
2916 &mut self,
2917 position: DisplayPoint,
2918 goal_column: u32,
2919 reset: bool,
2920 window: &mut Window,
2921 cx: &mut Context<Self>,
2922 ) {
2923 if !self.focus_handle.is_focused(window) {
2924 self.last_focused_descendant = None;
2925 window.focus(&self.focus_handle);
2926 }
2927
2928 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2929
2930 if reset {
2931 let pointer_position = display_map
2932 .buffer_snapshot
2933 .anchor_before(position.to_point(&display_map));
2934
2935 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2936 s.clear_disjoint();
2937 s.set_pending_anchor_range(
2938 pointer_position..pointer_position,
2939 SelectMode::Character,
2940 );
2941 });
2942 }
2943
2944 let tail = self.selections.newest::<Point>(cx).tail();
2945 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2946
2947 if !reset {
2948 self.select_columns(
2949 tail.to_display_point(&display_map),
2950 position,
2951 goal_column,
2952 &display_map,
2953 window,
2954 cx,
2955 );
2956 }
2957 }
2958
2959 fn update_selection(
2960 &mut self,
2961 position: DisplayPoint,
2962 goal_column: u32,
2963 scroll_delta: gpui::Point<f32>,
2964 window: &mut Window,
2965 cx: &mut Context<Self>,
2966 ) {
2967 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2968
2969 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2970 let tail = tail.to_display_point(&display_map);
2971 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2972 } else if let Some(mut pending) = self.selections.pending_anchor() {
2973 let buffer = self.buffer.read(cx).snapshot(cx);
2974 let head;
2975 let tail;
2976 let mode = self.selections.pending_mode().unwrap();
2977 match &mode {
2978 SelectMode::Character => {
2979 head = position.to_point(&display_map);
2980 tail = pending.tail().to_point(&buffer);
2981 }
2982 SelectMode::Word(original_range) => {
2983 let original_display_range = original_range.start.to_display_point(&display_map)
2984 ..original_range.end.to_display_point(&display_map);
2985 let original_buffer_range = original_display_range.start.to_point(&display_map)
2986 ..original_display_range.end.to_point(&display_map);
2987 if movement::is_inside_word(&display_map, position)
2988 || original_display_range.contains(&position)
2989 {
2990 let word_range = movement::surrounding_word(&display_map, position);
2991 if word_range.start < original_display_range.start {
2992 head = word_range.start.to_point(&display_map);
2993 } else {
2994 head = word_range.end.to_point(&display_map);
2995 }
2996 } else {
2997 head = position.to_point(&display_map);
2998 }
2999
3000 if head <= original_buffer_range.start {
3001 tail = original_buffer_range.end;
3002 } else {
3003 tail = original_buffer_range.start;
3004 }
3005 }
3006 SelectMode::Line(original_range) => {
3007 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3008
3009 let position = display_map
3010 .clip_point(position, Bias::Left)
3011 .to_point(&display_map);
3012 let line_start = display_map.prev_line_boundary(position).0;
3013 let next_line_start = buffer.clip_point(
3014 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3015 Bias::Left,
3016 );
3017
3018 if line_start < original_range.start {
3019 head = line_start
3020 } else {
3021 head = next_line_start
3022 }
3023
3024 if head <= original_range.start {
3025 tail = original_range.end;
3026 } else {
3027 tail = original_range.start;
3028 }
3029 }
3030 SelectMode::All => {
3031 return;
3032 }
3033 };
3034
3035 if head < tail {
3036 pending.start = buffer.anchor_before(head);
3037 pending.end = buffer.anchor_before(tail);
3038 pending.reversed = true;
3039 } else {
3040 pending.start = buffer.anchor_before(tail);
3041 pending.end = buffer.anchor_before(head);
3042 pending.reversed = false;
3043 }
3044
3045 self.change_selections(None, window, cx, |s| {
3046 s.set_pending(pending, mode);
3047 });
3048 } else {
3049 log::error!("update_selection dispatched with no pending selection");
3050 return;
3051 }
3052
3053 self.apply_scroll_delta(scroll_delta, window, cx);
3054 cx.notify();
3055 }
3056
3057 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3058 self.columnar_selection_tail.take();
3059 if self.selections.pending_anchor().is_some() {
3060 let selections = self.selections.all::<usize>(cx);
3061 self.change_selections(None, window, cx, |s| {
3062 s.select(selections);
3063 s.clear_pending();
3064 });
3065 }
3066 }
3067
3068 fn select_columns(
3069 &mut self,
3070 tail: DisplayPoint,
3071 head: DisplayPoint,
3072 goal_column: u32,
3073 display_map: &DisplaySnapshot,
3074 window: &mut Window,
3075 cx: &mut Context<Self>,
3076 ) {
3077 let start_row = cmp::min(tail.row(), head.row());
3078 let end_row = cmp::max(tail.row(), head.row());
3079 let start_column = cmp::min(tail.column(), goal_column);
3080 let end_column = cmp::max(tail.column(), goal_column);
3081 let reversed = start_column < tail.column();
3082
3083 let selection_ranges = (start_row.0..=end_row.0)
3084 .map(DisplayRow)
3085 .filter_map(|row| {
3086 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3087 let start = display_map
3088 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3089 .to_point(display_map);
3090 let end = display_map
3091 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3092 .to_point(display_map);
3093 if reversed {
3094 Some(end..start)
3095 } else {
3096 Some(start..end)
3097 }
3098 } else {
3099 None
3100 }
3101 })
3102 .collect::<Vec<_>>();
3103
3104 self.change_selections(None, window, cx, |s| {
3105 s.select_ranges(selection_ranges);
3106 });
3107 cx.notify();
3108 }
3109
3110 pub fn has_pending_nonempty_selection(&self) -> bool {
3111 let pending_nonempty_selection = match self.selections.pending_anchor() {
3112 Some(Selection { start, end, .. }) => start != end,
3113 None => false,
3114 };
3115
3116 pending_nonempty_selection
3117 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3118 }
3119
3120 pub fn has_pending_selection(&self) -> bool {
3121 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3122 }
3123
3124 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3125 self.selection_mark_mode = false;
3126
3127 if self.clear_expanded_diff_hunks(cx) {
3128 cx.notify();
3129 return;
3130 }
3131 if self.dismiss_menus_and_popups(true, window, cx) {
3132 return;
3133 }
3134
3135 if self.mode.is_full()
3136 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3137 {
3138 return;
3139 }
3140
3141 cx.propagate();
3142 }
3143
3144 pub fn dismiss_menus_and_popups(
3145 &mut self,
3146 is_user_requested: bool,
3147 window: &mut Window,
3148 cx: &mut Context<Self>,
3149 ) -> bool {
3150 if self.take_rename(false, window, cx).is_some() {
3151 return true;
3152 }
3153
3154 if hide_hover(self, cx) {
3155 return true;
3156 }
3157
3158 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3159 return true;
3160 }
3161
3162 if self.hide_context_menu(window, cx).is_some() {
3163 return true;
3164 }
3165
3166 if self.mouse_context_menu.take().is_some() {
3167 return true;
3168 }
3169
3170 if is_user_requested && self.discard_inline_completion(true, cx) {
3171 return true;
3172 }
3173
3174 if self.snippet_stack.pop().is_some() {
3175 return true;
3176 }
3177
3178 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3179 self.dismiss_diagnostics(cx);
3180 return true;
3181 }
3182
3183 false
3184 }
3185
3186 fn linked_editing_ranges_for(
3187 &self,
3188 selection: Range<text::Anchor>,
3189 cx: &App,
3190 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3191 if self.linked_edit_ranges.is_empty() {
3192 return None;
3193 }
3194 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3195 selection.end.buffer_id.and_then(|end_buffer_id| {
3196 if selection.start.buffer_id != Some(end_buffer_id) {
3197 return None;
3198 }
3199 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3200 let snapshot = buffer.read(cx).snapshot();
3201 self.linked_edit_ranges
3202 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3203 .map(|ranges| (ranges, snapshot, buffer))
3204 })?;
3205 use text::ToOffset as TO;
3206 // find offset from the start of current range to current cursor position
3207 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3208
3209 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3210 let start_difference = start_offset - start_byte_offset;
3211 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3212 let end_difference = end_offset - start_byte_offset;
3213 // Current range has associated linked ranges.
3214 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3215 for range in linked_ranges.iter() {
3216 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3217 let end_offset = start_offset + end_difference;
3218 let start_offset = start_offset + start_difference;
3219 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3220 continue;
3221 }
3222 if self.selections.disjoint_anchor_ranges().any(|s| {
3223 if s.start.buffer_id != selection.start.buffer_id
3224 || s.end.buffer_id != selection.end.buffer_id
3225 {
3226 return false;
3227 }
3228 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3229 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3230 }) {
3231 continue;
3232 }
3233 let start = buffer_snapshot.anchor_after(start_offset);
3234 let end = buffer_snapshot.anchor_after(end_offset);
3235 linked_edits
3236 .entry(buffer.clone())
3237 .or_default()
3238 .push(start..end);
3239 }
3240 Some(linked_edits)
3241 }
3242
3243 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3244 let text: Arc<str> = text.into();
3245
3246 if self.read_only(cx) {
3247 return;
3248 }
3249
3250 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3251
3252 let selections = self.selections.all_adjusted(cx);
3253 let mut bracket_inserted = false;
3254 let mut edits = Vec::new();
3255 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3256 let mut new_selections = Vec::with_capacity(selections.len());
3257 let mut new_autoclose_regions = Vec::new();
3258 let snapshot = self.buffer.read(cx).read(cx);
3259 let mut clear_linked_edit_ranges = false;
3260
3261 for (selection, autoclose_region) in
3262 self.selections_with_autoclose_regions(selections, &snapshot)
3263 {
3264 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3265 // Determine if the inserted text matches the opening or closing
3266 // bracket of any of this language's bracket pairs.
3267 let mut bracket_pair = None;
3268 let mut is_bracket_pair_start = false;
3269 let mut is_bracket_pair_end = false;
3270 if !text.is_empty() {
3271 let mut bracket_pair_matching_end = None;
3272 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3273 // and they are removing the character that triggered IME popup.
3274 for (pair, enabled) in scope.brackets() {
3275 if !pair.close && !pair.surround {
3276 continue;
3277 }
3278
3279 if enabled && pair.start.ends_with(text.as_ref()) {
3280 let prefix_len = pair.start.len() - text.len();
3281 let preceding_text_matches_prefix = prefix_len == 0
3282 || (selection.start.column >= (prefix_len as u32)
3283 && snapshot.contains_str_at(
3284 Point::new(
3285 selection.start.row,
3286 selection.start.column - (prefix_len as u32),
3287 ),
3288 &pair.start[..prefix_len],
3289 ));
3290 if preceding_text_matches_prefix {
3291 bracket_pair = Some(pair.clone());
3292 is_bracket_pair_start = true;
3293 break;
3294 }
3295 }
3296 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3297 {
3298 // take first bracket pair matching end, but don't break in case a later bracket
3299 // pair matches start
3300 bracket_pair_matching_end = Some(pair.clone());
3301 }
3302 }
3303 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3304 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3305 is_bracket_pair_end = true;
3306 }
3307 }
3308
3309 if let Some(bracket_pair) = bracket_pair {
3310 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3311 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3312 let auto_surround =
3313 self.use_auto_surround && snapshot_settings.use_auto_surround;
3314 if selection.is_empty() {
3315 if is_bracket_pair_start {
3316 // If the inserted text is a suffix of an opening bracket and the
3317 // selection is preceded by the rest of the opening bracket, then
3318 // insert the closing bracket.
3319 let following_text_allows_autoclose = snapshot
3320 .chars_at(selection.start)
3321 .next()
3322 .map_or(true, |c| scope.should_autoclose_before(c));
3323
3324 let preceding_text_allows_autoclose = selection.start.column == 0
3325 || snapshot.reversed_chars_at(selection.start).next().map_or(
3326 true,
3327 |c| {
3328 bracket_pair.start != bracket_pair.end
3329 || !snapshot
3330 .char_classifier_at(selection.start)
3331 .is_word(c)
3332 },
3333 );
3334
3335 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3336 && bracket_pair.start.len() == 1
3337 {
3338 let target = bracket_pair.start.chars().next().unwrap();
3339 let current_line_count = snapshot
3340 .reversed_chars_at(selection.start)
3341 .take_while(|&c| c != '\n')
3342 .filter(|&c| c == target)
3343 .count();
3344 current_line_count % 2 == 1
3345 } else {
3346 false
3347 };
3348
3349 if autoclose
3350 && bracket_pair.close
3351 && following_text_allows_autoclose
3352 && preceding_text_allows_autoclose
3353 && !is_closing_quote
3354 {
3355 let anchor = snapshot.anchor_before(selection.end);
3356 new_selections.push((selection.map(|_| anchor), text.len()));
3357 new_autoclose_regions.push((
3358 anchor,
3359 text.len(),
3360 selection.id,
3361 bracket_pair.clone(),
3362 ));
3363 edits.push((
3364 selection.range(),
3365 format!("{}{}", text, bracket_pair.end).into(),
3366 ));
3367 bracket_inserted = true;
3368 continue;
3369 }
3370 }
3371
3372 if let Some(region) = autoclose_region {
3373 // If the selection is followed by an auto-inserted closing bracket,
3374 // then don't insert that closing bracket again; just move the selection
3375 // past the closing bracket.
3376 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3377 && text.as_ref() == region.pair.end.as_str();
3378 if should_skip {
3379 let anchor = snapshot.anchor_after(selection.end);
3380 new_selections
3381 .push((selection.map(|_| anchor), region.pair.end.len()));
3382 continue;
3383 }
3384 }
3385
3386 let always_treat_brackets_as_autoclosed = snapshot
3387 .language_settings_at(selection.start, cx)
3388 .always_treat_brackets_as_autoclosed;
3389 if always_treat_brackets_as_autoclosed
3390 && is_bracket_pair_end
3391 && snapshot.contains_str_at(selection.end, text.as_ref())
3392 {
3393 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3394 // and the inserted text is a closing bracket and the selection is followed
3395 // by the closing bracket then move the selection past the closing bracket.
3396 let anchor = snapshot.anchor_after(selection.end);
3397 new_selections.push((selection.map(|_| anchor), text.len()));
3398 continue;
3399 }
3400 }
3401 // If an opening bracket is 1 character long and is typed while
3402 // text is selected, then surround that text with the bracket pair.
3403 else if auto_surround
3404 && bracket_pair.surround
3405 && is_bracket_pair_start
3406 && bracket_pair.start.chars().count() == 1
3407 {
3408 edits.push((selection.start..selection.start, text.clone()));
3409 edits.push((
3410 selection.end..selection.end,
3411 bracket_pair.end.as_str().into(),
3412 ));
3413 bracket_inserted = true;
3414 new_selections.push((
3415 Selection {
3416 id: selection.id,
3417 start: snapshot.anchor_after(selection.start),
3418 end: snapshot.anchor_before(selection.end),
3419 reversed: selection.reversed,
3420 goal: selection.goal,
3421 },
3422 0,
3423 ));
3424 continue;
3425 }
3426 }
3427 }
3428
3429 if self.auto_replace_emoji_shortcode
3430 && selection.is_empty()
3431 && text.as_ref().ends_with(':')
3432 {
3433 if let Some(possible_emoji_short_code) =
3434 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3435 {
3436 if !possible_emoji_short_code.is_empty() {
3437 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3438 let emoji_shortcode_start = Point::new(
3439 selection.start.row,
3440 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3441 );
3442
3443 // Remove shortcode from buffer
3444 edits.push((
3445 emoji_shortcode_start..selection.start,
3446 "".to_string().into(),
3447 ));
3448 new_selections.push((
3449 Selection {
3450 id: selection.id,
3451 start: snapshot.anchor_after(emoji_shortcode_start),
3452 end: snapshot.anchor_before(selection.start),
3453 reversed: selection.reversed,
3454 goal: selection.goal,
3455 },
3456 0,
3457 ));
3458
3459 // Insert emoji
3460 let selection_start_anchor = snapshot.anchor_after(selection.start);
3461 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3462 edits.push((selection.start..selection.end, emoji.to_string().into()));
3463
3464 continue;
3465 }
3466 }
3467 }
3468 }
3469
3470 // If not handling any auto-close operation, then just replace the selected
3471 // text with the given input and move the selection to the end of the
3472 // newly inserted text.
3473 let anchor = snapshot.anchor_after(selection.end);
3474 if !self.linked_edit_ranges.is_empty() {
3475 let start_anchor = snapshot.anchor_before(selection.start);
3476
3477 let is_word_char = text.chars().next().map_or(true, |char| {
3478 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3479 classifier.is_word(char)
3480 });
3481
3482 if is_word_char {
3483 if let Some(ranges) = self
3484 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3485 {
3486 for (buffer, edits) in ranges {
3487 linked_edits
3488 .entry(buffer.clone())
3489 .or_default()
3490 .extend(edits.into_iter().map(|range| (range, text.clone())));
3491 }
3492 }
3493 } else {
3494 clear_linked_edit_ranges = true;
3495 }
3496 }
3497
3498 new_selections.push((selection.map(|_| anchor), 0));
3499 edits.push((selection.start..selection.end, text.clone()));
3500 }
3501
3502 drop(snapshot);
3503
3504 self.transact(window, cx, |this, window, cx| {
3505 if clear_linked_edit_ranges {
3506 this.linked_edit_ranges.clear();
3507 }
3508 let initial_buffer_versions =
3509 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3510
3511 this.buffer.update(cx, |buffer, cx| {
3512 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3513 });
3514 for (buffer, edits) in linked_edits {
3515 buffer.update(cx, |buffer, cx| {
3516 let snapshot = buffer.snapshot();
3517 let edits = edits
3518 .into_iter()
3519 .map(|(range, text)| {
3520 use text::ToPoint as TP;
3521 let end_point = TP::to_point(&range.end, &snapshot);
3522 let start_point = TP::to_point(&range.start, &snapshot);
3523 (start_point..end_point, text)
3524 })
3525 .sorted_by_key(|(range, _)| range.start);
3526 buffer.edit(edits, None, cx);
3527 })
3528 }
3529 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3530 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3531 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3532 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3533 .zip(new_selection_deltas)
3534 .map(|(selection, delta)| Selection {
3535 id: selection.id,
3536 start: selection.start + delta,
3537 end: selection.end + delta,
3538 reversed: selection.reversed,
3539 goal: SelectionGoal::None,
3540 })
3541 .collect::<Vec<_>>();
3542
3543 let mut i = 0;
3544 for (position, delta, selection_id, pair) in new_autoclose_regions {
3545 let position = position.to_offset(&map.buffer_snapshot) + delta;
3546 let start = map.buffer_snapshot.anchor_before(position);
3547 let end = map.buffer_snapshot.anchor_after(position);
3548 while let Some(existing_state) = this.autoclose_regions.get(i) {
3549 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3550 Ordering::Less => i += 1,
3551 Ordering::Greater => break,
3552 Ordering::Equal => {
3553 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3554 Ordering::Less => i += 1,
3555 Ordering::Equal => break,
3556 Ordering::Greater => break,
3557 }
3558 }
3559 }
3560 }
3561 this.autoclose_regions.insert(
3562 i,
3563 AutocloseRegion {
3564 selection_id,
3565 range: start..end,
3566 pair,
3567 },
3568 );
3569 }
3570
3571 let had_active_inline_completion = this.has_active_inline_completion();
3572 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3573 s.select(new_selections)
3574 });
3575
3576 if !bracket_inserted {
3577 if let Some(on_type_format_task) =
3578 this.trigger_on_type_formatting(text.to_string(), window, cx)
3579 {
3580 on_type_format_task.detach_and_log_err(cx);
3581 }
3582 }
3583
3584 let editor_settings = EditorSettings::get_global(cx);
3585 if bracket_inserted
3586 && (editor_settings.auto_signature_help
3587 || editor_settings.show_signature_help_after_edits)
3588 {
3589 this.show_signature_help(&ShowSignatureHelp, window, cx);
3590 }
3591
3592 let trigger_in_words =
3593 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3594 if this.hard_wrap.is_some() {
3595 let latest: Range<Point> = this.selections.newest(cx).range();
3596 if latest.is_empty()
3597 && this
3598 .buffer()
3599 .read(cx)
3600 .snapshot(cx)
3601 .line_len(MultiBufferRow(latest.start.row))
3602 == latest.start.column
3603 {
3604 this.rewrap_impl(
3605 RewrapOptions {
3606 override_language_settings: true,
3607 preserve_existing_whitespace: true,
3608 },
3609 cx,
3610 )
3611 }
3612 }
3613 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3614 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3615 this.refresh_inline_completion(true, false, window, cx);
3616 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3617 });
3618 }
3619
3620 fn find_possible_emoji_shortcode_at_position(
3621 snapshot: &MultiBufferSnapshot,
3622 position: Point,
3623 ) -> Option<String> {
3624 let mut chars = Vec::new();
3625 let mut found_colon = false;
3626 for char in snapshot.reversed_chars_at(position).take(100) {
3627 // Found a possible emoji shortcode in the middle of the buffer
3628 if found_colon {
3629 if char.is_whitespace() {
3630 chars.reverse();
3631 return Some(chars.iter().collect());
3632 }
3633 // If the previous character is not a whitespace, we are in the middle of a word
3634 // and we only want to complete the shortcode if the word is made up of other emojis
3635 let mut containing_word = String::new();
3636 for ch in snapshot
3637 .reversed_chars_at(position)
3638 .skip(chars.len() + 1)
3639 .take(100)
3640 {
3641 if ch.is_whitespace() {
3642 break;
3643 }
3644 containing_word.push(ch);
3645 }
3646 let containing_word = containing_word.chars().rev().collect::<String>();
3647 if util::word_consists_of_emojis(containing_word.as_str()) {
3648 chars.reverse();
3649 return Some(chars.iter().collect());
3650 }
3651 }
3652
3653 if char.is_whitespace() || !char.is_ascii() {
3654 return None;
3655 }
3656 if char == ':' {
3657 found_colon = true;
3658 } else {
3659 chars.push(char);
3660 }
3661 }
3662 // Found a possible emoji shortcode at the beginning of the buffer
3663 chars.reverse();
3664 Some(chars.iter().collect())
3665 }
3666
3667 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3668 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3669 self.transact(window, cx, |this, window, cx| {
3670 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3671 let selections = this.selections.all::<usize>(cx);
3672 let multi_buffer = this.buffer.read(cx);
3673 let buffer = multi_buffer.snapshot(cx);
3674 selections
3675 .iter()
3676 .map(|selection| {
3677 let start_point = selection.start.to_point(&buffer);
3678 let mut indent =
3679 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3680 indent.len = cmp::min(indent.len, start_point.column);
3681 let start = selection.start;
3682 let end = selection.end;
3683 let selection_is_empty = start == end;
3684 let language_scope = buffer.language_scope_at(start);
3685 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3686 &language_scope
3687 {
3688 let insert_extra_newline =
3689 insert_extra_newline_brackets(&buffer, start..end, language)
3690 || insert_extra_newline_tree_sitter(&buffer, start..end);
3691
3692 // Comment extension on newline is allowed only for cursor selections
3693 let comment_delimiter = maybe!({
3694 if !selection_is_empty {
3695 return None;
3696 }
3697
3698 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3699 return None;
3700 }
3701
3702 let delimiters = language.line_comment_prefixes();
3703 let max_len_of_delimiter =
3704 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3705 let (snapshot, range) =
3706 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3707
3708 let mut index_of_first_non_whitespace = 0;
3709 let comment_candidate = snapshot
3710 .chars_for_range(range)
3711 .skip_while(|c| {
3712 let should_skip = c.is_whitespace();
3713 if should_skip {
3714 index_of_first_non_whitespace += 1;
3715 }
3716 should_skip
3717 })
3718 .take(max_len_of_delimiter)
3719 .collect::<String>();
3720 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3721 comment_candidate.starts_with(comment_prefix.as_ref())
3722 })?;
3723 let cursor_is_placed_after_comment_marker =
3724 index_of_first_non_whitespace + comment_prefix.len()
3725 <= start_point.column as usize;
3726 if cursor_is_placed_after_comment_marker {
3727 Some(comment_prefix.clone())
3728 } else {
3729 None
3730 }
3731 });
3732 (comment_delimiter, insert_extra_newline)
3733 } else {
3734 (None, false)
3735 };
3736
3737 let capacity_for_delimiter = comment_delimiter
3738 .as_deref()
3739 .map(str::len)
3740 .unwrap_or_default();
3741 let mut new_text =
3742 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3743 new_text.push('\n');
3744 new_text.extend(indent.chars());
3745 if let Some(delimiter) = &comment_delimiter {
3746 new_text.push_str(delimiter);
3747 }
3748 if insert_extra_newline {
3749 new_text = new_text.repeat(2);
3750 }
3751
3752 let anchor = buffer.anchor_after(end);
3753 let new_selection = selection.map(|_| anchor);
3754 (
3755 (start..end, new_text),
3756 (insert_extra_newline, new_selection),
3757 )
3758 })
3759 .unzip()
3760 };
3761
3762 this.edit_with_autoindent(edits, cx);
3763 let buffer = this.buffer.read(cx).snapshot(cx);
3764 let new_selections = selection_fixup_info
3765 .into_iter()
3766 .map(|(extra_newline_inserted, new_selection)| {
3767 let mut cursor = new_selection.end.to_point(&buffer);
3768 if extra_newline_inserted {
3769 cursor.row -= 1;
3770 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3771 }
3772 new_selection.map(|_| cursor)
3773 })
3774 .collect();
3775
3776 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3777 s.select(new_selections)
3778 });
3779 this.refresh_inline_completion(true, false, window, cx);
3780 });
3781 }
3782
3783 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3784 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3785
3786 let buffer = self.buffer.read(cx);
3787 let snapshot = buffer.snapshot(cx);
3788
3789 let mut edits = Vec::new();
3790 let mut rows = Vec::new();
3791
3792 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3793 let cursor = selection.head();
3794 let row = cursor.row;
3795
3796 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3797
3798 let newline = "\n".to_string();
3799 edits.push((start_of_line..start_of_line, newline));
3800
3801 rows.push(row + rows_inserted as u32);
3802 }
3803
3804 self.transact(window, cx, |editor, window, cx| {
3805 editor.edit(edits, cx);
3806
3807 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3808 let mut index = 0;
3809 s.move_cursors_with(|map, _, _| {
3810 let row = rows[index];
3811 index += 1;
3812
3813 let point = Point::new(row, 0);
3814 let boundary = map.next_line_boundary(point).1;
3815 let clipped = map.clip_point(boundary, Bias::Left);
3816
3817 (clipped, SelectionGoal::None)
3818 });
3819 });
3820
3821 let mut indent_edits = Vec::new();
3822 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3823 for row in rows {
3824 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3825 for (row, indent) in indents {
3826 if indent.len == 0 {
3827 continue;
3828 }
3829
3830 let text = match indent.kind {
3831 IndentKind::Space => " ".repeat(indent.len as usize),
3832 IndentKind::Tab => "\t".repeat(indent.len as usize),
3833 };
3834 let point = Point::new(row.0, 0);
3835 indent_edits.push((point..point, text));
3836 }
3837 }
3838 editor.edit(indent_edits, cx);
3839 });
3840 }
3841
3842 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3843 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3844
3845 let buffer = self.buffer.read(cx);
3846 let snapshot = buffer.snapshot(cx);
3847
3848 let mut edits = Vec::new();
3849 let mut rows = Vec::new();
3850 let mut rows_inserted = 0;
3851
3852 for selection in self.selections.all_adjusted(cx) {
3853 let cursor = selection.head();
3854 let row = cursor.row;
3855
3856 let point = Point::new(row + 1, 0);
3857 let start_of_line = snapshot.clip_point(point, Bias::Left);
3858
3859 let newline = "\n".to_string();
3860 edits.push((start_of_line..start_of_line, newline));
3861
3862 rows_inserted += 1;
3863 rows.push(row + rows_inserted);
3864 }
3865
3866 self.transact(window, cx, |editor, window, cx| {
3867 editor.edit(edits, cx);
3868
3869 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3870 let mut index = 0;
3871 s.move_cursors_with(|map, _, _| {
3872 let row = rows[index];
3873 index += 1;
3874
3875 let point = Point::new(row, 0);
3876 let boundary = map.next_line_boundary(point).1;
3877 let clipped = map.clip_point(boundary, Bias::Left);
3878
3879 (clipped, SelectionGoal::None)
3880 });
3881 });
3882
3883 let mut indent_edits = Vec::new();
3884 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3885 for row in rows {
3886 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3887 for (row, indent) in indents {
3888 if indent.len == 0 {
3889 continue;
3890 }
3891
3892 let text = match indent.kind {
3893 IndentKind::Space => " ".repeat(indent.len as usize),
3894 IndentKind::Tab => "\t".repeat(indent.len as usize),
3895 };
3896 let point = Point::new(row.0, 0);
3897 indent_edits.push((point..point, text));
3898 }
3899 }
3900 editor.edit(indent_edits, cx);
3901 });
3902 }
3903
3904 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3905 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3906 original_indent_columns: Vec::new(),
3907 });
3908 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3909 }
3910
3911 fn insert_with_autoindent_mode(
3912 &mut self,
3913 text: &str,
3914 autoindent_mode: Option<AutoindentMode>,
3915 window: &mut Window,
3916 cx: &mut Context<Self>,
3917 ) {
3918 if self.read_only(cx) {
3919 return;
3920 }
3921
3922 let text: Arc<str> = text.into();
3923 self.transact(window, cx, |this, window, cx| {
3924 let old_selections = this.selections.all_adjusted(cx);
3925 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3926 let anchors = {
3927 let snapshot = buffer.read(cx);
3928 old_selections
3929 .iter()
3930 .map(|s| {
3931 let anchor = snapshot.anchor_after(s.head());
3932 s.map(|_| anchor)
3933 })
3934 .collect::<Vec<_>>()
3935 };
3936 buffer.edit(
3937 old_selections
3938 .iter()
3939 .map(|s| (s.start..s.end, text.clone())),
3940 autoindent_mode,
3941 cx,
3942 );
3943 anchors
3944 });
3945
3946 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3947 s.select_anchors(selection_anchors);
3948 });
3949
3950 cx.notify();
3951 });
3952 }
3953
3954 fn trigger_completion_on_input(
3955 &mut self,
3956 text: &str,
3957 trigger_in_words: bool,
3958 window: &mut Window,
3959 cx: &mut Context<Self>,
3960 ) {
3961 let ignore_completion_provider = self
3962 .context_menu
3963 .borrow()
3964 .as_ref()
3965 .map(|menu| match menu {
3966 CodeContextMenu::Completions(completions_menu) => {
3967 completions_menu.ignore_completion_provider
3968 }
3969 CodeContextMenu::CodeActions(_) => false,
3970 })
3971 .unwrap_or(false);
3972
3973 if ignore_completion_provider {
3974 self.show_word_completions(&ShowWordCompletions, window, cx);
3975 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3976 self.show_completions(
3977 &ShowCompletions {
3978 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3979 },
3980 window,
3981 cx,
3982 );
3983 } else {
3984 self.hide_context_menu(window, cx);
3985 }
3986 }
3987
3988 fn is_completion_trigger(
3989 &self,
3990 text: &str,
3991 trigger_in_words: bool,
3992 cx: &mut Context<Self>,
3993 ) -> bool {
3994 let position = self.selections.newest_anchor().head();
3995 let multibuffer = self.buffer.read(cx);
3996 let Some(buffer) = position
3997 .buffer_id
3998 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3999 else {
4000 return false;
4001 };
4002
4003 if let Some(completion_provider) = &self.completion_provider {
4004 completion_provider.is_completion_trigger(
4005 &buffer,
4006 position.text_anchor,
4007 text,
4008 trigger_in_words,
4009 cx,
4010 )
4011 } else {
4012 false
4013 }
4014 }
4015
4016 /// If any empty selections is touching the start of its innermost containing autoclose
4017 /// region, expand it to select the brackets.
4018 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4019 let selections = self.selections.all::<usize>(cx);
4020 let buffer = self.buffer.read(cx).read(cx);
4021 let new_selections = self
4022 .selections_with_autoclose_regions(selections, &buffer)
4023 .map(|(mut selection, region)| {
4024 if !selection.is_empty() {
4025 return selection;
4026 }
4027
4028 if let Some(region) = region {
4029 let mut range = region.range.to_offset(&buffer);
4030 if selection.start == range.start && range.start >= region.pair.start.len() {
4031 range.start -= region.pair.start.len();
4032 if buffer.contains_str_at(range.start, ®ion.pair.start)
4033 && buffer.contains_str_at(range.end, ®ion.pair.end)
4034 {
4035 range.end += region.pair.end.len();
4036 selection.start = range.start;
4037 selection.end = range.end;
4038
4039 return selection;
4040 }
4041 }
4042 }
4043
4044 let always_treat_brackets_as_autoclosed = buffer
4045 .language_settings_at(selection.start, cx)
4046 .always_treat_brackets_as_autoclosed;
4047
4048 if !always_treat_brackets_as_autoclosed {
4049 return selection;
4050 }
4051
4052 if let Some(scope) = buffer.language_scope_at(selection.start) {
4053 for (pair, enabled) in scope.brackets() {
4054 if !enabled || !pair.close {
4055 continue;
4056 }
4057
4058 if buffer.contains_str_at(selection.start, &pair.end) {
4059 let pair_start_len = pair.start.len();
4060 if buffer.contains_str_at(
4061 selection.start.saturating_sub(pair_start_len),
4062 &pair.start,
4063 ) {
4064 selection.start -= pair_start_len;
4065 selection.end += pair.end.len();
4066
4067 return selection;
4068 }
4069 }
4070 }
4071 }
4072
4073 selection
4074 })
4075 .collect();
4076
4077 drop(buffer);
4078 self.change_selections(None, window, cx, |selections| {
4079 selections.select(new_selections)
4080 });
4081 }
4082
4083 /// Iterate the given selections, and for each one, find the smallest surrounding
4084 /// autoclose region. This uses the ordering of the selections and the autoclose
4085 /// regions to avoid repeated comparisons.
4086 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4087 &'a self,
4088 selections: impl IntoIterator<Item = Selection<D>>,
4089 buffer: &'a MultiBufferSnapshot,
4090 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4091 let mut i = 0;
4092 let mut regions = self.autoclose_regions.as_slice();
4093 selections.into_iter().map(move |selection| {
4094 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4095
4096 let mut enclosing = None;
4097 while let Some(pair_state) = regions.get(i) {
4098 if pair_state.range.end.to_offset(buffer) < range.start {
4099 regions = ®ions[i + 1..];
4100 i = 0;
4101 } else if pair_state.range.start.to_offset(buffer) > range.end {
4102 break;
4103 } else {
4104 if pair_state.selection_id == selection.id {
4105 enclosing = Some(pair_state);
4106 }
4107 i += 1;
4108 }
4109 }
4110
4111 (selection, enclosing)
4112 })
4113 }
4114
4115 /// Remove any autoclose regions that no longer contain their selection.
4116 fn invalidate_autoclose_regions(
4117 &mut self,
4118 mut selections: &[Selection<Anchor>],
4119 buffer: &MultiBufferSnapshot,
4120 ) {
4121 self.autoclose_regions.retain(|state| {
4122 let mut i = 0;
4123 while let Some(selection) = selections.get(i) {
4124 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4125 selections = &selections[1..];
4126 continue;
4127 }
4128 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4129 break;
4130 }
4131 if selection.id == state.selection_id {
4132 return true;
4133 } else {
4134 i += 1;
4135 }
4136 }
4137 false
4138 });
4139 }
4140
4141 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4142 let offset = position.to_offset(buffer);
4143 let (word_range, kind) = buffer.surrounding_word(offset, true);
4144 if offset > word_range.start && kind == Some(CharKind::Word) {
4145 Some(
4146 buffer
4147 .text_for_range(word_range.start..offset)
4148 .collect::<String>(),
4149 )
4150 } else {
4151 None
4152 }
4153 }
4154
4155 pub fn toggle_inlay_hints(
4156 &mut self,
4157 _: &ToggleInlayHints,
4158 _: &mut Window,
4159 cx: &mut Context<Self>,
4160 ) {
4161 self.refresh_inlay_hints(
4162 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4163 cx,
4164 );
4165 }
4166
4167 pub fn inlay_hints_enabled(&self) -> bool {
4168 self.inlay_hint_cache.enabled
4169 }
4170
4171 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4172 if self.semantics_provider.is_none() || !self.mode.is_full() {
4173 return;
4174 }
4175
4176 let reason_description = reason.description();
4177 let ignore_debounce = matches!(
4178 reason,
4179 InlayHintRefreshReason::SettingsChange(_)
4180 | InlayHintRefreshReason::Toggle(_)
4181 | InlayHintRefreshReason::ExcerptsRemoved(_)
4182 | InlayHintRefreshReason::ModifiersChanged(_)
4183 );
4184 let (invalidate_cache, required_languages) = match reason {
4185 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4186 match self.inlay_hint_cache.modifiers_override(enabled) {
4187 Some(enabled) => {
4188 if enabled {
4189 (InvalidationStrategy::RefreshRequested, None)
4190 } else {
4191 self.splice_inlays(
4192 &self
4193 .visible_inlay_hints(cx)
4194 .iter()
4195 .map(|inlay| inlay.id)
4196 .collect::<Vec<InlayId>>(),
4197 Vec::new(),
4198 cx,
4199 );
4200 return;
4201 }
4202 }
4203 None => return,
4204 }
4205 }
4206 InlayHintRefreshReason::Toggle(enabled) => {
4207 if self.inlay_hint_cache.toggle(enabled) {
4208 if enabled {
4209 (InvalidationStrategy::RefreshRequested, None)
4210 } else {
4211 self.splice_inlays(
4212 &self
4213 .visible_inlay_hints(cx)
4214 .iter()
4215 .map(|inlay| inlay.id)
4216 .collect::<Vec<InlayId>>(),
4217 Vec::new(),
4218 cx,
4219 );
4220 return;
4221 }
4222 } else {
4223 return;
4224 }
4225 }
4226 InlayHintRefreshReason::SettingsChange(new_settings) => {
4227 match self.inlay_hint_cache.update_settings(
4228 &self.buffer,
4229 new_settings,
4230 self.visible_inlay_hints(cx),
4231 cx,
4232 ) {
4233 ControlFlow::Break(Some(InlaySplice {
4234 to_remove,
4235 to_insert,
4236 })) => {
4237 self.splice_inlays(&to_remove, to_insert, cx);
4238 return;
4239 }
4240 ControlFlow::Break(None) => return,
4241 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4242 }
4243 }
4244 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4245 if let Some(InlaySplice {
4246 to_remove,
4247 to_insert,
4248 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4249 {
4250 self.splice_inlays(&to_remove, to_insert, cx);
4251 }
4252 self.display_map.update(cx, |display_map, _| {
4253 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4254 });
4255 return;
4256 }
4257 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4258 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4259 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4260 }
4261 InlayHintRefreshReason::RefreshRequested => {
4262 (InvalidationStrategy::RefreshRequested, None)
4263 }
4264 };
4265
4266 if let Some(InlaySplice {
4267 to_remove,
4268 to_insert,
4269 }) = self.inlay_hint_cache.spawn_hint_refresh(
4270 reason_description,
4271 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4272 invalidate_cache,
4273 ignore_debounce,
4274 cx,
4275 ) {
4276 self.splice_inlays(&to_remove, to_insert, cx);
4277 }
4278 }
4279
4280 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4281 self.display_map
4282 .read(cx)
4283 .current_inlays()
4284 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4285 .cloned()
4286 .collect()
4287 }
4288
4289 pub fn excerpts_for_inlay_hints_query(
4290 &self,
4291 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4292 cx: &mut Context<Editor>,
4293 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4294 let Some(project) = self.project.as_ref() else {
4295 return HashMap::default();
4296 };
4297 let project = project.read(cx);
4298 let multi_buffer = self.buffer().read(cx);
4299 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4300 let multi_buffer_visible_start = self
4301 .scroll_manager
4302 .anchor()
4303 .anchor
4304 .to_point(&multi_buffer_snapshot);
4305 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4306 multi_buffer_visible_start
4307 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4308 Bias::Left,
4309 );
4310 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4311 multi_buffer_snapshot
4312 .range_to_buffer_ranges(multi_buffer_visible_range)
4313 .into_iter()
4314 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4315 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4316 let buffer_file = project::File::from_dyn(buffer.file())?;
4317 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4318 let worktree_entry = buffer_worktree
4319 .read(cx)
4320 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4321 if worktree_entry.is_ignored {
4322 return None;
4323 }
4324
4325 let language = buffer.language()?;
4326 if let Some(restrict_to_languages) = restrict_to_languages {
4327 if !restrict_to_languages.contains(language) {
4328 return None;
4329 }
4330 }
4331 Some((
4332 excerpt_id,
4333 (
4334 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4335 buffer.version().clone(),
4336 excerpt_visible_range,
4337 ),
4338 ))
4339 })
4340 .collect()
4341 }
4342
4343 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4344 TextLayoutDetails {
4345 text_system: window.text_system().clone(),
4346 editor_style: self.style.clone().unwrap(),
4347 rem_size: window.rem_size(),
4348 scroll_anchor: self.scroll_manager.anchor(),
4349 visible_rows: self.visible_line_count(),
4350 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4351 }
4352 }
4353
4354 pub fn splice_inlays(
4355 &self,
4356 to_remove: &[InlayId],
4357 to_insert: Vec<Inlay>,
4358 cx: &mut Context<Self>,
4359 ) {
4360 self.display_map.update(cx, |display_map, cx| {
4361 display_map.splice_inlays(to_remove, to_insert, cx)
4362 });
4363 cx.notify();
4364 }
4365
4366 fn trigger_on_type_formatting(
4367 &self,
4368 input: String,
4369 window: &mut Window,
4370 cx: &mut Context<Self>,
4371 ) -> Option<Task<Result<()>>> {
4372 if input.len() != 1 {
4373 return None;
4374 }
4375
4376 let project = self.project.as_ref()?;
4377 let position = self.selections.newest_anchor().head();
4378 let (buffer, buffer_position) = self
4379 .buffer
4380 .read(cx)
4381 .text_anchor_for_position(position, cx)?;
4382
4383 let settings = language_settings::language_settings(
4384 buffer
4385 .read(cx)
4386 .language_at(buffer_position)
4387 .map(|l| l.name()),
4388 buffer.read(cx).file(),
4389 cx,
4390 );
4391 if !settings.use_on_type_format {
4392 return None;
4393 }
4394
4395 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4396 // hence we do LSP request & edit on host side only — add formats to host's history.
4397 let push_to_lsp_host_history = true;
4398 // If this is not the host, append its history with new edits.
4399 let push_to_client_history = project.read(cx).is_via_collab();
4400
4401 let on_type_formatting = project.update(cx, |project, cx| {
4402 project.on_type_format(
4403 buffer.clone(),
4404 buffer_position,
4405 input,
4406 push_to_lsp_host_history,
4407 cx,
4408 )
4409 });
4410 Some(cx.spawn_in(window, async move |editor, cx| {
4411 if let Some(transaction) = on_type_formatting.await? {
4412 if push_to_client_history {
4413 buffer
4414 .update(cx, |buffer, _| {
4415 buffer.push_transaction(transaction, Instant::now());
4416 buffer.finalize_last_transaction();
4417 })
4418 .ok();
4419 }
4420 editor.update(cx, |editor, cx| {
4421 editor.refresh_document_highlights(cx);
4422 })?;
4423 }
4424 Ok(())
4425 }))
4426 }
4427
4428 pub fn show_word_completions(
4429 &mut self,
4430 _: &ShowWordCompletions,
4431 window: &mut Window,
4432 cx: &mut Context<Self>,
4433 ) {
4434 self.open_completions_menu(true, None, window, cx);
4435 }
4436
4437 pub fn show_completions(
4438 &mut self,
4439 options: &ShowCompletions,
4440 window: &mut Window,
4441 cx: &mut Context<Self>,
4442 ) {
4443 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4444 }
4445
4446 fn open_completions_menu(
4447 &mut self,
4448 ignore_completion_provider: bool,
4449 trigger: Option<&str>,
4450 window: &mut Window,
4451 cx: &mut Context<Self>,
4452 ) {
4453 if self.pending_rename.is_some() {
4454 return;
4455 }
4456 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4457 return;
4458 }
4459
4460 let position = self.selections.newest_anchor().head();
4461 if position.diff_base_anchor.is_some() {
4462 return;
4463 }
4464 let (buffer, buffer_position) =
4465 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4466 output
4467 } else {
4468 return;
4469 };
4470 let buffer_snapshot = buffer.read(cx).snapshot();
4471 let show_completion_documentation = buffer_snapshot
4472 .settings_at(buffer_position, cx)
4473 .show_completion_documentation;
4474
4475 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4476
4477 let trigger_kind = match trigger {
4478 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4479 CompletionTriggerKind::TRIGGER_CHARACTER
4480 }
4481 _ => CompletionTriggerKind::INVOKED,
4482 };
4483 let completion_context = CompletionContext {
4484 trigger_character: trigger.and_then(|trigger| {
4485 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4486 Some(String::from(trigger))
4487 } else {
4488 None
4489 }
4490 }),
4491 trigger_kind,
4492 };
4493
4494 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4495 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4496 let word_to_exclude = buffer_snapshot
4497 .text_for_range(old_range.clone())
4498 .collect::<String>();
4499 (
4500 buffer_snapshot.anchor_before(old_range.start)
4501 ..buffer_snapshot.anchor_after(old_range.end),
4502 Some(word_to_exclude),
4503 )
4504 } else {
4505 (buffer_position..buffer_position, None)
4506 };
4507
4508 let completion_settings = language_settings(
4509 buffer_snapshot
4510 .language_at(buffer_position)
4511 .map(|language| language.name()),
4512 buffer_snapshot.file(),
4513 cx,
4514 )
4515 .completions;
4516
4517 // The document can be large, so stay in reasonable bounds when searching for words,
4518 // otherwise completion pop-up might be slow to appear.
4519 const WORD_LOOKUP_ROWS: u32 = 5_000;
4520 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4521 let min_word_search = buffer_snapshot.clip_point(
4522 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4523 Bias::Left,
4524 );
4525 let max_word_search = buffer_snapshot.clip_point(
4526 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4527 Bias::Right,
4528 );
4529 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4530 ..buffer_snapshot.point_to_offset(max_word_search);
4531
4532 let provider = self
4533 .completion_provider
4534 .as_ref()
4535 .filter(|_| !ignore_completion_provider);
4536 let skip_digits = query
4537 .as_ref()
4538 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4539
4540 let (mut words, provided_completions) = match provider {
4541 Some(provider) => {
4542 let completions = provider.completions(
4543 position.excerpt_id,
4544 &buffer,
4545 buffer_position,
4546 completion_context,
4547 window,
4548 cx,
4549 );
4550
4551 let words = match completion_settings.words {
4552 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4553 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4554 .background_spawn(async move {
4555 buffer_snapshot.words_in_range(WordsQuery {
4556 fuzzy_contents: None,
4557 range: word_search_range,
4558 skip_digits,
4559 })
4560 }),
4561 };
4562
4563 (words, completions)
4564 }
4565 None => (
4566 cx.background_spawn(async move {
4567 buffer_snapshot.words_in_range(WordsQuery {
4568 fuzzy_contents: None,
4569 range: word_search_range,
4570 skip_digits,
4571 })
4572 }),
4573 Task::ready(Ok(None)),
4574 ),
4575 };
4576
4577 let sort_completions = provider
4578 .as_ref()
4579 .map_or(false, |provider| provider.sort_completions());
4580
4581 let filter_completions = provider
4582 .as_ref()
4583 .map_or(true, |provider| provider.filter_completions());
4584
4585 let id = post_inc(&mut self.next_completion_id);
4586 let task = cx.spawn_in(window, async move |editor, cx| {
4587 async move {
4588 editor.update(cx, |this, _| {
4589 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4590 })?;
4591
4592 let mut completions = Vec::new();
4593 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4594 completions.extend(provided_completions);
4595 if completion_settings.words == WordsCompletionMode::Fallback {
4596 words = Task::ready(BTreeMap::default());
4597 }
4598 }
4599
4600 let mut words = words.await;
4601 if let Some(word_to_exclude) = &word_to_exclude {
4602 words.remove(word_to_exclude);
4603 }
4604 for lsp_completion in &completions {
4605 words.remove(&lsp_completion.new_text);
4606 }
4607 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4608 replace_range: old_range.clone(),
4609 new_text: word.clone(),
4610 label: CodeLabel::plain(word, None),
4611 icon_path: None,
4612 documentation: None,
4613 source: CompletionSource::BufferWord {
4614 word_range,
4615 resolved: false,
4616 },
4617 insert_text_mode: Some(InsertTextMode::AS_IS),
4618 confirm: None,
4619 }));
4620
4621 let menu = if completions.is_empty() {
4622 None
4623 } else {
4624 let mut menu = CompletionsMenu::new(
4625 id,
4626 sort_completions,
4627 show_completion_documentation,
4628 ignore_completion_provider,
4629 position,
4630 buffer.clone(),
4631 completions.into(),
4632 );
4633
4634 menu.filter(
4635 if filter_completions {
4636 query.as_deref()
4637 } else {
4638 None
4639 },
4640 cx.background_executor().clone(),
4641 )
4642 .await;
4643
4644 menu.visible().then_some(menu)
4645 };
4646
4647 editor.update_in(cx, |editor, window, cx| {
4648 match editor.context_menu.borrow().as_ref() {
4649 None => {}
4650 Some(CodeContextMenu::Completions(prev_menu)) => {
4651 if prev_menu.id > id {
4652 return;
4653 }
4654 }
4655 _ => return,
4656 }
4657
4658 if editor.focus_handle.is_focused(window) && menu.is_some() {
4659 let mut menu = menu.unwrap();
4660 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4661
4662 *editor.context_menu.borrow_mut() =
4663 Some(CodeContextMenu::Completions(menu));
4664
4665 if editor.show_edit_predictions_in_menu() {
4666 editor.update_visible_inline_completion(window, cx);
4667 } else {
4668 editor.discard_inline_completion(false, cx);
4669 }
4670
4671 cx.notify();
4672 } else if editor.completion_tasks.len() <= 1 {
4673 // If there are no more completion tasks and the last menu was
4674 // empty, we should hide it.
4675 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4676 // If it was already hidden and we don't show inline
4677 // completions in the menu, we should also show the
4678 // inline-completion when available.
4679 if was_hidden && editor.show_edit_predictions_in_menu() {
4680 editor.update_visible_inline_completion(window, cx);
4681 }
4682 }
4683 })?;
4684
4685 anyhow::Ok(())
4686 }
4687 .log_err()
4688 .await
4689 });
4690
4691 self.completion_tasks.push((id, task));
4692 }
4693
4694 #[cfg(feature = "test-support")]
4695 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4696 let menu = self.context_menu.borrow();
4697 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4698 let completions = menu.completions.borrow();
4699 Some(completions.to_vec())
4700 } else {
4701 None
4702 }
4703 }
4704
4705 pub fn confirm_completion(
4706 &mut self,
4707 action: &ConfirmCompletion,
4708 window: &mut Window,
4709 cx: &mut Context<Self>,
4710 ) -> Option<Task<Result<()>>> {
4711 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4712 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4713 }
4714
4715 pub fn confirm_completion_insert(
4716 &mut self,
4717 _: &ConfirmCompletionInsert,
4718 window: &mut Window,
4719 cx: &mut Context<Self>,
4720 ) -> Option<Task<Result<()>>> {
4721 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4722 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4723 }
4724
4725 pub fn confirm_completion_replace(
4726 &mut self,
4727 _: &ConfirmCompletionReplace,
4728 window: &mut Window,
4729 cx: &mut Context<Self>,
4730 ) -> Option<Task<Result<()>>> {
4731 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4732 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4733 }
4734
4735 pub fn compose_completion(
4736 &mut self,
4737 action: &ComposeCompletion,
4738 window: &mut Window,
4739 cx: &mut Context<Self>,
4740 ) -> Option<Task<Result<()>>> {
4741 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4742 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4743 }
4744
4745 fn do_completion(
4746 &mut self,
4747 item_ix: Option<usize>,
4748 intent: CompletionIntent,
4749 window: &mut Window,
4750 cx: &mut Context<Editor>,
4751 ) -> Option<Task<Result<()>>> {
4752 use language::ToOffset as _;
4753
4754 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4755 else {
4756 return None;
4757 };
4758
4759 let candidate_id = {
4760 let entries = completions_menu.entries.borrow();
4761 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4762 if self.show_edit_predictions_in_menu() {
4763 self.discard_inline_completion(true, cx);
4764 }
4765 mat.candidate_id
4766 };
4767
4768 let buffer_handle = completions_menu.buffer;
4769 let completion = completions_menu
4770 .completions
4771 .borrow()
4772 .get(candidate_id)?
4773 .clone();
4774 cx.stop_propagation();
4775
4776 let snippet;
4777 let new_text;
4778 if completion.is_snippet() {
4779 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4780 new_text = snippet.as_ref().unwrap().text.clone();
4781 } else {
4782 snippet = None;
4783 new_text = completion.new_text.clone();
4784 };
4785
4786 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4787 let buffer = buffer_handle.read(cx);
4788 let snapshot = self.buffer.read(cx).snapshot(cx);
4789 let replace_range_multibuffer = {
4790 let excerpt = snapshot
4791 .excerpt_containing(self.selections.newest_anchor().range())
4792 .unwrap();
4793 let multibuffer_anchor = snapshot
4794 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
4795 .unwrap()
4796 ..snapshot
4797 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
4798 .unwrap();
4799 multibuffer_anchor.start.to_offset(&snapshot)
4800 ..multibuffer_anchor.end.to_offset(&snapshot)
4801 };
4802 let newest_anchor = self.selections.newest_anchor();
4803 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
4804 return None;
4805 }
4806
4807 let old_text = buffer
4808 .text_for_range(replace_range.clone())
4809 .collect::<String>();
4810 let lookbehind = newest_anchor
4811 .start
4812 .text_anchor
4813 .to_offset(buffer)
4814 .saturating_sub(replace_range.start);
4815 let lookahead = replace_range
4816 .end
4817 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
4818 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
4819 let suffix = &old_text[lookbehind.min(old_text.len())..];
4820
4821 let selections = self.selections.all::<usize>(cx);
4822 let mut ranges = Vec::new();
4823 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4824
4825 for selection in &selections {
4826 let range = if selection.id == newest_anchor.id {
4827 replace_range_multibuffer.clone()
4828 } else {
4829 let mut range = selection.range();
4830
4831 // if prefix is present, don't duplicate it
4832 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
4833 range.start = range.start.saturating_sub(lookbehind);
4834
4835 // if suffix is also present, mimic the newest cursor and replace it
4836 if selection.id != newest_anchor.id
4837 && snapshot.contains_str_at(range.end, suffix)
4838 {
4839 range.end += lookahead;
4840 }
4841 }
4842 range
4843 };
4844
4845 ranges.push(range);
4846
4847 if !self.linked_edit_ranges.is_empty() {
4848 let start_anchor = snapshot.anchor_before(selection.head());
4849 let end_anchor = snapshot.anchor_after(selection.tail());
4850 if let Some(ranges) = self
4851 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4852 {
4853 for (buffer, edits) in ranges {
4854 linked_edits
4855 .entry(buffer.clone())
4856 .or_default()
4857 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
4858 }
4859 }
4860 }
4861 }
4862
4863 cx.emit(EditorEvent::InputHandled {
4864 utf16_range_to_replace: None,
4865 text: new_text.clone().into(),
4866 });
4867
4868 self.transact(window, cx, |this, window, cx| {
4869 if let Some(mut snippet) = snippet {
4870 snippet.text = new_text.to_string();
4871 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4872 } else {
4873 this.buffer.update(cx, |buffer, cx| {
4874 let auto_indent = match completion.insert_text_mode {
4875 Some(InsertTextMode::AS_IS) => None,
4876 _ => this.autoindent_mode.clone(),
4877 };
4878 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
4879 buffer.edit(edits, auto_indent, cx);
4880 });
4881 }
4882 for (buffer, edits) in linked_edits {
4883 buffer.update(cx, |buffer, cx| {
4884 let snapshot = buffer.snapshot();
4885 let edits = edits
4886 .into_iter()
4887 .map(|(range, text)| {
4888 use text::ToPoint as TP;
4889 let end_point = TP::to_point(&range.end, &snapshot);
4890 let start_point = TP::to_point(&range.start, &snapshot);
4891 (start_point..end_point, text)
4892 })
4893 .sorted_by_key(|(range, _)| range.start);
4894 buffer.edit(edits, None, cx);
4895 })
4896 }
4897
4898 this.refresh_inline_completion(true, false, window, cx);
4899 });
4900
4901 let show_new_completions_on_confirm = completion
4902 .confirm
4903 .as_ref()
4904 .map_or(false, |confirm| confirm(intent, window, cx));
4905 if show_new_completions_on_confirm {
4906 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4907 }
4908
4909 let provider = self.completion_provider.as_ref()?;
4910 drop(completion);
4911 let apply_edits = provider.apply_additional_edits_for_completion(
4912 buffer_handle,
4913 completions_menu.completions.clone(),
4914 candidate_id,
4915 true,
4916 cx,
4917 );
4918
4919 let editor_settings = EditorSettings::get_global(cx);
4920 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4921 // After the code completion is finished, users often want to know what signatures are needed.
4922 // so we should automatically call signature_help
4923 self.show_signature_help(&ShowSignatureHelp, window, cx);
4924 }
4925
4926 Some(cx.foreground_executor().spawn(async move {
4927 apply_edits.await?;
4928 Ok(())
4929 }))
4930 }
4931
4932 pub fn toggle_code_actions(
4933 &mut self,
4934 action: &ToggleCodeActions,
4935 window: &mut Window,
4936 cx: &mut Context<Self>,
4937 ) {
4938 let mut context_menu = self.context_menu.borrow_mut();
4939 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4940 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4941 // Toggle if we're selecting the same one
4942 *context_menu = None;
4943 cx.notify();
4944 return;
4945 } else {
4946 // Otherwise, clear it and start a new one
4947 *context_menu = None;
4948 cx.notify();
4949 }
4950 }
4951 drop(context_menu);
4952 let snapshot = self.snapshot(window, cx);
4953 let deployed_from_indicator = action.deployed_from_indicator;
4954 let mut task = self.code_actions_task.take();
4955 let action = action.clone();
4956 cx.spawn_in(window, async move |editor, cx| {
4957 while let Some(prev_task) = task {
4958 prev_task.await.log_err();
4959 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4960 }
4961
4962 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4963 if editor.focus_handle.is_focused(window) {
4964 let multibuffer_point = action
4965 .deployed_from_indicator
4966 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4967 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4968 let (buffer, buffer_row) = snapshot
4969 .buffer_snapshot
4970 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4971 .and_then(|(buffer_snapshot, range)| {
4972 editor
4973 .buffer
4974 .read(cx)
4975 .buffer(buffer_snapshot.remote_id())
4976 .map(|buffer| (buffer, range.start.row))
4977 })?;
4978 let (_, code_actions) = editor
4979 .available_code_actions
4980 .clone()
4981 .and_then(|(location, code_actions)| {
4982 let snapshot = location.buffer.read(cx).snapshot();
4983 let point_range = location.range.to_point(&snapshot);
4984 let point_range = point_range.start.row..=point_range.end.row;
4985 if point_range.contains(&buffer_row) {
4986 Some((location, code_actions))
4987 } else {
4988 None
4989 }
4990 })
4991 .unzip();
4992 let buffer_id = buffer.read(cx).remote_id();
4993 let tasks = editor
4994 .tasks
4995 .get(&(buffer_id, buffer_row))
4996 .map(|t| Arc::new(t.to_owned()));
4997 if tasks.is_none() && code_actions.is_none() {
4998 return None;
4999 }
5000
5001 editor.completion_tasks.clear();
5002 editor.discard_inline_completion(false, cx);
5003 let task_context =
5004 tasks
5005 .as_ref()
5006 .zip(editor.project.clone())
5007 .map(|(tasks, project)| {
5008 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5009 });
5010
5011 let debugger_flag = cx.has_flag::<Debugger>();
5012
5013 Some(cx.spawn_in(window, async move |editor, cx| {
5014 let task_context = match task_context {
5015 Some(task_context) => task_context.await,
5016 None => None,
5017 };
5018 let resolved_tasks =
5019 tasks
5020 .zip(task_context)
5021 .map(|(tasks, task_context)| ResolvedTasks {
5022 templates: tasks.resolve(&task_context).collect(),
5023 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5024 multibuffer_point.row,
5025 tasks.column,
5026 )),
5027 });
5028 let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
5029 tasks
5030 .templates
5031 .iter()
5032 .filter(|task| {
5033 if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
5034 debugger_flag
5035 } else {
5036 true
5037 }
5038 })
5039 .count()
5040 == 1
5041 }) && code_actions
5042 .as_ref()
5043 .map_or(true, |actions| actions.is_empty());
5044 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5045 *editor.context_menu.borrow_mut() =
5046 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5047 buffer,
5048 actions: CodeActionContents::new(
5049 resolved_tasks,
5050 code_actions,
5051 cx,
5052 ),
5053 selected_item: Default::default(),
5054 scroll_handle: UniformListScrollHandle::default(),
5055 deployed_from_indicator,
5056 }));
5057 if spawn_straight_away {
5058 if let Some(task) = editor.confirm_code_action(
5059 &ConfirmCodeAction { item_ix: Some(0) },
5060 window,
5061 cx,
5062 ) {
5063 cx.notify();
5064 return task;
5065 }
5066 }
5067 cx.notify();
5068 Task::ready(Ok(()))
5069 }) {
5070 task.await
5071 } else {
5072 Ok(())
5073 }
5074 }))
5075 } else {
5076 Some(Task::ready(Ok(())))
5077 }
5078 })?;
5079 if let Some(task) = spawned_test_task {
5080 task.await?;
5081 }
5082
5083 Ok::<_, anyhow::Error>(())
5084 })
5085 .detach_and_log_err(cx);
5086 }
5087
5088 pub fn confirm_code_action(
5089 &mut self,
5090 action: &ConfirmCodeAction,
5091 window: &mut Window,
5092 cx: &mut Context<Self>,
5093 ) -> Option<Task<Result<()>>> {
5094 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5095
5096 let actions_menu =
5097 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5098 menu
5099 } else {
5100 return None;
5101 };
5102
5103 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5104 let action = actions_menu.actions.get(action_ix)?;
5105 let title = action.label();
5106 let buffer = actions_menu.buffer;
5107 let workspace = self.workspace()?;
5108
5109 match action {
5110 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5111 match resolved_task.task_type() {
5112 task::TaskType::Script => workspace.update(cx, |workspace, cx| {
5113 workspace::tasks::schedule_resolved_task(
5114 workspace,
5115 task_source_kind,
5116 resolved_task,
5117 false,
5118 cx,
5119 );
5120
5121 Some(Task::ready(Ok(())))
5122 }),
5123 task::TaskType::Debug(debug_args) => {
5124 if debug_args.locator.is_some() {
5125 workspace.update(cx, |workspace, cx| {
5126 workspace::tasks::schedule_resolved_task(
5127 workspace,
5128 task_source_kind,
5129 resolved_task,
5130 false,
5131 cx,
5132 );
5133 });
5134
5135 return Some(Task::ready(Ok(())));
5136 }
5137
5138 if let Some(project) = self.project.as_ref() {
5139 project
5140 .update(cx, |project, cx| {
5141 project.start_debug_session(
5142 resolved_task.resolved_debug_adapter_config().unwrap(),
5143 cx,
5144 )
5145 })
5146 .detach_and_log_err(cx);
5147 Some(Task::ready(Ok(())))
5148 } else {
5149 Some(Task::ready(Ok(())))
5150 }
5151 }
5152 }
5153 }
5154 CodeActionsItem::CodeAction {
5155 excerpt_id,
5156 action,
5157 provider,
5158 } => {
5159 let apply_code_action =
5160 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5161 let workspace = workspace.downgrade();
5162 Some(cx.spawn_in(window, async move |editor, cx| {
5163 let project_transaction = apply_code_action.await?;
5164 Self::open_project_transaction(
5165 &editor,
5166 workspace,
5167 project_transaction,
5168 title,
5169 cx,
5170 )
5171 .await
5172 }))
5173 }
5174 }
5175 }
5176
5177 pub async fn open_project_transaction(
5178 this: &WeakEntity<Editor>,
5179 workspace: WeakEntity<Workspace>,
5180 transaction: ProjectTransaction,
5181 title: String,
5182 cx: &mut AsyncWindowContext,
5183 ) -> Result<()> {
5184 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5185 cx.update(|_, cx| {
5186 entries.sort_unstable_by_key(|(buffer, _)| {
5187 buffer.read(cx).file().map(|f| f.path().clone())
5188 });
5189 })?;
5190
5191 // If the project transaction's edits are all contained within this editor, then
5192 // avoid opening a new editor to display them.
5193
5194 if let Some((buffer, transaction)) = entries.first() {
5195 if entries.len() == 1 {
5196 let excerpt = this.update(cx, |editor, cx| {
5197 editor
5198 .buffer()
5199 .read(cx)
5200 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5201 })?;
5202 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5203 if excerpted_buffer == *buffer {
5204 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5205 let excerpt_range = excerpt_range.to_offset(buffer);
5206 buffer
5207 .edited_ranges_for_transaction::<usize>(transaction)
5208 .all(|range| {
5209 excerpt_range.start <= range.start
5210 && excerpt_range.end >= range.end
5211 })
5212 })?;
5213
5214 if all_edits_within_excerpt {
5215 return Ok(());
5216 }
5217 }
5218 }
5219 }
5220 } else {
5221 return Ok(());
5222 }
5223
5224 let mut ranges_to_highlight = Vec::new();
5225 let excerpt_buffer = cx.new(|cx| {
5226 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5227 for (buffer_handle, transaction) in &entries {
5228 let edited_ranges = buffer_handle
5229 .read(cx)
5230 .edited_ranges_for_transaction::<Point>(transaction)
5231 .collect::<Vec<_>>();
5232 let (ranges, _) = multibuffer.set_excerpts_for_path(
5233 PathKey::for_buffer(buffer_handle, cx),
5234 buffer_handle.clone(),
5235 edited_ranges,
5236 DEFAULT_MULTIBUFFER_CONTEXT,
5237 cx,
5238 );
5239
5240 ranges_to_highlight.extend(ranges);
5241 }
5242 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5243 multibuffer
5244 })?;
5245
5246 workspace.update_in(cx, |workspace, window, cx| {
5247 let project = workspace.project().clone();
5248 let editor =
5249 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5250 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5251 editor.update(cx, |editor, cx| {
5252 editor.highlight_background::<Self>(
5253 &ranges_to_highlight,
5254 |theme| theme.editor_highlighted_line_background,
5255 cx,
5256 );
5257 });
5258 })?;
5259
5260 Ok(())
5261 }
5262
5263 pub fn clear_code_action_providers(&mut self) {
5264 self.code_action_providers.clear();
5265 self.available_code_actions.take();
5266 }
5267
5268 pub fn add_code_action_provider(
5269 &mut self,
5270 provider: Rc<dyn CodeActionProvider>,
5271 window: &mut Window,
5272 cx: &mut Context<Self>,
5273 ) {
5274 if self
5275 .code_action_providers
5276 .iter()
5277 .any(|existing_provider| existing_provider.id() == provider.id())
5278 {
5279 return;
5280 }
5281
5282 self.code_action_providers.push(provider);
5283 self.refresh_code_actions(window, cx);
5284 }
5285
5286 pub fn remove_code_action_provider(
5287 &mut self,
5288 id: Arc<str>,
5289 window: &mut Window,
5290 cx: &mut Context<Self>,
5291 ) {
5292 self.code_action_providers
5293 .retain(|provider| provider.id() != id);
5294 self.refresh_code_actions(window, cx);
5295 }
5296
5297 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5298 let newest_selection = self.selections.newest_anchor().clone();
5299 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5300 let buffer = self.buffer.read(cx);
5301 if newest_selection.head().diff_base_anchor.is_some() {
5302 return None;
5303 }
5304 let (start_buffer, start) =
5305 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5306 let (end_buffer, end) =
5307 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5308 if start_buffer != end_buffer {
5309 return None;
5310 }
5311
5312 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5313 cx.background_executor()
5314 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5315 .await;
5316
5317 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5318 let providers = this.code_action_providers.clone();
5319 let tasks = this
5320 .code_action_providers
5321 .iter()
5322 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5323 .collect::<Vec<_>>();
5324 (providers, tasks)
5325 })?;
5326
5327 let mut actions = Vec::new();
5328 for (provider, provider_actions) in
5329 providers.into_iter().zip(future::join_all(tasks).await)
5330 {
5331 if let Some(provider_actions) = provider_actions.log_err() {
5332 actions.extend(provider_actions.into_iter().map(|action| {
5333 AvailableCodeAction {
5334 excerpt_id: newest_selection.start.excerpt_id,
5335 action,
5336 provider: provider.clone(),
5337 }
5338 }));
5339 }
5340 }
5341
5342 this.update(cx, |this, cx| {
5343 this.available_code_actions = if actions.is_empty() {
5344 None
5345 } else {
5346 Some((
5347 Location {
5348 buffer: start_buffer,
5349 range: start..end,
5350 },
5351 actions.into(),
5352 ))
5353 };
5354 cx.notify();
5355 })
5356 }));
5357 None
5358 }
5359
5360 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5361 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5362 self.show_git_blame_inline = false;
5363
5364 self.show_git_blame_inline_delay_task =
5365 Some(cx.spawn_in(window, async move |this, cx| {
5366 cx.background_executor().timer(delay).await;
5367
5368 this.update(cx, |this, cx| {
5369 this.show_git_blame_inline = true;
5370 cx.notify();
5371 })
5372 .log_err();
5373 }));
5374 }
5375 }
5376
5377 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5378 if self.pending_rename.is_some() {
5379 return None;
5380 }
5381
5382 let provider = self.semantics_provider.clone()?;
5383 let buffer = self.buffer.read(cx);
5384 let newest_selection = self.selections.newest_anchor().clone();
5385 let cursor_position = newest_selection.head();
5386 let (cursor_buffer, cursor_buffer_position) =
5387 buffer.text_anchor_for_position(cursor_position, cx)?;
5388 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5389 if cursor_buffer != tail_buffer {
5390 return None;
5391 }
5392 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5393 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5394 cx.background_executor()
5395 .timer(Duration::from_millis(debounce))
5396 .await;
5397
5398 let highlights = if let Some(highlights) = cx
5399 .update(|cx| {
5400 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5401 })
5402 .ok()
5403 .flatten()
5404 {
5405 highlights.await.log_err()
5406 } else {
5407 None
5408 };
5409
5410 if let Some(highlights) = highlights {
5411 this.update(cx, |this, cx| {
5412 if this.pending_rename.is_some() {
5413 return;
5414 }
5415
5416 let buffer_id = cursor_position.buffer_id;
5417 let buffer = this.buffer.read(cx);
5418 if !buffer
5419 .text_anchor_for_position(cursor_position, cx)
5420 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5421 {
5422 return;
5423 }
5424
5425 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5426 let mut write_ranges = Vec::new();
5427 let mut read_ranges = Vec::new();
5428 for highlight in highlights {
5429 for (excerpt_id, excerpt_range) in
5430 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5431 {
5432 let start = highlight
5433 .range
5434 .start
5435 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5436 let end = highlight
5437 .range
5438 .end
5439 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5440 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5441 continue;
5442 }
5443
5444 let range = Anchor {
5445 buffer_id,
5446 excerpt_id,
5447 text_anchor: start,
5448 diff_base_anchor: None,
5449 }..Anchor {
5450 buffer_id,
5451 excerpt_id,
5452 text_anchor: end,
5453 diff_base_anchor: None,
5454 };
5455 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5456 write_ranges.push(range);
5457 } else {
5458 read_ranges.push(range);
5459 }
5460 }
5461 }
5462
5463 this.highlight_background::<DocumentHighlightRead>(
5464 &read_ranges,
5465 |theme| theme.editor_document_highlight_read_background,
5466 cx,
5467 );
5468 this.highlight_background::<DocumentHighlightWrite>(
5469 &write_ranges,
5470 |theme| theme.editor_document_highlight_write_background,
5471 cx,
5472 );
5473 cx.notify();
5474 })
5475 .log_err();
5476 }
5477 }));
5478 None
5479 }
5480
5481 fn prepare_highlight_query_from_selection(
5482 &mut self,
5483 cx: &mut Context<Editor>,
5484 ) -> Option<(String, Range<Anchor>)> {
5485 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5486 return None;
5487 }
5488 if !EditorSettings::get_global(cx).selection_highlight {
5489 return None;
5490 }
5491 if self.selections.count() != 1 || self.selections.line_mode {
5492 return None;
5493 }
5494 let selection = self.selections.newest::<Point>(cx);
5495 if selection.is_empty() || selection.start.row != selection.end.row {
5496 return None;
5497 }
5498 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5499 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5500 let query = multi_buffer_snapshot
5501 .text_for_range(selection_anchor_range.clone())
5502 .collect::<String>();
5503 if query.trim().is_empty() {
5504 return None;
5505 }
5506 Some((query, selection_anchor_range))
5507 }
5508
5509 fn update_selection_occurrence_highlights(
5510 &mut self,
5511 query_text: String,
5512 query_range: Range<Anchor>,
5513 multi_buffer_range_to_query: Range<Point>,
5514 use_debounce: bool,
5515 window: &mut Window,
5516 cx: &mut Context<Editor>,
5517 ) -> Task<()> {
5518 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5519 cx.spawn_in(window, async move |editor, cx| {
5520 if use_debounce {
5521 cx.background_executor()
5522 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5523 .await;
5524 }
5525 let match_task = cx.background_spawn(async move {
5526 let buffer_ranges = multi_buffer_snapshot
5527 .range_to_buffer_ranges(multi_buffer_range_to_query)
5528 .into_iter()
5529 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5530 let mut match_ranges = Vec::new();
5531 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5532 match_ranges.extend(
5533 project::search::SearchQuery::text(
5534 query_text.clone(),
5535 false,
5536 false,
5537 false,
5538 Default::default(),
5539 Default::default(),
5540 false,
5541 None,
5542 )
5543 .unwrap()
5544 .search(&buffer_snapshot, Some(search_range.clone()))
5545 .await
5546 .into_iter()
5547 .filter_map(|match_range| {
5548 let match_start = buffer_snapshot
5549 .anchor_after(search_range.start + match_range.start);
5550 let match_end =
5551 buffer_snapshot.anchor_before(search_range.start + match_range.end);
5552 let match_anchor_range = Anchor::range_in_buffer(
5553 excerpt_id,
5554 buffer_snapshot.remote_id(),
5555 match_start..match_end,
5556 );
5557 (match_anchor_range != query_range).then_some(match_anchor_range)
5558 }),
5559 );
5560 }
5561 match_ranges
5562 });
5563 let match_ranges = match_task.await;
5564 editor
5565 .update_in(cx, |editor, _, cx| {
5566 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5567 if !match_ranges.is_empty() {
5568 editor.highlight_background::<SelectedTextHighlight>(
5569 &match_ranges,
5570 |theme| theme.editor_document_highlight_bracket_background,
5571 cx,
5572 )
5573 }
5574 })
5575 .log_err();
5576 })
5577 }
5578
5579 fn refresh_selected_text_highlights(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
5580 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5581 else {
5582 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5583 self.quick_selection_highlight_task.take();
5584 self.debounced_selection_highlight_task.take();
5585 return;
5586 };
5587 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5588 if self
5589 .quick_selection_highlight_task
5590 .as_ref()
5591 .map_or(true, |(prev_anchor_range, _)| {
5592 prev_anchor_range != &query_range
5593 })
5594 {
5595 let multi_buffer_visible_start = self
5596 .scroll_manager
5597 .anchor()
5598 .anchor
5599 .to_point(&multi_buffer_snapshot);
5600 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5601 multi_buffer_visible_start
5602 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5603 Bias::Left,
5604 );
5605 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5606 self.quick_selection_highlight_task = Some((
5607 query_range.clone(),
5608 self.update_selection_occurrence_highlights(
5609 query_text.clone(),
5610 query_range.clone(),
5611 multi_buffer_visible_range,
5612 false,
5613 window,
5614 cx,
5615 ),
5616 ));
5617 }
5618 if self
5619 .debounced_selection_highlight_task
5620 .as_ref()
5621 .map_or(true, |(prev_anchor_range, _)| {
5622 prev_anchor_range != &query_range
5623 })
5624 {
5625 let multi_buffer_start = multi_buffer_snapshot
5626 .anchor_before(0)
5627 .to_point(&multi_buffer_snapshot);
5628 let multi_buffer_end = multi_buffer_snapshot
5629 .anchor_after(multi_buffer_snapshot.len())
5630 .to_point(&multi_buffer_snapshot);
5631 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
5632 self.debounced_selection_highlight_task = Some((
5633 query_range.clone(),
5634 self.update_selection_occurrence_highlights(
5635 query_text,
5636 query_range,
5637 multi_buffer_full_range,
5638 true,
5639 window,
5640 cx,
5641 ),
5642 ));
5643 }
5644 }
5645
5646 pub fn refresh_inline_completion(
5647 &mut self,
5648 debounce: bool,
5649 user_requested: bool,
5650 window: &mut Window,
5651 cx: &mut Context<Self>,
5652 ) -> Option<()> {
5653 let provider = self.edit_prediction_provider()?;
5654 let cursor = self.selections.newest_anchor().head();
5655 let (buffer, cursor_buffer_position) =
5656 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5657
5658 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5659 self.discard_inline_completion(false, cx);
5660 return None;
5661 }
5662
5663 if !user_requested
5664 && (!self.should_show_edit_predictions()
5665 || !self.is_focused(window)
5666 || buffer.read(cx).is_empty())
5667 {
5668 self.discard_inline_completion(false, cx);
5669 return None;
5670 }
5671
5672 self.update_visible_inline_completion(window, cx);
5673 provider.refresh(
5674 self.project.clone(),
5675 buffer,
5676 cursor_buffer_position,
5677 debounce,
5678 cx,
5679 );
5680 Some(())
5681 }
5682
5683 fn show_edit_predictions_in_menu(&self) -> bool {
5684 match self.edit_prediction_settings {
5685 EditPredictionSettings::Disabled => false,
5686 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5687 }
5688 }
5689
5690 pub fn edit_predictions_enabled(&self) -> bool {
5691 match self.edit_prediction_settings {
5692 EditPredictionSettings::Disabled => false,
5693 EditPredictionSettings::Enabled { .. } => true,
5694 }
5695 }
5696
5697 fn edit_prediction_requires_modifier(&self) -> bool {
5698 match self.edit_prediction_settings {
5699 EditPredictionSettings::Disabled => false,
5700 EditPredictionSettings::Enabled {
5701 preview_requires_modifier,
5702 ..
5703 } => preview_requires_modifier,
5704 }
5705 }
5706
5707 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5708 if self.edit_prediction_provider.is_none() {
5709 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5710 } else {
5711 let selection = self.selections.newest_anchor();
5712 let cursor = selection.head();
5713
5714 if let Some((buffer, cursor_buffer_position)) =
5715 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5716 {
5717 self.edit_prediction_settings =
5718 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5719 }
5720 }
5721 }
5722
5723 fn edit_prediction_settings_at_position(
5724 &self,
5725 buffer: &Entity<Buffer>,
5726 buffer_position: language::Anchor,
5727 cx: &App,
5728 ) -> EditPredictionSettings {
5729 if !self.mode.is_full()
5730 || !self.show_inline_completions_override.unwrap_or(true)
5731 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5732 {
5733 return EditPredictionSettings::Disabled;
5734 }
5735
5736 let buffer = buffer.read(cx);
5737
5738 let file = buffer.file();
5739
5740 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5741 return EditPredictionSettings::Disabled;
5742 };
5743
5744 let by_provider = matches!(
5745 self.menu_inline_completions_policy,
5746 MenuInlineCompletionsPolicy::ByProvider
5747 );
5748
5749 let show_in_menu = by_provider
5750 && self
5751 .edit_prediction_provider
5752 .as_ref()
5753 .map_or(false, |provider| {
5754 provider.provider.show_completions_in_menu()
5755 });
5756
5757 let preview_requires_modifier =
5758 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5759
5760 EditPredictionSettings::Enabled {
5761 show_in_menu,
5762 preview_requires_modifier,
5763 }
5764 }
5765
5766 fn should_show_edit_predictions(&self) -> bool {
5767 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5768 }
5769
5770 pub fn edit_prediction_preview_is_active(&self) -> bool {
5771 matches!(
5772 self.edit_prediction_preview,
5773 EditPredictionPreview::Active { .. }
5774 )
5775 }
5776
5777 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5778 let cursor = self.selections.newest_anchor().head();
5779 if let Some((buffer, cursor_position)) =
5780 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5781 {
5782 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5783 } else {
5784 false
5785 }
5786 }
5787
5788 fn edit_predictions_enabled_in_buffer(
5789 &self,
5790 buffer: &Entity<Buffer>,
5791 buffer_position: language::Anchor,
5792 cx: &App,
5793 ) -> bool {
5794 maybe!({
5795 if self.read_only(cx) {
5796 return Some(false);
5797 }
5798 let provider = self.edit_prediction_provider()?;
5799 if !provider.is_enabled(&buffer, buffer_position, cx) {
5800 return Some(false);
5801 }
5802 let buffer = buffer.read(cx);
5803 let Some(file) = buffer.file() else {
5804 return Some(true);
5805 };
5806 let settings = all_language_settings(Some(file), cx);
5807 Some(settings.edit_predictions_enabled_for_file(file, cx))
5808 })
5809 .unwrap_or(false)
5810 }
5811
5812 fn cycle_inline_completion(
5813 &mut self,
5814 direction: Direction,
5815 window: &mut Window,
5816 cx: &mut Context<Self>,
5817 ) -> Option<()> {
5818 let provider = self.edit_prediction_provider()?;
5819 let cursor = self.selections.newest_anchor().head();
5820 let (buffer, cursor_buffer_position) =
5821 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5822 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5823 return None;
5824 }
5825
5826 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5827 self.update_visible_inline_completion(window, cx);
5828
5829 Some(())
5830 }
5831
5832 pub fn show_inline_completion(
5833 &mut self,
5834 _: &ShowEditPrediction,
5835 window: &mut Window,
5836 cx: &mut Context<Self>,
5837 ) {
5838 if !self.has_active_inline_completion() {
5839 self.refresh_inline_completion(false, true, window, cx);
5840 return;
5841 }
5842
5843 self.update_visible_inline_completion(window, cx);
5844 }
5845
5846 pub fn display_cursor_names(
5847 &mut self,
5848 _: &DisplayCursorNames,
5849 window: &mut Window,
5850 cx: &mut Context<Self>,
5851 ) {
5852 self.show_cursor_names(window, cx);
5853 }
5854
5855 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5856 self.show_cursor_names = true;
5857 cx.notify();
5858 cx.spawn_in(window, async move |this, cx| {
5859 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5860 this.update(cx, |this, cx| {
5861 this.show_cursor_names = false;
5862 cx.notify()
5863 })
5864 .ok()
5865 })
5866 .detach();
5867 }
5868
5869 pub fn next_edit_prediction(
5870 &mut self,
5871 _: &NextEditPrediction,
5872 window: &mut Window,
5873 cx: &mut Context<Self>,
5874 ) {
5875 if self.has_active_inline_completion() {
5876 self.cycle_inline_completion(Direction::Next, window, cx);
5877 } else {
5878 let is_copilot_disabled = self
5879 .refresh_inline_completion(false, true, window, cx)
5880 .is_none();
5881 if is_copilot_disabled {
5882 cx.propagate();
5883 }
5884 }
5885 }
5886
5887 pub fn previous_edit_prediction(
5888 &mut self,
5889 _: &PreviousEditPrediction,
5890 window: &mut Window,
5891 cx: &mut Context<Self>,
5892 ) {
5893 if self.has_active_inline_completion() {
5894 self.cycle_inline_completion(Direction::Prev, window, cx);
5895 } else {
5896 let is_copilot_disabled = self
5897 .refresh_inline_completion(false, true, window, cx)
5898 .is_none();
5899 if is_copilot_disabled {
5900 cx.propagate();
5901 }
5902 }
5903 }
5904
5905 pub fn accept_edit_prediction(
5906 &mut self,
5907 _: &AcceptEditPrediction,
5908 window: &mut Window,
5909 cx: &mut Context<Self>,
5910 ) {
5911 if self.show_edit_predictions_in_menu() {
5912 self.hide_context_menu(window, cx);
5913 }
5914
5915 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5916 return;
5917 };
5918
5919 self.report_inline_completion_event(
5920 active_inline_completion.completion_id.clone(),
5921 true,
5922 cx,
5923 );
5924
5925 match &active_inline_completion.completion {
5926 InlineCompletion::Move { target, .. } => {
5927 let target = *target;
5928
5929 if let Some(position_map) = &self.last_position_map {
5930 if position_map
5931 .visible_row_range
5932 .contains(&target.to_display_point(&position_map.snapshot).row())
5933 || !self.edit_prediction_requires_modifier()
5934 {
5935 self.unfold_ranges(&[target..target], true, false, cx);
5936 // Note that this is also done in vim's handler of the Tab action.
5937 self.change_selections(
5938 Some(Autoscroll::newest()),
5939 window,
5940 cx,
5941 |selections| {
5942 selections.select_anchor_ranges([target..target]);
5943 },
5944 );
5945 self.clear_row_highlights::<EditPredictionPreview>();
5946
5947 self.edit_prediction_preview
5948 .set_previous_scroll_position(None);
5949 } else {
5950 self.edit_prediction_preview
5951 .set_previous_scroll_position(Some(
5952 position_map.snapshot.scroll_anchor,
5953 ));
5954
5955 self.highlight_rows::<EditPredictionPreview>(
5956 target..target,
5957 cx.theme().colors().editor_highlighted_line_background,
5958 true,
5959 cx,
5960 );
5961 self.request_autoscroll(Autoscroll::fit(), cx);
5962 }
5963 }
5964 }
5965 InlineCompletion::Edit { edits, .. } => {
5966 if let Some(provider) = self.edit_prediction_provider() {
5967 provider.accept(cx);
5968 }
5969
5970 let snapshot = self.buffer.read(cx).snapshot(cx);
5971 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5972
5973 self.buffer.update(cx, |buffer, cx| {
5974 buffer.edit(edits.iter().cloned(), None, cx)
5975 });
5976
5977 self.change_selections(None, window, cx, |s| {
5978 s.select_anchor_ranges([last_edit_end..last_edit_end])
5979 });
5980
5981 self.update_visible_inline_completion(window, cx);
5982 if self.active_inline_completion.is_none() {
5983 self.refresh_inline_completion(true, true, window, cx);
5984 }
5985
5986 cx.notify();
5987 }
5988 }
5989
5990 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5991 }
5992
5993 pub fn accept_partial_inline_completion(
5994 &mut self,
5995 _: &AcceptPartialEditPrediction,
5996 window: &mut Window,
5997 cx: &mut Context<Self>,
5998 ) {
5999 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6000 return;
6001 };
6002 if self.selections.count() != 1 {
6003 return;
6004 }
6005
6006 self.report_inline_completion_event(
6007 active_inline_completion.completion_id.clone(),
6008 true,
6009 cx,
6010 );
6011
6012 match &active_inline_completion.completion {
6013 InlineCompletion::Move { target, .. } => {
6014 let target = *target;
6015 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6016 selections.select_anchor_ranges([target..target]);
6017 });
6018 }
6019 InlineCompletion::Edit { edits, .. } => {
6020 // Find an insertion that starts at the cursor position.
6021 let snapshot = self.buffer.read(cx).snapshot(cx);
6022 let cursor_offset = self.selections.newest::<usize>(cx).head();
6023 let insertion = edits.iter().find_map(|(range, text)| {
6024 let range = range.to_offset(&snapshot);
6025 if range.is_empty() && range.start == cursor_offset {
6026 Some(text)
6027 } else {
6028 None
6029 }
6030 });
6031
6032 if let Some(text) = insertion {
6033 let mut partial_completion = text
6034 .chars()
6035 .by_ref()
6036 .take_while(|c| c.is_alphabetic())
6037 .collect::<String>();
6038 if partial_completion.is_empty() {
6039 partial_completion = text
6040 .chars()
6041 .by_ref()
6042 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6043 .collect::<String>();
6044 }
6045
6046 cx.emit(EditorEvent::InputHandled {
6047 utf16_range_to_replace: None,
6048 text: partial_completion.clone().into(),
6049 });
6050
6051 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6052
6053 self.refresh_inline_completion(true, true, window, cx);
6054 cx.notify();
6055 } else {
6056 self.accept_edit_prediction(&Default::default(), window, cx);
6057 }
6058 }
6059 }
6060 }
6061
6062 fn discard_inline_completion(
6063 &mut self,
6064 should_report_inline_completion_event: bool,
6065 cx: &mut Context<Self>,
6066 ) -> bool {
6067 if should_report_inline_completion_event {
6068 let completion_id = self
6069 .active_inline_completion
6070 .as_ref()
6071 .and_then(|active_completion| active_completion.completion_id.clone());
6072
6073 self.report_inline_completion_event(completion_id, false, cx);
6074 }
6075
6076 if let Some(provider) = self.edit_prediction_provider() {
6077 provider.discard(cx);
6078 }
6079
6080 self.take_active_inline_completion(cx)
6081 }
6082
6083 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6084 let Some(provider) = self.edit_prediction_provider() else {
6085 return;
6086 };
6087
6088 let Some((_, buffer, _)) = self
6089 .buffer
6090 .read(cx)
6091 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6092 else {
6093 return;
6094 };
6095
6096 let extension = buffer
6097 .read(cx)
6098 .file()
6099 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6100
6101 let event_type = match accepted {
6102 true => "Edit Prediction Accepted",
6103 false => "Edit Prediction Discarded",
6104 };
6105 telemetry::event!(
6106 event_type,
6107 provider = provider.name(),
6108 prediction_id = id,
6109 suggestion_accepted = accepted,
6110 file_extension = extension,
6111 );
6112 }
6113
6114 pub fn has_active_inline_completion(&self) -> bool {
6115 self.active_inline_completion.is_some()
6116 }
6117
6118 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6119 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6120 return false;
6121 };
6122
6123 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6124 self.clear_highlights::<InlineCompletionHighlight>(cx);
6125 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6126 true
6127 }
6128
6129 /// Returns true when we're displaying the edit prediction popover below the cursor
6130 /// like we are not previewing and the LSP autocomplete menu is visible
6131 /// or we are in `when_holding_modifier` mode.
6132 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6133 if self.edit_prediction_preview_is_active()
6134 || !self.show_edit_predictions_in_menu()
6135 || !self.edit_predictions_enabled()
6136 {
6137 return false;
6138 }
6139
6140 if self.has_visible_completions_menu() {
6141 return true;
6142 }
6143
6144 has_completion && self.edit_prediction_requires_modifier()
6145 }
6146
6147 fn handle_modifiers_changed(
6148 &mut self,
6149 modifiers: Modifiers,
6150 position_map: &PositionMap,
6151 window: &mut Window,
6152 cx: &mut Context<Self>,
6153 ) {
6154 if self.show_edit_predictions_in_menu() {
6155 self.update_edit_prediction_preview(&modifiers, window, cx);
6156 }
6157
6158 self.update_selection_mode(&modifiers, position_map, window, cx);
6159
6160 let mouse_position = window.mouse_position();
6161 if !position_map.text_hitbox.is_hovered(window) {
6162 return;
6163 }
6164
6165 self.update_hovered_link(
6166 position_map.point_for_position(mouse_position),
6167 &position_map.snapshot,
6168 modifiers,
6169 window,
6170 cx,
6171 )
6172 }
6173
6174 fn update_selection_mode(
6175 &mut self,
6176 modifiers: &Modifiers,
6177 position_map: &PositionMap,
6178 window: &mut Window,
6179 cx: &mut Context<Self>,
6180 ) {
6181 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6182 return;
6183 }
6184
6185 let mouse_position = window.mouse_position();
6186 let point_for_position = position_map.point_for_position(mouse_position);
6187 let position = point_for_position.previous_valid;
6188
6189 self.select(
6190 SelectPhase::BeginColumnar {
6191 position,
6192 reset: false,
6193 goal_column: point_for_position.exact_unclipped.column(),
6194 },
6195 window,
6196 cx,
6197 );
6198 }
6199
6200 fn update_edit_prediction_preview(
6201 &mut self,
6202 modifiers: &Modifiers,
6203 window: &mut Window,
6204 cx: &mut Context<Self>,
6205 ) {
6206 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6207 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6208 return;
6209 };
6210
6211 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6212 if matches!(
6213 self.edit_prediction_preview,
6214 EditPredictionPreview::Inactive { .. }
6215 ) {
6216 self.edit_prediction_preview = EditPredictionPreview::Active {
6217 previous_scroll_position: None,
6218 since: Instant::now(),
6219 };
6220
6221 self.update_visible_inline_completion(window, cx);
6222 cx.notify();
6223 }
6224 } else if let EditPredictionPreview::Active {
6225 previous_scroll_position,
6226 since,
6227 } = self.edit_prediction_preview
6228 {
6229 if let (Some(previous_scroll_position), Some(position_map)) =
6230 (previous_scroll_position, self.last_position_map.as_ref())
6231 {
6232 self.set_scroll_position(
6233 previous_scroll_position
6234 .scroll_position(&position_map.snapshot.display_snapshot),
6235 window,
6236 cx,
6237 );
6238 }
6239
6240 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6241 released_too_fast: since.elapsed() < Duration::from_millis(200),
6242 };
6243 self.clear_row_highlights::<EditPredictionPreview>();
6244 self.update_visible_inline_completion(window, cx);
6245 cx.notify();
6246 }
6247 }
6248
6249 fn update_visible_inline_completion(
6250 &mut self,
6251 _window: &mut Window,
6252 cx: &mut Context<Self>,
6253 ) -> Option<()> {
6254 let selection = self.selections.newest_anchor();
6255 let cursor = selection.head();
6256 let multibuffer = self.buffer.read(cx).snapshot(cx);
6257 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6258 let excerpt_id = cursor.excerpt_id;
6259
6260 let show_in_menu = self.show_edit_predictions_in_menu();
6261 let completions_menu_has_precedence = !show_in_menu
6262 && (self.context_menu.borrow().is_some()
6263 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6264
6265 if completions_menu_has_precedence
6266 || !offset_selection.is_empty()
6267 || self
6268 .active_inline_completion
6269 .as_ref()
6270 .map_or(false, |completion| {
6271 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6272 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6273 !invalidation_range.contains(&offset_selection.head())
6274 })
6275 {
6276 self.discard_inline_completion(false, cx);
6277 return None;
6278 }
6279
6280 self.take_active_inline_completion(cx);
6281 let Some(provider) = self.edit_prediction_provider() else {
6282 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6283 return None;
6284 };
6285
6286 let (buffer, cursor_buffer_position) =
6287 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6288
6289 self.edit_prediction_settings =
6290 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6291
6292 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6293
6294 if self.edit_prediction_indent_conflict {
6295 let cursor_point = cursor.to_point(&multibuffer);
6296
6297 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6298
6299 if let Some((_, indent)) = indents.iter().next() {
6300 if indent.len == cursor_point.column {
6301 self.edit_prediction_indent_conflict = false;
6302 }
6303 }
6304 }
6305
6306 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6307 let edits = inline_completion
6308 .edits
6309 .into_iter()
6310 .flat_map(|(range, new_text)| {
6311 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6312 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6313 Some((start..end, new_text))
6314 })
6315 .collect::<Vec<_>>();
6316 if edits.is_empty() {
6317 return None;
6318 }
6319
6320 let first_edit_start = edits.first().unwrap().0.start;
6321 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6322 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6323
6324 let last_edit_end = edits.last().unwrap().0.end;
6325 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6326 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6327
6328 let cursor_row = cursor.to_point(&multibuffer).row;
6329
6330 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6331
6332 let mut inlay_ids = Vec::new();
6333 let invalidation_row_range;
6334 let move_invalidation_row_range = if cursor_row < edit_start_row {
6335 Some(cursor_row..edit_end_row)
6336 } else if cursor_row > edit_end_row {
6337 Some(edit_start_row..cursor_row)
6338 } else {
6339 None
6340 };
6341 let is_move =
6342 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6343 let completion = if is_move {
6344 invalidation_row_range =
6345 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6346 let target = first_edit_start;
6347 InlineCompletion::Move { target, snapshot }
6348 } else {
6349 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6350 && !self.inline_completions_hidden_for_vim_mode;
6351
6352 if show_completions_in_buffer {
6353 if edits
6354 .iter()
6355 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6356 {
6357 let mut inlays = Vec::new();
6358 for (range, new_text) in &edits {
6359 let inlay = Inlay::inline_completion(
6360 post_inc(&mut self.next_inlay_id),
6361 range.start,
6362 new_text.as_str(),
6363 );
6364 inlay_ids.push(inlay.id);
6365 inlays.push(inlay);
6366 }
6367
6368 self.splice_inlays(&[], inlays, cx);
6369 } else {
6370 let background_color = cx.theme().status().deleted_background;
6371 self.highlight_text::<InlineCompletionHighlight>(
6372 edits.iter().map(|(range, _)| range.clone()).collect(),
6373 HighlightStyle {
6374 background_color: Some(background_color),
6375 ..Default::default()
6376 },
6377 cx,
6378 );
6379 }
6380 }
6381
6382 invalidation_row_range = edit_start_row..edit_end_row;
6383
6384 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6385 if provider.show_tab_accept_marker() {
6386 EditDisplayMode::TabAccept
6387 } else {
6388 EditDisplayMode::Inline
6389 }
6390 } else {
6391 EditDisplayMode::DiffPopover
6392 };
6393
6394 InlineCompletion::Edit {
6395 edits,
6396 edit_preview: inline_completion.edit_preview,
6397 display_mode,
6398 snapshot,
6399 }
6400 };
6401
6402 let invalidation_range = multibuffer
6403 .anchor_before(Point::new(invalidation_row_range.start, 0))
6404 ..multibuffer.anchor_after(Point::new(
6405 invalidation_row_range.end,
6406 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6407 ));
6408
6409 self.stale_inline_completion_in_menu = None;
6410 self.active_inline_completion = Some(InlineCompletionState {
6411 inlay_ids,
6412 completion,
6413 completion_id: inline_completion.id,
6414 invalidation_range,
6415 });
6416
6417 cx.notify();
6418
6419 Some(())
6420 }
6421
6422 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6423 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6424 }
6425
6426 fn render_code_actions_indicator(
6427 &self,
6428 _style: &EditorStyle,
6429 row: DisplayRow,
6430 is_active: bool,
6431 breakpoint: Option<&(Anchor, Breakpoint)>,
6432 cx: &mut Context<Self>,
6433 ) -> Option<IconButton> {
6434 let color = Color::Muted;
6435 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6436 let show_tooltip = !self.context_menu_visible();
6437
6438 if self.available_code_actions.is_some() {
6439 Some(
6440 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6441 .shape(ui::IconButtonShape::Square)
6442 .icon_size(IconSize::XSmall)
6443 .icon_color(color)
6444 .toggle_state(is_active)
6445 .when(show_tooltip, |this| {
6446 this.tooltip({
6447 let focus_handle = self.focus_handle.clone();
6448 move |window, cx| {
6449 Tooltip::for_action_in(
6450 "Toggle Code Actions",
6451 &ToggleCodeActions {
6452 deployed_from_indicator: None,
6453 },
6454 &focus_handle,
6455 window,
6456 cx,
6457 )
6458 }
6459 })
6460 })
6461 .on_click(cx.listener(move |editor, _e, window, cx| {
6462 window.focus(&editor.focus_handle(cx));
6463 editor.toggle_code_actions(
6464 &ToggleCodeActions {
6465 deployed_from_indicator: Some(row),
6466 },
6467 window,
6468 cx,
6469 );
6470 }))
6471 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6472 editor.set_breakpoint_context_menu(
6473 row,
6474 position,
6475 event.down.position,
6476 window,
6477 cx,
6478 );
6479 })),
6480 )
6481 } else {
6482 None
6483 }
6484 }
6485
6486 fn clear_tasks(&mut self) {
6487 self.tasks.clear()
6488 }
6489
6490 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6491 if self.tasks.insert(key, value).is_some() {
6492 // This case should hopefully be rare, but just in case...
6493 log::error!(
6494 "multiple different run targets found on a single line, only the last target will be rendered"
6495 )
6496 }
6497 }
6498
6499 /// Get all display points of breakpoints that will be rendered within editor
6500 ///
6501 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6502 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6503 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6504 fn active_breakpoints(
6505 &self,
6506 range: Range<DisplayRow>,
6507 window: &mut Window,
6508 cx: &mut Context<Self>,
6509 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6510 let mut breakpoint_display_points = HashMap::default();
6511
6512 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6513 return breakpoint_display_points;
6514 };
6515
6516 let snapshot = self.snapshot(window, cx);
6517
6518 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6519 let Some(project) = self.project.as_ref() else {
6520 return breakpoint_display_points;
6521 };
6522
6523 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6524 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6525
6526 for (buffer_snapshot, range, excerpt_id) in
6527 multi_buffer_snapshot.range_to_buffer_ranges(range)
6528 {
6529 let Some(buffer) = project.read_with(cx, |this, cx| {
6530 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6531 }) else {
6532 continue;
6533 };
6534 let breakpoints = breakpoint_store.read(cx).breakpoints(
6535 &buffer,
6536 Some(
6537 buffer_snapshot.anchor_before(range.start)
6538 ..buffer_snapshot.anchor_after(range.end),
6539 ),
6540 buffer_snapshot,
6541 cx,
6542 );
6543 for (anchor, breakpoint) in breakpoints {
6544 let multi_buffer_anchor =
6545 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6546 let position = multi_buffer_anchor
6547 .to_point(&multi_buffer_snapshot)
6548 .to_display_point(&snapshot);
6549
6550 breakpoint_display_points
6551 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6552 }
6553 }
6554
6555 breakpoint_display_points
6556 }
6557
6558 fn breakpoint_context_menu(
6559 &self,
6560 anchor: Anchor,
6561 window: &mut Window,
6562 cx: &mut Context<Self>,
6563 ) -> Entity<ui::ContextMenu> {
6564 let weak_editor = cx.weak_entity();
6565 let focus_handle = self.focus_handle(cx);
6566
6567 let row = self
6568 .buffer
6569 .read(cx)
6570 .snapshot(cx)
6571 .summary_for_anchor::<Point>(&anchor)
6572 .row;
6573
6574 let breakpoint = self
6575 .breakpoint_at_row(row, window, cx)
6576 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6577
6578 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6579 "Edit Log Breakpoint"
6580 } else {
6581 "Set Log Breakpoint"
6582 };
6583
6584 let condition_breakpoint_msg = if breakpoint
6585 .as_ref()
6586 .is_some_and(|bp| bp.1.condition.is_some())
6587 {
6588 "Edit Condition Breakpoint"
6589 } else {
6590 "Set Condition Breakpoint"
6591 };
6592
6593 let hit_condition_breakpoint_msg = if breakpoint
6594 .as_ref()
6595 .is_some_and(|bp| bp.1.hit_condition.is_some())
6596 {
6597 "Edit Hit Condition Breakpoint"
6598 } else {
6599 "Set Hit Condition Breakpoint"
6600 };
6601
6602 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6603 "Unset Breakpoint"
6604 } else {
6605 "Set Breakpoint"
6606 };
6607
6608 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6609 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6610
6611 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6612 BreakpointState::Enabled => Some("Disable"),
6613 BreakpointState::Disabled => Some("Enable"),
6614 });
6615
6616 let (anchor, breakpoint) =
6617 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6618
6619 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6620 menu.on_blur_subscription(Subscription::new(|| {}))
6621 .context(focus_handle)
6622 .when(run_to_cursor, |this| {
6623 let weak_editor = weak_editor.clone();
6624 this.entry("Run to cursor", None, move |window, cx| {
6625 weak_editor
6626 .update(cx, |editor, cx| {
6627 editor.change_selections(None, window, cx, |s| {
6628 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6629 });
6630 })
6631 .ok();
6632
6633 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6634 })
6635 .separator()
6636 })
6637 .when_some(toggle_state_msg, |this, msg| {
6638 this.entry(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::InvertState,
6648 cx,
6649 );
6650 })
6651 .log_err();
6652 }
6653 })
6654 })
6655 .entry(set_breakpoint_msg, None, {
6656 let weak_editor = weak_editor.clone();
6657 let breakpoint = breakpoint.clone();
6658 move |_window, cx| {
6659 weak_editor
6660 .update(cx, |this, cx| {
6661 this.edit_breakpoint_at_anchor(
6662 anchor,
6663 breakpoint.as_ref().clone(),
6664 BreakpointEditAction::Toggle,
6665 cx,
6666 );
6667 })
6668 .log_err();
6669 }
6670 })
6671 .entry(log_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::Log,
6681 window,
6682 cx,
6683 );
6684 })
6685 .log_err();
6686 }
6687 })
6688 .entry(condition_breakpoint_msg, None, {
6689 let breakpoint = breakpoint.clone();
6690 let weak_editor = weak_editor.clone();
6691 move |window, cx| {
6692 weak_editor
6693 .update(cx, |this, cx| {
6694 this.add_edit_breakpoint_block(
6695 anchor,
6696 breakpoint.as_ref(),
6697 BreakpointPromptEditAction::Condition,
6698 window,
6699 cx,
6700 );
6701 })
6702 .log_err();
6703 }
6704 })
6705 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6706 weak_editor
6707 .update(cx, |this, cx| {
6708 this.add_edit_breakpoint_block(
6709 anchor,
6710 breakpoint.as_ref(),
6711 BreakpointPromptEditAction::HitCondition,
6712 window,
6713 cx,
6714 );
6715 })
6716 .log_err();
6717 })
6718 })
6719 }
6720
6721 fn render_breakpoint(
6722 &self,
6723 position: Anchor,
6724 row: DisplayRow,
6725 breakpoint: &Breakpoint,
6726 cx: &mut Context<Self>,
6727 ) -> IconButton {
6728 let (color, icon) = {
6729 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6730 (false, false) => ui::IconName::DebugBreakpoint,
6731 (true, false) => ui::IconName::DebugLogBreakpoint,
6732 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6733 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6734 };
6735
6736 let color = if self
6737 .gutter_breakpoint_indicator
6738 .0
6739 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6740 {
6741 Color::Hint
6742 } else {
6743 Color::Debugger
6744 };
6745
6746 (color, icon)
6747 };
6748
6749 let breakpoint = Arc::from(breakpoint.clone());
6750
6751 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6752 .icon_size(IconSize::XSmall)
6753 .size(ui::ButtonSize::None)
6754 .icon_color(color)
6755 .style(ButtonStyle::Transparent)
6756 .on_click(cx.listener({
6757 let breakpoint = breakpoint.clone();
6758
6759 move |editor, event: &ClickEvent, window, cx| {
6760 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6761 BreakpointEditAction::InvertState
6762 } else {
6763 BreakpointEditAction::Toggle
6764 };
6765
6766 window.focus(&editor.focus_handle(cx));
6767 editor.edit_breakpoint_at_anchor(
6768 position,
6769 breakpoint.as_ref().clone(),
6770 edit_action,
6771 cx,
6772 );
6773 }
6774 }))
6775 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6776 editor.set_breakpoint_context_menu(
6777 row,
6778 Some(position),
6779 event.down.position,
6780 window,
6781 cx,
6782 );
6783 }))
6784 }
6785
6786 fn build_tasks_context(
6787 project: &Entity<Project>,
6788 buffer: &Entity<Buffer>,
6789 buffer_row: u32,
6790 tasks: &Arc<RunnableTasks>,
6791 cx: &mut Context<Self>,
6792 ) -> Task<Option<task::TaskContext>> {
6793 let position = Point::new(buffer_row, tasks.column);
6794 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6795 let location = Location {
6796 buffer: buffer.clone(),
6797 range: range_start..range_start,
6798 };
6799 // Fill in the environmental variables from the tree-sitter captures
6800 let mut captured_task_variables = TaskVariables::default();
6801 for (capture_name, value) in tasks.extra_variables.clone() {
6802 captured_task_variables.insert(
6803 task::VariableName::Custom(capture_name.into()),
6804 value.clone(),
6805 );
6806 }
6807 project.update(cx, |project, cx| {
6808 project.task_store().update(cx, |task_store, cx| {
6809 task_store.task_context_for_location(captured_task_variables, location, cx)
6810 })
6811 })
6812 }
6813
6814 pub fn spawn_nearest_task(
6815 &mut self,
6816 action: &SpawnNearestTask,
6817 window: &mut Window,
6818 cx: &mut Context<Self>,
6819 ) {
6820 let Some((workspace, _)) = self.workspace.clone() else {
6821 return;
6822 };
6823 let Some(project) = self.project.clone() else {
6824 return;
6825 };
6826
6827 // Try to find a closest, enclosing node using tree-sitter that has a
6828 // task
6829 let Some((buffer, buffer_row, tasks)) = self
6830 .find_enclosing_node_task(cx)
6831 // Or find the task that's closest in row-distance.
6832 .or_else(|| self.find_closest_task(cx))
6833 else {
6834 return;
6835 };
6836
6837 let reveal_strategy = action.reveal;
6838 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6839 cx.spawn_in(window, async move |_, cx| {
6840 let context = task_context.await?;
6841 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6842
6843 let resolved = resolved_task.resolved.as_mut()?;
6844 resolved.reveal = reveal_strategy;
6845
6846 workspace
6847 .update(cx, |workspace, cx| {
6848 workspace::tasks::schedule_resolved_task(
6849 workspace,
6850 task_source_kind,
6851 resolved_task,
6852 false,
6853 cx,
6854 );
6855 })
6856 .ok()
6857 })
6858 .detach();
6859 }
6860
6861 fn find_closest_task(
6862 &mut self,
6863 cx: &mut Context<Self>,
6864 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6865 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6866
6867 let ((buffer_id, row), tasks) = self
6868 .tasks
6869 .iter()
6870 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6871
6872 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6873 let tasks = Arc::new(tasks.to_owned());
6874 Some((buffer, *row, tasks))
6875 }
6876
6877 fn find_enclosing_node_task(
6878 &mut self,
6879 cx: &mut Context<Self>,
6880 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6881 let snapshot = self.buffer.read(cx).snapshot(cx);
6882 let offset = self.selections.newest::<usize>(cx).head();
6883 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6884 let buffer_id = excerpt.buffer().remote_id();
6885
6886 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6887 let mut cursor = layer.node().walk();
6888
6889 while cursor.goto_first_child_for_byte(offset).is_some() {
6890 if cursor.node().end_byte() == offset {
6891 cursor.goto_next_sibling();
6892 }
6893 }
6894
6895 // Ascend to the smallest ancestor that contains the range and has a task.
6896 loop {
6897 let node = cursor.node();
6898 let node_range = node.byte_range();
6899 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6900
6901 // Check if this node contains our offset
6902 if node_range.start <= offset && node_range.end >= offset {
6903 // If it contains offset, check for task
6904 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6905 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6906 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6907 }
6908 }
6909
6910 if !cursor.goto_parent() {
6911 break;
6912 }
6913 }
6914 None
6915 }
6916
6917 fn render_run_indicator(
6918 &self,
6919 _style: &EditorStyle,
6920 is_active: bool,
6921 row: DisplayRow,
6922 breakpoint: Option<(Anchor, Breakpoint)>,
6923 cx: &mut Context<Self>,
6924 ) -> IconButton {
6925 let color = Color::Muted;
6926 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6927
6928 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6929 .shape(ui::IconButtonShape::Square)
6930 .icon_size(IconSize::XSmall)
6931 .icon_color(color)
6932 .toggle_state(is_active)
6933 .on_click(cx.listener(move |editor, _e, window, cx| {
6934 window.focus(&editor.focus_handle(cx));
6935 editor.toggle_code_actions(
6936 &ToggleCodeActions {
6937 deployed_from_indicator: Some(row),
6938 },
6939 window,
6940 cx,
6941 );
6942 }))
6943 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6944 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6945 }))
6946 }
6947
6948 pub fn context_menu_visible(&self) -> bool {
6949 !self.edit_prediction_preview_is_active()
6950 && self
6951 .context_menu
6952 .borrow()
6953 .as_ref()
6954 .map_or(false, |menu| menu.visible())
6955 }
6956
6957 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6958 self.context_menu
6959 .borrow()
6960 .as_ref()
6961 .map(|menu| menu.origin())
6962 }
6963
6964 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6965 self.context_menu_options = Some(options);
6966 }
6967
6968 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6969 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6970
6971 fn render_edit_prediction_popover(
6972 &mut self,
6973 text_bounds: &Bounds<Pixels>,
6974 content_origin: gpui::Point<Pixels>,
6975 editor_snapshot: &EditorSnapshot,
6976 visible_row_range: Range<DisplayRow>,
6977 scroll_top: f32,
6978 scroll_bottom: f32,
6979 line_layouts: &[LineWithInvisibles],
6980 line_height: Pixels,
6981 scroll_pixel_position: gpui::Point<Pixels>,
6982 newest_selection_head: Option<DisplayPoint>,
6983 editor_width: Pixels,
6984 style: &EditorStyle,
6985 window: &mut Window,
6986 cx: &mut App,
6987 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6988 let active_inline_completion = self.active_inline_completion.as_ref()?;
6989
6990 if self.edit_prediction_visible_in_cursor_popover(true) {
6991 return None;
6992 }
6993
6994 match &active_inline_completion.completion {
6995 InlineCompletion::Move { target, .. } => {
6996 let target_display_point = target.to_display_point(editor_snapshot);
6997
6998 if self.edit_prediction_requires_modifier() {
6999 if !self.edit_prediction_preview_is_active() {
7000 return None;
7001 }
7002
7003 self.render_edit_prediction_modifier_jump_popover(
7004 text_bounds,
7005 content_origin,
7006 visible_row_range,
7007 line_layouts,
7008 line_height,
7009 scroll_pixel_position,
7010 newest_selection_head,
7011 target_display_point,
7012 window,
7013 cx,
7014 )
7015 } else {
7016 self.render_edit_prediction_eager_jump_popover(
7017 text_bounds,
7018 content_origin,
7019 editor_snapshot,
7020 visible_row_range,
7021 scroll_top,
7022 scroll_bottom,
7023 line_height,
7024 scroll_pixel_position,
7025 target_display_point,
7026 editor_width,
7027 window,
7028 cx,
7029 )
7030 }
7031 }
7032 InlineCompletion::Edit {
7033 display_mode: EditDisplayMode::Inline,
7034 ..
7035 } => None,
7036 InlineCompletion::Edit {
7037 display_mode: EditDisplayMode::TabAccept,
7038 edits,
7039 ..
7040 } => {
7041 let range = &edits.first()?.0;
7042 let target_display_point = range.end.to_display_point(editor_snapshot);
7043
7044 self.render_edit_prediction_end_of_line_popover(
7045 "Accept",
7046 editor_snapshot,
7047 visible_row_range,
7048 target_display_point,
7049 line_height,
7050 scroll_pixel_position,
7051 content_origin,
7052 editor_width,
7053 window,
7054 cx,
7055 )
7056 }
7057 InlineCompletion::Edit {
7058 edits,
7059 edit_preview,
7060 display_mode: EditDisplayMode::DiffPopover,
7061 snapshot,
7062 } => self.render_edit_prediction_diff_popover(
7063 text_bounds,
7064 content_origin,
7065 editor_snapshot,
7066 visible_row_range,
7067 line_layouts,
7068 line_height,
7069 scroll_pixel_position,
7070 newest_selection_head,
7071 editor_width,
7072 style,
7073 edits,
7074 edit_preview,
7075 snapshot,
7076 window,
7077 cx,
7078 ),
7079 }
7080 }
7081
7082 fn render_edit_prediction_modifier_jump_popover(
7083 &mut self,
7084 text_bounds: &Bounds<Pixels>,
7085 content_origin: gpui::Point<Pixels>,
7086 visible_row_range: Range<DisplayRow>,
7087 line_layouts: &[LineWithInvisibles],
7088 line_height: Pixels,
7089 scroll_pixel_position: gpui::Point<Pixels>,
7090 newest_selection_head: Option<DisplayPoint>,
7091 target_display_point: DisplayPoint,
7092 window: &mut Window,
7093 cx: &mut App,
7094 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7095 let scrolled_content_origin =
7096 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7097
7098 const SCROLL_PADDING_Y: Pixels = px(12.);
7099
7100 if target_display_point.row() < visible_row_range.start {
7101 return self.render_edit_prediction_scroll_popover(
7102 |_| SCROLL_PADDING_Y,
7103 IconName::ArrowUp,
7104 visible_row_range,
7105 line_layouts,
7106 newest_selection_head,
7107 scrolled_content_origin,
7108 window,
7109 cx,
7110 );
7111 } else if target_display_point.row() >= visible_row_range.end {
7112 return self.render_edit_prediction_scroll_popover(
7113 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7114 IconName::ArrowDown,
7115 visible_row_range,
7116 line_layouts,
7117 newest_selection_head,
7118 scrolled_content_origin,
7119 window,
7120 cx,
7121 );
7122 }
7123
7124 const POLE_WIDTH: Pixels = px(2.);
7125
7126 let line_layout =
7127 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7128 let target_column = target_display_point.column() as usize;
7129
7130 let target_x = line_layout.x_for_index(target_column);
7131 let target_y =
7132 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7133
7134 let flag_on_right = target_x < text_bounds.size.width / 2.;
7135
7136 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7137 border_color.l += 0.001;
7138
7139 let mut element = v_flex()
7140 .items_end()
7141 .when(flag_on_right, |el| el.items_start())
7142 .child(if flag_on_right {
7143 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7144 .rounded_bl(px(0.))
7145 .rounded_tl(px(0.))
7146 .border_l_2()
7147 .border_color(border_color)
7148 } else {
7149 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7150 .rounded_br(px(0.))
7151 .rounded_tr(px(0.))
7152 .border_r_2()
7153 .border_color(border_color)
7154 })
7155 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7156 .into_any();
7157
7158 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7159
7160 let mut origin = scrolled_content_origin + point(target_x, target_y)
7161 - point(
7162 if flag_on_right {
7163 POLE_WIDTH
7164 } else {
7165 size.width - POLE_WIDTH
7166 },
7167 size.height - line_height,
7168 );
7169
7170 origin.x = origin.x.max(content_origin.x);
7171
7172 element.prepaint_at(origin, window, cx);
7173
7174 Some((element, origin))
7175 }
7176
7177 fn render_edit_prediction_scroll_popover(
7178 &mut self,
7179 to_y: impl Fn(Size<Pixels>) -> Pixels,
7180 scroll_icon: IconName,
7181 visible_row_range: Range<DisplayRow>,
7182 line_layouts: &[LineWithInvisibles],
7183 newest_selection_head: Option<DisplayPoint>,
7184 scrolled_content_origin: gpui::Point<Pixels>,
7185 window: &mut Window,
7186 cx: &mut App,
7187 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7188 let mut element = self
7189 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7190 .into_any();
7191
7192 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7193
7194 let cursor = newest_selection_head?;
7195 let cursor_row_layout =
7196 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7197 let cursor_column = cursor.column() as usize;
7198
7199 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7200
7201 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7202
7203 element.prepaint_at(origin, window, cx);
7204 Some((element, origin))
7205 }
7206
7207 fn render_edit_prediction_eager_jump_popover(
7208 &mut self,
7209 text_bounds: &Bounds<Pixels>,
7210 content_origin: gpui::Point<Pixels>,
7211 editor_snapshot: &EditorSnapshot,
7212 visible_row_range: Range<DisplayRow>,
7213 scroll_top: f32,
7214 scroll_bottom: f32,
7215 line_height: Pixels,
7216 scroll_pixel_position: gpui::Point<Pixels>,
7217 target_display_point: DisplayPoint,
7218 editor_width: Pixels,
7219 window: &mut Window,
7220 cx: &mut App,
7221 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7222 if target_display_point.row().as_f32() < scroll_top {
7223 let mut element = self
7224 .render_edit_prediction_line_popover(
7225 "Jump to Edit",
7226 Some(IconName::ArrowUp),
7227 window,
7228 cx,
7229 )?
7230 .into_any();
7231
7232 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7233 let offset = point(
7234 (text_bounds.size.width - size.width) / 2.,
7235 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7236 );
7237
7238 let origin = text_bounds.origin + offset;
7239 element.prepaint_at(origin, window, cx);
7240 Some((element, origin))
7241 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7242 let mut element = self
7243 .render_edit_prediction_line_popover(
7244 "Jump to Edit",
7245 Some(IconName::ArrowDown),
7246 window,
7247 cx,
7248 )?
7249 .into_any();
7250
7251 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7252 let offset = point(
7253 (text_bounds.size.width - size.width) / 2.,
7254 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7255 );
7256
7257 let origin = text_bounds.origin + offset;
7258 element.prepaint_at(origin, window, cx);
7259 Some((element, origin))
7260 } else {
7261 self.render_edit_prediction_end_of_line_popover(
7262 "Jump to Edit",
7263 editor_snapshot,
7264 visible_row_range,
7265 target_display_point,
7266 line_height,
7267 scroll_pixel_position,
7268 content_origin,
7269 editor_width,
7270 window,
7271 cx,
7272 )
7273 }
7274 }
7275
7276 fn render_edit_prediction_end_of_line_popover(
7277 self: &mut Editor,
7278 label: &'static str,
7279 editor_snapshot: &EditorSnapshot,
7280 visible_row_range: Range<DisplayRow>,
7281 target_display_point: DisplayPoint,
7282 line_height: Pixels,
7283 scroll_pixel_position: gpui::Point<Pixels>,
7284 content_origin: gpui::Point<Pixels>,
7285 editor_width: Pixels,
7286 window: &mut Window,
7287 cx: &mut App,
7288 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7289 let target_line_end = DisplayPoint::new(
7290 target_display_point.row(),
7291 editor_snapshot.line_len(target_display_point.row()),
7292 );
7293
7294 let mut element = self
7295 .render_edit_prediction_line_popover(label, None, window, cx)?
7296 .into_any();
7297
7298 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7299
7300 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7301
7302 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7303 let mut origin = start_point
7304 + line_origin
7305 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7306 origin.x = origin.x.max(content_origin.x);
7307
7308 let max_x = content_origin.x + editor_width - size.width;
7309
7310 if origin.x > max_x {
7311 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7312
7313 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7314 origin.y += offset;
7315 IconName::ArrowUp
7316 } else {
7317 origin.y -= offset;
7318 IconName::ArrowDown
7319 };
7320
7321 element = self
7322 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7323 .into_any();
7324
7325 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7326
7327 origin.x = content_origin.x + editor_width - size.width - px(2.);
7328 }
7329
7330 element.prepaint_at(origin, window, cx);
7331 Some((element, origin))
7332 }
7333
7334 fn render_edit_prediction_diff_popover(
7335 self: &Editor,
7336 text_bounds: &Bounds<Pixels>,
7337 content_origin: gpui::Point<Pixels>,
7338 editor_snapshot: &EditorSnapshot,
7339 visible_row_range: Range<DisplayRow>,
7340 line_layouts: &[LineWithInvisibles],
7341 line_height: Pixels,
7342 scroll_pixel_position: gpui::Point<Pixels>,
7343 newest_selection_head: Option<DisplayPoint>,
7344 editor_width: Pixels,
7345 style: &EditorStyle,
7346 edits: &Vec<(Range<Anchor>, String)>,
7347 edit_preview: &Option<language::EditPreview>,
7348 snapshot: &language::BufferSnapshot,
7349 window: &mut Window,
7350 cx: &mut App,
7351 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7352 let edit_start = edits
7353 .first()
7354 .unwrap()
7355 .0
7356 .start
7357 .to_display_point(editor_snapshot);
7358 let edit_end = edits
7359 .last()
7360 .unwrap()
7361 .0
7362 .end
7363 .to_display_point(editor_snapshot);
7364
7365 let is_visible = visible_row_range.contains(&edit_start.row())
7366 || visible_row_range.contains(&edit_end.row());
7367 if !is_visible {
7368 return None;
7369 }
7370
7371 let highlighted_edits =
7372 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7373
7374 let styled_text = highlighted_edits.to_styled_text(&style.text);
7375 let line_count = highlighted_edits.text.lines().count();
7376
7377 const BORDER_WIDTH: Pixels = px(1.);
7378
7379 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7380 let has_keybind = keybind.is_some();
7381
7382 let mut element = h_flex()
7383 .items_start()
7384 .child(
7385 h_flex()
7386 .bg(cx.theme().colors().editor_background)
7387 .border(BORDER_WIDTH)
7388 .shadow_sm()
7389 .border_color(cx.theme().colors().border)
7390 .rounded_l_lg()
7391 .when(line_count > 1, |el| el.rounded_br_lg())
7392 .pr_1()
7393 .child(styled_text),
7394 )
7395 .child(
7396 h_flex()
7397 .h(line_height + BORDER_WIDTH * 2.)
7398 .px_1p5()
7399 .gap_1()
7400 // Workaround: For some reason, there's a gap if we don't do this
7401 .ml(-BORDER_WIDTH)
7402 .shadow(smallvec![gpui::BoxShadow {
7403 color: gpui::black().opacity(0.05),
7404 offset: point(px(1.), px(1.)),
7405 blur_radius: px(2.),
7406 spread_radius: px(0.),
7407 }])
7408 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7409 .border(BORDER_WIDTH)
7410 .border_color(cx.theme().colors().border)
7411 .rounded_r_lg()
7412 .id("edit_prediction_diff_popover_keybind")
7413 .when(!has_keybind, |el| {
7414 let status_colors = cx.theme().status();
7415
7416 el.bg(status_colors.error_background)
7417 .border_color(status_colors.error.opacity(0.6))
7418 .child(Icon::new(IconName::Info).color(Color::Error))
7419 .cursor_default()
7420 .hoverable_tooltip(move |_window, cx| {
7421 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7422 })
7423 })
7424 .children(keybind),
7425 )
7426 .into_any();
7427
7428 let longest_row =
7429 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7430 let longest_line_width = if visible_row_range.contains(&longest_row) {
7431 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7432 } else {
7433 layout_line(
7434 longest_row,
7435 editor_snapshot,
7436 style,
7437 editor_width,
7438 |_| false,
7439 window,
7440 cx,
7441 )
7442 .width
7443 };
7444
7445 let viewport_bounds =
7446 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7447 right: -EditorElement::SCROLLBAR_WIDTH,
7448 ..Default::default()
7449 });
7450
7451 let x_after_longest =
7452 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7453 - scroll_pixel_position.x;
7454
7455 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7456
7457 // Fully visible if it can be displayed within the window (allow overlapping other
7458 // panes). However, this is only allowed if the popover starts within text_bounds.
7459 let can_position_to_the_right = x_after_longest < text_bounds.right()
7460 && x_after_longest + element_bounds.width < viewport_bounds.right();
7461
7462 let mut origin = if can_position_to_the_right {
7463 point(
7464 x_after_longest,
7465 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7466 - scroll_pixel_position.y,
7467 )
7468 } else {
7469 let cursor_row = newest_selection_head.map(|head| head.row());
7470 let above_edit = edit_start
7471 .row()
7472 .0
7473 .checked_sub(line_count as u32)
7474 .map(DisplayRow);
7475 let below_edit = Some(edit_end.row() + 1);
7476 let above_cursor =
7477 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7478 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7479
7480 // Place the edit popover adjacent to the edit if there is a location
7481 // available that is onscreen and does not obscure the cursor. Otherwise,
7482 // place it adjacent to the cursor.
7483 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7484 .into_iter()
7485 .flatten()
7486 .find(|&start_row| {
7487 let end_row = start_row + line_count as u32;
7488 visible_row_range.contains(&start_row)
7489 && visible_row_range.contains(&end_row)
7490 && cursor_row.map_or(true, |cursor_row| {
7491 !((start_row..end_row).contains(&cursor_row))
7492 })
7493 })?;
7494
7495 content_origin
7496 + point(
7497 -scroll_pixel_position.x,
7498 row_target.as_f32() * line_height - scroll_pixel_position.y,
7499 )
7500 };
7501
7502 origin.x -= BORDER_WIDTH;
7503
7504 window.defer_draw(element, origin, 1);
7505
7506 // Do not return an element, since it will already be drawn due to defer_draw.
7507 None
7508 }
7509
7510 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7511 px(30.)
7512 }
7513
7514 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7515 if self.read_only(cx) {
7516 cx.theme().players().read_only()
7517 } else {
7518 self.style.as_ref().unwrap().local_player
7519 }
7520 }
7521
7522 fn render_edit_prediction_accept_keybind(
7523 &self,
7524 window: &mut Window,
7525 cx: &App,
7526 ) -> Option<AnyElement> {
7527 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7528 let accept_keystroke = accept_binding.keystroke()?;
7529
7530 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7531
7532 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7533 Color::Accent
7534 } else {
7535 Color::Muted
7536 };
7537
7538 h_flex()
7539 .px_0p5()
7540 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7541 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7542 .text_size(TextSize::XSmall.rems(cx))
7543 .child(h_flex().children(ui::render_modifiers(
7544 &accept_keystroke.modifiers,
7545 PlatformStyle::platform(),
7546 Some(modifiers_color),
7547 Some(IconSize::XSmall.rems().into()),
7548 true,
7549 )))
7550 .when(is_platform_style_mac, |parent| {
7551 parent.child(accept_keystroke.key.clone())
7552 })
7553 .when(!is_platform_style_mac, |parent| {
7554 parent.child(
7555 Key::new(
7556 util::capitalize(&accept_keystroke.key),
7557 Some(Color::Default),
7558 )
7559 .size(Some(IconSize::XSmall.rems().into())),
7560 )
7561 })
7562 .into_any()
7563 .into()
7564 }
7565
7566 fn render_edit_prediction_line_popover(
7567 &self,
7568 label: impl Into<SharedString>,
7569 icon: Option<IconName>,
7570 window: &mut Window,
7571 cx: &App,
7572 ) -> Option<Stateful<Div>> {
7573 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7574
7575 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7576 let has_keybind = keybind.is_some();
7577
7578 let result = h_flex()
7579 .id("ep-line-popover")
7580 .py_0p5()
7581 .pl_1()
7582 .pr(padding_right)
7583 .gap_1()
7584 .rounded_md()
7585 .border_1()
7586 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7587 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7588 .shadow_sm()
7589 .when(!has_keybind, |el| {
7590 let status_colors = cx.theme().status();
7591
7592 el.bg(status_colors.error_background)
7593 .border_color(status_colors.error.opacity(0.6))
7594 .pl_2()
7595 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7596 .cursor_default()
7597 .hoverable_tooltip(move |_window, cx| {
7598 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7599 })
7600 })
7601 .children(keybind)
7602 .child(
7603 Label::new(label)
7604 .size(LabelSize::Small)
7605 .when(!has_keybind, |el| {
7606 el.color(cx.theme().status().error.into()).strikethrough()
7607 }),
7608 )
7609 .when(!has_keybind, |el| {
7610 el.child(
7611 h_flex().ml_1().child(
7612 Icon::new(IconName::Info)
7613 .size(IconSize::Small)
7614 .color(cx.theme().status().error.into()),
7615 ),
7616 )
7617 })
7618 .when_some(icon, |element, icon| {
7619 element.child(
7620 div()
7621 .mt(px(1.5))
7622 .child(Icon::new(icon).size(IconSize::Small)),
7623 )
7624 });
7625
7626 Some(result)
7627 }
7628
7629 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7630 let accent_color = cx.theme().colors().text_accent;
7631 let editor_bg_color = cx.theme().colors().editor_background;
7632 editor_bg_color.blend(accent_color.opacity(0.1))
7633 }
7634
7635 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7636 let accent_color = cx.theme().colors().text_accent;
7637 let editor_bg_color = cx.theme().colors().editor_background;
7638 editor_bg_color.blend(accent_color.opacity(0.6))
7639 }
7640
7641 fn render_edit_prediction_cursor_popover(
7642 &self,
7643 min_width: Pixels,
7644 max_width: Pixels,
7645 cursor_point: Point,
7646 style: &EditorStyle,
7647 accept_keystroke: Option<&gpui::Keystroke>,
7648 _window: &Window,
7649 cx: &mut Context<Editor>,
7650 ) -> Option<AnyElement> {
7651 let provider = self.edit_prediction_provider.as_ref()?;
7652
7653 if provider.provider.needs_terms_acceptance(cx) {
7654 return Some(
7655 h_flex()
7656 .min_w(min_width)
7657 .flex_1()
7658 .px_2()
7659 .py_1()
7660 .gap_3()
7661 .elevation_2(cx)
7662 .hover(|style| style.bg(cx.theme().colors().element_hover))
7663 .id("accept-terms")
7664 .cursor_pointer()
7665 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7666 .on_click(cx.listener(|this, _event, window, cx| {
7667 cx.stop_propagation();
7668 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7669 window.dispatch_action(
7670 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7671 cx,
7672 );
7673 }))
7674 .child(
7675 h_flex()
7676 .flex_1()
7677 .gap_2()
7678 .child(Icon::new(IconName::ZedPredict))
7679 .child(Label::new("Accept Terms of Service"))
7680 .child(div().w_full())
7681 .child(
7682 Icon::new(IconName::ArrowUpRight)
7683 .color(Color::Muted)
7684 .size(IconSize::Small),
7685 )
7686 .into_any_element(),
7687 )
7688 .into_any(),
7689 );
7690 }
7691
7692 let is_refreshing = provider.provider.is_refreshing(cx);
7693
7694 fn pending_completion_container() -> Div {
7695 h_flex()
7696 .h_full()
7697 .flex_1()
7698 .gap_2()
7699 .child(Icon::new(IconName::ZedPredict))
7700 }
7701
7702 let completion = match &self.active_inline_completion {
7703 Some(prediction) => {
7704 if !self.has_visible_completions_menu() {
7705 const RADIUS: Pixels = px(6.);
7706 const BORDER_WIDTH: Pixels = px(1.);
7707
7708 return Some(
7709 h_flex()
7710 .elevation_2(cx)
7711 .border(BORDER_WIDTH)
7712 .border_color(cx.theme().colors().border)
7713 .when(accept_keystroke.is_none(), |el| {
7714 el.border_color(cx.theme().status().error)
7715 })
7716 .rounded(RADIUS)
7717 .rounded_tl(px(0.))
7718 .overflow_hidden()
7719 .child(div().px_1p5().child(match &prediction.completion {
7720 InlineCompletion::Move { target, snapshot } => {
7721 use text::ToPoint as _;
7722 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7723 {
7724 Icon::new(IconName::ZedPredictDown)
7725 } else {
7726 Icon::new(IconName::ZedPredictUp)
7727 }
7728 }
7729 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7730 }))
7731 .child(
7732 h_flex()
7733 .gap_1()
7734 .py_1()
7735 .px_2()
7736 .rounded_r(RADIUS - BORDER_WIDTH)
7737 .border_l_1()
7738 .border_color(cx.theme().colors().border)
7739 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7740 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7741 el.child(
7742 Label::new("Hold")
7743 .size(LabelSize::Small)
7744 .when(accept_keystroke.is_none(), |el| {
7745 el.strikethrough()
7746 })
7747 .line_height_style(LineHeightStyle::UiLabel),
7748 )
7749 })
7750 .id("edit_prediction_cursor_popover_keybind")
7751 .when(accept_keystroke.is_none(), |el| {
7752 let status_colors = cx.theme().status();
7753
7754 el.bg(status_colors.error_background)
7755 .border_color(status_colors.error.opacity(0.6))
7756 .child(Icon::new(IconName::Info).color(Color::Error))
7757 .cursor_default()
7758 .hoverable_tooltip(move |_window, cx| {
7759 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7760 .into()
7761 })
7762 })
7763 .when_some(
7764 accept_keystroke.as_ref(),
7765 |el, accept_keystroke| {
7766 el.child(h_flex().children(ui::render_modifiers(
7767 &accept_keystroke.modifiers,
7768 PlatformStyle::platform(),
7769 Some(Color::Default),
7770 Some(IconSize::XSmall.rems().into()),
7771 false,
7772 )))
7773 },
7774 ),
7775 )
7776 .into_any(),
7777 );
7778 }
7779
7780 self.render_edit_prediction_cursor_popover_preview(
7781 prediction,
7782 cursor_point,
7783 style,
7784 cx,
7785 )?
7786 }
7787
7788 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7789 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7790 stale_completion,
7791 cursor_point,
7792 style,
7793 cx,
7794 )?,
7795
7796 None => {
7797 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7798 }
7799 },
7800
7801 None => pending_completion_container().child(Label::new("No Prediction")),
7802 };
7803
7804 let completion = if is_refreshing {
7805 completion
7806 .with_animation(
7807 "loading-completion",
7808 Animation::new(Duration::from_secs(2))
7809 .repeat()
7810 .with_easing(pulsating_between(0.4, 0.8)),
7811 |label, delta| label.opacity(delta),
7812 )
7813 .into_any_element()
7814 } else {
7815 completion.into_any_element()
7816 };
7817
7818 let has_completion = self.active_inline_completion.is_some();
7819
7820 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7821 Some(
7822 h_flex()
7823 .min_w(min_width)
7824 .max_w(max_width)
7825 .flex_1()
7826 .elevation_2(cx)
7827 .border_color(cx.theme().colors().border)
7828 .child(
7829 div()
7830 .flex_1()
7831 .py_1()
7832 .px_2()
7833 .overflow_hidden()
7834 .child(completion),
7835 )
7836 .when_some(accept_keystroke, |el, accept_keystroke| {
7837 if !accept_keystroke.modifiers.modified() {
7838 return el;
7839 }
7840
7841 el.child(
7842 h_flex()
7843 .h_full()
7844 .border_l_1()
7845 .rounded_r_lg()
7846 .border_color(cx.theme().colors().border)
7847 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7848 .gap_1()
7849 .py_1()
7850 .px_2()
7851 .child(
7852 h_flex()
7853 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7854 .when(is_platform_style_mac, |parent| parent.gap_1())
7855 .child(h_flex().children(ui::render_modifiers(
7856 &accept_keystroke.modifiers,
7857 PlatformStyle::platform(),
7858 Some(if !has_completion {
7859 Color::Muted
7860 } else {
7861 Color::Default
7862 }),
7863 None,
7864 false,
7865 ))),
7866 )
7867 .child(Label::new("Preview").into_any_element())
7868 .opacity(if has_completion { 1.0 } else { 0.4 }),
7869 )
7870 })
7871 .into_any(),
7872 )
7873 }
7874
7875 fn render_edit_prediction_cursor_popover_preview(
7876 &self,
7877 completion: &InlineCompletionState,
7878 cursor_point: Point,
7879 style: &EditorStyle,
7880 cx: &mut Context<Editor>,
7881 ) -> Option<Div> {
7882 use text::ToPoint as _;
7883
7884 fn render_relative_row_jump(
7885 prefix: impl Into<String>,
7886 current_row: u32,
7887 target_row: u32,
7888 ) -> Div {
7889 let (row_diff, arrow) = if target_row < current_row {
7890 (current_row - target_row, IconName::ArrowUp)
7891 } else {
7892 (target_row - current_row, IconName::ArrowDown)
7893 };
7894
7895 h_flex()
7896 .child(
7897 Label::new(format!("{}{}", prefix.into(), row_diff))
7898 .color(Color::Muted)
7899 .size(LabelSize::Small),
7900 )
7901 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7902 }
7903
7904 match &completion.completion {
7905 InlineCompletion::Move {
7906 target, snapshot, ..
7907 } => Some(
7908 h_flex()
7909 .px_2()
7910 .gap_2()
7911 .flex_1()
7912 .child(
7913 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7914 Icon::new(IconName::ZedPredictDown)
7915 } else {
7916 Icon::new(IconName::ZedPredictUp)
7917 },
7918 )
7919 .child(Label::new("Jump to Edit")),
7920 ),
7921
7922 InlineCompletion::Edit {
7923 edits,
7924 edit_preview,
7925 snapshot,
7926 display_mode: _,
7927 } => {
7928 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7929
7930 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7931 &snapshot,
7932 &edits,
7933 edit_preview.as_ref()?,
7934 true,
7935 cx,
7936 )
7937 .first_line_preview();
7938
7939 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7940 .with_default_highlights(&style.text, highlighted_edits.highlights);
7941
7942 let preview = h_flex()
7943 .gap_1()
7944 .min_w_16()
7945 .child(styled_text)
7946 .when(has_more_lines, |parent| parent.child("…"));
7947
7948 let left = if first_edit_row != cursor_point.row {
7949 render_relative_row_jump("", cursor_point.row, first_edit_row)
7950 .into_any_element()
7951 } else {
7952 Icon::new(IconName::ZedPredict).into_any_element()
7953 };
7954
7955 Some(
7956 h_flex()
7957 .h_full()
7958 .flex_1()
7959 .gap_2()
7960 .pr_1()
7961 .overflow_x_hidden()
7962 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7963 .child(left)
7964 .child(preview),
7965 )
7966 }
7967 }
7968 }
7969
7970 fn render_context_menu(
7971 &self,
7972 style: &EditorStyle,
7973 max_height_in_lines: u32,
7974 window: &mut Window,
7975 cx: &mut Context<Editor>,
7976 ) -> Option<AnyElement> {
7977 let menu = self.context_menu.borrow();
7978 let menu = menu.as_ref()?;
7979 if !menu.visible() {
7980 return None;
7981 };
7982 Some(menu.render(style, max_height_in_lines, window, cx))
7983 }
7984
7985 fn render_context_menu_aside(
7986 &mut self,
7987 max_size: Size<Pixels>,
7988 window: &mut Window,
7989 cx: &mut Context<Editor>,
7990 ) -> Option<AnyElement> {
7991 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7992 if menu.visible() {
7993 menu.render_aside(self, max_size, window, cx)
7994 } else {
7995 None
7996 }
7997 })
7998 }
7999
8000 fn hide_context_menu(
8001 &mut self,
8002 window: &mut Window,
8003 cx: &mut Context<Self>,
8004 ) -> Option<CodeContextMenu> {
8005 cx.notify();
8006 self.completion_tasks.clear();
8007 let context_menu = self.context_menu.borrow_mut().take();
8008 self.stale_inline_completion_in_menu.take();
8009 self.update_visible_inline_completion(window, cx);
8010 context_menu
8011 }
8012
8013 fn show_snippet_choices(
8014 &mut self,
8015 choices: &Vec<String>,
8016 selection: Range<Anchor>,
8017 cx: &mut Context<Self>,
8018 ) {
8019 if selection.start.buffer_id.is_none() {
8020 return;
8021 }
8022 let buffer_id = selection.start.buffer_id.unwrap();
8023 let buffer = self.buffer().read(cx).buffer(buffer_id);
8024 let id = post_inc(&mut self.next_completion_id);
8025
8026 if let Some(buffer) = buffer {
8027 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8028 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
8029 ));
8030 }
8031 }
8032
8033 pub fn insert_snippet(
8034 &mut self,
8035 insertion_ranges: &[Range<usize>],
8036 snippet: Snippet,
8037 window: &mut Window,
8038 cx: &mut Context<Self>,
8039 ) -> Result<()> {
8040 struct Tabstop<T> {
8041 is_end_tabstop: bool,
8042 ranges: Vec<Range<T>>,
8043 choices: Option<Vec<String>>,
8044 }
8045
8046 let tabstops = self.buffer.update(cx, |buffer, cx| {
8047 let snippet_text: Arc<str> = snippet.text.clone().into();
8048 let edits = insertion_ranges
8049 .iter()
8050 .cloned()
8051 .map(|range| (range, snippet_text.clone()));
8052 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8053
8054 let snapshot = &*buffer.read(cx);
8055 let snippet = &snippet;
8056 snippet
8057 .tabstops
8058 .iter()
8059 .map(|tabstop| {
8060 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8061 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8062 });
8063 let mut tabstop_ranges = tabstop
8064 .ranges
8065 .iter()
8066 .flat_map(|tabstop_range| {
8067 let mut delta = 0_isize;
8068 insertion_ranges.iter().map(move |insertion_range| {
8069 let insertion_start = insertion_range.start as isize + delta;
8070 delta +=
8071 snippet.text.len() as isize - insertion_range.len() as isize;
8072
8073 let start = ((insertion_start + tabstop_range.start) as usize)
8074 .min(snapshot.len());
8075 let end = ((insertion_start + tabstop_range.end) as usize)
8076 .min(snapshot.len());
8077 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8078 })
8079 })
8080 .collect::<Vec<_>>();
8081 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8082
8083 Tabstop {
8084 is_end_tabstop,
8085 ranges: tabstop_ranges,
8086 choices: tabstop.choices.clone(),
8087 }
8088 })
8089 .collect::<Vec<_>>()
8090 });
8091 if let Some(tabstop) = tabstops.first() {
8092 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8093 s.select_ranges(tabstop.ranges.iter().cloned());
8094 });
8095
8096 if let Some(choices) = &tabstop.choices {
8097 if let Some(selection) = tabstop.ranges.first() {
8098 self.show_snippet_choices(choices, selection.clone(), cx)
8099 }
8100 }
8101
8102 // If we're already at the last tabstop and it's at the end of the snippet,
8103 // we're done, we don't need to keep the state around.
8104 if !tabstop.is_end_tabstop {
8105 let choices = tabstops
8106 .iter()
8107 .map(|tabstop| tabstop.choices.clone())
8108 .collect();
8109
8110 let ranges = tabstops
8111 .into_iter()
8112 .map(|tabstop| tabstop.ranges)
8113 .collect::<Vec<_>>();
8114
8115 self.snippet_stack.push(SnippetState {
8116 active_index: 0,
8117 ranges,
8118 choices,
8119 });
8120 }
8121
8122 // Check whether the just-entered snippet ends with an auto-closable bracket.
8123 if self.autoclose_regions.is_empty() {
8124 let snapshot = self.buffer.read(cx).snapshot(cx);
8125 for selection in &mut self.selections.all::<Point>(cx) {
8126 let selection_head = selection.head();
8127 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8128 continue;
8129 };
8130
8131 let mut bracket_pair = None;
8132 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8133 let prev_chars = snapshot
8134 .reversed_chars_at(selection_head)
8135 .collect::<String>();
8136 for (pair, enabled) in scope.brackets() {
8137 if enabled
8138 && pair.close
8139 && prev_chars.starts_with(pair.start.as_str())
8140 && next_chars.starts_with(pair.end.as_str())
8141 {
8142 bracket_pair = Some(pair.clone());
8143 break;
8144 }
8145 }
8146 if let Some(pair) = bracket_pair {
8147 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8148 let autoclose_enabled =
8149 self.use_autoclose && snapshot_settings.use_autoclose;
8150 if autoclose_enabled {
8151 let start = snapshot.anchor_after(selection_head);
8152 let end = snapshot.anchor_after(selection_head);
8153 self.autoclose_regions.push(AutocloseRegion {
8154 selection_id: selection.id,
8155 range: start..end,
8156 pair,
8157 });
8158 }
8159 }
8160 }
8161 }
8162 }
8163 Ok(())
8164 }
8165
8166 pub fn move_to_next_snippet_tabstop(
8167 &mut self,
8168 window: &mut Window,
8169 cx: &mut Context<Self>,
8170 ) -> bool {
8171 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8172 }
8173
8174 pub fn move_to_prev_snippet_tabstop(
8175 &mut self,
8176 window: &mut Window,
8177 cx: &mut Context<Self>,
8178 ) -> bool {
8179 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8180 }
8181
8182 pub fn move_to_snippet_tabstop(
8183 &mut self,
8184 bias: Bias,
8185 window: &mut Window,
8186 cx: &mut Context<Self>,
8187 ) -> bool {
8188 if let Some(mut snippet) = self.snippet_stack.pop() {
8189 match bias {
8190 Bias::Left => {
8191 if snippet.active_index > 0 {
8192 snippet.active_index -= 1;
8193 } else {
8194 self.snippet_stack.push(snippet);
8195 return false;
8196 }
8197 }
8198 Bias::Right => {
8199 if snippet.active_index + 1 < snippet.ranges.len() {
8200 snippet.active_index += 1;
8201 } else {
8202 self.snippet_stack.push(snippet);
8203 return false;
8204 }
8205 }
8206 }
8207 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8208 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8209 s.select_anchor_ranges(current_ranges.iter().cloned())
8210 });
8211
8212 if let Some(choices) = &snippet.choices[snippet.active_index] {
8213 if let Some(selection) = current_ranges.first() {
8214 self.show_snippet_choices(&choices, selection.clone(), cx);
8215 }
8216 }
8217
8218 // If snippet state is not at the last tabstop, push it back on the stack
8219 if snippet.active_index + 1 < snippet.ranges.len() {
8220 self.snippet_stack.push(snippet);
8221 }
8222 return true;
8223 }
8224 }
8225
8226 false
8227 }
8228
8229 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8230 self.transact(window, cx, |this, window, cx| {
8231 this.select_all(&SelectAll, window, cx);
8232 this.insert("", window, cx);
8233 });
8234 }
8235
8236 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8237 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8238 self.transact(window, cx, |this, window, cx| {
8239 this.select_autoclose_pair(window, cx);
8240 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8241 if !this.linked_edit_ranges.is_empty() {
8242 let selections = this.selections.all::<MultiBufferPoint>(cx);
8243 let snapshot = this.buffer.read(cx).snapshot(cx);
8244
8245 for selection in selections.iter() {
8246 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8247 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8248 if selection_start.buffer_id != selection_end.buffer_id {
8249 continue;
8250 }
8251 if let Some(ranges) =
8252 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8253 {
8254 for (buffer, entries) in ranges {
8255 linked_ranges.entry(buffer).or_default().extend(entries);
8256 }
8257 }
8258 }
8259 }
8260
8261 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8262 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8263 for selection in &mut selections {
8264 if selection.is_empty() {
8265 let old_head = selection.head();
8266 let mut new_head =
8267 movement::left(&display_map, old_head.to_display_point(&display_map))
8268 .to_point(&display_map);
8269 if let Some((buffer, line_buffer_range)) = display_map
8270 .buffer_snapshot
8271 .buffer_line_for_row(MultiBufferRow(old_head.row))
8272 {
8273 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8274 let indent_len = match indent_size.kind {
8275 IndentKind::Space => {
8276 buffer.settings_at(line_buffer_range.start, cx).tab_size
8277 }
8278 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8279 };
8280 if old_head.column <= indent_size.len && old_head.column > 0 {
8281 let indent_len = indent_len.get();
8282 new_head = cmp::min(
8283 new_head,
8284 MultiBufferPoint::new(
8285 old_head.row,
8286 ((old_head.column - 1) / indent_len) * indent_len,
8287 ),
8288 );
8289 }
8290 }
8291
8292 selection.set_head(new_head, SelectionGoal::None);
8293 }
8294 }
8295
8296 this.signature_help_state.set_backspace_pressed(true);
8297 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8298 s.select(selections)
8299 });
8300 this.insert("", window, cx);
8301 let empty_str: Arc<str> = Arc::from("");
8302 for (buffer, edits) in linked_ranges {
8303 let snapshot = buffer.read(cx).snapshot();
8304 use text::ToPoint as TP;
8305
8306 let edits = edits
8307 .into_iter()
8308 .map(|range| {
8309 let end_point = TP::to_point(&range.end, &snapshot);
8310 let mut start_point = TP::to_point(&range.start, &snapshot);
8311
8312 if end_point == start_point {
8313 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8314 .saturating_sub(1);
8315 start_point =
8316 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8317 };
8318
8319 (start_point..end_point, empty_str.clone())
8320 })
8321 .sorted_by_key(|(range, _)| range.start)
8322 .collect::<Vec<_>>();
8323 buffer.update(cx, |this, cx| {
8324 this.edit(edits, None, cx);
8325 })
8326 }
8327 this.refresh_inline_completion(true, false, window, cx);
8328 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8329 });
8330 }
8331
8332 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8333 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8334 self.transact(window, cx, |this, window, cx| {
8335 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8336 s.move_with(|map, selection| {
8337 if selection.is_empty() {
8338 let cursor = movement::right(map, selection.head());
8339 selection.end = cursor;
8340 selection.reversed = true;
8341 selection.goal = SelectionGoal::None;
8342 }
8343 })
8344 });
8345 this.insert("", window, cx);
8346 this.refresh_inline_completion(true, false, window, cx);
8347 });
8348 }
8349
8350 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8351 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8352 if self.move_to_prev_snippet_tabstop(window, cx) {
8353 return;
8354 }
8355 self.outdent(&Outdent, window, cx);
8356 }
8357
8358 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8359 if self.move_to_next_snippet_tabstop(window, cx) {
8360 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8361 return;
8362 }
8363 if self.read_only(cx) {
8364 return;
8365 }
8366 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8367 let mut selections = self.selections.all_adjusted(cx);
8368 let buffer = self.buffer.read(cx);
8369 let snapshot = buffer.snapshot(cx);
8370 let rows_iter = selections.iter().map(|s| s.head().row);
8371 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8372
8373 let mut edits = Vec::new();
8374 let mut prev_edited_row = 0;
8375 let mut row_delta = 0;
8376 for selection in &mut selections {
8377 if selection.start.row != prev_edited_row {
8378 row_delta = 0;
8379 }
8380 prev_edited_row = selection.end.row;
8381
8382 // If the selection is non-empty, then increase the indentation of the selected lines.
8383 if !selection.is_empty() {
8384 row_delta =
8385 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8386 continue;
8387 }
8388
8389 // If the selection is empty and the cursor is in the leading whitespace before the
8390 // suggested indentation, then auto-indent the line.
8391 let cursor = selection.head();
8392 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8393 if let Some(suggested_indent) =
8394 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8395 {
8396 if cursor.column < suggested_indent.len
8397 && cursor.column <= current_indent.len
8398 && current_indent.len <= suggested_indent.len
8399 {
8400 selection.start = Point::new(cursor.row, suggested_indent.len);
8401 selection.end = selection.start;
8402 if row_delta == 0 {
8403 edits.extend(Buffer::edit_for_indent_size_adjustment(
8404 cursor.row,
8405 current_indent,
8406 suggested_indent,
8407 ));
8408 row_delta = suggested_indent.len - current_indent.len;
8409 }
8410 continue;
8411 }
8412 }
8413
8414 // Otherwise, insert a hard or soft tab.
8415 let settings = buffer.language_settings_at(cursor, cx);
8416 let tab_size = if settings.hard_tabs {
8417 IndentSize::tab()
8418 } else {
8419 let tab_size = settings.tab_size.get();
8420 let indent_remainder = snapshot
8421 .text_for_range(Point::new(cursor.row, 0)..cursor)
8422 .flat_map(str::chars)
8423 .fold(row_delta % tab_size, |counter: u32, c| {
8424 if c == '\t' {
8425 0
8426 } else {
8427 (counter + 1) % tab_size
8428 }
8429 });
8430
8431 let chars_to_next_tab_stop = tab_size - indent_remainder;
8432 IndentSize::spaces(chars_to_next_tab_stop)
8433 };
8434 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8435 selection.end = selection.start;
8436 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8437 row_delta += tab_size.len;
8438 }
8439
8440 self.transact(window, cx, |this, window, cx| {
8441 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8442 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8443 s.select(selections)
8444 });
8445 this.refresh_inline_completion(true, false, window, cx);
8446 });
8447 }
8448
8449 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8450 if self.read_only(cx) {
8451 return;
8452 }
8453 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8454 let mut selections = self.selections.all::<Point>(cx);
8455 let mut prev_edited_row = 0;
8456 let mut row_delta = 0;
8457 let mut edits = Vec::new();
8458 let buffer = self.buffer.read(cx);
8459 let snapshot = buffer.snapshot(cx);
8460 for selection in &mut selections {
8461 if selection.start.row != prev_edited_row {
8462 row_delta = 0;
8463 }
8464 prev_edited_row = selection.end.row;
8465
8466 row_delta =
8467 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8468 }
8469
8470 self.transact(window, cx, |this, window, cx| {
8471 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8472 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8473 s.select(selections)
8474 });
8475 });
8476 }
8477
8478 fn indent_selection(
8479 buffer: &MultiBuffer,
8480 snapshot: &MultiBufferSnapshot,
8481 selection: &mut Selection<Point>,
8482 edits: &mut Vec<(Range<Point>, String)>,
8483 delta_for_start_row: u32,
8484 cx: &App,
8485 ) -> u32 {
8486 let settings = buffer.language_settings_at(selection.start, cx);
8487 let tab_size = settings.tab_size.get();
8488 let indent_kind = if settings.hard_tabs {
8489 IndentKind::Tab
8490 } else {
8491 IndentKind::Space
8492 };
8493 let mut start_row = selection.start.row;
8494 let mut end_row = selection.end.row + 1;
8495
8496 // If a selection ends at the beginning of a line, don't indent
8497 // that last line.
8498 if selection.end.column == 0 && selection.end.row > selection.start.row {
8499 end_row -= 1;
8500 }
8501
8502 // Avoid re-indenting a row that has already been indented by a
8503 // previous selection, but still update this selection's column
8504 // to reflect that indentation.
8505 if delta_for_start_row > 0 {
8506 start_row += 1;
8507 selection.start.column += delta_for_start_row;
8508 if selection.end.row == selection.start.row {
8509 selection.end.column += delta_for_start_row;
8510 }
8511 }
8512
8513 let mut delta_for_end_row = 0;
8514 let has_multiple_rows = start_row + 1 != end_row;
8515 for row in start_row..end_row {
8516 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8517 let indent_delta = match (current_indent.kind, indent_kind) {
8518 (IndentKind::Space, IndentKind::Space) => {
8519 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8520 IndentSize::spaces(columns_to_next_tab_stop)
8521 }
8522 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8523 (_, IndentKind::Tab) => IndentSize::tab(),
8524 };
8525
8526 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8527 0
8528 } else {
8529 selection.start.column
8530 };
8531 let row_start = Point::new(row, start);
8532 edits.push((
8533 row_start..row_start,
8534 indent_delta.chars().collect::<String>(),
8535 ));
8536
8537 // Update this selection's endpoints to reflect the indentation.
8538 if row == selection.start.row {
8539 selection.start.column += indent_delta.len;
8540 }
8541 if row == selection.end.row {
8542 selection.end.column += indent_delta.len;
8543 delta_for_end_row = indent_delta.len;
8544 }
8545 }
8546
8547 if selection.start.row == selection.end.row {
8548 delta_for_start_row + delta_for_end_row
8549 } else {
8550 delta_for_end_row
8551 }
8552 }
8553
8554 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8555 if self.read_only(cx) {
8556 return;
8557 }
8558 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8559 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8560 let selections = self.selections.all::<Point>(cx);
8561 let mut deletion_ranges = Vec::new();
8562 let mut last_outdent = None;
8563 {
8564 let buffer = self.buffer.read(cx);
8565 let snapshot = buffer.snapshot(cx);
8566 for selection in &selections {
8567 let settings = buffer.language_settings_at(selection.start, cx);
8568 let tab_size = settings.tab_size.get();
8569 let mut rows = selection.spanned_rows(false, &display_map);
8570
8571 // Avoid re-outdenting a row that has already been outdented by a
8572 // previous selection.
8573 if let Some(last_row) = last_outdent {
8574 if last_row == rows.start {
8575 rows.start = rows.start.next_row();
8576 }
8577 }
8578 let has_multiple_rows = rows.len() > 1;
8579 for row in rows.iter_rows() {
8580 let indent_size = snapshot.indent_size_for_line(row);
8581 if indent_size.len > 0 {
8582 let deletion_len = match indent_size.kind {
8583 IndentKind::Space => {
8584 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8585 if columns_to_prev_tab_stop == 0 {
8586 tab_size
8587 } else {
8588 columns_to_prev_tab_stop
8589 }
8590 }
8591 IndentKind::Tab => 1,
8592 };
8593 let start = if has_multiple_rows
8594 || deletion_len > selection.start.column
8595 || indent_size.len < selection.start.column
8596 {
8597 0
8598 } else {
8599 selection.start.column - deletion_len
8600 };
8601 deletion_ranges.push(
8602 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8603 );
8604 last_outdent = Some(row);
8605 }
8606 }
8607 }
8608 }
8609
8610 self.transact(window, cx, |this, window, cx| {
8611 this.buffer.update(cx, |buffer, cx| {
8612 let empty_str: Arc<str> = Arc::default();
8613 buffer.edit(
8614 deletion_ranges
8615 .into_iter()
8616 .map(|range| (range, empty_str.clone())),
8617 None,
8618 cx,
8619 );
8620 });
8621 let selections = this.selections.all::<usize>(cx);
8622 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8623 s.select(selections)
8624 });
8625 });
8626 }
8627
8628 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8629 if self.read_only(cx) {
8630 return;
8631 }
8632 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8633 let selections = self
8634 .selections
8635 .all::<usize>(cx)
8636 .into_iter()
8637 .map(|s| s.range());
8638
8639 self.transact(window, cx, |this, window, cx| {
8640 this.buffer.update(cx, |buffer, cx| {
8641 buffer.autoindent_ranges(selections, cx);
8642 });
8643 let selections = this.selections.all::<usize>(cx);
8644 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8645 s.select(selections)
8646 });
8647 });
8648 }
8649
8650 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8651 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8653 let selections = self.selections.all::<Point>(cx);
8654
8655 let mut new_cursors = Vec::new();
8656 let mut edit_ranges = Vec::new();
8657 let mut selections = selections.iter().peekable();
8658 while let Some(selection) = selections.next() {
8659 let mut rows = selection.spanned_rows(false, &display_map);
8660 let goal_display_column = selection.head().to_display_point(&display_map).column();
8661
8662 // Accumulate contiguous regions of rows that we want to delete.
8663 while let Some(next_selection) = selections.peek() {
8664 let next_rows = next_selection.spanned_rows(false, &display_map);
8665 if next_rows.start <= rows.end {
8666 rows.end = next_rows.end;
8667 selections.next().unwrap();
8668 } else {
8669 break;
8670 }
8671 }
8672
8673 let buffer = &display_map.buffer_snapshot;
8674 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8675 let edit_end;
8676 let cursor_buffer_row;
8677 if buffer.max_point().row >= rows.end.0 {
8678 // If there's a line after the range, delete the \n from the end of the row range
8679 // and position the cursor on the next line.
8680 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8681 cursor_buffer_row = rows.end;
8682 } else {
8683 // If there isn't a line after the range, delete the \n from the line before the
8684 // start of the row range and position the cursor there.
8685 edit_start = edit_start.saturating_sub(1);
8686 edit_end = buffer.len();
8687 cursor_buffer_row = rows.start.previous_row();
8688 }
8689
8690 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8691 *cursor.column_mut() =
8692 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8693
8694 new_cursors.push((
8695 selection.id,
8696 buffer.anchor_after(cursor.to_point(&display_map)),
8697 ));
8698 edit_ranges.push(edit_start..edit_end);
8699 }
8700
8701 self.transact(window, cx, |this, window, cx| {
8702 let buffer = this.buffer.update(cx, |buffer, cx| {
8703 let empty_str: Arc<str> = Arc::default();
8704 buffer.edit(
8705 edit_ranges
8706 .into_iter()
8707 .map(|range| (range, empty_str.clone())),
8708 None,
8709 cx,
8710 );
8711 buffer.snapshot(cx)
8712 });
8713 let new_selections = new_cursors
8714 .into_iter()
8715 .map(|(id, cursor)| {
8716 let cursor = cursor.to_point(&buffer);
8717 Selection {
8718 id,
8719 start: cursor,
8720 end: cursor,
8721 reversed: false,
8722 goal: SelectionGoal::None,
8723 }
8724 })
8725 .collect();
8726
8727 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8728 s.select(new_selections);
8729 });
8730 });
8731 }
8732
8733 pub fn join_lines_impl(
8734 &mut self,
8735 insert_whitespace: bool,
8736 window: &mut Window,
8737 cx: &mut Context<Self>,
8738 ) {
8739 if self.read_only(cx) {
8740 return;
8741 }
8742 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8743 for selection in self.selections.all::<Point>(cx) {
8744 let start = MultiBufferRow(selection.start.row);
8745 // Treat single line selections as if they include the next line. Otherwise this action
8746 // would do nothing for single line selections individual cursors.
8747 let end = if selection.start.row == selection.end.row {
8748 MultiBufferRow(selection.start.row + 1)
8749 } else {
8750 MultiBufferRow(selection.end.row)
8751 };
8752
8753 if let Some(last_row_range) = row_ranges.last_mut() {
8754 if start <= last_row_range.end {
8755 last_row_range.end = end;
8756 continue;
8757 }
8758 }
8759 row_ranges.push(start..end);
8760 }
8761
8762 let snapshot = self.buffer.read(cx).snapshot(cx);
8763 let mut cursor_positions = Vec::new();
8764 for row_range in &row_ranges {
8765 let anchor = snapshot.anchor_before(Point::new(
8766 row_range.end.previous_row().0,
8767 snapshot.line_len(row_range.end.previous_row()),
8768 ));
8769 cursor_positions.push(anchor..anchor);
8770 }
8771
8772 self.transact(window, cx, |this, window, cx| {
8773 for row_range in row_ranges.into_iter().rev() {
8774 for row in row_range.iter_rows().rev() {
8775 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8776 let next_line_row = row.next_row();
8777 let indent = snapshot.indent_size_for_line(next_line_row);
8778 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8779
8780 let replace =
8781 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8782 " "
8783 } else {
8784 ""
8785 };
8786
8787 this.buffer.update(cx, |buffer, cx| {
8788 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8789 });
8790 }
8791 }
8792
8793 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8794 s.select_anchor_ranges(cursor_positions)
8795 });
8796 });
8797 }
8798
8799 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8800 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8801 self.join_lines_impl(true, window, cx);
8802 }
8803
8804 pub fn sort_lines_case_sensitive(
8805 &mut self,
8806 _: &SortLinesCaseSensitive,
8807 window: &mut Window,
8808 cx: &mut Context<Self>,
8809 ) {
8810 self.manipulate_lines(window, cx, |lines| lines.sort())
8811 }
8812
8813 pub fn sort_lines_case_insensitive(
8814 &mut self,
8815 _: &SortLinesCaseInsensitive,
8816 window: &mut Window,
8817 cx: &mut Context<Self>,
8818 ) {
8819 self.manipulate_lines(window, cx, |lines| {
8820 lines.sort_by_key(|line| line.to_lowercase())
8821 })
8822 }
8823
8824 pub fn unique_lines_case_insensitive(
8825 &mut self,
8826 _: &UniqueLinesCaseInsensitive,
8827 window: &mut Window,
8828 cx: &mut Context<Self>,
8829 ) {
8830 self.manipulate_lines(window, cx, |lines| {
8831 let mut seen = HashSet::default();
8832 lines.retain(|line| seen.insert(line.to_lowercase()));
8833 })
8834 }
8835
8836 pub fn unique_lines_case_sensitive(
8837 &mut self,
8838 _: &UniqueLinesCaseSensitive,
8839 window: &mut Window,
8840 cx: &mut Context<Self>,
8841 ) {
8842 self.manipulate_lines(window, cx, |lines| {
8843 let mut seen = HashSet::default();
8844 lines.retain(|line| seen.insert(*line));
8845 })
8846 }
8847
8848 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8849 let Some(project) = self.project.clone() else {
8850 return;
8851 };
8852 self.reload(project, window, cx)
8853 .detach_and_notify_err(window, cx);
8854 }
8855
8856 pub fn restore_file(
8857 &mut self,
8858 _: &::git::RestoreFile,
8859 window: &mut Window,
8860 cx: &mut Context<Self>,
8861 ) {
8862 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8863 let mut buffer_ids = HashSet::default();
8864 let snapshot = self.buffer().read(cx).snapshot(cx);
8865 for selection in self.selections.all::<usize>(cx) {
8866 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8867 }
8868
8869 let buffer = self.buffer().read(cx);
8870 let ranges = buffer_ids
8871 .into_iter()
8872 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8873 .collect::<Vec<_>>();
8874
8875 self.restore_hunks_in_ranges(ranges, window, cx);
8876 }
8877
8878 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8879 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8880 let selections = self
8881 .selections
8882 .all(cx)
8883 .into_iter()
8884 .map(|s| s.range())
8885 .collect();
8886 self.restore_hunks_in_ranges(selections, window, cx);
8887 }
8888
8889 pub fn restore_hunks_in_ranges(
8890 &mut self,
8891 ranges: Vec<Range<Point>>,
8892 window: &mut Window,
8893 cx: &mut Context<Editor>,
8894 ) {
8895 let mut revert_changes = HashMap::default();
8896 let chunk_by = self
8897 .snapshot(window, cx)
8898 .hunks_for_ranges(ranges)
8899 .into_iter()
8900 .chunk_by(|hunk| hunk.buffer_id);
8901 for (buffer_id, hunks) in &chunk_by {
8902 let hunks = hunks.collect::<Vec<_>>();
8903 for hunk in &hunks {
8904 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8905 }
8906 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8907 }
8908 drop(chunk_by);
8909 if !revert_changes.is_empty() {
8910 self.transact(window, cx, |editor, window, cx| {
8911 editor.restore(revert_changes, window, cx);
8912 });
8913 }
8914 }
8915
8916 pub fn open_active_item_in_terminal(
8917 &mut self,
8918 _: &OpenInTerminal,
8919 window: &mut Window,
8920 cx: &mut Context<Self>,
8921 ) {
8922 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8923 let project_path = buffer.read(cx).project_path(cx)?;
8924 let project = self.project.as_ref()?.read(cx);
8925 let entry = project.entry_for_path(&project_path, cx)?;
8926 let parent = match &entry.canonical_path {
8927 Some(canonical_path) => canonical_path.to_path_buf(),
8928 None => project.absolute_path(&project_path, cx)?,
8929 }
8930 .parent()?
8931 .to_path_buf();
8932 Some(parent)
8933 }) {
8934 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8935 }
8936 }
8937
8938 fn set_breakpoint_context_menu(
8939 &mut self,
8940 display_row: DisplayRow,
8941 position: Option<Anchor>,
8942 clicked_point: gpui::Point<Pixels>,
8943 window: &mut Window,
8944 cx: &mut Context<Self>,
8945 ) {
8946 if !cx.has_flag::<Debugger>() {
8947 return;
8948 }
8949 let source = self
8950 .buffer
8951 .read(cx)
8952 .snapshot(cx)
8953 .anchor_before(Point::new(display_row.0, 0u32));
8954
8955 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8956
8957 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8958 self,
8959 source,
8960 clicked_point,
8961 context_menu,
8962 window,
8963 cx,
8964 );
8965 }
8966
8967 fn add_edit_breakpoint_block(
8968 &mut self,
8969 anchor: Anchor,
8970 breakpoint: &Breakpoint,
8971 edit_action: BreakpointPromptEditAction,
8972 window: &mut Window,
8973 cx: &mut Context<Self>,
8974 ) {
8975 let weak_editor = cx.weak_entity();
8976 let bp_prompt = cx.new(|cx| {
8977 BreakpointPromptEditor::new(
8978 weak_editor,
8979 anchor,
8980 breakpoint.clone(),
8981 edit_action,
8982 window,
8983 cx,
8984 )
8985 });
8986
8987 let height = bp_prompt.update(cx, |this, cx| {
8988 this.prompt
8989 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8990 });
8991 let cloned_prompt = bp_prompt.clone();
8992 let blocks = vec![BlockProperties {
8993 style: BlockStyle::Sticky,
8994 placement: BlockPlacement::Above(anchor),
8995 height: Some(height),
8996 render: Arc::new(move |cx| {
8997 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8998 cloned_prompt.clone().into_any_element()
8999 }),
9000 priority: 0,
9001 }];
9002
9003 let focus_handle = bp_prompt.focus_handle(cx);
9004 window.focus(&focus_handle);
9005
9006 let block_ids = self.insert_blocks(blocks, None, cx);
9007 bp_prompt.update(cx, |prompt, _| {
9008 prompt.add_block_ids(block_ids);
9009 });
9010 }
9011
9012 pub(crate) fn breakpoint_at_row(
9013 &self,
9014 row: u32,
9015 window: &mut Window,
9016 cx: &mut Context<Self>,
9017 ) -> Option<(Anchor, Breakpoint)> {
9018 let snapshot = self.snapshot(window, cx);
9019 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9020
9021 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9022 }
9023
9024 pub(crate) fn breakpoint_at_anchor(
9025 &self,
9026 breakpoint_position: Anchor,
9027 snapshot: &EditorSnapshot,
9028 cx: &mut Context<Self>,
9029 ) -> Option<(Anchor, Breakpoint)> {
9030 let project = self.project.clone()?;
9031
9032 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9033 snapshot
9034 .buffer_snapshot
9035 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9036 })?;
9037
9038 let enclosing_excerpt = breakpoint_position.excerpt_id;
9039 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9040 let buffer_snapshot = buffer.read(cx).snapshot();
9041
9042 let row = buffer_snapshot
9043 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9044 .row;
9045
9046 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9047 let anchor_end = snapshot
9048 .buffer_snapshot
9049 .anchor_after(Point::new(row, line_len));
9050
9051 let bp = self
9052 .breakpoint_store
9053 .as_ref()?
9054 .read_with(cx, |breakpoint_store, cx| {
9055 breakpoint_store
9056 .breakpoints(
9057 &buffer,
9058 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9059 &buffer_snapshot,
9060 cx,
9061 )
9062 .next()
9063 .and_then(|(anchor, bp)| {
9064 let breakpoint_row = buffer_snapshot
9065 .summary_for_anchor::<text::PointUtf16>(anchor)
9066 .row;
9067
9068 if breakpoint_row == row {
9069 snapshot
9070 .buffer_snapshot
9071 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9072 .map(|anchor| (anchor, bp.clone()))
9073 } else {
9074 None
9075 }
9076 })
9077 });
9078 bp
9079 }
9080
9081 pub fn edit_log_breakpoint(
9082 &mut self,
9083 _: &EditLogBreakpoint,
9084 window: &mut Window,
9085 cx: &mut Context<Self>,
9086 ) {
9087 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9088 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9089 message: None,
9090 state: BreakpointState::Enabled,
9091 condition: None,
9092 hit_condition: None,
9093 });
9094
9095 self.add_edit_breakpoint_block(
9096 anchor,
9097 &breakpoint,
9098 BreakpointPromptEditAction::Log,
9099 window,
9100 cx,
9101 );
9102 }
9103 }
9104
9105 fn breakpoints_at_cursors(
9106 &self,
9107 window: &mut Window,
9108 cx: &mut Context<Self>,
9109 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9110 let snapshot = self.snapshot(window, cx);
9111 let cursors = self
9112 .selections
9113 .disjoint_anchors()
9114 .into_iter()
9115 .map(|selection| {
9116 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9117
9118 let breakpoint_position = self
9119 .breakpoint_at_row(cursor_position.row, window, cx)
9120 .map(|bp| bp.0)
9121 .unwrap_or_else(|| {
9122 snapshot
9123 .display_snapshot
9124 .buffer_snapshot
9125 .anchor_after(Point::new(cursor_position.row, 0))
9126 });
9127
9128 let breakpoint = self
9129 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9130 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9131
9132 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9133 })
9134 // 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.
9135 .collect::<HashMap<Anchor, _>>();
9136
9137 cursors.into_iter().collect()
9138 }
9139
9140 pub fn enable_breakpoint(
9141 &mut self,
9142 _: &crate::actions::EnableBreakpoint,
9143 window: &mut Window,
9144 cx: &mut Context<Self>,
9145 ) {
9146 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9147 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9148 continue;
9149 };
9150 self.edit_breakpoint_at_anchor(
9151 anchor,
9152 breakpoint,
9153 BreakpointEditAction::InvertState,
9154 cx,
9155 );
9156 }
9157 }
9158
9159 pub fn disable_breakpoint(
9160 &mut self,
9161 _: &crate::actions::DisableBreakpoint,
9162 window: &mut Window,
9163 cx: &mut Context<Self>,
9164 ) {
9165 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9166 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9167 continue;
9168 };
9169 self.edit_breakpoint_at_anchor(
9170 anchor,
9171 breakpoint,
9172 BreakpointEditAction::InvertState,
9173 cx,
9174 );
9175 }
9176 }
9177
9178 pub fn toggle_breakpoint(
9179 &mut self,
9180 _: &crate::actions::ToggleBreakpoint,
9181 window: &mut Window,
9182 cx: &mut Context<Self>,
9183 ) {
9184 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9185 if let Some(breakpoint) = breakpoint {
9186 self.edit_breakpoint_at_anchor(
9187 anchor,
9188 breakpoint,
9189 BreakpointEditAction::Toggle,
9190 cx,
9191 );
9192 } else {
9193 self.edit_breakpoint_at_anchor(
9194 anchor,
9195 Breakpoint::new_standard(),
9196 BreakpointEditAction::Toggle,
9197 cx,
9198 );
9199 }
9200 }
9201 }
9202
9203 pub fn edit_breakpoint_at_anchor(
9204 &mut self,
9205 breakpoint_position: Anchor,
9206 breakpoint: Breakpoint,
9207 edit_action: BreakpointEditAction,
9208 cx: &mut Context<Self>,
9209 ) {
9210 let Some(breakpoint_store) = &self.breakpoint_store else {
9211 return;
9212 };
9213
9214 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9215 if breakpoint_position == Anchor::min() {
9216 self.buffer()
9217 .read(cx)
9218 .excerpt_buffer_ids()
9219 .into_iter()
9220 .next()
9221 } else {
9222 None
9223 }
9224 }) else {
9225 return;
9226 };
9227
9228 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9229 return;
9230 };
9231
9232 breakpoint_store.update(cx, |breakpoint_store, cx| {
9233 breakpoint_store.toggle_breakpoint(
9234 buffer,
9235 (breakpoint_position.text_anchor, breakpoint),
9236 edit_action,
9237 cx,
9238 );
9239 });
9240
9241 cx.notify();
9242 }
9243
9244 #[cfg(any(test, feature = "test-support"))]
9245 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9246 self.breakpoint_store.clone()
9247 }
9248
9249 pub fn prepare_restore_change(
9250 &self,
9251 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9252 hunk: &MultiBufferDiffHunk,
9253 cx: &mut App,
9254 ) -> Option<()> {
9255 if hunk.is_created_file() {
9256 return None;
9257 }
9258 let buffer = self.buffer.read(cx);
9259 let diff = buffer.diff_for(hunk.buffer_id)?;
9260 let buffer = buffer.buffer(hunk.buffer_id)?;
9261 let buffer = buffer.read(cx);
9262 let original_text = diff
9263 .read(cx)
9264 .base_text()
9265 .as_rope()
9266 .slice(hunk.diff_base_byte_range.clone());
9267 let buffer_snapshot = buffer.snapshot();
9268 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9269 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9270 probe
9271 .0
9272 .start
9273 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9274 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9275 }) {
9276 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9277 Some(())
9278 } else {
9279 None
9280 }
9281 }
9282
9283 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9284 self.manipulate_lines(window, cx, |lines| lines.reverse())
9285 }
9286
9287 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9288 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9289 }
9290
9291 fn manipulate_lines<Fn>(
9292 &mut self,
9293 window: &mut Window,
9294 cx: &mut Context<Self>,
9295 mut callback: Fn,
9296 ) where
9297 Fn: FnMut(&mut Vec<&str>),
9298 {
9299 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9300
9301 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9302 let buffer = self.buffer.read(cx).snapshot(cx);
9303
9304 let mut edits = Vec::new();
9305
9306 let selections = self.selections.all::<Point>(cx);
9307 let mut selections = selections.iter().peekable();
9308 let mut contiguous_row_selections = Vec::new();
9309 let mut new_selections = Vec::new();
9310 let mut added_lines = 0;
9311 let mut removed_lines = 0;
9312
9313 while let Some(selection) = selections.next() {
9314 let (start_row, end_row) = consume_contiguous_rows(
9315 &mut contiguous_row_selections,
9316 selection,
9317 &display_map,
9318 &mut selections,
9319 );
9320
9321 let start_point = Point::new(start_row.0, 0);
9322 let end_point = Point::new(
9323 end_row.previous_row().0,
9324 buffer.line_len(end_row.previous_row()),
9325 );
9326 let text = buffer
9327 .text_for_range(start_point..end_point)
9328 .collect::<String>();
9329
9330 let mut lines = text.split('\n').collect_vec();
9331
9332 let lines_before = lines.len();
9333 callback(&mut lines);
9334 let lines_after = lines.len();
9335
9336 edits.push((start_point..end_point, lines.join("\n")));
9337
9338 // Selections must change based on added and removed line count
9339 let start_row =
9340 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9341 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9342 new_selections.push(Selection {
9343 id: selection.id,
9344 start: start_row,
9345 end: end_row,
9346 goal: SelectionGoal::None,
9347 reversed: selection.reversed,
9348 });
9349
9350 if lines_after > lines_before {
9351 added_lines += lines_after - lines_before;
9352 } else if lines_before > lines_after {
9353 removed_lines += lines_before - lines_after;
9354 }
9355 }
9356
9357 self.transact(window, cx, |this, window, cx| {
9358 let buffer = this.buffer.update(cx, |buffer, cx| {
9359 buffer.edit(edits, None, cx);
9360 buffer.snapshot(cx)
9361 });
9362
9363 // Recalculate offsets on newly edited buffer
9364 let new_selections = new_selections
9365 .iter()
9366 .map(|s| {
9367 let start_point = Point::new(s.start.0, 0);
9368 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9369 Selection {
9370 id: s.id,
9371 start: buffer.point_to_offset(start_point),
9372 end: buffer.point_to_offset(end_point),
9373 goal: s.goal,
9374 reversed: s.reversed,
9375 }
9376 })
9377 .collect();
9378
9379 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9380 s.select(new_selections);
9381 });
9382
9383 this.request_autoscroll(Autoscroll::fit(), cx);
9384 });
9385 }
9386
9387 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9388 self.manipulate_text(window, cx, |text| {
9389 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9390 if has_upper_case_characters {
9391 text.to_lowercase()
9392 } else {
9393 text.to_uppercase()
9394 }
9395 })
9396 }
9397
9398 pub fn convert_to_upper_case(
9399 &mut self,
9400 _: &ConvertToUpperCase,
9401 window: &mut Window,
9402 cx: &mut Context<Self>,
9403 ) {
9404 self.manipulate_text(window, cx, |text| text.to_uppercase())
9405 }
9406
9407 pub fn convert_to_lower_case(
9408 &mut self,
9409 _: &ConvertToLowerCase,
9410 window: &mut Window,
9411 cx: &mut Context<Self>,
9412 ) {
9413 self.manipulate_text(window, cx, |text| text.to_lowercase())
9414 }
9415
9416 pub fn convert_to_title_case(
9417 &mut self,
9418 _: &ConvertToTitleCase,
9419 window: &mut Window,
9420 cx: &mut Context<Self>,
9421 ) {
9422 self.manipulate_text(window, cx, |text| {
9423 text.split('\n')
9424 .map(|line| line.to_case(Case::Title))
9425 .join("\n")
9426 })
9427 }
9428
9429 pub fn convert_to_snake_case(
9430 &mut self,
9431 _: &ConvertToSnakeCase,
9432 window: &mut Window,
9433 cx: &mut Context<Self>,
9434 ) {
9435 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9436 }
9437
9438 pub fn convert_to_kebab_case(
9439 &mut self,
9440 _: &ConvertToKebabCase,
9441 window: &mut Window,
9442 cx: &mut Context<Self>,
9443 ) {
9444 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9445 }
9446
9447 pub fn convert_to_upper_camel_case(
9448 &mut self,
9449 _: &ConvertToUpperCamelCase,
9450 window: &mut Window,
9451 cx: &mut Context<Self>,
9452 ) {
9453 self.manipulate_text(window, cx, |text| {
9454 text.split('\n')
9455 .map(|line| line.to_case(Case::UpperCamel))
9456 .join("\n")
9457 })
9458 }
9459
9460 pub fn convert_to_lower_camel_case(
9461 &mut self,
9462 _: &ConvertToLowerCamelCase,
9463 window: &mut Window,
9464 cx: &mut Context<Self>,
9465 ) {
9466 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9467 }
9468
9469 pub fn convert_to_opposite_case(
9470 &mut self,
9471 _: &ConvertToOppositeCase,
9472 window: &mut Window,
9473 cx: &mut Context<Self>,
9474 ) {
9475 self.manipulate_text(window, cx, |text| {
9476 text.chars()
9477 .fold(String::with_capacity(text.len()), |mut t, c| {
9478 if c.is_uppercase() {
9479 t.extend(c.to_lowercase());
9480 } else {
9481 t.extend(c.to_uppercase());
9482 }
9483 t
9484 })
9485 })
9486 }
9487
9488 pub fn convert_to_rot13(
9489 &mut self,
9490 _: &ConvertToRot13,
9491 window: &mut Window,
9492 cx: &mut Context<Self>,
9493 ) {
9494 self.manipulate_text(window, cx, |text| {
9495 text.chars()
9496 .map(|c| match c {
9497 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9498 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9499 _ => c,
9500 })
9501 .collect()
9502 })
9503 }
9504
9505 pub fn convert_to_rot47(
9506 &mut self,
9507 _: &ConvertToRot47,
9508 window: &mut Window,
9509 cx: &mut Context<Self>,
9510 ) {
9511 self.manipulate_text(window, cx, |text| {
9512 text.chars()
9513 .map(|c| {
9514 let code_point = c as u32;
9515 if code_point >= 33 && code_point <= 126 {
9516 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9517 }
9518 c
9519 })
9520 .collect()
9521 })
9522 }
9523
9524 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9525 where
9526 Fn: FnMut(&str) -> String,
9527 {
9528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9529 let buffer = self.buffer.read(cx).snapshot(cx);
9530
9531 let mut new_selections = Vec::new();
9532 let mut edits = Vec::new();
9533 let mut selection_adjustment = 0i32;
9534
9535 for selection in self.selections.all::<usize>(cx) {
9536 let selection_is_empty = selection.is_empty();
9537
9538 let (start, end) = if selection_is_empty {
9539 let word_range = movement::surrounding_word(
9540 &display_map,
9541 selection.start.to_display_point(&display_map),
9542 );
9543 let start = word_range.start.to_offset(&display_map, Bias::Left);
9544 let end = word_range.end.to_offset(&display_map, Bias::Left);
9545 (start, end)
9546 } else {
9547 (selection.start, selection.end)
9548 };
9549
9550 let text = buffer.text_for_range(start..end).collect::<String>();
9551 let old_length = text.len() as i32;
9552 let text = callback(&text);
9553
9554 new_selections.push(Selection {
9555 start: (start as i32 - selection_adjustment) as usize,
9556 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9557 goal: SelectionGoal::None,
9558 ..selection
9559 });
9560
9561 selection_adjustment += old_length - text.len() as i32;
9562
9563 edits.push((start..end, text));
9564 }
9565
9566 self.transact(window, cx, |this, window, cx| {
9567 this.buffer.update(cx, |buffer, cx| {
9568 buffer.edit(edits, None, cx);
9569 });
9570
9571 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9572 s.select(new_selections);
9573 });
9574
9575 this.request_autoscroll(Autoscroll::fit(), cx);
9576 });
9577 }
9578
9579 pub fn duplicate(
9580 &mut self,
9581 upwards: bool,
9582 whole_lines: bool,
9583 window: &mut Window,
9584 cx: &mut Context<Self>,
9585 ) {
9586 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9587
9588 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9589 let buffer = &display_map.buffer_snapshot;
9590 let selections = self.selections.all::<Point>(cx);
9591
9592 let mut edits = Vec::new();
9593 let mut selections_iter = selections.iter().peekable();
9594 while let Some(selection) = selections_iter.next() {
9595 let mut rows = selection.spanned_rows(false, &display_map);
9596 // duplicate line-wise
9597 if whole_lines || selection.start == selection.end {
9598 // Avoid duplicating the same lines twice.
9599 while let Some(next_selection) = selections_iter.peek() {
9600 let next_rows = next_selection.spanned_rows(false, &display_map);
9601 if next_rows.start < rows.end {
9602 rows.end = next_rows.end;
9603 selections_iter.next().unwrap();
9604 } else {
9605 break;
9606 }
9607 }
9608
9609 // Copy the text from the selected row region and splice it either at the start
9610 // or end of the region.
9611 let start = Point::new(rows.start.0, 0);
9612 let end = Point::new(
9613 rows.end.previous_row().0,
9614 buffer.line_len(rows.end.previous_row()),
9615 );
9616 let text = buffer
9617 .text_for_range(start..end)
9618 .chain(Some("\n"))
9619 .collect::<String>();
9620 let insert_location = if upwards {
9621 Point::new(rows.end.0, 0)
9622 } else {
9623 start
9624 };
9625 edits.push((insert_location..insert_location, text));
9626 } else {
9627 // duplicate character-wise
9628 let start = selection.start;
9629 let end = selection.end;
9630 let text = buffer.text_for_range(start..end).collect::<String>();
9631 edits.push((selection.end..selection.end, text));
9632 }
9633 }
9634
9635 self.transact(window, cx, |this, _, cx| {
9636 this.buffer.update(cx, |buffer, cx| {
9637 buffer.edit(edits, None, cx);
9638 });
9639
9640 this.request_autoscroll(Autoscroll::fit(), cx);
9641 });
9642 }
9643
9644 pub fn duplicate_line_up(
9645 &mut self,
9646 _: &DuplicateLineUp,
9647 window: &mut Window,
9648 cx: &mut Context<Self>,
9649 ) {
9650 self.duplicate(true, true, window, cx);
9651 }
9652
9653 pub fn duplicate_line_down(
9654 &mut self,
9655 _: &DuplicateLineDown,
9656 window: &mut Window,
9657 cx: &mut Context<Self>,
9658 ) {
9659 self.duplicate(false, true, window, cx);
9660 }
9661
9662 pub fn duplicate_selection(
9663 &mut self,
9664 _: &DuplicateSelection,
9665 window: &mut Window,
9666 cx: &mut Context<Self>,
9667 ) {
9668 self.duplicate(false, false, window, cx);
9669 }
9670
9671 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9672 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9673
9674 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9675 let buffer = self.buffer.read(cx).snapshot(cx);
9676
9677 let mut edits = Vec::new();
9678 let mut unfold_ranges = Vec::new();
9679 let mut refold_creases = Vec::new();
9680
9681 let selections = self.selections.all::<Point>(cx);
9682 let mut selections = selections.iter().peekable();
9683 let mut contiguous_row_selections = Vec::new();
9684 let mut new_selections = Vec::new();
9685
9686 while let Some(selection) = selections.next() {
9687 // Find all the selections that span a contiguous row range
9688 let (start_row, end_row) = consume_contiguous_rows(
9689 &mut contiguous_row_selections,
9690 selection,
9691 &display_map,
9692 &mut selections,
9693 );
9694
9695 // Move the text spanned by the row range to be before the line preceding the row range
9696 if start_row.0 > 0 {
9697 let range_to_move = Point::new(
9698 start_row.previous_row().0,
9699 buffer.line_len(start_row.previous_row()),
9700 )
9701 ..Point::new(
9702 end_row.previous_row().0,
9703 buffer.line_len(end_row.previous_row()),
9704 );
9705 let insertion_point = display_map
9706 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9707 .0;
9708
9709 // Don't move lines across excerpts
9710 if buffer
9711 .excerpt_containing(insertion_point..range_to_move.end)
9712 .is_some()
9713 {
9714 let text = buffer
9715 .text_for_range(range_to_move.clone())
9716 .flat_map(|s| s.chars())
9717 .skip(1)
9718 .chain(['\n'])
9719 .collect::<String>();
9720
9721 edits.push((
9722 buffer.anchor_after(range_to_move.start)
9723 ..buffer.anchor_before(range_to_move.end),
9724 String::new(),
9725 ));
9726 let insertion_anchor = buffer.anchor_after(insertion_point);
9727 edits.push((insertion_anchor..insertion_anchor, text));
9728
9729 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9730
9731 // Move selections up
9732 new_selections.extend(contiguous_row_selections.drain(..).map(
9733 |mut selection| {
9734 selection.start.row -= row_delta;
9735 selection.end.row -= row_delta;
9736 selection
9737 },
9738 ));
9739
9740 // Move folds up
9741 unfold_ranges.push(range_to_move.clone());
9742 for fold in display_map.folds_in_range(
9743 buffer.anchor_before(range_to_move.start)
9744 ..buffer.anchor_after(range_to_move.end),
9745 ) {
9746 let mut start = fold.range.start.to_point(&buffer);
9747 let mut end = fold.range.end.to_point(&buffer);
9748 start.row -= row_delta;
9749 end.row -= row_delta;
9750 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9751 }
9752 }
9753 }
9754
9755 // If we didn't move line(s), preserve the existing selections
9756 new_selections.append(&mut contiguous_row_selections);
9757 }
9758
9759 self.transact(window, cx, |this, window, cx| {
9760 this.unfold_ranges(&unfold_ranges, true, true, cx);
9761 this.buffer.update(cx, |buffer, cx| {
9762 for (range, text) in edits {
9763 buffer.edit([(range, text)], None, cx);
9764 }
9765 });
9766 this.fold_creases(refold_creases, true, window, cx);
9767 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9768 s.select(new_selections);
9769 })
9770 });
9771 }
9772
9773 pub fn move_line_down(
9774 &mut self,
9775 _: &MoveLineDown,
9776 window: &mut Window,
9777 cx: &mut Context<Self>,
9778 ) {
9779 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9780
9781 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9782 let buffer = self.buffer.read(cx).snapshot(cx);
9783
9784 let mut edits = Vec::new();
9785 let mut unfold_ranges = Vec::new();
9786 let mut refold_creases = Vec::new();
9787
9788 let selections = self.selections.all::<Point>(cx);
9789 let mut selections = selections.iter().peekable();
9790 let mut contiguous_row_selections = Vec::new();
9791 let mut new_selections = Vec::new();
9792
9793 while let Some(selection) = selections.next() {
9794 // Find all the selections that span a contiguous row range
9795 let (start_row, end_row) = consume_contiguous_rows(
9796 &mut contiguous_row_selections,
9797 selection,
9798 &display_map,
9799 &mut selections,
9800 );
9801
9802 // Move the text spanned by the row range to be after the last line of the row range
9803 if end_row.0 <= buffer.max_point().row {
9804 let range_to_move =
9805 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9806 let insertion_point = display_map
9807 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9808 .0;
9809
9810 // Don't move lines across excerpt boundaries
9811 if buffer
9812 .excerpt_containing(range_to_move.start..insertion_point)
9813 .is_some()
9814 {
9815 let mut text = String::from("\n");
9816 text.extend(buffer.text_for_range(range_to_move.clone()));
9817 text.pop(); // Drop trailing newline
9818 edits.push((
9819 buffer.anchor_after(range_to_move.start)
9820 ..buffer.anchor_before(range_to_move.end),
9821 String::new(),
9822 ));
9823 let insertion_anchor = buffer.anchor_after(insertion_point);
9824 edits.push((insertion_anchor..insertion_anchor, text));
9825
9826 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9827
9828 // Move selections down
9829 new_selections.extend(contiguous_row_selections.drain(..).map(
9830 |mut selection| {
9831 selection.start.row += row_delta;
9832 selection.end.row += row_delta;
9833 selection
9834 },
9835 ));
9836
9837 // Move folds down
9838 unfold_ranges.push(range_to_move.clone());
9839 for fold in display_map.folds_in_range(
9840 buffer.anchor_before(range_to_move.start)
9841 ..buffer.anchor_after(range_to_move.end),
9842 ) {
9843 let mut start = fold.range.start.to_point(&buffer);
9844 let mut end = fold.range.end.to_point(&buffer);
9845 start.row += row_delta;
9846 end.row += row_delta;
9847 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9848 }
9849 }
9850 }
9851
9852 // If we didn't move line(s), preserve the existing selections
9853 new_selections.append(&mut contiguous_row_selections);
9854 }
9855
9856 self.transact(window, cx, |this, window, cx| {
9857 this.unfold_ranges(&unfold_ranges, true, true, cx);
9858 this.buffer.update(cx, |buffer, cx| {
9859 for (range, text) in edits {
9860 buffer.edit([(range, text)], None, cx);
9861 }
9862 });
9863 this.fold_creases(refold_creases, true, window, cx);
9864 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9865 s.select(new_selections)
9866 });
9867 });
9868 }
9869
9870 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9871 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9872 let text_layout_details = &self.text_layout_details(window);
9873 self.transact(window, cx, |this, window, cx| {
9874 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9875 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9876 s.move_with(|display_map, selection| {
9877 if !selection.is_empty() {
9878 return;
9879 }
9880
9881 let mut head = selection.head();
9882 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9883 if head.column() == display_map.line_len(head.row()) {
9884 transpose_offset = display_map
9885 .buffer_snapshot
9886 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9887 }
9888
9889 if transpose_offset == 0 {
9890 return;
9891 }
9892
9893 *head.column_mut() += 1;
9894 head = display_map.clip_point(head, Bias::Right);
9895 let goal = SelectionGoal::HorizontalPosition(
9896 display_map
9897 .x_for_display_point(head, text_layout_details)
9898 .into(),
9899 );
9900 selection.collapse_to(head, goal);
9901
9902 let transpose_start = display_map
9903 .buffer_snapshot
9904 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9905 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9906 let transpose_end = display_map
9907 .buffer_snapshot
9908 .clip_offset(transpose_offset + 1, Bias::Right);
9909 if let Some(ch) =
9910 display_map.buffer_snapshot.chars_at(transpose_start).next()
9911 {
9912 edits.push((transpose_start..transpose_offset, String::new()));
9913 edits.push((transpose_end..transpose_end, ch.to_string()));
9914 }
9915 }
9916 });
9917 edits
9918 });
9919 this.buffer
9920 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9921 let selections = this.selections.all::<usize>(cx);
9922 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9923 s.select(selections);
9924 });
9925 });
9926 }
9927
9928 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9929 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9930 self.rewrap_impl(RewrapOptions::default(), cx)
9931 }
9932
9933 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9934 let buffer = self.buffer.read(cx).snapshot(cx);
9935 let selections = self.selections.all::<Point>(cx);
9936 let mut selections = selections.iter().peekable();
9937
9938 let mut edits = Vec::new();
9939 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9940
9941 while let Some(selection) = selections.next() {
9942 let mut start_row = selection.start.row;
9943 let mut end_row = selection.end.row;
9944
9945 // Skip selections that overlap with a range that has already been rewrapped.
9946 let selection_range = start_row..end_row;
9947 if rewrapped_row_ranges
9948 .iter()
9949 .any(|range| range.overlaps(&selection_range))
9950 {
9951 continue;
9952 }
9953
9954 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9955
9956 // Since not all lines in the selection may be at the same indent
9957 // level, choose the indent size that is the most common between all
9958 // of the lines.
9959 //
9960 // If there is a tie, we use the deepest indent.
9961 let (indent_size, indent_end) = {
9962 let mut indent_size_occurrences = HashMap::default();
9963 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9964
9965 for row in start_row..=end_row {
9966 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9967 rows_by_indent_size.entry(indent).or_default().push(row);
9968 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9969 }
9970
9971 let indent_size = indent_size_occurrences
9972 .into_iter()
9973 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9974 .map(|(indent, _)| indent)
9975 .unwrap_or_default();
9976 let row = rows_by_indent_size[&indent_size][0];
9977 let indent_end = Point::new(row, indent_size.len);
9978
9979 (indent_size, indent_end)
9980 };
9981
9982 let mut line_prefix = indent_size.chars().collect::<String>();
9983
9984 let mut inside_comment = false;
9985 if let Some(comment_prefix) =
9986 buffer
9987 .language_scope_at(selection.head())
9988 .and_then(|language| {
9989 language
9990 .line_comment_prefixes()
9991 .iter()
9992 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9993 .cloned()
9994 })
9995 {
9996 line_prefix.push_str(&comment_prefix);
9997 inside_comment = true;
9998 }
9999
10000 let language_settings = buffer.language_settings_at(selection.head(), cx);
10001 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10002 RewrapBehavior::InComments => inside_comment,
10003 RewrapBehavior::InSelections => !selection.is_empty(),
10004 RewrapBehavior::Anywhere => true,
10005 };
10006
10007 let should_rewrap = options.override_language_settings
10008 || allow_rewrap_based_on_language
10009 || self.hard_wrap.is_some();
10010 if !should_rewrap {
10011 continue;
10012 }
10013
10014 if selection.is_empty() {
10015 'expand_upwards: while start_row > 0 {
10016 let prev_row = start_row - 1;
10017 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10018 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10019 {
10020 start_row = prev_row;
10021 } else {
10022 break 'expand_upwards;
10023 }
10024 }
10025
10026 'expand_downwards: while end_row < buffer.max_point().row {
10027 let next_row = end_row + 1;
10028 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10029 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10030 {
10031 end_row = next_row;
10032 } else {
10033 break 'expand_downwards;
10034 }
10035 }
10036 }
10037
10038 let start = Point::new(start_row, 0);
10039 let start_offset = start.to_offset(&buffer);
10040 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10041 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10042 let Some(lines_without_prefixes) = selection_text
10043 .lines()
10044 .map(|line| {
10045 line.strip_prefix(&line_prefix)
10046 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10047 .ok_or_else(|| {
10048 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10049 })
10050 })
10051 .collect::<Result<Vec<_>, _>>()
10052 .log_err()
10053 else {
10054 continue;
10055 };
10056
10057 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10058 buffer
10059 .language_settings_at(Point::new(start_row, 0), cx)
10060 .preferred_line_length as usize
10061 });
10062 let wrapped_text = wrap_with_prefix(
10063 line_prefix,
10064 lines_without_prefixes.join("\n"),
10065 wrap_column,
10066 tab_size,
10067 options.preserve_existing_whitespace,
10068 );
10069
10070 // TODO: should always use char-based diff while still supporting cursor behavior that
10071 // matches vim.
10072 let mut diff_options = DiffOptions::default();
10073 if options.override_language_settings {
10074 diff_options.max_word_diff_len = 0;
10075 diff_options.max_word_diff_line_count = 0;
10076 } else {
10077 diff_options.max_word_diff_len = usize::MAX;
10078 diff_options.max_word_diff_line_count = usize::MAX;
10079 }
10080
10081 for (old_range, new_text) in
10082 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10083 {
10084 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10085 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10086 edits.push((edit_start..edit_end, new_text));
10087 }
10088
10089 rewrapped_row_ranges.push(start_row..=end_row);
10090 }
10091
10092 self.buffer
10093 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10094 }
10095
10096 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10097 let mut text = String::new();
10098 let buffer = self.buffer.read(cx).snapshot(cx);
10099 let mut selections = self.selections.all::<Point>(cx);
10100 let mut clipboard_selections = Vec::with_capacity(selections.len());
10101 {
10102 let max_point = buffer.max_point();
10103 let mut is_first = true;
10104 for selection in &mut selections {
10105 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10106 if is_entire_line {
10107 selection.start = Point::new(selection.start.row, 0);
10108 if !selection.is_empty() && selection.end.column == 0 {
10109 selection.end = cmp::min(max_point, selection.end);
10110 } else {
10111 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10112 }
10113 selection.goal = SelectionGoal::None;
10114 }
10115 if is_first {
10116 is_first = false;
10117 } else {
10118 text += "\n";
10119 }
10120 let mut len = 0;
10121 for chunk in buffer.text_for_range(selection.start..selection.end) {
10122 text.push_str(chunk);
10123 len += chunk.len();
10124 }
10125 clipboard_selections.push(ClipboardSelection {
10126 len,
10127 is_entire_line,
10128 first_line_indent: buffer
10129 .indent_size_for_line(MultiBufferRow(selection.start.row))
10130 .len,
10131 });
10132 }
10133 }
10134
10135 self.transact(window, cx, |this, window, cx| {
10136 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10137 s.select(selections);
10138 });
10139 this.insert("", window, cx);
10140 });
10141 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10142 }
10143
10144 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10145 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10146 let item = self.cut_common(window, cx);
10147 cx.write_to_clipboard(item);
10148 }
10149
10150 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10151 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10152 self.change_selections(None, window, cx, |s| {
10153 s.move_with(|snapshot, sel| {
10154 if sel.is_empty() {
10155 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10156 }
10157 });
10158 });
10159 let item = self.cut_common(window, cx);
10160 cx.set_global(KillRing(item))
10161 }
10162
10163 pub fn kill_ring_yank(
10164 &mut self,
10165 _: &KillRingYank,
10166 window: &mut Window,
10167 cx: &mut Context<Self>,
10168 ) {
10169 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10170 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10171 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10172 (kill_ring.text().to_string(), kill_ring.metadata_json())
10173 } else {
10174 return;
10175 }
10176 } else {
10177 return;
10178 };
10179 self.do_paste(&text, metadata, false, window, cx);
10180 }
10181
10182 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10183 self.do_copy(true, cx);
10184 }
10185
10186 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10187 self.do_copy(false, cx);
10188 }
10189
10190 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10191 let selections = self.selections.all::<Point>(cx);
10192 let buffer = self.buffer.read(cx).read(cx);
10193 let mut text = String::new();
10194
10195 let mut clipboard_selections = Vec::with_capacity(selections.len());
10196 {
10197 let max_point = buffer.max_point();
10198 let mut is_first = true;
10199 for selection in &selections {
10200 let mut start = selection.start;
10201 let mut end = selection.end;
10202 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10203 if is_entire_line {
10204 start = Point::new(start.row, 0);
10205 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10206 }
10207
10208 let mut trimmed_selections = Vec::new();
10209 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10210 let row = MultiBufferRow(start.row);
10211 let first_indent = buffer.indent_size_for_line(row);
10212 if first_indent.len == 0 || start.column > first_indent.len {
10213 trimmed_selections.push(start..end);
10214 } else {
10215 trimmed_selections.push(
10216 Point::new(row.0, first_indent.len)
10217 ..Point::new(row.0, buffer.line_len(row)),
10218 );
10219 for row in start.row + 1..=end.row {
10220 let mut line_len = buffer.line_len(MultiBufferRow(row));
10221 if row == end.row {
10222 line_len = end.column;
10223 }
10224 if line_len == 0 {
10225 trimmed_selections
10226 .push(Point::new(row, 0)..Point::new(row, line_len));
10227 continue;
10228 }
10229 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10230 if row_indent_size.len >= first_indent.len {
10231 trimmed_selections.push(
10232 Point::new(row, first_indent.len)..Point::new(row, line_len),
10233 );
10234 } else {
10235 trimmed_selections.clear();
10236 trimmed_selections.push(start..end);
10237 break;
10238 }
10239 }
10240 }
10241 } else {
10242 trimmed_selections.push(start..end);
10243 }
10244
10245 for trimmed_range in trimmed_selections {
10246 if is_first {
10247 is_first = false;
10248 } else {
10249 text += "\n";
10250 }
10251 let mut len = 0;
10252 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10253 text.push_str(chunk);
10254 len += chunk.len();
10255 }
10256 clipboard_selections.push(ClipboardSelection {
10257 len,
10258 is_entire_line,
10259 first_line_indent: buffer
10260 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10261 .len,
10262 });
10263 }
10264 }
10265 }
10266
10267 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10268 text,
10269 clipboard_selections,
10270 ));
10271 }
10272
10273 pub fn do_paste(
10274 &mut self,
10275 text: &String,
10276 clipboard_selections: Option<Vec<ClipboardSelection>>,
10277 handle_entire_lines: bool,
10278 window: &mut Window,
10279 cx: &mut Context<Self>,
10280 ) {
10281 if self.read_only(cx) {
10282 return;
10283 }
10284
10285 let clipboard_text = Cow::Borrowed(text);
10286
10287 self.transact(window, cx, |this, window, cx| {
10288 if let Some(mut clipboard_selections) = clipboard_selections {
10289 let old_selections = this.selections.all::<usize>(cx);
10290 let all_selections_were_entire_line =
10291 clipboard_selections.iter().all(|s| s.is_entire_line);
10292 let first_selection_indent_column =
10293 clipboard_selections.first().map(|s| s.first_line_indent);
10294 if clipboard_selections.len() != old_selections.len() {
10295 clipboard_selections.drain(..);
10296 }
10297 let cursor_offset = this.selections.last::<usize>(cx).head();
10298 let mut auto_indent_on_paste = true;
10299
10300 this.buffer.update(cx, |buffer, cx| {
10301 let snapshot = buffer.read(cx);
10302 auto_indent_on_paste = snapshot
10303 .language_settings_at(cursor_offset, cx)
10304 .auto_indent_on_paste;
10305
10306 let mut start_offset = 0;
10307 let mut edits = Vec::new();
10308 let mut original_indent_columns = Vec::new();
10309 for (ix, selection) in old_selections.iter().enumerate() {
10310 let to_insert;
10311 let entire_line;
10312 let original_indent_column;
10313 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10314 let end_offset = start_offset + clipboard_selection.len;
10315 to_insert = &clipboard_text[start_offset..end_offset];
10316 entire_line = clipboard_selection.is_entire_line;
10317 start_offset = end_offset + 1;
10318 original_indent_column = Some(clipboard_selection.first_line_indent);
10319 } else {
10320 to_insert = clipboard_text.as_str();
10321 entire_line = all_selections_were_entire_line;
10322 original_indent_column = first_selection_indent_column
10323 }
10324
10325 // If the corresponding selection was empty when this slice of the
10326 // clipboard text was written, then the entire line containing the
10327 // selection was copied. If this selection is also currently empty,
10328 // then paste the line before the current line of the buffer.
10329 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10330 let column = selection.start.to_point(&snapshot).column as usize;
10331 let line_start = selection.start - column;
10332 line_start..line_start
10333 } else {
10334 selection.range()
10335 };
10336
10337 edits.push((range, to_insert));
10338 original_indent_columns.push(original_indent_column);
10339 }
10340 drop(snapshot);
10341
10342 buffer.edit(
10343 edits,
10344 if auto_indent_on_paste {
10345 Some(AutoindentMode::Block {
10346 original_indent_columns,
10347 })
10348 } else {
10349 None
10350 },
10351 cx,
10352 );
10353 });
10354
10355 let selections = this.selections.all::<usize>(cx);
10356 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10357 s.select(selections)
10358 });
10359 } else {
10360 this.insert(&clipboard_text, window, cx);
10361 }
10362 });
10363 }
10364
10365 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10366 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10367 if let Some(item) = cx.read_from_clipboard() {
10368 let entries = item.entries();
10369
10370 match entries.first() {
10371 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10372 // of all the pasted entries.
10373 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10374 .do_paste(
10375 clipboard_string.text(),
10376 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10377 true,
10378 window,
10379 cx,
10380 ),
10381 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10382 }
10383 }
10384 }
10385
10386 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10387 if self.read_only(cx) {
10388 return;
10389 }
10390
10391 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10392
10393 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10394 if let Some((selections, _)) =
10395 self.selection_history.transaction(transaction_id).cloned()
10396 {
10397 self.change_selections(None, window, cx, |s| {
10398 s.select_anchors(selections.to_vec());
10399 });
10400 } else {
10401 log::error!(
10402 "No entry in selection_history found for undo. \
10403 This may correspond to a bug where undo does not update the selection. \
10404 If this is occurring, please add details to \
10405 https://github.com/zed-industries/zed/issues/22692"
10406 );
10407 }
10408 self.request_autoscroll(Autoscroll::fit(), cx);
10409 self.unmark_text(window, cx);
10410 self.refresh_inline_completion(true, false, window, cx);
10411 cx.emit(EditorEvent::Edited { transaction_id });
10412 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10413 }
10414 }
10415
10416 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10417 if self.read_only(cx) {
10418 return;
10419 }
10420
10421 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10422
10423 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10424 if let Some((_, Some(selections))) =
10425 self.selection_history.transaction(transaction_id).cloned()
10426 {
10427 self.change_selections(None, window, cx, |s| {
10428 s.select_anchors(selections.to_vec());
10429 });
10430 } else {
10431 log::error!(
10432 "No entry in selection_history found for redo. \
10433 This may correspond to a bug where undo does not update the selection. \
10434 If this is occurring, please add details to \
10435 https://github.com/zed-industries/zed/issues/22692"
10436 );
10437 }
10438 self.request_autoscroll(Autoscroll::fit(), cx);
10439 self.unmark_text(window, cx);
10440 self.refresh_inline_completion(true, false, window, cx);
10441 cx.emit(EditorEvent::Edited { transaction_id });
10442 }
10443 }
10444
10445 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10446 self.buffer
10447 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10448 }
10449
10450 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10451 self.buffer
10452 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10453 }
10454
10455 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10456 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10457 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10458 s.move_with(|map, selection| {
10459 let cursor = if selection.is_empty() {
10460 movement::left(map, selection.start)
10461 } else {
10462 selection.start
10463 };
10464 selection.collapse_to(cursor, SelectionGoal::None);
10465 });
10466 })
10467 }
10468
10469 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10470 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10471 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10472 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10473 })
10474 }
10475
10476 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10477 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10478 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10479 s.move_with(|map, selection| {
10480 let cursor = if selection.is_empty() {
10481 movement::right(map, selection.end)
10482 } else {
10483 selection.end
10484 };
10485 selection.collapse_to(cursor, SelectionGoal::None)
10486 });
10487 })
10488 }
10489
10490 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10491 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10492 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10493 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10494 })
10495 }
10496
10497 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10498 if self.take_rename(true, window, cx).is_some() {
10499 return;
10500 }
10501
10502 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10503 cx.propagate();
10504 return;
10505 }
10506
10507 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10508
10509 let text_layout_details = &self.text_layout_details(window);
10510 let selection_count = self.selections.count();
10511 let first_selection = self.selections.first_anchor();
10512
10513 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10514 s.move_with(|map, selection| {
10515 if !selection.is_empty() {
10516 selection.goal = SelectionGoal::None;
10517 }
10518 let (cursor, goal) = movement::up(
10519 map,
10520 selection.start,
10521 selection.goal,
10522 false,
10523 text_layout_details,
10524 );
10525 selection.collapse_to(cursor, goal);
10526 });
10527 });
10528
10529 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10530 {
10531 cx.propagate();
10532 }
10533 }
10534
10535 pub fn move_up_by_lines(
10536 &mut self,
10537 action: &MoveUpByLines,
10538 window: &mut Window,
10539 cx: &mut Context<Self>,
10540 ) {
10541 if self.take_rename(true, window, cx).is_some() {
10542 return;
10543 }
10544
10545 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10546 cx.propagate();
10547 return;
10548 }
10549
10550 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10551
10552 let text_layout_details = &self.text_layout_details(window);
10553
10554 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10555 s.move_with(|map, selection| {
10556 if !selection.is_empty() {
10557 selection.goal = SelectionGoal::None;
10558 }
10559 let (cursor, goal) = movement::up_by_rows(
10560 map,
10561 selection.start,
10562 action.lines,
10563 selection.goal,
10564 false,
10565 text_layout_details,
10566 );
10567 selection.collapse_to(cursor, goal);
10568 });
10569 })
10570 }
10571
10572 pub fn move_down_by_lines(
10573 &mut self,
10574 action: &MoveDownByLines,
10575 window: &mut Window,
10576 cx: &mut Context<Self>,
10577 ) {
10578 if self.take_rename(true, window, cx).is_some() {
10579 return;
10580 }
10581
10582 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10583 cx.propagate();
10584 return;
10585 }
10586
10587 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10588
10589 let text_layout_details = &self.text_layout_details(window);
10590
10591 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10592 s.move_with(|map, selection| {
10593 if !selection.is_empty() {
10594 selection.goal = SelectionGoal::None;
10595 }
10596 let (cursor, goal) = movement::down_by_rows(
10597 map,
10598 selection.start,
10599 action.lines,
10600 selection.goal,
10601 false,
10602 text_layout_details,
10603 );
10604 selection.collapse_to(cursor, goal);
10605 });
10606 })
10607 }
10608
10609 pub fn select_down_by_lines(
10610 &mut self,
10611 action: &SelectDownByLines,
10612 window: &mut Window,
10613 cx: &mut Context<Self>,
10614 ) {
10615 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10616 let text_layout_details = &self.text_layout_details(window);
10617 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10618 s.move_heads_with(|map, head, goal| {
10619 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10620 })
10621 })
10622 }
10623
10624 pub fn select_up_by_lines(
10625 &mut self,
10626 action: &SelectUpByLines,
10627 window: &mut Window,
10628 cx: &mut Context<Self>,
10629 ) {
10630 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10631 let text_layout_details = &self.text_layout_details(window);
10632 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10633 s.move_heads_with(|map, head, goal| {
10634 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10635 })
10636 })
10637 }
10638
10639 pub fn select_page_up(
10640 &mut self,
10641 _: &SelectPageUp,
10642 window: &mut Window,
10643 cx: &mut Context<Self>,
10644 ) {
10645 let Some(row_count) = self.visible_row_count() else {
10646 return;
10647 };
10648
10649 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10650
10651 let text_layout_details = &self.text_layout_details(window);
10652
10653 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10654 s.move_heads_with(|map, head, goal| {
10655 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10656 })
10657 })
10658 }
10659
10660 pub fn move_page_up(
10661 &mut self,
10662 action: &MovePageUp,
10663 window: &mut Window,
10664 cx: &mut Context<Self>,
10665 ) {
10666 if self.take_rename(true, window, cx).is_some() {
10667 return;
10668 }
10669
10670 if self
10671 .context_menu
10672 .borrow_mut()
10673 .as_mut()
10674 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10675 .unwrap_or(false)
10676 {
10677 return;
10678 }
10679
10680 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10681 cx.propagate();
10682 return;
10683 }
10684
10685 let Some(row_count) = self.visible_row_count() else {
10686 return;
10687 };
10688
10689 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10690
10691 let autoscroll = if action.center_cursor {
10692 Autoscroll::center()
10693 } else {
10694 Autoscroll::fit()
10695 };
10696
10697 let text_layout_details = &self.text_layout_details(window);
10698
10699 self.change_selections(Some(autoscroll), window, cx, |s| {
10700 s.move_with(|map, selection| {
10701 if !selection.is_empty() {
10702 selection.goal = SelectionGoal::None;
10703 }
10704 let (cursor, goal) = movement::up_by_rows(
10705 map,
10706 selection.end,
10707 row_count,
10708 selection.goal,
10709 false,
10710 text_layout_details,
10711 );
10712 selection.collapse_to(cursor, goal);
10713 });
10714 });
10715 }
10716
10717 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10718 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10719 let text_layout_details = &self.text_layout_details(window);
10720 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10721 s.move_heads_with(|map, head, goal| {
10722 movement::up(map, head, goal, false, text_layout_details)
10723 })
10724 })
10725 }
10726
10727 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10728 self.take_rename(true, window, cx);
10729
10730 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10731 cx.propagate();
10732 return;
10733 }
10734
10735 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10736
10737 let text_layout_details = &self.text_layout_details(window);
10738 let selection_count = self.selections.count();
10739 let first_selection = self.selections.first_anchor();
10740
10741 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10742 s.move_with(|map, selection| {
10743 if !selection.is_empty() {
10744 selection.goal = SelectionGoal::None;
10745 }
10746 let (cursor, goal) = movement::down(
10747 map,
10748 selection.end,
10749 selection.goal,
10750 false,
10751 text_layout_details,
10752 );
10753 selection.collapse_to(cursor, goal);
10754 });
10755 });
10756
10757 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10758 {
10759 cx.propagate();
10760 }
10761 }
10762
10763 pub fn select_page_down(
10764 &mut self,
10765 _: &SelectPageDown,
10766 window: &mut Window,
10767 cx: &mut Context<Self>,
10768 ) {
10769 let Some(row_count) = self.visible_row_count() else {
10770 return;
10771 };
10772
10773 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10774
10775 let text_layout_details = &self.text_layout_details(window);
10776
10777 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10778 s.move_heads_with(|map, head, goal| {
10779 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10780 })
10781 })
10782 }
10783
10784 pub fn move_page_down(
10785 &mut self,
10786 action: &MovePageDown,
10787 window: &mut Window,
10788 cx: &mut Context<Self>,
10789 ) {
10790 if self.take_rename(true, window, cx).is_some() {
10791 return;
10792 }
10793
10794 if self
10795 .context_menu
10796 .borrow_mut()
10797 .as_mut()
10798 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10799 .unwrap_or(false)
10800 {
10801 return;
10802 }
10803
10804 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10805 cx.propagate();
10806 return;
10807 }
10808
10809 let Some(row_count) = self.visible_row_count() else {
10810 return;
10811 };
10812
10813 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10814
10815 let autoscroll = if action.center_cursor {
10816 Autoscroll::center()
10817 } else {
10818 Autoscroll::fit()
10819 };
10820
10821 let text_layout_details = &self.text_layout_details(window);
10822 self.change_selections(Some(autoscroll), window, cx, |s| {
10823 s.move_with(|map, selection| {
10824 if !selection.is_empty() {
10825 selection.goal = SelectionGoal::None;
10826 }
10827 let (cursor, goal) = movement::down_by_rows(
10828 map,
10829 selection.end,
10830 row_count,
10831 selection.goal,
10832 false,
10833 text_layout_details,
10834 );
10835 selection.collapse_to(cursor, goal);
10836 });
10837 });
10838 }
10839
10840 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10841 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10842 let text_layout_details = &self.text_layout_details(window);
10843 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10844 s.move_heads_with(|map, head, goal| {
10845 movement::down(map, head, goal, false, text_layout_details)
10846 })
10847 });
10848 }
10849
10850 pub fn context_menu_first(
10851 &mut self,
10852 _: &ContextMenuFirst,
10853 _window: &mut Window,
10854 cx: &mut Context<Self>,
10855 ) {
10856 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10857 context_menu.select_first(self.completion_provider.as_deref(), cx);
10858 }
10859 }
10860
10861 pub fn context_menu_prev(
10862 &mut self,
10863 _: &ContextMenuPrevious,
10864 _window: &mut Window,
10865 cx: &mut Context<Self>,
10866 ) {
10867 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10868 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10869 }
10870 }
10871
10872 pub fn context_menu_next(
10873 &mut self,
10874 _: &ContextMenuNext,
10875 _window: &mut Window,
10876 cx: &mut Context<Self>,
10877 ) {
10878 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10879 context_menu.select_next(self.completion_provider.as_deref(), cx);
10880 }
10881 }
10882
10883 pub fn context_menu_last(
10884 &mut self,
10885 _: &ContextMenuLast,
10886 _window: &mut Window,
10887 cx: &mut Context<Self>,
10888 ) {
10889 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10890 context_menu.select_last(self.completion_provider.as_deref(), cx);
10891 }
10892 }
10893
10894 pub fn move_to_previous_word_start(
10895 &mut self,
10896 _: &MoveToPreviousWordStart,
10897 window: &mut Window,
10898 cx: &mut Context<Self>,
10899 ) {
10900 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10901 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10902 s.move_cursors_with(|map, head, _| {
10903 (
10904 movement::previous_word_start(map, head),
10905 SelectionGoal::None,
10906 )
10907 });
10908 })
10909 }
10910
10911 pub fn move_to_previous_subword_start(
10912 &mut self,
10913 _: &MoveToPreviousSubwordStart,
10914 window: &mut Window,
10915 cx: &mut Context<Self>,
10916 ) {
10917 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10918 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10919 s.move_cursors_with(|map, head, _| {
10920 (
10921 movement::previous_subword_start(map, head),
10922 SelectionGoal::None,
10923 )
10924 });
10925 })
10926 }
10927
10928 pub fn select_to_previous_word_start(
10929 &mut self,
10930 _: &SelectToPreviousWordStart,
10931 window: &mut Window,
10932 cx: &mut Context<Self>,
10933 ) {
10934 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10935 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10936 s.move_heads_with(|map, head, _| {
10937 (
10938 movement::previous_word_start(map, head),
10939 SelectionGoal::None,
10940 )
10941 });
10942 })
10943 }
10944
10945 pub fn select_to_previous_subword_start(
10946 &mut self,
10947 _: &SelectToPreviousSubwordStart,
10948 window: &mut Window,
10949 cx: &mut Context<Self>,
10950 ) {
10951 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10952 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10953 s.move_heads_with(|map, head, _| {
10954 (
10955 movement::previous_subword_start(map, head),
10956 SelectionGoal::None,
10957 )
10958 });
10959 })
10960 }
10961
10962 pub fn delete_to_previous_word_start(
10963 &mut self,
10964 action: &DeleteToPreviousWordStart,
10965 window: &mut Window,
10966 cx: &mut Context<Self>,
10967 ) {
10968 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10969 self.transact(window, cx, |this, window, cx| {
10970 this.select_autoclose_pair(window, cx);
10971 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10972 s.move_with(|map, selection| {
10973 if selection.is_empty() {
10974 let cursor = if action.ignore_newlines {
10975 movement::previous_word_start(map, selection.head())
10976 } else {
10977 movement::previous_word_start_or_newline(map, selection.head())
10978 };
10979 selection.set_head(cursor, SelectionGoal::None);
10980 }
10981 });
10982 });
10983 this.insert("", window, cx);
10984 });
10985 }
10986
10987 pub fn delete_to_previous_subword_start(
10988 &mut self,
10989 _: &DeleteToPreviousSubwordStart,
10990 window: &mut Window,
10991 cx: &mut Context<Self>,
10992 ) {
10993 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10994 self.transact(window, cx, |this, window, cx| {
10995 this.select_autoclose_pair(window, cx);
10996 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10997 s.move_with(|map, selection| {
10998 if selection.is_empty() {
10999 let cursor = movement::previous_subword_start(map, selection.head());
11000 selection.set_head(cursor, SelectionGoal::None);
11001 }
11002 });
11003 });
11004 this.insert("", window, cx);
11005 });
11006 }
11007
11008 pub fn move_to_next_word_end(
11009 &mut self,
11010 _: &MoveToNextWordEnd,
11011 window: &mut Window,
11012 cx: &mut Context<Self>,
11013 ) {
11014 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11015 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11016 s.move_cursors_with(|map, head, _| {
11017 (movement::next_word_end(map, head), SelectionGoal::None)
11018 });
11019 })
11020 }
11021
11022 pub fn move_to_next_subword_end(
11023 &mut self,
11024 _: &MoveToNextSubwordEnd,
11025 window: &mut Window,
11026 cx: &mut Context<Self>,
11027 ) {
11028 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11029 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11030 s.move_cursors_with(|map, head, _| {
11031 (movement::next_subword_end(map, head), SelectionGoal::None)
11032 });
11033 })
11034 }
11035
11036 pub fn select_to_next_word_end(
11037 &mut self,
11038 _: &SelectToNextWordEnd,
11039 window: &mut Window,
11040 cx: &mut Context<Self>,
11041 ) {
11042 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11043 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11044 s.move_heads_with(|map, head, _| {
11045 (movement::next_word_end(map, head), SelectionGoal::None)
11046 });
11047 })
11048 }
11049
11050 pub fn select_to_next_subword_end(
11051 &mut self,
11052 _: &SelectToNextSubwordEnd,
11053 window: &mut Window,
11054 cx: &mut Context<Self>,
11055 ) {
11056 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11057 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11058 s.move_heads_with(|map, head, _| {
11059 (movement::next_subword_end(map, head), SelectionGoal::None)
11060 });
11061 })
11062 }
11063
11064 pub fn delete_to_next_word_end(
11065 &mut self,
11066 action: &DeleteToNextWordEnd,
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 = if action.ignore_newlines {
11076 movement::next_word_end(map, selection.head())
11077 } else {
11078 movement::next_word_end_or_newline(map, selection.head())
11079 };
11080 selection.set_head(cursor, SelectionGoal::None);
11081 }
11082 });
11083 });
11084 this.insert("", window, cx);
11085 });
11086 }
11087
11088 pub fn delete_to_next_subword_end(
11089 &mut self,
11090 _: &DeleteToNextSubwordEnd,
11091 window: &mut Window,
11092 cx: &mut Context<Self>,
11093 ) {
11094 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11095 self.transact(window, cx, |this, window, cx| {
11096 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11097 s.move_with(|map, selection| {
11098 if selection.is_empty() {
11099 let cursor = movement::next_subword_end(map, selection.head());
11100 selection.set_head(cursor, SelectionGoal::None);
11101 }
11102 });
11103 });
11104 this.insert("", window, cx);
11105 });
11106 }
11107
11108 pub fn move_to_beginning_of_line(
11109 &mut self,
11110 action: &MoveToBeginningOfLine,
11111 window: &mut Window,
11112 cx: &mut Context<Self>,
11113 ) {
11114 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11115 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11116 s.move_cursors_with(|map, head, _| {
11117 (
11118 movement::indented_line_beginning(
11119 map,
11120 head,
11121 action.stop_at_soft_wraps,
11122 action.stop_at_indent,
11123 ),
11124 SelectionGoal::None,
11125 )
11126 });
11127 })
11128 }
11129
11130 pub fn select_to_beginning_of_line(
11131 &mut self,
11132 action: &SelectToBeginningOfLine,
11133 window: &mut Window,
11134 cx: &mut Context<Self>,
11135 ) {
11136 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11137 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11138 s.move_heads_with(|map, head, _| {
11139 (
11140 movement::indented_line_beginning(
11141 map,
11142 head,
11143 action.stop_at_soft_wraps,
11144 action.stop_at_indent,
11145 ),
11146 SelectionGoal::None,
11147 )
11148 });
11149 });
11150 }
11151
11152 pub fn delete_to_beginning_of_line(
11153 &mut self,
11154 action: &DeleteToBeginningOfLine,
11155 window: &mut Window,
11156 cx: &mut Context<Self>,
11157 ) {
11158 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11159 self.transact(window, cx, |this, window, cx| {
11160 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11161 s.move_with(|_, selection| {
11162 selection.reversed = true;
11163 });
11164 });
11165
11166 this.select_to_beginning_of_line(
11167 &SelectToBeginningOfLine {
11168 stop_at_soft_wraps: false,
11169 stop_at_indent: action.stop_at_indent,
11170 },
11171 window,
11172 cx,
11173 );
11174 this.backspace(&Backspace, window, cx);
11175 });
11176 }
11177
11178 pub fn move_to_end_of_line(
11179 &mut self,
11180 action: &MoveToEndOfLine,
11181 window: &mut Window,
11182 cx: &mut Context<Self>,
11183 ) {
11184 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11185 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11186 s.move_cursors_with(|map, head, _| {
11187 (
11188 movement::line_end(map, head, action.stop_at_soft_wraps),
11189 SelectionGoal::None,
11190 )
11191 });
11192 })
11193 }
11194
11195 pub fn select_to_end_of_line(
11196 &mut self,
11197 action: &SelectToEndOfLine,
11198 window: &mut Window,
11199 cx: &mut Context<Self>,
11200 ) {
11201 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11202 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11203 s.move_heads_with(|map, head, _| {
11204 (
11205 movement::line_end(map, head, action.stop_at_soft_wraps),
11206 SelectionGoal::None,
11207 )
11208 });
11209 })
11210 }
11211
11212 pub fn delete_to_end_of_line(
11213 &mut self,
11214 _: &DeleteToEndOfLine,
11215 window: &mut Window,
11216 cx: &mut Context<Self>,
11217 ) {
11218 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11219 self.transact(window, cx, |this, window, cx| {
11220 this.select_to_end_of_line(
11221 &SelectToEndOfLine {
11222 stop_at_soft_wraps: false,
11223 },
11224 window,
11225 cx,
11226 );
11227 this.delete(&Delete, window, cx);
11228 });
11229 }
11230
11231 pub fn cut_to_end_of_line(
11232 &mut self,
11233 _: &CutToEndOfLine,
11234 window: &mut Window,
11235 cx: &mut Context<Self>,
11236 ) {
11237 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11238 self.transact(window, cx, |this, window, cx| {
11239 this.select_to_end_of_line(
11240 &SelectToEndOfLine {
11241 stop_at_soft_wraps: false,
11242 },
11243 window,
11244 cx,
11245 );
11246 this.cut(&Cut, window, cx);
11247 });
11248 }
11249
11250 pub fn move_to_start_of_paragraph(
11251 &mut self,
11252 _: &MoveToStartOfParagraph,
11253 window: &mut Window,
11254 cx: &mut Context<Self>,
11255 ) {
11256 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11257 cx.propagate();
11258 return;
11259 }
11260 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11261 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11262 s.move_with(|map, selection| {
11263 selection.collapse_to(
11264 movement::start_of_paragraph(map, selection.head(), 1),
11265 SelectionGoal::None,
11266 )
11267 });
11268 })
11269 }
11270
11271 pub fn move_to_end_of_paragraph(
11272 &mut self,
11273 _: &MoveToEndOfParagraph,
11274 window: &mut Window,
11275 cx: &mut Context<Self>,
11276 ) {
11277 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11278 cx.propagate();
11279 return;
11280 }
11281 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11282 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11283 s.move_with(|map, selection| {
11284 selection.collapse_to(
11285 movement::end_of_paragraph(map, selection.head(), 1),
11286 SelectionGoal::None,
11287 )
11288 });
11289 })
11290 }
11291
11292 pub fn select_to_start_of_paragraph(
11293 &mut self,
11294 _: &SelectToStartOfParagraph,
11295 window: &mut Window,
11296 cx: &mut Context<Self>,
11297 ) {
11298 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11299 cx.propagate();
11300 return;
11301 }
11302 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11303 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11304 s.move_heads_with(|map, head, _| {
11305 (
11306 movement::start_of_paragraph(map, head, 1),
11307 SelectionGoal::None,
11308 )
11309 });
11310 })
11311 }
11312
11313 pub fn select_to_end_of_paragraph(
11314 &mut self,
11315 _: &SelectToEndOfParagraph,
11316 window: &mut Window,
11317 cx: &mut Context<Self>,
11318 ) {
11319 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11320 cx.propagate();
11321 return;
11322 }
11323 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11324 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11325 s.move_heads_with(|map, head, _| {
11326 (
11327 movement::end_of_paragraph(map, head, 1),
11328 SelectionGoal::None,
11329 )
11330 });
11331 })
11332 }
11333
11334 pub fn move_to_start_of_excerpt(
11335 &mut self,
11336 _: &MoveToStartOfExcerpt,
11337 window: &mut Window,
11338 cx: &mut Context<Self>,
11339 ) {
11340 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11341 cx.propagate();
11342 return;
11343 }
11344 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11345 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11346 s.move_with(|map, selection| {
11347 selection.collapse_to(
11348 movement::start_of_excerpt(
11349 map,
11350 selection.head(),
11351 workspace::searchable::Direction::Prev,
11352 ),
11353 SelectionGoal::None,
11354 )
11355 });
11356 })
11357 }
11358
11359 pub fn move_to_start_of_next_excerpt(
11360 &mut self,
11361 _: &MoveToStartOfNextExcerpt,
11362 window: &mut Window,
11363 cx: &mut Context<Self>,
11364 ) {
11365 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11366 cx.propagate();
11367 return;
11368 }
11369
11370 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11371 s.move_with(|map, selection| {
11372 selection.collapse_to(
11373 movement::start_of_excerpt(
11374 map,
11375 selection.head(),
11376 workspace::searchable::Direction::Next,
11377 ),
11378 SelectionGoal::None,
11379 )
11380 });
11381 })
11382 }
11383
11384 pub fn move_to_end_of_excerpt(
11385 &mut self,
11386 _: &MoveToEndOfExcerpt,
11387 window: &mut Window,
11388 cx: &mut Context<Self>,
11389 ) {
11390 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11391 cx.propagate();
11392 return;
11393 }
11394 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11395 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11396 s.move_with(|map, selection| {
11397 selection.collapse_to(
11398 movement::end_of_excerpt(
11399 map,
11400 selection.head(),
11401 workspace::searchable::Direction::Next,
11402 ),
11403 SelectionGoal::None,
11404 )
11405 });
11406 })
11407 }
11408
11409 pub fn move_to_end_of_previous_excerpt(
11410 &mut self,
11411 _: &MoveToEndOfPreviousExcerpt,
11412 window: &mut Window,
11413 cx: &mut Context<Self>,
11414 ) {
11415 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11416 cx.propagate();
11417 return;
11418 }
11419 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11420 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11421 s.move_with(|map, selection| {
11422 selection.collapse_to(
11423 movement::end_of_excerpt(
11424 map,
11425 selection.head(),
11426 workspace::searchable::Direction::Prev,
11427 ),
11428 SelectionGoal::None,
11429 )
11430 });
11431 })
11432 }
11433
11434 pub fn select_to_start_of_excerpt(
11435 &mut self,
11436 _: &SelectToStartOfExcerpt,
11437 window: &mut Window,
11438 cx: &mut Context<Self>,
11439 ) {
11440 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11441 cx.propagate();
11442 return;
11443 }
11444 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11445 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11446 s.move_heads_with(|map, head, _| {
11447 (
11448 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11449 SelectionGoal::None,
11450 )
11451 });
11452 })
11453 }
11454
11455 pub fn select_to_start_of_next_excerpt(
11456 &mut self,
11457 _: &SelectToStartOfNextExcerpt,
11458 window: &mut Window,
11459 cx: &mut Context<Self>,
11460 ) {
11461 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11462 cx.propagate();
11463 return;
11464 }
11465 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11466 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11467 s.move_heads_with(|map, head, _| {
11468 (
11469 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11470 SelectionGoal::None,
11471 )
11472 });
11473 })
11474 }
11475
11476 pub fn select_to_end_of_excerpt(
11477 &mut self,
11478 _: &SelectToEndOfExcerpt,
11479 window: &mut Window,
11480 cx: &mut Context<Self>,
11481 ) {
11482 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11483 cx.propagate();
11484 return;
11485 }
11486 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11487 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11488 s.move_heads_with(|map, head, _| {
11489 (
11490 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11491 SelectionGoal::None,
11492 )
11493 });
11494 })
11495 }
11496
11497 pub fn select_to_end_of_previous_excerpt(
11498 &mut self,
11499 _: &SelectToEndOfPreviousExcerpt,
11500 window: &mut Window,
11501 cx: &mut Context<Self>,
11502 ) {
11503 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11504 cx.propagate();
11505 return;
11506 }
11507 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11508 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11509 s.move_heads_with(|map, head, _| {
11510 (
11511 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11512 SelectionGoal::None,
11513 )
11514 });
11515 })
11516 }
11517
11518 pub fn move_to_beginning(
11519 &mut self,
11520 _: &MoveToBeginning,
11521 window: &mut Window,
11522 cx: &mut Context<Self>,
11523 ) {
11524 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11525 cx.propagate();
11526 return;
11527 }
11528 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11529 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11530 s.select_ranges(vec![0..0]);
11531 });
11532 }
11533
11534 pub fn select_to_beginning(
11535 &mut self,
11536 _: &SelectToBeginning,
11537 window: &mut Window,
11538 cx: &mut Context<Self>,
11539 ) {
11540 let mut selection = self.selections.last::<Point>(cx);
11541 selection.set_head(Point::zero(), SelectionGoal::None);
11542 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11543 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11544 s.select(vec![selection]);
11545 });
11546 }
11547
11548 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11549 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11550 cx.propagate();
11551 return;
11552 }
11553 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11554 let cursor = self.buffer.read(cx).read(cx).len();
11555 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11556 s.select_ranges(vec![cursor..cursor])
11557 });
11558 }
11559
11560 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11561 self.nav_history = nav_history;
11562 }
11563
11564 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11565 self.nav_history.as_ref()
11566 }
11567
11568 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11569 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11570 }
11571
11572 fn push_to_nav_history(
11573 &mut self,
11574 cursor_anchor: Anchor,
11575 new_position: Option<Point>,
11576 is_deactivate: bool,
11577 cx: &mut Context<Self>,
11578 ) {
11579 if let Some(nav_history) = self.nav_history.as_mut() {
11580 let buffer = self.buffer.read(cx).read(cx);
11581 let cursor_position = cursor_anchor.to_point(&buffer);
11582 let scroll_state = self.scroll_manager.anchor();
11583 let scroll_top_row = scroll_state.top_row(&buffer);
11584 drop(buffer);
11585
11586 if let Some(new_position) = new_position {
11587 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11588 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11589 return;
11590 }
11591 }
11592
11593 nav_history.push(
11594 Some(NavigationData {
11595 cursor_anchor,
11596 cursor_position,
11597 scroll_anchor: scroll_state,
11598 scroll_top_row,
11599 }),
11600 cx,
11601 );
11602 cx.emit(EditorEvent::PushedToNavHistory {
11603 anchor: cursor_anchor,
11604 is_deactivate,
11605 })
11606 }
11607 }
11608
11609 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11610 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11611 let buffer = self.buffer.read(cx).snapshot(cx);
11612 let mut selection = self.selections.first::<usize>(cx);
11613 selection.set_head(buffer.len(), SelectionGoal::None);
11614 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11615 s.select(vec![selection]);
11616 });
11617 }
11618
11619 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11620 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11621 let end = self.buffer.read(cx).read(cx).len();
11622 self.change_selections(None, window, cx, |s| {
11623 s.select_ranges(vec![0..end]);
11624 });
11625 }
11626
11627 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11628 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11629 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11630 let mut selections = self.selections.all::<Point>(cx);
11631 let max_point = display_map.buffer_snapshot.max_point();
11632 for selection in &mut selections {
11633 let rows = selection.spanned_rows(true, &display_map);
11634 selection.start = Point::new(rows.start.0, 0);
11635 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11636 selection.reversed = false;
11637 }
11638 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11639 s.select(selections);
11640 });
11641 }
11642
11643 pub fn split_selection_into_lines(
11644 &mut self,
11645 _: &SplitSelectionIntoLines,
11646 window: &mut Window,
11647 cx: &mut Context<Self>,
11648 ) {
11649 let selections = self
11650 .selections
11651 .all::<Point>(cx)
11652 .into_iter()
11653 .map(|selection| selection.start..selection.end)
11654 .collect::<Vec<_>>();
11655 self.unfold_ranges(&selections, true, true, cx);
11656
11657 let mut new_selection_ranges = Vec::new();
11658 {
11659 let buffer = self.buffer.read(cx).read(cx);
11660 for selection in selections {
11661 for row in selection.start.row..selection.end.row {
11662 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11663 new_selection_ranges.push(cursor..cursor);
11664 }
11665
11666 let is_multiline_selection = selection.start.row != selection.end.row;
11667 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11668 // so this action feels more ergonomic when paired with other selection operations
11669 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11670 if !should_skip_last {
11671 new_selection_ranges.push(selection.end..selection.end);
11672 }
11673 }
11674 }
11675 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11676 s.select_ranges(new_selection_ranges);
11677 });
11678 }
11679
11680 pub fn add_selection_above(
11681 &mut self,
11682 _: &AddSelectionAbove,
11683 window: &mut Window,
11684 cx: &mut Context<Self>,
11685 ) {
11686 self.add_selection(true, window, cx);
11687 }
11688
11689 pub fn add_selection_below(
11690 &mut self,
11691 _: &AddSelectionBelow,
11692 window: &mut Window,
11693 cx: &mut Context<Self>,
11694 ) {
11695 self.add_selection(false, window, cx);
11696 }
11697
11698 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11699 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11700
11701 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11702 let mut selections = self.selections.all::<Point>(cx);
11703 let text_layout_details = self.text_layout_details(window);
11704 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11705 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11706 let range = oldest_selection.display_range(&display_map).sorted();
11707
11708 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11709 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11710 let positions = start_x.min(end_x)..start_x.max(end_x);
11711
11712 selections.clear();
11713 let mut stack = Vec::new();
11714 for row in range.start.row().0..=range.end.row().0 {
11715 if let Some(selection) = self.selections.build_columnar_selection(
11716 &display_map,
11717 DisplayRow(row),
11718 &positions,
11719 oldest_selection.reversed,
11720 &text_layout_details,
11721 ) {
11722 stack.push(selection.id);
11723 selections.push(selection);
11724 }
11725 }
11726
11727 if above {
11728 stack.reverse();
11729 }
11730
11731 AddSelectionsState { above, stack }
11732 });
11733
11734 let last_added_selection = *state.stack.last().unwrap();
11735 let mut new_selections = Vec::new();
11736 if above == state.above {
11737 let end_row = if above {
11738 DisplayRow(0)
11739 } else {
11740 display_map.max_point().row()
11741 };
11742
11743 'outer: for selection in selections {
11744 if selection.id == last_added_selection {
11745 let range = selection.display_range(&display_map).sorted();
11746 debug_assert_eq!(range.start.row(), range.end.row());
11747 let mut row = range.start.row();
11748 let positions =
11749 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11750 px(start)..px(end)
11751 } else {
11752 let start_x =
11753 display_map.x_for_display_point(range.start, &text_layout_details);
11754 let end_x =
11755 display_map.x_for_display_point(range.end, &text_layout_details);
11756 start_x.min(end_x)..start_x.max(end_x)
11757 };
11758
11759 while row != end_row {
11760 if above {
11761 row.0 -= 1;
11762 } else {
11763 row.0 += 1;
11764 }
11765
11766 if let Some(new_selection) = self.selections.build_columnar_selection(
11767 &display_map,
11768 row,
11769 &positions,
11770 selection.reversed,
11771 &text_layout_details,
11772 ) {
11773 state.stack.push(new_selection.id);
11774 if above {
11775 new_selections.push(new_selection);
11776 new_selections.push(selection);
11777 } else {
11778 new_selections.push(selection);
11779 new_selections.push(new_selection);
11780 }
11781
11782 continue 'outer;
11783 }
11784 }
11785 }
11786
11787 new_selections.push(selection);
11788 }
11789 } else {
11790 new_selections = selections;
11791 new_selections.retain(|s| s.id != last_added_selection);
11792 state.stack.pop();
11793 }
11794
11795 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11796 s.select(new_selections);
11797 });
11798 if state.stack.len() > 1 {
11799 self.add_selections_state = Some(state);
11800 }
11801 }
11802
11803 pub fn select_next_match_internal(
11804 &mut self,
11805 display_map: &DisplaySnapshot,
11806 replace_newest: bool,
11807 autoscroll: Option<Autoscroll>,
11808 window: &mut Window,
11809 cx: &mut Context<Self>,
11810 ) -> Result<()> {
11811 fn select_next_match_ranges(
11812 this: &mut Editor,
11813 range: Range<usize>,
11814 replace_newest: bool,
11815 auto_scroll: Option<Autoscroll>,
11816 window: &mut Window,
11817 cx: &mut Context<Editor>,
11818 ) {
11819 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
11820 this.change_selections(auto_scroll, window, cx, |s| {
11821 if replace_newest {
11822 s.delete(s.newest_anchor().id);
11823 }
11824 s.insert_range(range.clone());
11825 });
11826 }
11827
11828 let buffer = &display_map.buffer_snapshot;
11829 let mut selections = self.selections.all::<usize>(cx);
11830 if let Some(mut select_next_state) = self.select_next_state.take() {
11831 let query = &select_next_state.query;
11832 if !select_next_state.done {
11833 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11834 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11835 let mut next_selected_range = None;
11836
11837 let bytes_after_last_selection =
11838 buffer.bytes_in_range(last_selection.end..buffer.len());
11839 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11840 let query_matches = query
11841 .stream_find_iter(bytes_after_last_selection)
11842 .map(|result| (last_selection.end, result))
11843 .chain(
11844 query
11845 .stream_find_iter(bytes_before_first_selection)
11846 .map(|result| (0, result)),
11847 );
11848
11849 for (start_offset, query_match) in query_matches {
11850 let query_match = query_match.unwrap(); // can only fail due to I/O
11851 let offset_range =
11852 start_offset + query_match.start()..start_offset + query_match.end();
11853 let display_range = offset_range.start.to_display_point(display_map)
11854 ..offset_range.end.to_display_point(display_map);
11855
11856 if !select_next_state.wordwise
11857 || (!movement::is_inside_word(display_map, display_range.start)
11858 && !movement::is_inside_word(display_map, display_range.end))
11859 {
11860 // TODO: This is n^2, because we might check all the selections
11861 if !selections
11862 .iter()
11863 .any(|selection| selection.range().overlaps(&offset_range))
11864 {
11865 next_selected_range = Some(offset_range);
11866 break;
11867 }
11868 }
11869 }
11870
11871 if let Some(next_selected_range) = next_selected_range {
11872 select_next_match_ranges(
11873 self,
11874 next_selected_range,
11875 replace_newest,
11876 autoscroll,
11877 window,
11878 cx,
11879 );
11880 } else {
11881 select_next_state.done = true;
11882 }
11883 }
11884
11885 self.select_next_state = Some(select_next_state);
11886 } else {
11887 let mut only_carets = true;
11888 let mut same_text_selected = true;
11889 let mut selected_text = None;
11890
11891 let mut selections_iter = selections.iter().peekable();
11892 while let Some(selection) = selections_iter.next() {
11893 if selection.start != selection.end {
11894 only_carets = false;
11895 }
11896
11897 if same_text_selected {
11898 if selected_text.is_none() {
11899 selected_text =
11900 Some(buffer.text_for_range(selection.range()).collect::<String>());
11901 }
11902
11903 if let Some(next_selection) = selections_iter.peek() {
11904 if next_selection.range().len() == selection.range().len() {
11905 let next_selected_text = buffer
11906 .text_for_range(next_selection.range())
11907 .collect::<String>();
11908 if Some(next_selected_text) != selected_text {
11909 same_text_selected = false;
11910 selected_text = None;
11911 }
11912 } else {
11913 same_text_selected = false;
11914 selected_text = None;
11915 }
11916 }
11917 }
11918 }
11919
11920 if only_carets {
11921 for selection in &mut selections {
11922 let word_range = movement::surrounding_word(
11923 display_map,
11924 selection.start.to_display_point(display_map),
11925 );
11926 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11927 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11928 selection.goal = SelectionGoal::None;
11929 selection.reversed = false;
11930 select_next_match_ranges(
11931 self,
11932 selection.start..selection.end,
11933 replace_newest,
11934 autoscroll,
11935 window,
11936 cx,
11937 );
11938 }
11939
11940 if selections.len() == 1 {
11941 let selection = selections
11942 .last()
11943 .expect("ensured that there's only one selection");
11944 let query = buffer
11945 .text_for_range(selection.start..selection.end)
11946 .collect::<String>();
11947 let is_empty = query.is_empty();
11948 let select_state = SelectNextState {
11949 query: AhoCorasick::new(&[query])?,
11950 wordwise: true,
11951 done: is_empty,
11952 };
11953 self.select_next_state = Some(select_state);
11954 } else {
11955 self.select_next_state = None;
11956 }
11957 } else if let Some(selected_text) = selected_text {
11958 self.select_next_state = Some(SelectNextState {
11959 query: AhoCorasick::new(&[selected_text])?,
11960 wordwise: false,
11961 done: false,
11962 });
11963 self.select_next_match_internal(
11964 display_map,
11965 replace_newest,
11966 autoscroll,
11967 window,
11968 cx,
11969 )?;
11970 }
11971 }
11972 Ok(())
11973 }
11974
11975 pub fn select_all_matches(
11976 &mut self,
11977 _action: &SelectAllMatches,
11978 window: &mut Window,
11979 cx: &mut Context<Self>,
11980 ) -> Result<()> {
11981 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11982
11983 self.push_to_selection_history();
11984 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11985
11986 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11987 let Some(select_next_state) = self.select_next_state.as_mut() else {
11988 return Ok(());
11989 };
11990 if select_next_state.done {
11991 return Ok(());
11992 }
11993
11994 let mut new_selections = Vec::new();
11995
11996 let reversed = self.selections.oldest::<usize>(cx).reversed;
11997 let buffer = &display_map.buffer_snapshot;
11998 let query_matches = select_next_state
11999 .query
12000 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12001
12002 for query_match in query_matches.into_iter() {
12003 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12004 let offset_range = if reversed {
12005 query_match.end()..query_match.start()
12006 } else {
12007 query_match.start()..query_match.end()
12008 };
12009 let display_range = offset_range.start.to_display_point(&display_map)
12010 ..offset_range.end.to_display_point(&display_map);
12011
12012 if !select_next_state.wordwise
12013 || (!movement::is_inside_word(&display_map, display_range.start)
12014 && !movement::is_inside_word(&display_map, display_range.end))
12015 {
12016 new_selections.push(offset_range.start..offset_range.end);
12017 }
12018 }
12019
12020 select_next_state.done = true;
12021 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12022 self.change_selections(None, window, cx, |selections| {
12023 selections.select_ranges(new_selections)
12024 });
12025
12026 Ok(())
12027 }
12028
12029 pub fn select_next(
12030 &mut self,
12031 action: &SelectNext,
12032 window: &mut Window,
12033 cx: &mut Context<Self>,
12034 ) -> Result<()> {
12035 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12036 self.push_to_selection_history();
12037 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12038 self.select_next_match_internal(
12039 &display_map,
12040 action.replace_newest,
12041 Some(Autoscroll::newest()),
12042 window,
12043 cx,
12044 )?;
12045 Ok(())
12046 }
12047
12048 pub fn select_previous(
12049 &mut self,
12050 action: &SelectPrevious,
12051 window: &mut Window,
12052 cx: &mut Context<Self>,
12053 ) -> Result<()> {
12054 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12055 self.push_to_selection_history();
12056 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12057 let buffer = &display_map.buffer_snapshot;
12058 let mut selections = self.selections.all::<usize>(cx);
12059 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12060 let query = &select_prev_state.query;
12061 if !select_prev_state.done {
12062 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12063 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12064 let mut next_selected_range = None;
12065 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12066 let bytes_before_last_selection =
12067 buffer.reversed_bytes_in_range(0..last_selection.start);
12068 let bytes_after_first_selection =
12069 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12070 let query_matches = query
12071 .stream_find_iter(bytes_before_last_selection)
12072 .map(|result| (last_selection.start, result))
12073 .chain(
12074 query
12075 .stream_find_iter(bytes_after_first_selection)
12076 .map(|result| (buffer.len(), result)),
12077 );
12078 for (end_offset, query_match) in query_matches {
12079 let query_match = query_match.unwrap(); // can only fail due to I/O
12080 let offset_range =
12081 end_offset - query_match.end()..end_offset - query_match.start();
12082 let display_range = offset_range.start.to_display_point(&display_map)
12083 ..offset_range.end.to_display_point(&display_map);
12084
12085 if !select_prev_state.wordwise
12086 || (!movement::is_inside_word(&display_map, display_range.start)
12087 && !movement::is_inside_word(&display_map, display_range.end))
12088 {
12089 next_selected_range = Some(offset_range);
12090 break;
12091 }
12092 }
12093
12094 if let Some(next_selected_range) = next_selected_range {
12095 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
12096 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12097 if action.replace_newest {
12098 s.delete(s.newest_anchor().id);
12099 }
12100 s.insert_range(next_selected_range);
12101 });
12102 } else {
12103 select_prev_state.done = true;
12104 }
12105 }
12106
12107 self.select_prev_state = Some(select_prev_state);
12108 } else {
12109 let mut only_carets = true;
12110 let mut same_text_selected = true;
12111 let mut selected_text = None;
12112
12113 let mut selections_iter = selections.iter().peekable();
12114 while let Some(selection) = selections_iter.next() {
12115 if selection.start != selection.end {
12116 only_carets = false;
12117 }
12118
12119 if same_text_selected {
12120 if selected_text.is_none() {
12121 selected_text =
12122 Some(buffer.text_for_range(selection.range()).collect::<String>());
12123 }
12124
12125 if let Some(next_selection) = selections_iter.peek() {
12126 if next_selection.range().len() == selection.range().len() {
12127 let next_selected_text = buffer
12128 .text_for_range(next_selection.range())
12129 .collect::<String>();
12130 if Some(next_selected_text) != selected_text {
12131 same_text_selected = false;
12132 selected_text = None;
12133 }
12134 } else {
12135 same_text_selected = false;
12136 selected_text = None;
12137 }
12138 }
12139 }
12140 }
12141
12142 if only_carets {
12143 for selection in &mut selections {
12144 let word_range = movement::surrounding_word(
12145 &display_map,
12146 selection.start.to_display_point(&display_map),
12147 );
12148 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12149 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12150 selection.goal = SelectionGoal::None;
12151 selection.reversed = false;
12152 }
12153 if selections.len() == 1 {
12154 let selection = selections
12155 .last()
12156 .expect("ensured that there's only one selection");
12157 let query = buffer
12158 .text_for_range(selection.start..selection.end)
12159 .collect::<String>();
12160 let is_empty = query.is_empty();
12161 let select_state = SelectNextState {
12162 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12163 wordwise: true,
12164 done: is_empty,
12165 };
12166 self.select_prev_state = Some(select_state);
12167 } else {
12168 self.select_prev_state = None;
12169 }
12170
12171 self.unfold_ranges(
12172 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
12173 false,
12174 true,
12175 cx,
12176 );
12177 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12178 s.select(selections);
12179 });
12180 } else if let Some(selected_text) = selected_text {
12181 self.select_prev_state = Some(SelectNextState {
12182 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12183 wordwise: false,
12184 done: false,
12185 });
12186 self.select_previous(action, window, cx)?;
12187 }
12188 }
12189 Ok(())
12190 }
12191
12192 pub fn find_next_match(
12193 &mut self,
12194 _: &FindNextMatch,
12195 window: &mut Window,
12196 cx: &mut Context<Self>,
12197 ) -> Result<()> {
12198 let selections = self.selections.disjoint_anchors();
12199 match selections.first() {
12200 Some(first) if selections.len() >= 2 => {
12201 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12202 s.select_ranges([first.range()]);
12203 });
12204 }
12205 _ => self.select_next(
12206 &SelectNext {
12207 replace_newest: true,
12208 },
12209 window,
12210 cx,
12211 )?,
12212 }
12213 Ok(())
12214 }
12215
12216 pub fn find_previous_match(
12217 &mut self,
12218 _: &FindPreviousMatch,
12219 window: &mut Window,
12220 cx: &mut Context<Self>,
12221 ) -> Result<()> {
12222 let selections = self.selections.disjoint_anchors();
12223 match selections.last() {
12224 Some(last) if selections.len() >= 2 => {
12225 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12226 s.select_ranges([last.range()]);
12227 });
12228 }
12229 _ => self.select_previous(
12230 &SelectPrevious {
12231 replace_newest: true,
12232 },
12233 window,
12234 cx,
12235 )?,
12236 }
12237 Ok(())
12238 }
12239
12240 pub fn toggle_comments(
12241 &mut self,
12242 action: &ToggleComments,
12243 window: &mut Window,
12244 cx: &mut Context<Self>,
12245 ) {
12246 if self.read_only(cx) {
12247 return;
12248 }
12249 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12250 let text_layout_details = &self.text_layout_details(window);
12251 self.transact(window, cx, |this, window, cx| {
12252 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12253 let mut edits = Vec::new();
12254 let mut selection_edit_ranges = Vec::new();
12255 let mut last_toggled_row = None;
12256 let snapshot = this.buffer.read(cx).read(cx);
12257 let empty_str: Arc<str> = Arc::default();
12258 let mut suffixes_inserted = Vec::new();
12259 let ignore_indent = action.ignore_indent;
12260
12261 fn comment_prefix_range(
12262 snapshot: &MultiBufferSnapshot,
12263 row: MultiBufferRow,
12264 comment_prefix: &str,
12265 comment_prefix_whitespace: &str,
12266 ignore_indent: bool,
12267 ) -> Range<Point> {
12268 let indent_size = if ignore_indent {
12269 0
12270 } else {
12271 snapshot.indent_size_for_line(row).len
12272 };
12273
12274 let start = Point::new(row.0, indent_size);
12275
12276 let mut line_bytes = snapshot
12277 .bytes_in_range(start..snapshot.max_point())
12278 .flatten()
12279 .copied();
12280
12281 // If this line currently begins with the line comment prefix, then record
12282 // the range containing the prefix.
12283 if line_bytes
12284 .by_ref()
12285 .take(comment_prefix.len())
12286 .eq(comment_prefix.bytes())
12287 {
12288 // Include any whitespace that matches the comment prefix.
12289 let matching_whitespace_len = line_bytes
12290 .zip(comment_prefix_whitespace.bytes())
12291 .take_while(|(a, b)| a == b)
12292 .count() as u32;
12293 let end = Point::new(
12294 start.row,
12295 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12296 );
12297 start..end
12298 } else {
12299 start..start
12300 }
12301 }
12302
12303 fn comment_suffix_range(
12304 snapshot: &MultiBufferSnapshot,
12305 row: MultiBufferRow,
12306 comment_suffix: &str,
12307 comment_suffix_has_leading_space: bool,
12308 ) -> Range<Point> {
12309 let end = Point::new(row.0, snapshot.line_len(row));
12310 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12311
12312 let mut line_end_bytes = snapshot
12313 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12314 .flatten()
12315 .copied();
12316
12317 let leading_space_len = if suffix_start_column > 0
12318 && line_end_bytes.next() == Some(b' ')
12319 && comment_suffix_has_leading_space
12320 {
12321 1
12322 } else {
12323 0
12324 };
12325
12326 // If this line currently begins with the line comment prefix, then record
12327 // the range containing the prefix.
12328 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12329 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12330 start..end
12331 } else {
12332 end..end
12333 }
12334 }
12335
12336 // TODO: Handle selections that cross excerpts
12337 for selection in &mut selections {
12338 let start_column = snapshot
12339 .indent_size_for_line(MultiBufferRow(selection.start.row))
12340 .len;
12341 let language = if let Some(language) =
12342 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12343 {
12344 language
12345 } else {
12346 continue;
12347 };
12348
12349 selection_edit_ranges.clear();
12350
12351 // If multiple selections contain a given row, avoid processing that
12352 // row more than once.
12353 let mut start_row = MultiBufferRow(selection.start.row);
12354 if last_toggled_row == Some(start_row) {
12355 start_row = start_row.next_row();
12356 }
12357 let end_row =
12358 if selection.end.row > selection.start.row && selection.end.column == 0 {
12359 MultiBufferRow(selection.end.row - 1)
12360 } else {
12361 MultiBufferRow(selection.end.row)
12362 };
12363 last_toggled_row = Some(end_row);
12364
12365 if start_row > end_row {
12366 continue;
12367 }
12368
12369 // If the language has line comments, toggle those.
12370 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12371
12372 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12373 if ignore_indent {
12374 full_comment_prefixes = full_comment_prefixes
12375 .into_iter()
12376 .map(|s| Arc::from(s.trim_end()))
12377 .collect();
12378 }
12379
12380 if !full_comment_prefixes.is_empty() {
12381 let first_prefix = full_comment_prefixes
12382 .first()
12383 .expect("prefixes is non-empty");
12384 let prefix_trimmed_lengths = full_comment_prefixes
12385 .iter()
12386 .map(|p| p.trim_end_matches(' ').len())
12387 .collect::<SmallVec<[usize; 4]>>();
12388
12389 let mut all_selection_lines_are_comments = true;
12390
12391 for row in start_row.0..=end_row.0 {
12392 let row = MultiBufferRow(row);
12393 if start_row < end_row && snapshot.is_line_blank(row) {
12394 continue;
12395 }
12396
12397 let prefix_range = full_comment_prefixes
12398 .iter()
12399 .zip(prefix_trimmed_lengths.iter().copied())
12400 .map(|(prefix, trimmed_prefix_len)| {
12401 comment_prefix_range(
12402 snapshot.deref(),
12403 row,
12404 &prefix[..trimmed_prefix_len],
12405 &prefix[trimmed_prefix_len..],
12406 ignore_indent,
12407 )
12408 })
12409 .max_by_key(|range| range.end.column - range.start.column)
12410 .expect("prefixes is non-empty");
12411
12412 if prefix_range.is_empty() {
12413 all_selection_lines_are_comments = false;
12414 }
12415
12416 selection_edit_ranges.push(prefix_range);
12417 }
12418
12419 if all_selection_lines_are_comments {
12420 edits.extend(
12421 selection_edit_ranges
12422 .iter()
12423 .cloned()
12424 .map(|range| (range, empty_str.clone())),
12425 );
12426 } else {
12427 let min_column = selection_edit_ranges
12428 .iter()
12429 .map(|range| range.start.column)
12430 .min()
12431 .unwrap_or(0);
12432 edits.extend(selection_edit_ranges.iter().map(|range| {
12433 let position = Point::new(range.start.row, min_column);
12434 (position..position, first_prefix.clone())
12435 }));
12436 }
12437 } else if let Some((full_comment_prefix, comment_suffix)) =
12438 language.block_comment_delimiters()
12439 {
12440 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12441 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12442 let prefix_range = comment_prefix_range(
12443 snapshot.deref(),
12444 start_row,
12445 comment_prefix,
12446 comment_prefix_whitespace,
12447 ignore_indent,
12448 );
12449 let suffix_range = comment_suffix_range(
12450 snapshot.deref(),
12451 end_row,
12452 comment_suffix.trim_start_matches(' '),
12453 comment_suffix.starts_with(' '),
12454 );
12455
12456 if prefix_range.is_empty() || suffix_range.is_empty() {
12457 edits.push((
12458 prefix_range.start..prefix_range.start,
12459 full_comment_prefix.clone(),
12460 ));
12461 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12462 suffixes_inserted.push((end_row, comment_suffix.len()));
12463 } else {
12464 edits.push((prefix_range, empty_str.clone()));
12465 edits.push((suffix_range, empty_str.clone()));
12466 }
12467 } else {
12468 continue;
12469 }
12470 }
12471
12472 drop(snapshot);
12473 this.buffer.update(cx, |buffer, cx| {
12474 buffer.edit(edits, None, cx);
12475 });
12476
12477 // Adjust selections so that they end before any comment suffixes that
12478 // were inserted.
12479 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12480 let mut selections = this.selections.all::<Point>(cx);
12481 let snapshot = this.buffer.read(cx).read(cx);
12482 for selection in &mut selections {
12483 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12484 match row.cmp(&MultiBufferRow(selection.end.row)) {
12485 Ordering::Less => {
12486 suffixes_inserted.next();
12487 continue;
12488 }
12489 Ordering::Greater => break,
12490 Ordering::Equal => {
12491 if selection.end.column == snapshot.line_len(row) {
12492 if selection.is_empty() {
12493 selection.start.column -= suffix_len as u32;
12494 }
12495 selection.end.column -= suffix_len as u32;
12496 }
12497 break;
12498 }
12499 }
12500 }
12501 }
12502
12503 drop(snapshot);
12504 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12505 s.select(selections)
12506 });
12507
12508 let selections = this.selections.all::<Point>(cx);
12509 let selections_on_single_row = selections.windows(2).all(|selections| {
12510 selections[0].start.row == selections[1].start.row
12511 && selections[0].end.row == selections[1].end.row
12512 && selections[0].start.row == selections[0].end.row
12513 });
12514 let selections_selecting = selections
12515 .iter()
12516 .any(|selection| selection.start != selection.end);
12517 let advance_downwards = action.advance_downwards
12518 && selections_on_single_row
12519 && !selections_selecting
12520 && !matches!(this.mode, EditorMode::SingleLine { .. });
12521
12522 if advance_downwards {
12523 let snapshot = this.buffer.read(cx).snapshot(cx);
12524
12525 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12526 s.move_cursors_with(|display_snapshot, display_point, _| {
12527 let mut point = display_point.to_point(display_snapshot);
12528 point.row += 1;
12529 point = snapshot.clip_point(point, Bias::Left);
12530 let display_point = point.to_display_point(display_snapshot);
12531 let goal = SelectionGoal::HorizontalPosition(
12532 display_snapshot
12533 .x_for_display_point(display_point, text_layout_details)
12534 .into(),
12535 );
12536 (display_point, goal)
12537 })
12538 });
12539 }
12540 });
12541 }
12542
12543 pub fn select_enclosing_symbol(
12544 &mut self,
12545 _: &SelectEnclosingSymbol,
12546 window: &mut Window,
12547 cx: &mut Context<Self>,
12548 ) {
12549 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12550
12551 let buffer = self.buffer.read(cx).snapshot(cx);
12552 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12553
12554 fn update_selection(
12555 selection: &Selection<usize>,
12556 buffer_snap: &MultiBufferSnapshot,
12557 ) -> Option<Selection<usize>> {
12558 let cursor = selection.head();
12559 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12560 for symbol in symbols.iter().rev() {
12561 let start = symbol.range.start.to_offset(buffer_snap);
12562 let end = symbol.range.end.to_offset(buffer_snap);
12563 let new_range = start..end;
12564 if start < selection.start || end > selection.end {
12565 return Some(Selection {
12566 id: selection.id,
12567 start: new_range.start,
12568 end: new_range.end,
12569 goal: SelectionGoal::None,
12570 reversed: selection.reversed,
12571 });
12572 }
12573 }
12574 None
12575 }
12576
12577 let mut selected_larger_symbol = false;
12578 let new_selections = old_selections
12579 .iter()
12580 .map(|selection| match update_selection(selection, &buffer) {
12581 Some(new_selection) => {
12582 if new_selection.range() != selection.range() {
12583 selected_larger_symbol = true;
12584 }
12585 new_selection
12586 }
12587 None => selection.clone(),
12588 })
12589 .collect::<Vec<_>>();
12590
12591 if selected_larger_symbol {
12592 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12593 s.select(new_selections);
12594 });
12595 }
12596 }
12597
12598 pub fn select_larger_syntax_node(
12599 &mut self,
12600 _: &SelectLargerSyntaxNode,
12601 window: &mut Window,
12602 cx: &mut Context<Self>,
12603 ) {
12604 let Some(visible_row_count) = self.visible_row_count() else {
12605 return;
12606 };
12607 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12608 if old_selections.is_empty() {
12609 return;
12610 }
12611
12612 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12613
12614 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12615 let buffer = self.buffer.read(cx).snapshot(cx);
12616
12617 let mut selected_larger_node = false;
12618 let mut new_selections = old_selections
12619 .iter()
12620 .map(|selection| {
12621 let old_range = selection.start..selection.end;
12622
12623 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
12624 // manually select word at selection
12625 if ["string_content", "inline"].contains(&node.kind()) {
12626 let word_range = {
12627 let display_point = buffer
12628 .offset_to_point(old_range.start)
12629 .to_display_point(&display_map);
12630 let Range { start, end } =
12631 movement::surrounding_word(&display_map, display_point);
12632 start.to_point(&display_map).to_offset(&buffer)
12633 ..end.to_point(&display_map).to_offset(&buffer)
12634 };
12635 // ignore if word is already selected
12636 if !word_range.is_empty() && old_range != word_range {
12637 let last_word_range = {
12638 let display_point = buffer
12639 .offset_to_point(old_range.end)
12640 .to_display_point(&display_map);
12641 let Range { start, end } =
12642 movement::surrounding_word(&display_map, display_point);
12643 start.to_point(&display_map).to_offset(&buffer)
12644 ..end.to_point(&display_map).to_offset(&buffer)
12645 };
12646 // only select word if start and end point belongs to same word
12647 if word_range == last_word_range {
12648 selected_larger_node = true;
12649 return Selection {
12650 id: selection.id,
12651 start: word_range.start,
12652 end: word_range.end,
12653 goal: SelectionGoal::None,
12654 reversed: selection.reversed,
12655 };
12656 }
12657 }
12658 }
12659 }
12660
12661 let mut new_range = old_range.clone();
12662 let mut new_node = None;
12663 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12664 {
12665 new_node = Some(node);
12666 new_range = match containing_range {
12667 MultiOrSingleBufferOffsetRange::Single(_) => break,
12668 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12669 };
12670 if !display_map.intersects_fold(new_range.start)
12671 && !display_map.intersects_fold(new_range.end)
12672 {
12673 break;
12674 }
12675 }
12676
12677 if let Some(node) = new_node {
12678 // Log the ancestor, to support using this action as a way to explore TreeSitter
12679 // nodes. Parent and grandparent are also logged because this operation will not
12680 // visit nodes that have the same range as their parent.
12681 log::info!("Node: {node:?}");
12682 let parent = node.parent();
12683 log::info!("Parent: {parent:?}");
12684 let grandparent = parent.and_then(|x| x.parent());
12685 log::info!("Grandparent: {grandparent:?}");
12686 }
12687
12688 selected_larger_node |= new_range != old_range;
12689 Selection {
12690 id: selection.id,
12691 start: new_range.start,
12692 end: new_range.end,
12693 goal: SelectionGoal::None,
12694 reversed: selection.reversed,
12695 }
12696 })
12697 .collect::<Vec<_>>();
12698
12699 if !selected_larger_node {
12700 return; // don't put this call in the history
12701 }
12702
12703 // scroll based on transformation done to the last selection created by the user
12704 let (last_old, last_new) = old_selections
12705 .last()
12706 .zip(new_selections.last().cloned())
12707 .expect("old_selections isn't empty");
12708
12709 // revert selection
12710 let is_selection_reversed = {
12711 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12712 new_selections.last_mut().expect("checked above").reversed =
12713 should_newest_selection_be_reversed;
12714 should_newest_selection_be_reversed
12715 };
12716
12717 if selected_larger_node {
12718 self.select_syntax_node_history.disable_clearing = true;
12719 self.change_selections(None, window, cx, |s| {
12720 s.select(new_selections.clone());
12721 });
12722 self.select_syntax_node_history.disable_clearing = false;
12723 }
12724
12725 let start_row = last_new.start.to_display_point(&display_map).row().0;
12726 let end_row = last_new.end.to_display_point(&display_map).row().0;
12727 let selection_height = end_row - start_row + 1;
12728 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12729
12730 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
12731 let scroll_behavior = if fits_on_the_screen {
12732 self.request_autoscroll(Autoscroll::fit(), cx);
12733 SelectSyntaxNodeScrollBehavior::FitSelection
12734 } else if is_selection_reversed {
12735 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12736 SelectSyntaxNodeScrollBehavior::CursorTop
12737 } else {
12738 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12739 SelectSyntaxNodeScrollBehavior::CursorBottom
12740 };
12741
12742 self.select_syntax_node_history.push((
12743 old_selections,
12744 scroll_behavior,
12745 is_selection_reversed,
12746 ));
12747 }
12748
12749 pub fn select_smaller_syntax_node(
12750 &mut self,
12751 _: &SelectSmallerSyntaxNode,
12752 window: &mut Window,
12753 cx: &mut Context<Self>,
12754 ) {
12755 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12756
12757 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12758 self.select_syntax_node_history.pop()
12759 {
12760 if let Some(selection) = selections.last_mut() {
12761 selection.reversed = is_selection_reversed;
12762 }
12763
12764 self.select_syntax_node_history.disable_clearing = true;
12765 self.change_selections(None, window, cx, |s| {
12766 s.select(selections.to_vec());
12767 });
12768 self.select_syntax_node_history.disable_clearing = false;
12769
12770 match scroll_behavior {
12771 SelectSyntaxNodeScrollBehavior::CursorTop => {
12772 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12773 }
12774 SelectSyntaxNodeScrollBehavior::FitSelection => {
12775 self.request_autoscroll(Autoscroll::fit(), cx);
12776 }
12777 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12778 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12779 }
12780 }
12781 }
12782 }
12783
12784 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12785 if !EditorSettings::get_global(cx).gutter.runnables {
12786 self.clear_tasks();
12787 return Task::ready(());
12788 }
12789 let project = self.project.as_ref().map(Entity::downgrade);
12790 let task_sources = self.lsp_task_sources(cx);
12791 cx.spawn_in(window, async move |editor, cx| {
12792 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12793 let Some(project) = project.and_then(|p| p.upgrade()) else {
12794 return;
12795 };
12796 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
12797 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12798 }) else {
12799 return;
12800 };
12801
12802 let hide_runnables = project
12803 .update(cx, |project, cx| {
12804 // Do not display any test indicators in non-dev server remote projects.
12805 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12806 })
12807 .unwrap_or(true);
12808 if hide_runnables {
12809 return;
12810 }
12811 let new_rows =
12812 cx.background_spawn({
12813 let snapshot = display_snapshot.clone();
12814 async move {
12815 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12816 }
12817 })
12818 .await;
12819 let Ok(lsp_tasks) =
12820 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
12821 else {
12822 return;
12823 };
12824 let lsp_tasks = lsp_tasks.await;
12825
12826 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
12827 lsp_tasks
12828 .into_iter()
12829 .flat_map(|(kind, tasks)| {
12830 tasks.into_iter().filter_map(move |(location, task)| {
12831 Some((kind.clone(), location?, task))
12832 })
12833 })
12834 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
12835 let buffer = location.target.buffer;
12836 let buffer_snapshot = buffer.read(cx).snapshot();
12837 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
12838 |(excerpt_id, snapshot, _)| {
12839 if snapshot.remote_id() == buffer_snapshot.remote_id() {
12840 display_snapshot
12841 .buffer_snapshot
12842 .anchor_in_excerpt(excerpt_id, location.target.range.start)
12843 } else {
12844 None
12845 }
12846 },
12847 );
12848 if let Some(offset) = offset {
12849 let task_buffer_range =
12850 location.target.range.to_point(&buffer_snapshot);
12851 let context_buffer_range =
12852 task_buffer_range.to_offset(&buffer_snapshot);
12853 let context_range = BufferOffset(context_buffer_range.start)
12854 ..BufferOffset(context_buffer_range.end);
12855
12856 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
12857 .or_insert_with(|| RunnableTasks {
12858 templates: Vec::new(),
12859 offset,
12860 column: task_buffer_range.start.column,
12861 extra_variables: HashMap::default(),
12862 context_range,
12863 })
12864 .templates
12865 .push((kind, task.original_task().clone()));
12866 }
12867
12868 acc
12869 })
12870 }) else {
12871 return;
12872 };
12873
12874 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12875 editor
12876 .update(cx, |editor, _| {
12877 editor.clear_tasks();
12878 for (key, mut value) in rows {
12879 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
12880 value.templates.extend(lsp_tasks.templates);
12881 }
12882
12883 editor.insert_tasks(key, value);
12884 }
12885 for (key, value) in lsp_tasks_by_rows {
12886 editor.insert_tasks(key, value);
12887 }
12888 })
12889 .ok();
12890 })
12891 }
12892 fn fetch_runnable_ranges(
12893 snapshot: &DisplaySnapshot,
12894 range: Range<Anchor>,
12895 ) -> Vec<language::RunnableRange> {
12896 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12897 }
12898
12899 fn runnable_rows(
12900 project: Entity<Project>,
12901 snapshot: DisplaySnapshot,
12902 runnable_ranges: Vec<RunnableRange>,
12903 mut cx: AsyncWindowContext,
12904 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
12905 runnable_ranges
12906 .into_iter()
12907 .filter_map(|mut runnable| {
12908 let tasks = cx
12909 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12910 .ok()?;
12911 if tasks.is_empty() {
12912 return None;
12913 }
12914
12915 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12916
12917 let row = snapshot
12918 .buffer_snapshot
12919 .buffer_line_for_row(MultiBufferRow(point.row))?
12920 .1
12921 .start
12922 .row;
12923
12924 let context_range =
12925 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12926 Some((
12927 (runnable.buffer_id, row),
12928 RunnableTasks {
12929 templates: tasks,
12930 offset: snapshot
12931 .buffer_snapshot
12932 .anchor_before(runnable.run_range.start),
12933 context_range,
12934 column: point.column,
12935 extra_variables: runnable.extra_captures,
12936 },
12937 ))
12938 })
12939 .collect()
12940 }
12941
12942 fn templates_with_tags(
12943 project: &Entity<Project>,
12944 runnable: &mut Runnable,
12945 cx: &mut App,
12946 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12947 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12948 let (worktree_id, file) = project
12949 .buffer_for_id(runnable.buffer, cx)
12950 .and_then(|buffer| buffer.read(cx).file())
12951 .map(|file| (file.worktree_id(cx), file.clone()))
12952 .unzip();
12953
12954 (
12955 project.task_store().read(cx).task_inventory().cloned(),
12956 worktree_id,
12957 file,
12958 )
12959 });
12960
12961 let mut templates_with_tags = mem::take(&mut runnable.tags)
12962 .into_iter()
12963 .flat_map(|RunnableTag(tag)| {
12964 inventory
12965 .as_ref()
12966 .into_iter()
12967 .flat_map(|inventory| {
12968 inventory.read(cx).list_tasks(
12969 file.clone(),
12970 Some(runnable.language.clone()),
12971 worktree_id,
12972 cx,
12973 )
12974 })
12975 .filter(move |(_, template)| {
12976 template.tags.iter().any(|source_tag| source_tag == &tag)
12977 })
12978 })
12979 .sorted_by_key(|(kind, _)| kind.to_owned())
12980 .collect::<Vec<_>>();
12981 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
12982 // Strongest source wins; if we have worktree tag binding, prefer that to
12983 // global and language bindings;
12984 // if we have a global binding, prefer that to language binding.
12985 let first_mismatch = templates_with_tags
12986 .iter()
12987 .position(|(tag_source, _)| tag_source != leading_tag_source);
12988 if let Some(index) = first_mismatch {
12989 templates_with_tags.truncate(index);
12990 }
12991 }
12992
12993 templates_with_tags
12994 }
12995
12996 pub fn move_to_enclosing_bracket(
12997 &mut self,
12998 _: &MoveToEnclosingBracket,
12999 window: &mut Window,
13000 cx: &mut Context<Self>,
13001 ) {
13002 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13003 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13004 s.move_offsets_with(|snapshot, selection| {
13005 let Some(enclosing_bracket_ranges) =
13006 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13007 else {
13008 return;
13009 };
13010
13011 let mut best_length = usize::MAX;
13012 let mut best_inside = false;
13013 let mut best_in_bracket_range = false;
13014 let mut best_destination = None;
13015 for (open, close) in enclosing_bracket_ranges {
13016 let close = close.to_inclusive();
13017 let length = close.end() - open.start;
13018 let inside = selection.start >= open.end && selection.end <= *close.start();
13019 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13020 || close.contains(&selection.head());
13021
13022 // If best is next to a bracket and current isn't, skip
13023 if !in_bracket_range && best_in_bracket_range {
13024 continue;
13025 }
13026
13027 // Prefer smaller lengths unless best is inside and current isn't
13028 if length > best_length && (best_inside || !inside) {
13029 continue;
13030 }
13031
13032 best_length = length;
13033 best_inside = inside;
13034 best_in_bracket_range = in_bracket_range;
13035 best_destination = Some(
13036 if close.contains(&selection.start) && close.contains(&selection.end) {
13037 if inside { open.end } else { open.start }
13038 } else if inside {
13039 *close.start()
13040 } else {
13041 *close.end()
13042 },
13043 );
13044 }
13045
13046 if let Some(destination) = best_destination {
13047 selection.collapse_to(destination, SelectionGoal::None);
13048 }
13049 })
13050 });
13051 }
13052
13053 pub fn undo_selection(
13054 &mut self,
13055 _: &UndoSelection,
13056 window: &mut Window,
13057 cx: &mut Context<Self>,
13058 ) {
13059 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13060 self.end_selection(window, cx);
13061 self.selection_history.mode = SelectionHistoryMode::Undoing;
13062 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13063 self.change_selections(None, window, cx, |s| {
13064 s.select_anchors(entry.selections.to_vec())
13065 });
13066 self.select_next_state = entry.select_next_state;
13067 self.select_prev_state = entry.select_prev_state;
13068 self.add_selections_state = entry.add_selections_state;
13069 self.request_autoscroll(Autoscroll::newest(), cx);
13070 }
13071 self.selection_history.mode = SelectionHistoryMode::Normal;
13072 }
13073
13074 pub fn redo_selection(
13075 &mut self,
13076 _: &RedoSelection,
13077 window: &mut Window,
13078 cx: &mut Context<Self>,
13079 ) {
13080 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13081 self.end_selection(window, cx);
13082 self.selection_history.mode = SelectionHistoryMode::Redoing;
13083 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13084 self.change_selections(None, window, cx, |s| {
13085 s.select_anchors(entry.selections.to_vec())
13086 });
13087 self.select_next_state = entry.select_next_state;
13088 self.select_prev_state = entry.select_prev_state;
13089 self.add_selections_state = entry.add_selections_state;
13090 self.request_autoscroll(Autoscroll::newest(), cx);
13091 }
13092 self.selection_history.mode = SelectionHistoryMode::Normal;
13093 }
13094
13095 pub fn expand_excerpts(
13096 &mut self,
13097 action: &ExpandExcerpts,
13098 _: &mut Window,
13099 cx: &mut Context<Self>,
13100 ) {
13101 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13102 }
13103
13104 pub fn expand_excerpts_down(
13105 &mut self,
13106 action: &ExpandExcerptsDown,
13107 _: &mut Window,
13108 cx: &mut Context<Self>,
13109 ) {
13110 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13111 }
13112
13113 pub fn expand_excerpts_up(
13114 &mut self,
13115 action: &ExpandExcerptsUp,
13116 _: &mut Window,
13117 cx: &mut Context<Self>,
13118 ) {
13119 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13120 }
13121
13122 pub fn expand_excerpts_for_direction(
13123 &mut self,
13124 lines: u32,
13125 direction: ExpandExcerptDirection,
13126
13127 cx: &mut Context<Self>,
13128 ) {
13129 let selections = self.selections.disjoint_anchors();
13130
13131 let lines = if lines == 0 {
13132 EditorSettings::get_global(cx).expand_excerpt_lines
13133 } else {
13134 lines
13135 };
13136
13137 self.buffer.update(cx, |buffer, cx| {
13138 let snapshot = buffer.snapshot(cx);
13139 let mut excerpt_ids = selections
13140 .iter()
13141 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13142 .collect::<Vec<_>>();
13143 excerpt_ids.sort();
13144 excerpt_ids.dedup();
13145 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13146 })
13147 }
13148
13149 pub fn expand_excerpt(
13150 &mut self,
13151 excerpt: ExcerptId,
13152 direction: ExpandExcerptDirection,
13153 window: &mut Window,
13154 cx: &mut Context<Self>,
13155 ) {
13156 let current_scroll_position = self.scroll_position(cx);
13157 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13158 let mut should_scroll_up = false;
13159
13160 if direction == ExpandExcerptDirection::Down {
13161 let multi_buffer = self.buffer.read(cx);
13162 let snapshot = multi_buffer.snapshot(cx);
13163 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13164 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13165 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13166 let buffer_snapshot = buffer.read(cx).snapshot();
13167 let excerpt_end_row =
13168 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13169 let last_row = buffer_snapshot.max_point().row;
13170 let lines_below = last_row.saturating_sub(excerpt_end_row);
13171 should_scroll_up = lines_below >= lines_to_expand;
13172 }
13173 }
13174 }
13175 }
13176
13177 self.buffer.update(cx, |buffer, cx| {
13178 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13179 });
13180
13181 if should_scroll_up {
13182 let new_scroll_position =
13183 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13184 self.set_scroll_position(new_scroll_position, window, cx);
13185 }
13186 }
13187
13188 pub fn go_to_singleton_buffer_point(
13189 &mut self,
13190 point: Point,
13191 window: &mut Window,
13192 cx: &mut Context<Self>,
13193 ) {
13194 self.go_to_singleton_buffer_range(point..point, window, cx);
13195 }
13196
13197 pub fn go_to_singleton_buffer_range(
13198 &mut self,
13199 range: Range<Point>,
13200 window: &mut Window,
13201 cx: &mut Context<Self>,
13202 ) {
13203 let multibuffer = self.buffer().read(cx);
13204 let Some(buffer) = multibuffer.as_singleton() else {
13205 return;
13206 };
13207 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13208 return;
13209 };
13210 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13211 return;
13212 };
13213 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13214 s.select_anchor_ranges([start..end])
13215 });
13216 }
13217
13218 pub fn go_to_diagnostic(
13219 &mut self,
13220 _: &GoToDiagnostic,
13221 window: &mut Window,
13222 cx: &mut Context<Self>,
13223 ) {
13224 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13225 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13226 }
13227
13228 pub fn go_to_prev_diagnostic(
13229 &mut self,
13230 _: &GoToPreviousDiagnostic,
13231 window: &mut Window,
13232 cx: &mut Context<Self>,
13233 ) {
13234 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13235 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13236 }
13237
13238 pub fn go_to_diagnostic_impl(
13239 &mut self,
13240 direction: Direction,
13241 window: &mut Window,
13242 cx: &mut Context<Self>,
13243 ) {
13244 let buffer = self.buffer.read(cx).snapshot(cx);
13245 let selection = self.selections.newest::<usize>(cx);
13246
13247 let mut active_group_id = None;
13248 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13249 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13250 active_group_id = Some(active_group.group_id);
13251 }
13252 }
13253
13254 fn filtered(
13255 snapshot: EditorSnapshot,
13256 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13257 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13258 diagnostics
13259 .filter(|entry| entry.range.start != entry.range.end)
13260 .filter(|entry| !entry.diagnostic.is_unnecessary)
13261 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13262 }
13263
13264 let snapshot = self.snapshot(window, cx);
13265 let before = filtered(
13266 snapshot.clone(),
13267 buffer
13268 .diagnostics_in_range(0..selection.start)
13269 .filter(|entry| entry.range.start <= selection.start),
13270 );
13271 let after = filtered(
13272 snapshot,
13273 buffer
13274 .diagnostics_in_range(selection.start..buffer.len())
13275 .filter(|entry| entry.range.start >= selection.start),
13276 );
13277
13278 let mut found: Option<DiagnosticEntry<usize>> = None;
13279 if direction == Direction::Prev {
13280 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13281 {
13282 for diagnostic in prev_diagnostics.into_iter().rev() {
13283 if diagnostic.range.start != selection.start
13284 || active_group_id
13285 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13286 {
13287 found = Some(diagnostic);
13288 break 'outer;
13289 }
13290 }
13291 }
13292 } else {
13293 for diagnostic in after.chain(before) {
13294 if diagnostic.range.start != selection.start
13295 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13296 {
13297 found = Some(diagnostic);
13298 break;
13299 }
13300 }
13301 }
13302 let Some(next_diagnostic) = found else {
13303 return;
13304 };
13305
13306 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13307 return;
13308 };
13309 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13310 s.select_ranges(vec![
13311 next_diagnostic.range.start..next_diagnostic.range.start,
13312 ])
13313 });
13314 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13315 self.refresh_inline_completion(false, true, window, cx);
13316 }
13317
13318 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13319 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13320 let snapshot = self.snapshot(window, cx);
13321 let selection = self.selections.newest::<Point>(cx);
13322 self.go_to_hunk_before_or_after_position(
13323 &snapshot,
13324 selection.head(),
13325 Direction::Next,
13326 window,
13327 cx,
13328 );
13329 }
13330
13331 pub fn go_to_hunk_before_or_after_position(
13332 &mut self,
13333 snapshot: &EditorSnapshot,
13334 position: Point,
13335 direction: Direction,
13336 window: &mut Window,
13337 cx: &mut Context<Editor>,
13338 ) {
13339 let row = if direction == Direction::Next {
13340 self.hunk_after_position(snapshot, position)
13341 .map(|hunk| hunk.row_range.start)
13342 } else {
13343 self.hunk_before_position(snapshot, position)
13344 };
13345
13346 if let Some(row) = row {
13347 let destination = Point::new(row.0, 0);
13348 let autoscroll = Autoscroll::center();
13349
13350 self.unfold_ranges(&[destination..destination], false, false, cx);
13351 self.change_selections(Some(autoscroll), window, cx, |s| {
13352 s.select_ranges([destination..destination]);
13353 });
13354 }
13355 }
13356
13357 fn hunk_after_position(
13358 &mut self,
13359 snapshot: &EditorSnapshot,
13360 position: Point,
13361 ) -> Option<MultiBufferDiffHunk> {
13362 snapshot
13363 .buffer_snapshot
13364 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13365 .find(|hunk| hunk.row_range.start.0 > position.row)
13366 .or_else(|| {
13367 snapshot
13368 .buffer_snapshot
13369 .diff_hunks_in_range(Point::zero()..position)
13370 .find(|hunk| hunk.row_range.end.0 < position.row)
13371 })
13372 }
13373
13374 fn go_to_prev_hunk(
13375 &mut self,
13376 _: &GoToPreviousHunk,
13377 window: &mut Window,
13378 cx: &mut Context<Self>,
13379 ) {
13380 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13381 let snapshot = self.snapshot(window, cx);
13382 let selection = self.selections.newest::<Point>(cx);
13383 self.go_to_hunk_before_or_after_position(
13384 &snapshot,
13385 selection.head(),
13386 Direction::Prev,
13387 window,
13388 cx,
13389 );
13390 }
13391
13392 fn hunk_before_position(
13393 &mut self,
13394 snapshot: &EditorSnapshot,
13395 position: Point,
13396 ) -> Option<MultiBufferRow> {
13397 snapshot
13398 .buffer_snapshot
13399 .diff_hunk_before(position)
13400 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13401 }
13402
13403 fn go_to_next_change(
13404 &mut self,
13405 _: &GoToNextChange,
13406 window: &mut Window,
13407 cx: &mut Context<Self>,
13408 ) {
13409 if let Some(selections) = self
13410 .change_list
13411 .next_change(1, Direction::Next)
13412 .map(|s| s.to_vec())
13413 {
13414 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13415 let map = s.display_map();
13416 s.select_display_ranges(selections.iter().map(|a| {
13417 let point = a.to_display_point(&map);
13418 point..point
13419 }))
13420 })
13421 }
13422 }
13423
13424 fn go_to_previous_change(
13425 &mut self,
13426 _: &GoToPreviousChange,
13427 window: &mut Window,
13428 cx: &mut Context<Self>,
13429 ) {
13430 if let Some(selections) = self
13431 .change_list
13432 .next_change(1, Direction::Prev)
13433 .map(|s| s.to_vec())
13434 {
13435 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13436 let map = s.display_map();
13437 s.select_display_ranges(selections.iter().map(|a| {
13438 let point = a.to_display_point(&map);
13439 point..point
13440 }))
13441 })
13442 }
13443 }
13444
13445 fn go_to_line<T: 'static>(
13446 &mut self,
13447 position: Anchor,
13448 highlight_color: Option<Hsla>,
13449 window: &mut Window,
13450 cx: &mut Context<Self>,
13451 ) {
13452 let snapshot = self.snapshot(window, cx).display_snapshot;
13453 let position = position.to_point(&snapshot.buffer_snapshot);
13454 let start = snapshot
13455 .buffer_snapshot
13456 .clip_point(Point::new(position.row, 0), Bias::Left);
13457 let end = start + Point::new(1, 0);
13458 let start = snapshot.buffer_snapshot.anchor_before(start);
13459 let end = snapshot.buffer_snapshot.anchor_before(end);
13460
13461 self.highlight_rows::<T>(
13462 start..end,
13463 highlight_color
13464 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13465 false,
13466 cx,
13467 );
13468 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13469 }
13470
13471 pub fn go_to_definition(
13472 &mut self,
13473 _: &GoToDefinition,
13474 window: &mut Window,
13475 cx: &mut Context<Self>,
13476 ) -> Task<Result<Navigated>> {
13477 let definition =
13478 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13479 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13480 cx.spawn_in(window, async move |editor, cx| {
13481 if definition.await? == Navigated::Yes {
13482 return Ok(Navigated::Yes);
13483 }
13484 match fallback_strategy {
13485 GoToDefinitionFallback::None => Ok(Navigated::No),
13486 GoToDefinitionFallback::FindAllReferences => {
13487 match editor.update_in(cx, |editor, window, cx| {
13488 editor.find_all_references(&FindAllReferences, window, cx)
13489 })? {
13490 Some(references) => references.await,
13491 None => Ok(Navigated::No),
13492 }
13493 }
13494 }
13495 })
13496 }
13497
13498 pub fn go_to_declaration(
13499 &mut self,
13500 _: &GoToDeclaration,
13501 window: &mut Window,
13502 cx: &mut Context<Self>,
13503 ) -> Task<Result<Navigated>> {
13504 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13505 }
13506
13507 pub fn go_to_declaration_split(
13508 &mut self,
13509 _: &GoToDeclaration,
13510 window: &mut Window,
13511 cx: &mut Context<Self>,
13512 ) -> Task<Result<Navigated>> {
13513 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13514 }
13515
13516 pub fn go_to_implementation(
13517 &mut self,
13518 _: &GoToImplementation,
13519 window: &mut Window,
13520 cx: &mut Context<Self>,
13521 ) -> Task<Result<Navigated>> {
13522 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13523 }
13524
13525 pub fn go_to_implementation_split(
13526 &mut self,
13527 _: &GoToImplementationSplit,
13528 window: &mut Window,
13529 cx: &mut Context<Self>,
13530 ) -> Task<Result<Navigated>> {
13531 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13532 }
13533
13534 pub fn go_to_type_definition(
13535 &mut self,
13536 _: &GoToTypeDefinition,
13537 window: &mut Window,
13538 cx: &mut Context<Self>,
13539 ) -> Task<Result<Navigated>> {
13540 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13541 }
13542
13543 pub fn go_to_definition_split(
13544 &mut self,
13545 _: &GoToDefinitionSplit,
13546 window: &mut Window,
13547 cx: &mut Context<Self>,
13548 ) -> Task<Result<Navigated>> {
13549 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13550 }
13551
13552 pub fn go_to_type_definition_split(
13553 &mut self,
13554 _: &GoToTypeDefinitionSplit,
13555 window: &mut Window,
13556 cx: &mut Context<Self>,
13557 ) -> Task<Result<Navigated>> {
13558 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13559 }
13560
13561 fn go_to_definition_of_kind(
13562 &mut self,
13563 kind: GotoDefinitionKind,
13564 split: bool,
13565 window: &mut Window,
13566 cx: &mut Context<Self>,
13567 ) -> Task<Result<Navigated>> {
13568 let Some(provider) = self.semantics_provider.clone() else {
13569 return Task::ready(Ok(Navigated::No));
13570 };
13571 let head = self.selections.newest::<usize>(cx).head();
13572 let buffer = self.buffer.read(cx);
13573 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13574 text_anchor
13575 } else {
13576 return Task::ready(Ok(Navigated::No));
13577 };
13578
13579 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13580 return Task::ready(Ok(Navigated::No));
13581 };
13582
13583 cx.spawn_in(window, async move |editor, cx| {
13584 let definitions = definitions.await?;
13585 let navigated = editor
13586 .update_in(cx, |editor, window, cx| {
13587 editor.navigate_to_hover_links(
13588 Some(kind),
13589 definitions
13590 .into_iter()
13591 .filter(|location| {
13592 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13593 })
13594 .map(HoverLink::Text)
13595 .collect::<Vec<_>>(),
13596 split,
13597 window,
13598 cx,
13599 )
13600 })?
13601 .await?;
13602 anyhow::Ok(navigated)
13603 })
13604 }
13605
13606 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13607 let selection = self.selections.newest_anchor();
13608 let head = selection.head();
13609 let tail = selection.tail();
13610
13611 let Some((buffer, start_position)) =
13612 self.buffer.read(cx).text_anchor_for_position(head, cx)
13613 else {
13614 return;
13615 };
13616
13617 let end_position = if head != tail {
13618 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13619 return;
13620 };
13621 Some(pos)
13622 } else {
13623 None
13624 };
13625
13626 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13627 let url = if let Some(end_pos) = end_position {
13628 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13629 } else {
13630 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13631 };
13632
13633 if let Some(url) = url {
13634 editor.update(cx, |_, cx| {
13635 cx.open_url(&url);
13636 })
13637 } else {
13638 Ok(())
13639 }
13640 });
13641
13642 url_finder.detach();
13643 }
13644
13645 pub fn open_selected_filename(
13646 &mut self,
13647 _: &OpenSelectedFilename,
13648 window: &mut Window,
13649 cx: &mut Context<Self>,
13650 ) {
13651 let Some(workspace) = self.workspace() else {
13652 return;
13653 };
13654
13655 let position = self.selections.newest_anchor().head();
13656
13657 let Some((buffer, buffer_position)) =
13658 self.buffer.read(cx).text_anchor_for_position(position, cx)
13659 else {
13660 return;
13661 };
13662
13663 let project = self.project.clone();
13664
13665 cx.spawn_in(window, async move |_, cx| {
13666 let result = find_file(&buffer, project, buffer_position, cx).await;
13667
13668 if let Some((_, path)) = result {
13669 workspace
13670 .update_in(cx, |workspace, window, cx| {
13671 workspace.open_resolved_path(path, window, cx)
13672 })?
13673 .await?;
13674 }
13675 anyhow::Ok(())
13676 })
13677 .detach();
13678 }
13679
13680 pub(crate) fn navigate_to_hover_links(
13681 &mut self,
13682 kind: Option<GotoDefinitionKind>,
13683 mut definitions: Vec<HoverLink>,
13684 split: bool,
13685 window: &mut Window,
13686 cx: &mut Context<Editor>,
13687 ) -> Task<Result<Navigated>> {
13688 // If there is one definition, just open it directly
13689 if definitions.len() == 1 {
13690 let definition = definitions.pop().unwrap();
13691
13692 enum TargetTaskResult {
13693 Location(Option<Location>),
13694 AlreadyNavigated,
13695 }
13696
13697 let target_task = match definition {
13698 HoverLink::Text(link) => {
13699 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13700 }
13701 HoverLink::InlayHint(lsp_location, server_id) => {
13702 let computation =
13703 self.compute_target_location(lsp_location, server_id, window, cx);
13704 cx.background_spawn(async move {
13705 let location = computation.await?;
13706 Ok(TargetTaskResult::Location(location))
13707 })
13708 }
13709 HoverLink::Url(url) => {
13710 cx.open_url(&url);
13711 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13712 }
13713 HoverLink::File(path) => {
13714 if let Some(workspace) = self.workspace() {
13715 cx.spawn_in(window, async move |_, cx| {
13716 workspace
13717 .update_in(cx, |workspace, window, cx| {
13718 workspace.open_resolved_path(path, window, cx)
13719 })?
13720 .await
13721 .map(|_| TargetTaskResult::AlreadyNavigated)
13722 })
13723 } else {
13724 Task::ready(Ok(TargetTaskResult::Location(None)))
13725 }
13726 }
13727 };
13728 cx.spawn_in(window, async move |editor, cx| {
13729 let target = match target_task.await.context("target resolution task")? {
13730 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13731 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13732 TargetTaskResult::Location(Some(target)) => target,
13733 };
13734
13735 editor.update_in(cx, |editor, window, cx| {
13736 let Some(workspace) = editor.workspace() else {
13737 return Navigated::No;
13738 };
13739 let pane = workspace.read(cx).active_pane().clone();
13740
13741 let range = target.range.to_point(target.buffer.read(cx));
13742 let range = editor.range_for_match(&range);
13743 let range = collapse_multiline_range(range);
13744
13745 if !split
13746 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13747 {
13748 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13749 } else {
13750 window.defer(cx, move |window, cx| {
13751 let target_editor: Entity<Self> =
13752 workspace.update(cx, |workspace, cx| {
13753 let pane = if split {
13754 workspace.adjacent_pane(window, cx)
13755 } else {
13756 workspace.active_pane().clone()
13757 };
13758
13759 workspace.open_project_item(
13760 pane,
13761 target.buffer.clone(),
13762 true,
13763 true,
13764 window,
13765 cx,
13766 )
13767 });
13768 target_editor.update(cx, |target_editor, cx| {
13769 // When selecting a definition in a different buffer, disable the nav history
13770 // to avoid creating a history entry at the previous cursor location.
13771 pane.update(cx, |pane, _| pane.disable_history());
13772 target_editor.go_to_singleton_buffer_range(range, window, cx);
13773 pane.update(cx, |pane, _| pane.enable_history());
13774 });
13775 });
13776 }
13777 Navigated::Yes
13778 })
13779 })
13780 } else if !definitions.is_empty() {
13781 cx.spawn_in(window, async move |editor, cx| {
13782 let (title, location_tasks, workspace) = editor
13783 .update_in(cx, |editor, window, cx| {
13784 let tab_kind = match kind {
13785 Some(GotoDefinitionKind::Implementation) => "Implementations",
13786 _ => "Definitions",
13787 };
13788 let title = definitions
13789 .iter()
13790 .find_map(|definition| match definition {
13791 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13792 let buffer = origin.buffer.read(cx);
13793 format!(
13794 "{} for {}",
13795 tab_kind,
13796 buffer
13797 .text_for_range(origin.range.clone())
13798 .collect::<String>()
13799 )
13800 }),
13801 HoverLink::InlayHint(_, _) => None,
13802 HoverLink::Url(_) => None,
13803 HoverLink::File(_) => None,
13804 })
13805 .unwrap_or(tab_kind.to_string());
13806 let location_tasks = definitions
13807 .into_iter()
13808 .map(|definition| match definition {
13809 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13810 HoverLink::InlayHint(lsp_location, server_id) => editor
13811 .compute_target_location(lsp_location, server_id, window, cx),
13812 HoverLink::Url(_) => Task::ready(Ok(None)),
13813 HoverLink::File(_) => Task::ready(Ok(None)),
13814 })
13815 .collect::<Vec<_>>();
13816 (title, location_tasks, editor.workspace().clone())
13817 })
13818 .context("location tasks preparation")?;
13819
13820 let locations = future::join_all(location_tasks)
13821 .await
13822 .into_iter()
13823 .filter_map(|location| location.transpose())
13824 .collect::<Result<_>>()
13825 .context("location tasks")?;
13826
13827 let Some(workspace) = workspace else {
13828 return Ok(Navigated::No);
13829 };
13830 let opened = workspace
13831 .update_in(cx, |workspace, window, cx| {
13832 Self::open_locations_in_multibuffer(
13833 workspace,
13834 locations,
13835 title,
13836 split,
13837 MultibufferSelectionMode::First,
13838 window,
13839 cx,
13840 )
13841 })
13842 .ok();
13843
13844 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13845 })
13846 } else {
13847 Task::ready(Ok(Navigated::No))
13848 }
13849 }
13850
13851 fn compute_target_location(
13852 &self,
13853 lsp_location: lsp::Location,
13854 server_id: LanguageServerId,
13855 window: &mut Window,
13856 cx: &mut Context<Self>,
13857 ) -> Task<anyhow::Result<Option<Location>>> {
13858 let Some(project) = self.project.clone() else {
13859 return Task::ready(Ok(None));
13860 };
13861
13862 cx.spawn_in(window, async move |editor, cx| {
13863 let location_task = editor.update(cx, |_, cx| {
13864 project.update(cx, |project, cx| {
13865 let language_server_name = project
13866 .language_server_statuses(cx)
13867 .find(|(id, _)| server_id == *id)
13868 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13869 language_server_name.map(|language_server_name| {
13870 project.open_local_buffer_via_lsp(
13871 lsp_location.uri.clone(),
13872 server_id,
13873 language_server_name,
13874 cx,
13875 )
13876 })
13877 })
13878 })?;
13879 let location = match location_task {
13880 Some(task) => Some({
13881 let target_buffer_handle = task.await.context("open local buffer")?;
13882 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13883 let target_start = target_buffer
13884 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13885 let target_end = target_buffer
13886 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13887 target_buffer.anchor_after(target_start)
13888 ..target_buffer.anchor_before(target_end)
13889 })?;
13890 Location {
13891 buffer: target_buffer_handle,
13892 range,
13893 }
13894 }),
13895 None => None,
13896 };
13897 Ok(location)
13898 })
13899 }
13900
13901 pub fn find_all_references(
13902 &mut self,
13903 _: &FindAllReferences,
13904 window: &mut Window,
13905 cx: &mut Context<Self>,
13906 ) -> Option<Task<Result<Navigated>>> {
13907 let selection = self.selections.newest::<usize>(cx);
13908 let multi_buffer = self.buffer.read(cx);
13909 let head = selection.head();
13910
13911 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13912 let head_anchor = multi_buffer_snapshot.anchor_at(
13913 head,
13914 if head < selection.tail() {
13915 Bias::Right
13916 } else {
13917 Bias::Left
13918 },
13919 );
13920
13921 match self
13922 .find_all_references_task_sources
13923 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13924 {
13925 Ok(_) => {
13926 log::info!(
13927 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13928 );
13929 return None;
13930 }
13931 Err(i) => {
13932 self.find_all_references_task_sources.insert(i, head_anchor);
13933 }
13934 }
13935
13936 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13937 let workspace = self.workspace()?;
13938 let project = workspace.read(cx).project().clone();
13939 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13940 Some(cx.spawn_in(window, async move |editor, cx| {
13941 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13942 if let Ok(i) = editor
13943 .find_all_references_task_sources
13944 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13945 {
13946 editor.find_all_references_task_sources.remove(i);
13947 }
13948 });
13949
13950 let locations = references.await?;
13951 if locations.is_empty() {
13952 return anyhow::Ok(Navigated::No);
13953 }
13954
13955 workspace.update_in(cx, |workspace, window, cx| {
13956 let title = locations
13957 .first()
13958 .as_ref()
13959 .map(|location| {
13960 let buffer = location.buffer.read(cx);
13961 format!(
13962 "References to `{}`",
13963 buffer
13964 .text_for_range(location.range.clone())
13965 .collect::<String>()
13966 )
13967 })
13968 .unwrap();
13969 Self::open_locations_in_multibuffer(
13970 workspace,
13971 locations,
13972 title,
13973 false,
13974 MultibufferSelectionMode::First,
13975 window,
13976 cx,
13977 );
13978 Navigated::Yes
13979 })
13980 }))
13981 }
13982
13983 /// Opens a multibuffer with the given project locations in it
13984 pub fn open_locations_in_multibuffer(
13985 workspace: &mut Workspace,
13986 mut locations: Vec<Location>,
13987 title: String,
13988 split: bool,
13989 multibuffer_selection_mode: MultibufferSelectionMode,
13990 window: &mut Window,
13991 cx: &mut Context<Workspace>,
13992 ) {
13993 // If there are multiple definitions, open them in a multibuffer
13994 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13995 let mut locations = locations.into_iter().peekable();
13996 let mut ranges: Vec<Range<Anchor>> = Vec::new();
13997 let capability = workspace.project().read(cx).capability();
13998
13999 let excerpt_buffer = cx.new(|cx| {
14000 let mut multibuffer = MultiBuffer::new(capability);
14001 while let Some(location) = locations.next() {
14002 let buffer = location.buffer.read(cx);
14003 let mut ranges_for_buffer = Vec::new();
14004 let range = location.range.to_point(buffer);
14005 ranges_for_buffer.push(range.clone());
14006
14007 while let Some(next_location) = locations.peek() {
14008 if next_location.buffer == location.buffer {
14009 ranges_for_buffer.push(next_location.range.to_point(buffer));
14010 locations.next();
14011 } else {
14012 break;
14013 }
14014 }
14015
14016 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14017 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14018 PathKey::for_buffer(&location.buffer, cx),
14019 location.buffer.clone(),
14020 ranges_for_buffer,
14021 DEFAULT_MULTIBUFFER_CONTEXT,
14022 cx,
14023 );
14024 ranges.extend(new_ranges)
14025 }
14026
14027 multibuffer.with_title(title)
14028 });
14029
14030 let editor = cx.new(|cx| {
14031 Editor::for_multibuffer(
14032 excerpt_buffer,
14033 Some(workspace.project().clone()),
14034 window,
14035 cx,
14036 )
14037 });
14038 editor.update(cx, |editor, cx| {
14039 match multibuffer_selection_mode {
14040 MultibufferSelectionMode::First => {
14041 if let Some(first_range) = ranges.first() {
14042 editor.change_selections(None, window, cx, |selections| {
14043 selections.clear_disjoint();
14044 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14045 });
14046 }
14047 editor.highlight_background::<Self>(
14048 &ranges,
14049 |theme| theme.editor_highlighted_line_background,
14050 cx,
14051 );
14052 }
14053 MultibufferSelectionMode::All => {
14054 editor.change_selections(None, window, cx, |selections| {
14055 selections.clear_disjoint();
14056 selections.select_anchor_ranges(ranges);
14057 });
14058 }
14059 }
14060 editor.register_buffers_with_language_servers(cx);
14061 });
14062
14063 let item = Box::new(editor);
14064 let item_id = item.item_id();
14065
14066 if split {
14067 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14068 } else {
14069 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14070 let (preview_item_id, preview_item_idx) =
14071 workspace.active_pane().update(cx, |pane, _| {
14072 (pane.preview_item_id(), pane.preview_item_idx())
14073 });
14074
14075 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14076
14077 if let Some(preview_item_id) = preview_item_id {
14078 workspace.active_pane().update(cx, |pane, cx| {
14079 pane.remove_item(preview_item_id, false, false, window, cx);
14080 });
14081 }
14082 } else {
14083 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14084 }
14085 }
14086 workspace.active_pane().update(cx, |pane, cx| {
14087 pane.set_preview_item_id(Some(item_id), cx);
14088 });
14089 }
14090
14091 pub fn rename(
14092 &mut self,
14093 _: &Rename,
14094 window: &mut Window,
14095 cx: &mut Context<Self>,
14096 ) -> Option<Task<Result<()>>> {
14097 use language::ToOffset as _;
14098
14099 let provider = self.semantics_provider.clone()?;
14100 let selection = self.selections.newest_anchor().clone();
14101 let (cursor_buffer, cursor_buffer_position) = self
14102 .buffer
14103 .read(cx)
14104 .text_anchor_for_position(selection.head(), cx)?;
14105 let (tail_buffer, cursor_buffer_position_end) = self
14106 .buffer
14107 .read(cx)
14108 .text_anchor_for_position(selection.tail(), cx)?;
14109 if tail_buffer != cursor_buffer {
14110 return None;
14111 }
14112
14113 let snapshot = cursor_buffer.read(cx).snapshot();
14114 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14115 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14116 let prepare_rename = provider
14117 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14118 .unwrap_or_else(|| Task::ready(Ok(None)));
14119 drop(snapshot);
14120
14121 Some(cx.spawn_in(window, async move |this, cx| {
14122 let rename_range = if let Some(range) = prepare_rename.await? {
14123 Some(range)
14124 } else {
14125 this.update(cx, |this, cx| {
14126 let buffer = this.buffer.read(cx).snapshot(cx);
14127 let mut buffer_highlights = this
14128 .document_highlights_for_position(selection.head(), &buffer)
14129 .filter(|highlight| {
14130 highlight.start.excerpt_id == selection.head().excerpt_id
14131 && highlight.end.excerpt_id == selection.head().excerpt_id
14132 });
14133 buffer_highlights
14134 .next()
14135 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14136 })?
14137 };
14138 if let Some(rename_range) = rename_range {
14139 this.update_in(cx, |this, window, cx| {
14140 let snapshot = cursor_buffer.read(cx).snapshot();
14141 let rename_buffer_range = rename_range.to_offset(&snapshot);
14142 let cursor_offset_in_rename_range =
14143 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14144 let cursor_offset_in_rename_range_end =
14145 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14146
14147 this.take_rename(false, window, cx);
14148 let buffer = this.buffer.read(cx).read(cx);
14149 let cursor_offset = selection.head().to_offset(&buffer);
14150 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14151 let rename_end = rename_start + rename_buffer_range.len();
14152 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14153 let mut old_highlight_id = None;
14154 let old_name: Arc<str> = buffer
14155 .chunks(rename_start..rename_end, true)
14156 .map(|chunk| {
14157 if old_highlight_id.is_none() {
14158 old_highlight_id = chunk.syntax_highlight_id;
14159 }
14160 chunk.text
14161 })
14162 .collect::<String>()
14163 .into();
14164
14165 drop(buffer);
14166
14167 // Position the selection in the rename editor so that it matches the current selection.
14168 this.show_local_selections = false;
14169 let rename_editor = cx.new(|cx| {
14170 let mut editor = Editor::single_line(window, cx);
14171 editor.buffer.update(cx, |buffer, cx| {
14172 buffer.edit([(0..0, old_name.clone())], None, cx)
14173 });
14174 let rename_selection_range = match cursor_offset_in_rename_range
14175 .cmp(&cursor_offset_in_rename_range_end)
14176 {
14177 Ordering::Equal => {
14178 editor.select_all(&SelectAll, window, cx);
14179 return editor;
14180 }
14181 Ordering::Less => {
14182 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14183 }
14184 Ordering::Greater => {
14185 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14186 }
14187 };
14188 if rename_selection_range.end > old_name.len() {
14189 editor.select_all(&SelectAll, window, cx);
14190 } else {
14191 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14192 s.select_ranges([rename_selection_range]);
14193 });
14194 }
14195 editor
14196 });
14197 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14198 if e == &EditorEvent::Focused {
14199 cx.emit(EditorEvent::FocusedIn)
14200 }
14201 })
14202 .detach();
14203
14204 let write_highlights =
14205 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14206 let read_highlights =
14207 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14208 let ranges = write_highlights
14209 .iter()
14210 .flat_map(|(_, ranges)| ranges.iter())
14211 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14212 .cloned()
14213 .collect();
14214
14215 this.highlight_text::<Rename>(
14216 ranges,
14217 HighlightStyle {
14218 fade_out: Some(0.6),
14219 ..Default::default()
14220 },
14221 cx,
14222 );
14223 let rename_focus_handle = rename_editor.focus_handle(cx);
14224 window.focus(&rename_focus_handle);
14225 let block_id = this.insert_blocks(
14226 [BlockProperties {
14227 style: BlockStyle::Flex,
14228 placement: BlockPlacement::Below(range.start),
14229 height: Some(1),
14230 render: Arc::new({
14231 let rename_editor = rename_editor.clone();
14232 move |cx: &mut BlockContext| {
14233 let mut text_style = cx.editor_style.text.clone();
14234 if let Some(highlight_style) = old_highlight_id
14235 .and_then(|h| h.style(&cx.editor_style.syntax))
14236 {
14237 text_style = text_style.highlight(highlight_style);
14238 }
14239 div()
14240 .block_mouse_down()
14241 .pl(cx.anchor_x)
14242 .child(EditorElement::new(
14243 &rename_editor,
14244 EditorStyle {
14245 background: cx.theme().system().transparent,
14246 local_player: cx.editor_style.local_player,
14247 text: text_style,
14248 scrollbar_width: cx.editor_style.scrollbar_width,
14249 syntax: cx.editor_style.syntax.clone(),
14250 status: cx.editor_style.status.clone(),
14251 inlay_hints_style: HighlightStyle {
14252 font_weight: Some(FontWeight::BOLD),
14253 ..make_inlay_hints_style(cx.app)
14254 },
14255 inline_completion_styles: make_suggestion_styles(
14256 cx.app,
14257 ),
14258 ..EditorStyle::default()
14259 },
14260 ))
14261 .into_any_element()
14262 }
14263 }),
14264 priority: 0,
14265 }],
14266 Some(Autoscroll::fit()),
14267 cx,
14268 )[0];
14269 this.pending_rename = Some(RenameState {
14270 range,
14271 old_name,
14272 editor: rename_editor,
14273 block_id,
14274 });
14275 })?;
14276 }
14277
14278 Ok(())
14279 }))
14280 }
14281
14282 pub fn confirm_rename(
14283 &mut self,
14284 _: &ConfirmRename,
14285 window: &mut Window,
14286 cx: &mut Context<Self>,
14287 ) -> Option<Task<Result<()>>> {
14288 let rename = self.take_rename(false, window, cx)?;
14289 let workspace = self.workspace()?.downgrade();
14290 let (buffer, start) = self
14291 .buffer
14292 .read(cx)
14293 .text_anchor_for_position(rename.range.start, cx)?;
14294 let (end_buffer, _) = self
14295 .buffer
14296 .read(cx)
14297 .text_anchor_for_position(rename.range.end, cx)?;
14298 if buffer != end_buffer {
14299 return None;
14300 }
14301
14302 let old_name = rename.old_name;
14303 let new_name = rename.editor.read(cx).text(cx);
14304
14305 let rename = self.semantics_provider.as_ref()?.perform_rename(
14306 &buffer,
14307 start,
14308 new_name.clone(),
14309 cx,
14310 )?;
14311
14312 Some(cx.spawn_in(window, async move |editor, cx| {
14313 let project_transaction = rename.await?;
14314 Self::open_project_transaction(
14315 &editor,
14316 workspace,
14317 project_transaction,
14318 format!("Rename: {} → {}", old_name, new_name),
14319 cx,
14320 )
14321 .await?;
14322
14323 editor.update(cx, |editor, cx| {
14324 editor.refresh_document_highlights(cx);
14325 })?;
14326 Ok(())
14327 }))
14328 }
14329
14330 fn take_rename(
14331 &mut self,
14332 moving_cursor: bool,
14333 window: &mut Window,
14334 cx: &mut Context<Self>,
14335 ) -> Option<RenameState> {
14336 let rename = self.pending_rename.take()?;
14337 if rename.editor.focus_handle(cx).is_focused(window) {
14338 window.focus(&self.focus_handle);
14339 }
14340
14341 self.remove_blocks(
14342 [rename.block_id].into_iter().collect(),
14343 Some(Autoscroll::fit()),
14344 cx,
14345 );
14346 self.clear_highlights::<Rename>(cx);
14347 self.show_local_selections = true;
14348
14349 if moving_cursor {
14350 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14351 editor.selections.newest::<usize>(cx).head()
14352 });
14353
14354 // Update the selection to match the position of the selection inside
14355 // the rename editor.
14356 let snapshot = self.buffer.read(cx).read(cx);
14357 let rename_range = rename.range.to_offset(&snapshot);
14358 let cursor_in_editor = snapshot
14359 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14360 .min(rename_range.end);
14361 drop(snapshot);
14362
14363 self.change_selections(None, window, cx, |s| {
14364 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14365 });
14366 } else {
14367 self.refresh_document_highlights(cx);
14368 }
14369
14370 Some(rename)
14371 }
14372
14373 pub fn pending_rename(&self) -> Option<&RenameState> {
14374 self.pending_rename.as_ref()
14375 }
14376
14377 fn format(
14378 &mut self,
14379 _: &Format,
14380 window: &mut Window,
14381 cx: &mut Context<Self>,
14382 ) -> Option<Task<Result<()>>> {
14383 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14384
14385 let project = match &self.project {
14386 Some(project) => project.clone(),
14387 None => return None,
14388 };
14389
14390 Some(self.perform_format(
14391 project,
14392 FormatTrigger::Manual,
14393 FormatTarget::Buffers,
14394 window,
14395 cx,
14396 ))
14397 }
14398
14399 fn format_selections(
14400 &mut self,
14401 _: &FormatSelections,
14402 window: &mut Window,
14403 cx: &mut Context<Self>,
14404 ) -> Option<Task<Result<()>>> {
14405 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14406
14407 let project = match &self.project {
14408 Some(project) => project.clone(),
14409 None => return None,
14410 };
14411
14412 let ranges = self
14413 .selections
14414 .all_adjusted(cx)
14415 .into_iter()
14416 .map(|selection| selection.range())
14417 .collect_vec();
14418
14419 Some(self.perform_format(
14420 project,
14421 FormatTrigger::Manual,
14422 FormatTarget::Ranges(ranges),
14423 window,
14424 cx,
14425 ))
14426 }
14427
14428 fn perform_format(
14429 &mut self,
14430 project: Entity<Project>,
14431 trigger: FormatTrigger,
14432 target: FormatTarget,
14433 window: &mut Window,
14434 cx: &mut Context<Self>,
14435 ) -> Task<Result<()>> {
14436 let buffer = self.buffer.clone();
14437 let (buffers, target) = match target {
14438 FormatTarget::Buffers => {
14439 let mut buffers = buffer.read(cx).all_buffers();
14440 if trigger == FormatTrigger::Save {
14441 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14442 }
14443 (buffers, LspFormatTarget::Buffers)
14444 }
14445 FormatTarget::Ranges(selection_ranges) => {
14446 let multi_buffer = buffer.read(cx);
14447 let snapshot = multi_buffer.read(cx);
14448 let mut buffers = HashSet::default();
14449 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14450 BTreeMap::new();
14451 for selection_range in selection_ranges {
14452 for (buffer, buffer_range, _) in
14453 snapshot.range_to_buffer_ranges(selection_range)
14454 {
14455 let buffer_id = buffer.remote_id();
14456 let start = buffer.anchor_before(buffer_range.start);
14457 let end = buffer.anchor_after(buffer_range.end);
14458 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14459 buffer_id_to_ranges
14460 .entry(buffer_id)
14461 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14462 .or_insert_with(|| vec![start..end]);
14463 }
14464 }
14465 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14466 }
14467 };
14468
14469 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14470 let selections_prev = transaction_id_prev
14471 .and_then(|transaction_id_prev| {
14472 // default to selections as they were after the last edit, if we have them,
14473 // instead of how they are now.
14474 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14475 // will take you back to where you made the last edit, instead of staying where you scrolled
14476 self.selection_history
14477 .transaction(transaction_id_prev)
14478 .map(|t| t.0.clone())
14479 })
14480 .unwrap_or_else(|| {
14481 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14482 self.selections.disjoint_anchors()
14483 });
14484
14485 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14486 let format = project.update(cx, |project, cx| {
14487 project.format(buffers, target, true, trigger, cx)
14488 });
14489
14490 cx.spawn_in(window, async move |editor, cx| {
14491 let transaction = futures::select_biased! {
14492 transaction = format.log_err().fuse() => transaction,
14493 () = timeout => {
14494 log::warn!("timed out waiting for formatting");
14495 None
14496 }
14497 };
14498
14499 buffer
14500 .update(cx, |buffer, cx| {
14501 if let Some(transaction) = transaction {
14502 if !buffer.is_singleton() {
14503 buffer.push_transaction(&transaction.0, cx);
14504 }
14505 }
14506 cx.notify();
14507 })
14508 .ok();
14509
14510 if let Some(transaction_id_now) =
14511 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14512 {
14513 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14514 if has_new_transaction {
14515 _ = editor.update(cx, |editor, _| {
14516 editor
14517 .selection_history
14518 .insert_transaction(transaction_id_now, selections_prev);
14519 });
14520 }
14521 }
14522
14523 Ok(())
14524 })
14525 }
14526
14527 fn organize_imports(
14528 &mut self,
14529 _: &OrganizeImports,
14530 window: &mut Window,
14531 cx: &mut Context<Self>,
14532 ) -> Option<Task<Result<()>>> {
14533 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14534 let project = match &self.project {
14535 Some(project) => project.clone(),
14536 None => return None,
14537 };
14538 Some(self.perform_code_action_kind(
14539 project,
14540 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14541 window,
14542 cx,
14543 ))
14544 }
14545
14546 fn perform_code_action_kind(
14547 &mut self,
14548 project: Entity<Project>,
14549 kind: CodeActionKind,
14550 window: &mut Window,
14551 cx: &mut Context<Self>,
14552 ) -> Task<Result<()>> {
14553 let buffer = self.buffer.clone();
14554 let buffers = buffer.read(cx).all_buffers();
14555 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14556 let apply_action = project.update(cx, |project, cx| {
14557 project.apply_code_action_kind(buffers, kind, true, cx)
14558 });
14559 cx.spawn_in(window, async move |_, cx| {
14560 let transaction = futures::select_biased! {
14561 () = timeout => {
14562 log::warn!("timed out waiting for executing code action");
14563 None
14564 }
14565 transaction = apply_action.log_err().fuse() => transaction,
14566 };
14567 buffer
14568 .update(cx, |buffer, cx| {
14569 // check if we need this
14570 if let Some(transaction) = transaction {
14571 if !buffer.is_singleton() {
14572 buffer.push_transaction(&transaction.0, cx);
14573 }
14574 }
14575 cx.notify();
14576 })
14577 .ok();
14578 Ok(())
14579 })
14580 }
14581
14582 fn restart_language_server(
14583 &mut self,
14584 _: &RestartLanguageServer,
14585 _: &mut Window,
14586 cx: &mut Context<Self>,
14587 ) {
14588 if let Some(project) = self.project.clone() {
14589 self.buffer.update(cx, |multi_buffer, cx| {
14590 project.update(cx, |project, cx| {
14591 project.restart_language_servers_for_buffers(
14592 multi_buffer.all_buffers().into_iter().collect(),
14593 cx,
14594 );
14595 });
14596 })
14597 }
14598 }
14599
14600 fn stop_language_server(
14601 &mut self,
14602 _: &StopLanguageServer,
14603 _: &mut Window,
14604 cx: &mut Context<Self>,
14605 ) {
14606 if let Some(project) = self.project.clone() {
14607 self.buffer.update(cx, |multi_buffer, cx| {
14608 project.update(cx, |project, cx| {
14609 project.stop_language_servers_for_buffers(
14610 multi_buffer.all_buffers().into_iter().collect(),
14611 cx,
14612 );
14613 cx.emit(project::Event::RefreshInlayHints);
14614 });
14615 });
14616 }
14617 }
14618
14619 fn cancel_language_server_work(
14620 workspace: &mut Workspace,
14621 _: &actions::CancelLanguageServerWork,
14622 _: &mut Window,
14623 cx: &mut Context<Workspace>,
14624 ) {
14625 let project = workspace.project();
14626 let buffers = workspace
14627 .active_item(cx)
14628 .and_then(|item| item.act_as::<Editor>(cx))
14629 .map_or(HashSet::default(), |editor| {
14630 editor.read(cx).buffer.read(cx).all_buffers()
14631 });
14632 project.update(cx, |project, cx| {
14633 project.cancel_language_server_work_for_buffers(buffers, cx);
14634 });
14635 }
14636
14637 fn show_character_palette(
14638 &mut self,
14639 _: &ShowCharacterPalette,
14640 window: &mut Window,
14641 _: &mut Context<Self>,
14642 ) {
14643 window.show_character_palette();
14644 }
14645
14646 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14647 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14648 let buffer = self.buffer.read(cx).snapshot(cx);
14649 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
14650 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
14651 let is_valid = buffer
14652 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14653 .any(|entry| {
14654 entry.diagnostic.is_primary
14655 && !entry.range.is_empty()
14656 && entry.range.start == primary_range_start
14657 && entry.diagnostic.message == active_diagnostics.active_message
14658 });
14659
14660 if !is_valid {
14661 self.dismiss_diagnostics(cx);
14662 }
14663 }
14664 }
14665
14666 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
14667 match &self.active_diagnostics {
14668 ActiveDiagnostic::Group(group) => Some(group),
14669 _ => None,
14670 }
14671 }
14672
14673 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
14674 self.dismiss_diagnostics(cx);
14675 self.active_diagnostics = ActiveDiagnostic::All;
14676 }
14677
14678 fn activate_diagnostics(
14679 &mut self,
14680 buffer_id: BufferId,
14681 diagnostic: DiagnosticEntry<usize>,
14682 window: &mut Window,
14683 cx: &mut Context<Self>,
14684 ) {
14685 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14686 return;
14687 }
14688 self.dismiss_diagnostics(cx);
14689 let snapshot = self.snapshot(window, cx);
14690 let Some(diagnostic_renderer) = cx
14691 .try_global::<GlobalDiagnosticRenderer>()
14692 .map(|g| g.0.clone())
14693 else {
14694 return;
14695 };
14696 let buffer = self.buffer.read(cx).snapshot(cx);
14697
14698 let diagnostic_group = buffer
14699 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
14700 .collect::<Vec<_>>();
14701
14702 let blocks = diagnostic_renderer.render_group(
14703 diagnostic_group,
14704 buffer_id,
14705 snapshot,
14706 cx.weak_entity(),
14707 cx,
14708 );
14709
14710 let blocks = self.display_map.update(cx, |display_map, cx| {
14711 display_map.insert_blocks(blocks, cx).into_iter().collect()
14712 });
14713 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
14714 active_range: buffer.anchor_before(diagnostic.range.start)
14715 ..buffer.anchor_after(diagnostic.range.end),
14716 active_message: diagnostic.diagnostic.message.clone(),
14717 group_id: diagnostic.diagnostic.group_id,
14718 blocks,
14719 });
14720 cx.notify();
14721 }
14722
14723 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14724 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14725 return;
14726 };
14727
14728 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
14729 if let ActiveDiagnostic::Group(group) = prev {
14730 self.display_map.update(cx, |display_map, cx| {
14731 display_map.remove_blocks(group.blocks, cx);
14732 });
14733 cx.notify();
14734 }
14735 }
14736
14737 /// Disable inline diagnostics rendering for this editor.
14738 pub fn disable_inline_diagnostics(&mut self) {
14739 self.inline_diagnostics_enabled = false;
14740 self.inline_diagnostics_update = Task::ready(());
14741 self.inline_diagnostics.clear();
14742 }
14743
14744 pub fn inline_diagnostics_enabled(&self) -> bool {
14745 self.inline_diagnostics_enabled
14746 }
14747
14748 pub fn show_inline_diagnostics(&self) -> bool {
14749 self.show_inline_diagnostics
14750 }
14751
14752 pub fn toggle_inline_diagnostics(
14753 &mut self,
14754 _: &ToggleInlineDiagnostics,
14755 window: &mut Window,
14756 cx: &mut Context<Editor>,
14757 ) {
14758 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14759 self.refresh_inline_diagnostics(false, window, cx);
14760 }
14761
14762 fn refresh_inline_diagnostics(
14763 &mut self,
14764 debounce: bool,
14765 window: &mut Window,
14766 cx: &mut Context<Self>,
14767 ) {
14768 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14769 self.inline_diagnostics_update = Task::ready(());
14770 self.inline_diagnostics.clear();
14771 return;
14772 }
14773
14774 let debounce_ms = ProjectSettings::get_global(cx)
14775 .diagnostics
14776 .inline
14777 .update_debounce_ms;
14778 let debounce = if debounce && debounce_ms > 0 {
14779 Some(Duration::from_millis(debounce_ms))
14780 } else {
14781 None
14782 };
14783 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14784 let editor = editor.upgrade().unwrap();
14785
14786 if let Some(debounce) = debounce {
14787 cx.background_executor().timer(debounce).await;
14788 }
14789 let Some(snapshot) = editor
14790 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14791 .ok()
14792 else {
14793 return;
14794 };
14795
14796 let new_inline_diagnostics = cx
14797 .background_spawn(async move {
14798 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14799 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14800 let message = diagnostic_entry
14801 .diagnostic
14802 .message
14803 .split_once('\n')
14804 .map(|(line, _)| line)
14805 .map(SharedString::new)
14806 .unwrap_or_else(|| {
14807 SharedString::from(diagnostic_entry.diagnostic.message)
14808 });
14809 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14810 let (Ok(i) | Err(i)) = inline_diagnostics
14811 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14812 inline_diagnostics.insert(
14813 i,
14814 (
14815 start_anchor,
14816 InlineDiagnostic {
14817 message,
14818 group_id: diagnostic_entry.diagnostic.group_id,
14819 start: diagnostic_entry.range.start.to_point(&snapshot),
14820 is_primary: diagnostic_entry.diagnostic.is_primary,
14821 severity: diagnostic_entry.diagnostic.severity,
14822 },
14823 ),
14824 );
14825 }
14826 inline_diagnostics
14827 })
14828 .await;
14829
14830 editor
14831 .update(cx, |editor, cx| {
14832 editor.inline_diagnostics = new_inline_diagnostics;
14833 cx.notify();
14834 })
14835 .ok();
14836 });
14837 }
14838
14839 pub fn set_selections_from_remote(
14840 &mut self,
14841 selections: Vec<Selection<Anchor>>,
14842 pending_selection: Option<Selection<Anchor>>,
14843 window: &mut Window,
14844 cx: &mut Context<Self>,
14845 ) {
14846 let old_cursor_position = self.selections.newest_anchor().head();
14847 self.selections.change_with(cx, |s| {
14848 s.select_anchors(selections);
14849 if let Some(pending_selection) = pending_selection {
14850 s.set_pending(pending_selection, SelectMode::Character);
14851 } else {
14852 s.clear_pending();
14853 }
14854 });
14855 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14856 }
14857
14858 fn push_to_selection_history(&mut self) {
14859 self.selection_history.push(SelectionHistoryEntry {
14860 selections: self.selections.disjoint_anchors(),
14861 select_next_state: self.select_next_state.clone(),
14862 select_prev_state: self.select_prev_state.clone(),
14863 add_selections_state: self.add_selections_state.clone(),
14864 });
14865 }
14866
14867 pub fn transact(
14868 &mut self,
14869 window: &mut Window,
14870 cx: &mut Context<Self>,
14871 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14872 ) -> Option<TransactionId> {
14873 self.start_transaction_at(Instant::now(), window, cx);
14874 update(self, window, cx);
14875 self.end_transaction_at(Instant::now(), cx)
14876 }
14877
14878 pub fn start_transaction_at(
14879 &mut self,
14880 now: Instant,
14881 window: &mut Window,
14882 cx: &mut Context<Self>,
14883 ) {
14884 self.end_selection(window, cx);
14885 if let Some(tx_id) = self
14886 .buffer
14887 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14888 {
14889 self.selection_history
14890 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14891 cx.emit(EditorEvent::TransactionBegun {
14892 transaction_id: tx_id,
14893 })
14894 }
14895 }
14896
14897 pub fn end_transaction_at(
14898 &mut self,
14899 now: Instant,
14900 cx: &mut Context<Self>,
14901 ) -> Option<TransactionId> {
14902 if let Some(transaction_id) = self
14903 .buffer
14904 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14905 {
14906 if let Some((_, end_selections)) =
14907 self.selection_history.transaction_mut(transaction_id)
14908 {
14909 *end_selections = Some(self.selections.disjoint_anchors());
14910 } else {
14911 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14912 }
14913
14914 cx.emit(EditorEvent::Edited { transaction_id });
14915 Some(transaction_id)
14916 } else {
14917 None
14918 }
14919 }
14920
14921 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14922 if self.selection_mark_mode {
14923 self.change_selections(None, window, cx, |s| {
14924 s.move_with(|_, sel| {
14925 sel.collapse_to(sel.head(), SelectionGoal::None);
14926 });
14927 })
14928 }
14929 self.selection_mark_mode = true;
14930 cx.notify();
14931 }
14932
14933 pub fn swap_selection_ends(
14934 &mut self,
14935 _: &actions::SwapSelectionEnds,
14936 window: &mut Window,
14937 cx: &mut Context<Self>,
14938 ) {
14939 self.change_selections(None, window, cx, |s| {
14940 s.move_with(|_, sel| {
14941 if sel.start != sel.end {
14942 sel.reversed = !sel.reversed
14943 }
14944 });
14945 });
14946 self.request_autoscroll(Autoscroll::newest(), cx);
14947 cx.notify();
14948 }
14949
14950 pub fn toggle_fold(
14951 &mut self,
14952 _: &actions::ToggleFold,
14953 window: &mut Window,
14954 cx: &mut Context<Self>,
14955 ) {
14956 if self.is_singleton(cx) {
14957 let selection = self.selections.newest::<Point>(cx);
14958
14959 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14960 let range = if selection.is_empty() {
14961 let point = selection.head().to_display_point(&display_map);
14962 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14963 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14964 .to_point(&display_map);
14965 start..end
14966 } else {
14967 selection.range()
14968 };
14969 if display_map.folds_in_range(range).next().is_some() {
14970 self.unfold_lines(&Default::default(), window, cx)
14971 } else {
14972 self.fold(&Default::default(), window, cx)
14973 }
14974 } else {
14975 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14976 let buffer_ids: HashSet<_> = self
14977 .selections
14978 .disjoint_anchor_ranges()
14979 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14980 .collect();
14981
14982 let should_unfold = buffer_ids
14983 .iter()
14984 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14985
14986 for buffer_id in buffer_ids {
14987 if should_unfold {
14988 self.unfold_buffer(buffer_id, cx);
14989 } else {
14990 self.fold_buffer(buffer_id, cx);
14991 }
14992 }
14993 }
14994 }
14995
14996 pub fn toggle_fold_recursive(
14997 &mut self,
14998 _: &actions::ToggleFoldRecursive,
14999 window: &mut Window,
15000 cx: &mut Context<Self>,
15001 ) {
15002 let selection = self.selections.newest::<Point>(cx);
15003
15004 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15005 let range = if selection.is_empty() {
15006 let point = selection.head().to_display_point(&display_map);
15007 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15008 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15009 .to_point(&display_map);
15010 start..end
15011 } else {
15012 selection.range()
15013 };
15014 if display_map.folds_in_range(range).next().is_some() {
15015 self.unfold_recursive(&Default::default(), window, cx)
15016 } else {
15017 self.fold_recursive(&Default::default(), window, cx)
15018 }
15019 }
15020
15021 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15022 if self.is_singleton(cx) {
15023 let mut to_fold = Vec::new();
15024 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15025 let selections = self.selections.all_adjusted(cx);
15026
15027 for selection in selections {
15028 let range = selection.range().sorted();
15029 let buffer_start_row = range.start.row;
15030
15031 if range.start.row != range.end.row {
15032 let mut found = false;
15033 let mut row = range.start.row;
15034 while row <= range.end.row {
15035 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15036 {
15037 found = true;
15038 row = crease.range().end.row + 1;
15039 to_fold.push(crease);
15040 } else {
15041 row += 1
15042 }
15043 }
15044 if found {
15045 continue;
15046 }
15047 }
15048
15049 for row in (0..=range.start.row).rev() {
15050 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15051 if crease.range().end.row >= buffer_start_row {
15052 to_fold.push(crease);
15053 if row <= range.start.row {
15054 break;
15055 }
15056 }
15057 }
15058 }
15059 }
15060
15061 self.fold_creases(to_fold, true, window, cx);
15062 } else {
15063 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15064 let buffer_ids = self
15065 .selections
15066 .disjoint_anchor_ranges()
15067 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15068 .collect::<HashSet<_>>();
15069 for buffer_id in buffer_ids {
15070 self.fold_buffer(buffer_id, cx);
15071 }
15072 }
15073 }
15074
15075 fn fold_at_level(
15076 &mut self,
15077 fold_at: &FoldAtLevel,
15078 window: &mut Window,
15079 cx: &mut Context<Self>,
15080 ) {
15081 if !self.buffer.read(cx).is_singleton() {
15082 return;
15083 }
15084
15085 let fold_at_level = fold_at.0;
15086 let snapshot = self.buffer.read(cx).snapshot(cx);
15087 let mut to_fold = Vec::new();
15088 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15089
15090 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15091 while start_row < end_row {
15092 match self
15093 .snapshot(window, cx)
15094 .crease_for_buffer_row(MultiBufferRow(start_row))
15095 {
15096 Some(crease) => {
15097 let nested_start_row = crease.range().start.row + 1;
15098 let nested_end_row = crease.range().end.row;
15099
15100 if current_level < fold_at_level {
15101 stack.push((nested_start_row, nested_end_row, current_level + 1));
15102 } else if current_level == fold_at_level {
15103 to_fold.push(crease);
15104 }
15105
15106 start_row = nested_end_row + 1;
15107 }
15108 None => start_row += 1,
15109 }
15110 }
15111 }
15112
15113 self.fold_creases(to_fold, true, window, cx);
15114 }
15115
15116 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15117 if self.buffer.read(cx).is_singleton() {
15118 let mut fold_ranges = Vec::new();
15119 let snapshot = self.buffer.read(cx).snapshot(cx);
15120
15121 for row in 0..snapshot.max_row().0 {
15122 if let Some(foldable_range) = self
15123 .snapshot(window, cx)
15124 .crease_for_buffer_row(MultiBufferRow(row))
15125 {
15126 fold_ranges.push(foldable_range);
15127 }
15128 }
15129
15130 self.fold_creases(fold_ranges, true, window, cx);
15131 } else {
15132 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15133 editor
15134 .update_in(cx, |editor, _, cx| {
15135 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15136 editor.fold_buffer(buffer_id, cx);
15137 }
15138 })
15139 .ok();
15140 });
15141 }
15142 }
15143
15144 pub fn fold_function_bodies(
15145 &mut self,
15146 _: &actions::FoldFunctionBodies,
15147 window: &mut Window,
15148 cx: &mut Context<Self>,
15149 ) {
15150 let snapshot = self.buffer.read(cx).snapshot(cx);
15151
15152 let ranges = snapshot
15153 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15154 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15155 .collect::<Vec<_>>();
15156
15157 let creases = ranges
15158 .into_iter()
15159 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15160 .collect();
15161
15162 self.fold_creases(creases, true, window, cx);
15163 }
15164
15165 pub fn fold_recursive(
15166 &mut self,
15167 _: &actions::FoldRecursive,
15168 window: &mut Window,
15169 cx: &mut Context<Self>,
15170 ) {
15171 let mut to_fold = Vec::new();
15172 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15173 let selections = self.selections.all_adjusted(cx);
15174
15175 for selection in selections {
15176 let range = selection.range().sorted();
15177 let buffer_start_row = range.start.row;
15178
15179 if range.start.row != range.end.row {
15180 let mut found = false;
15181 for row in range.start.row..=range.end.row {
15182 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15183 found = true;
15184 to_fold.push(crease);
15185 }
15186 }
15187 if found {
15188 continue;
15189 }
15190 }
15191
15192 for row in (0..=range.start.row).rev() {
15193 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15194 if crease.range().end.row >= buffer_start_row {
15195 to_fold.push(crease);
15196 } else {
15197 break;
15198 }
15199 }
15200 }
15201 }
15202
15203 self.fold_creases(to_fold, true, window, cx);
15204 }
15205
15206 pub fn fold_at(
15207 &mut self,
15208 buffer_row: MultiBufferRow,
15209 window: &mut Window,
15210 cx: &mut Context<Self>,
15211 ) {
15212 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15213
15214 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15215 let autoscroll = self
15216 .selections
15217 .all::<Point>(cx)
15218 .iter()
15219 .any(|selection| crease.range().overlaps(&selection.range()));
15220
15221 self.fold_creases(vec![crease], autoscroll, window, cx);
15222 }
15223 }
15224
15225 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15226 if self.is_singleton(cx) {
15227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15228 let buffer = &display_map.buffer_snapshot;
15229 let selections = self.selections.all::<Point>(cx);
15230 let ranges = selections
15231 .iter()
15232 .map(|s| {
15233 let range = s.display_range(&display_map).sorted();
15234 let mut start = range.start.to_point(&display_map);
15235 let mut end = range.end.to_point(&display_map);
15236 start.column = 0;
15237 end.column = buffer.line_len(MultiBufferRow(end.row));
15238 start..end
15239 })
15240 .collect::<Vec<_>>();
15241
15242 self.unfold_ranges(&ranges, true, true, cx);
15243 } else {
15244 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15245 let buffer_ids = self
15246 .selections
15247 .disjoint_anchor_ranges()
15248 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15249 .collect::<HashSet<_>>();
15250 for buffer_id in buffer_ids {
15251 self.unfold_buffer(buffer_id, cx);
15252 }
15253 }
15254 }
15255
15256 pub fn unfold_recursive(
15257 &mut self,
15258 _: &UnfoldRecursive,
15259 _window: &mut Window,
15260 cx: &mut Context<Self>,
15261 ) {
15262 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15263 let selections = self.selections.all::<Point>(cx);
15264 let ranges = selections
15265 .iter()
15266 .map(|s| {
15267 let mut range = s.display_range(&display_map).sorted();
15268 *range.start.column_mut() = 0;
15269 *range.end.column_mut() = display_map.line_len(range.end.row());
15270 let start = range.start.to_point(&display_map);
15271 let end = range.end.to_point(&display_map);
15272 start..end
15273 })
15274 .collect::<Vec<_>>();
15275
15276 self.unfold_ranges(&ranges, true, true, cx);
15277 }
15278
15279 pub fn unfold_at(
15280 &mut self,
15281 buffer_row: MultiBufferRow,
15282 _window: &mut Window,
15283 cx: &mut Context<Self>,
15284 ) {
15285 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15286
15287 let intersection_range = Point::new(buffer_row.0, 0)
15288 ..Point::new(
15289 buffer_row.0,
15290 display_map.buffer_snapshot.line_len(buffer_row),
15291 );
15292
15293 let autoscroll = self
15294 .selections
15295 .all::<Point>(cx)
15296 .iter()
15297 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15298
15299 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15300 }
15301
15302 pub fn unfold_all(
15303 &mut self,
15304 _: &actions::UnfoldAll,
15305 _window: &mut Window,
15306 cx: &mut Context<Self>,
15307 ) {
15308 if self.buffer.read(cx).is_singleton() {
15309 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15310 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15311 } else {
15312 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15313 editor
15314 .update(cx, |editor, cx| {
15315 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15316 editor.unfold_buffer(buffer_id, cx);
15317 }
15318 })
15319 .ok();
15320 });
15321 }
15322 }
15323
15324 pub fn fold_selected_ranges(
15325 &mut self,
15326 _: &FoldSelectedRanges,
15327 window: &mut Window,
15328 cx: &mut Context<Self>,
15329 ) {
15330 let selections = self.selections.all_adjusted(cx);
15331 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15332 let ranges = selections
15333 .into_iter()
15334 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15335 .collect::<Vec<_>>();
15336 self.fold_creases(ranges, true, window, cx);
15337 }
15338
15339 pub fn fold_ranges<T: ToOffset + Clone>(
15340 &mut self,
15341 ranges: Vec<Range<T>>,
15342 auto_scroll: bool,
15343 window: &mut Window,
15344 cx: &mut Context<Self>,
15345 ) {
15346 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15347 let ranges = ranges
15348 .into_iter()
15349 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15350 .collect::<Vec<_>>();
15351 self.fold_creases(ranges, auto_scroll, window, cx);
15352 }
15353
15354 pub fn fold_creases<T: ToOffset + Clone>(
15355 &mut self,
15356 creases: Vec<Crease<T>>,
15357 auto_scroll: bool,
15358 _window: &mut Window,
15359 cx: &mut Context<Self>,
15360 ) {
15361 if creases.is_empty() {
15362 return;
15363 }
15364
15365 let mut buffers_affected = HashSet::default();
15366 let multi_buffer = self.buffer().read(cx);
15367 for crease in &creases {
15368 if let Some((_, buffer, _)) =
15369 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15370 {
15371 buffers_affected.insert(buffer.read(cx).remote_id());
15372 };
15373 }
15374
15375 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15376
15377 if auto_scroll {
15378 self.request_autoscroll(Autoscroll::fit(), cx);
15379 }
15380
15381 cx.notify();
15382
15383 self.scrollbar_marker_state.dirty = true;
15384 self.folds_did_change(cx);
15385 }
15386
15387 /// Removes any folds whose ranges intersect any of the given ranges.
15388 pub fn unfold_ranges<T: ToOffset + Clone>(
15389 &mut self,
15390 ranges: &[Range<T>],
15391 inclusive: bool,
15392 auto_scroll: bool,
15393 cx: &mut Context<Self>,
15394 ) {
15395 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15396 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15397 });
15398 self.folds_did_change(cx);
15399 }
15400
15401 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15402 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15403 return;
15404 }
15405 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15406 self.display_map.update(cx, |display_map, cx| {
15407 display_map.fold_buffers([buffer_id], cx)
15408 });
15409 cx.emit(EditorEvent::BufferFoldToggled {
15410 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15411 folded: true,
15412 });
15413 cx.notify();
15414 }
15415
15416 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15417 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15418 return;
15419 }
15420 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15421 self.display_map.update(cx, |display_map, cx| {
15422 display_map.unfold_buffers([buffer_id], cx);
15423 });
15424 cx.emit(EditorEvent::BufferFoldToggled {
15425 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15426 folded: false,
15427 });
15428 cx.notify();
15429 }
15430
15431 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15432 self.display_map.read(cx).is_buffer_folded(buffer)
15433 }
15434
15435 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15436 self.display_map.read(cx).folded_buffers()
15437 }
15438
15439 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15440 self.display_map.update(cx, |display_map, cx| {
15441 display_map.disable_header_for_buffer(buffer_id, cx);
15442 });
15443 cx.notify();
15444 }
15445
15446 /// Removes any folds with the given ranges.
15447 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15448 &mut self,
15449 ranges: &[Range<T>],
15450 type_id: TypeId,
15451 auto_scroll: bool,
15452 cx: &mut Context<Self>,
15453 ) {
15454 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15455 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15456 });
15457 self.folds_did_change(cx);
15458 }
15459
15460 fn remove_folds_with<T: ToOffset + Clone>(
15461 &mut self,
15462 ranges: &[Range<T>],
15463 auto_scroll: bool,
15464 cx: &mut Context<Self>,
15465 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15466 ) {
15467 if ranges.is_empty() {
15468 return;
15469 }
15470
15471 let mut buffers_affected = HashSet::default();
15472 let multi_buffer = self.buffer().read(cx);
15473 for range in ranges {
15474 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15475 buffers_affected.insert(buffer.read(cx).remote_id());
15476 };
15477 }
15478
15479 self.display_map.update(cx, update);
15480
15481 if auto_scroll {
15482 self.request_autoscroll(Autoscroll::fit(), cx);
15483 }
15484
15485 cx.notify();
15486 self.scrollbar_marker_state.dirty = true;
15487 self.active_indent_guides_state.dirty = true;
15488 }
15489
15490 pub fn update_fold_widths(
15491 &mut self,
15492 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15493 cx: &mut Context<Self>,
15494 ) -> bool {
15495 self.display_map
15496 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15497 }
15498
15499 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15500 self.display_map.read(cx).fold_placeholder.clone()
15501 }
15502
15503 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15504 self.buffer.update(cx, |buffer, cx| {
15505 buffer.set_all_diff_hunks_expanded(cx);
15506 });
15507 }
15508
15509 pub fn expand_all_diff_hunks(
15510 &mut self,
15511 _: &ExpandAllDiffHunks,
15512 _window: &mut Window,
15513 cx: &mut Context<Self>,
15514 ) {
15515 self.buffer.update(cx, |buffer, cx| {
15516 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15517 });
15518 }
15519
15520 pub fn toggle_selected_diff_hunks(
15521 &mut self,
15522 _: &ToggleSelectedDiffHunks,
15523 _window: &mut Window,
15524 cx: &mut Context<Self>,
15525 ) {
15526 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15527 self.toggle_diff_hunks_in_ranges(ranges, cx);
15528 }
15529
15530 pub fn diff_hunks_in_ranges<'a>(
15531 &'a self,
15532 ranges: &'a [Range<Anchor>],
15533 buffer: &'a MultiBufferSnapshot,
15534 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15535 ranges.iter().flat_map(move |range| {
15536 let end_excerpt_id = range.end.excerpt_id;
15537 let range = range.to_point(buffer);
15538 let mut peek_end = range.end;
15539 if range.end.row < buffer.max_row().0 {
15540 peek_end = Point::new(range.end.row + 1, 0);
15541 }
15542 buffer
15543 .diff_hunks_in_range(range.start..peek_end)
15544 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15545 })
15546 }
15547
15548 pub fn has_stageable_diff_hunks_in_ranges(
15549 &self,
15550 ranges: &[Range<Anchor>],
15551 snapshot: &MultiBufferSnapshot,
15552 ) -> bool {
15553 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15554 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15555 }
15556
15557 pub fn toggle_staged_selected_diff_hunks(
15558 &mut self,
15559 _: &::git::ToggleStaged,
15560 _: &mut Window,
15561 cx: &mut Context<Self>,
15562 ) {
15563 let snapshot = self.buffer.read(cx).snapshot(cx);
15564 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15565 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15566 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15567 }
15568
15569 pub fn set_render_diff_hunk_controls(
15570 &mut self,
15571 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15572 cx: &mut Context<Self>,
15573 ) {
15574 self.render_diff_hunk_controls = render_diff_hunk_controls;
15575 cx.notify();
15576 }
15577
15578 pub fn stage_and_next(
15579 &mut self,
15580 _: &::git::StageAndNext,
15581 window: &mut Window,
15582 cx: &mut Context<Self>,
15583 ) {
15584 self.do_stage_or_unstage_and_next(true, window, cx);
15585 }
15586
15587 pub fn unstage_and_next(
15588 &mut self,
15589 _: &::git::UnstageAndNext,
15590 window: &mut Window,
15591 cx: &mut Context<Self>,
15592 ) {
15593 self.do_stage_or_unstage_and_next(false, window, cx);
15594 }
15595
15596 pub fn stage_or_unstage_diff_hunks(
15597 &mut self,
15598 stage: bool,
15599 ranges: Vec<Range<Anchor>>,
15600 cx: &mut Context<Self>,
15601 ) {
15602 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15603 cx.spawn(async move |this, cx| {
15604 task.await?;
15605 this.update(cx, |this, cx| {
15606 let snapshot = this.buffer.read(cx).snapshot(cx);
15607 let chunk_by = this
15608 .diff_hunks_in_ranges(&ranges, &snapshot)
15609 .chunk_by(|hunk| hunk.buffer_id);
15610 for (buffer_id, hunks) in &chunk_by {
15611 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15612 }
15613 })
15614 })
15615 .detach_and_log_err(cx);
15616 }
15617
15618 fn save_buffers_for_ranges_if_needed(
15619 &mut self,
15620 ranges: &[Range<Anchor>],
15621 cx: &mut Context<Editor>,
15622 ) -> Task<Result<()>> {
15623 let multibuffer = self.buffer.read(cx);
15624 let snapshot = multibuffer.read(cx);
15625 let buffer_ids: HashSet<_> = ranges
15626 .iter()
15627 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15628 .collect();
15629 drop(snapshot);
15630
15631 let mut buffers = HashSet::default();
15632 for buffer_id in buffer_ids {
15633 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15634 let buffer = buffer_entity.read(cx);
15635 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15636 {
15637 buffers.insert(buffer_entity);
15638 }
15639 }
15640 }
15641
15642 if let Some(project) = &self.project {
15643 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15644 } else {
15645 Task::ready(Ok(()))
15646 }
15647 }
15648
15649 fn do_stage_or_unstage_and_next(
15650 &mut self,
15651 stage: bool,
15652 window: &mut Window,
15653 cx: &mut Context<Self>,
15654 ) {
15655 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15656
15657 if ranges.iter().any(|range| range.start != range.end) {
15658 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15659 return;
15660 }
15661
15662 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15663 let snapshot = self.snapshot(window, cx);
15664 let position = self.selections.newest::<Point>(cx).head();
15665 let mut row = snapshot
15666 .buffer_snapshot
15667 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15668 .find(|hunk| hunk.row_range.start.0 > position.row)
15669 .map(|hunk| hunk.row_range.start);
15670
15671 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15672 // Outside of the project diff editor, wrap around to the beginning.
15673 if !all_diff_hunks_expanded {
15674 row = row.or_else(|| {
15675 snapshot
15676 .buffer_snapshot
15677 .diff_hunks_in_range(Point::zero()..position)
15678 .find(|hunk| hunk.row_range.end.0 < position.row)
15679 .map(|hunk| hunk.row_range.start)
15680 });
15681 }
15682
15683 if let Some(row) = row {
15684 let destination = Point::new(row.0, 0);
15685 let autoscroll = Autoscroll::center();
15686
15687 self.unfold_ranges(&[destination..destination], false, false, cx);
15688 self.change_selections(Some(autoscroll), window, cx, |s| {
15689 s.select_ranges([destination..destination]);
15690 });
15691 }
15692 }
15693
15694 fn do_stage_or_unstage(
15695 &self,
15696 stage: bool,
15697 buffer_id: BufferId,
15698 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15699 cx: &mut App,
15700 ) -> Option<()> {
15701 let project = self.project.as_ref()?;
15702 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15703 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15704 let buffer_snapshot = buffer.read(cx).snapshot();
15705 let file_exists = buffer_snapshot
15706 .file()
15707 .is_some_and(|file| file.disk_state().exists());
15708 diff.update(cx, |diff, cx| {
15709 diff.stage_or_unstage_hunks(
15710 stage,
15711 &hunks
15712 .map(|hunk| buffer_diff::DiffHunk {
15713 buffer_range: hunk.buffer_range,
15714 diff_base_byte_range: hunk.diff_base_byte_range,
15715 secondary_status: hunk.secondary_status,
15716 range: Point::zero()..Point::zero(), // unused
15717 })
15718 .collect::<Vec<_>>(),
15719 &buffer_snapshot,
15720 file_exists,
15721 cx,
15722 )
15723 });
15724 None
15725 }
15726
15727 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15728 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15729 self.buffer
15730 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15731 }
15732
15733 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15734 self.buffer.update(cx, |buffer, cx| {
15735 let ranges = vec![Anchor::min()..Anchor::max()];
15736 if !buffer.all_diff_hunks_expanded()
15737 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15738 {
15739 buffer.collapse_diff_hunks(ranges, cx);
15740 true
15741 } else {
15742 false
15743 }
15744 })
15745 }
15746
15747 fn toggle_diff_hunks_in_ranges(
15748 &mut self,
15749 ranges: Vec<Range<Anchor>>,
15750 cx: &mut Context<Editor>,
15751 ) {
15752 self.buffer.update(cx, |buffer, cx| {
15753 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15754 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15755 })
15756 }
15757
15758 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15759 self.buffer.update(cx, |buffer, cx| {
15760 let snapshot = buffer.snapshot(cx);
15761 let excerpt_id = range.end.excerpt_id;
15762 let point_range = range.to_point(&snapshot);
15763 let expand = !buffer.single_hunk_is_expanded(range, cx);
15764 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15765 })
15766 }
15767
15768 pub(crate) fn apply_all_diff_hunks(
15769 &mut self,
15770 _: &ApplyAllDiffHunks,
15771 window: &mut Window,
15772 cx: &mut Context<Self>,
15773 ) {
15774 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15775
15776 let buffers = self.buffer.read(cx).all_buffers();
15777 for branch_buffer in buffers {
15778 branch_buffer.update(cx, |branch_buffer, cx| {
15779 branch_buffer.merge_into_base(Vec::new(), cx);
15780 });
15781 }
15782
15783 if let Some(project) = self.project.clone() {
15784 self.save(true, project, window, cx).detach_and_log_err(cx);
15785 }
15786 }
15787
15788 pub(crate) fn apply_selected_diff_hunks(
15789 &mut self,
15790 _: &ApplyDiffHunk,
15791 window: &mut Window,
15792 cx: &mut Context<Self>,
15793 ) {
15794 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15795 let snapshot = self.snapshot(window, cx);
15796 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15797 let mut ranges_by_buffer = HashMap::default();
15798 self.transact(window, cx, |editor, _window, cx| {
15799 for hunk in hunks {
15800 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15801 ranges_by_buffer
15802 .entry(buffer.clone())
15803 .or_insert_with(Vec::new)
15804 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15805 }
15806 }
15807
15808 for (buffer, ranges) in ranges_by_buffer {
15809 buffer.update(cx, |buffer, cx| {
15810 buffer.merge_into_base(ranges, cx);
15811 });
15812 }
15813 });
15814
15815 if let Some(project) = self.project.clone() {
15816 self.save(true, project, window, cx).detach_and_log_err(cx);
15817 }
15818 }
15819
15820 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15821 if hovered != self.gutter_hovered {
15822 self.gutter_hovered = hovered;
15823 cx.notify();
15824 }
15825 }
15826
15827 pub fn insert_blocks(
15828 &mut self,
15829 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15830 autoscroll: Option<Autoscroll>,
15831 cx: &mut Context<Self>,
15832 ) -> Vec<CustomBlockId> {
15833 let blocks = self
15834 .display_map
15835 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15836 if let Some(autoscroll) = autoscroll {
15837 self.request_autoscroll(autoscroll, cx);
15838 }
15839 cx.notify();
15840 blocks
15841 }
15842
15843 pub fn resize_blocks(
15844 &mut self,
15845 heights: HashMap<CustomBlockId, u32>,
15846 autoscroll: Option<Autoscroll>,
15847 cx: &mut Context<Self>,
15848 ) {
15849 self.display_map
15850 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15851 if let Some(autoscroll) = autoscroll {
15852 self.request_autoscroll(autoscroll, cx);
15853 }
15854 cx.notify();
15855 }
15856
15857 pub fn replace_blocks(
15858 &mut self,
15859 renderers: HashMap<CustomBlockId, RenderBlock>,
15860 autoscroll: Option<Autoscroll>,
15861 cx: &mut Context<Self>,
15862 ) {
15863 self.display_map
15864 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15865 if let Some(autoscroll) = autoscroll {
15866 self.request_autoscroll(autoscroll, cx);
15867 }
15868 cx.notify();
15869 }
15870
15871 pub fn remove_blocks(
15872 &mut self,
15873 block_ids: HashSet<CustomBlockId>,
15874 autoscroll: Option<Autoscroll>,
15875 cx: &mut Context<Self>,
15876 ) {
15877 self.display_map.update(cx, |display_map, cx| {
15878 display_map.remove_blocks(block_ids, cx)
15879 });
15880 if let Some(autoscroll) = autoscroll {
15881 self.request_autoscroll(autoscroll, cx);
15882 }
15883 cx.notify();
15884 }
15885
15886 pub fn row_for_block(
15887 &self,
15888 block_id: CustomBlockId,
15889 cx: &mut Context<Self>,
15890 ) -> Option<DisplayRow> {
15891 self.display_map
15892 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15893 }
15894
15895 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15896 self.focused_block = Some(focused_block);
15897 }
15898
15899 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15900 self.focused_block.take()
15901 }
15902
15903 pub fn insert_creases(
15904 &mut self,
15905 creases: impl IntoIterator<Item = Crease<Anchor>>,
15906 cx: &mut Context<Self>,
15907 ) -> Vec<CreaseId> {
15908 self.display_map
15909 .update(cx, |map, cx| map.insert_creases(creases, cx))
15910 }
15911
15912 pub fn remove_creases(
15913 &mut self,
15914 ids: impl IntoIterator<Item = CreaseId>,
15915 cx: &mut Context<Self>,
15916 ) {
15917 self.display_map
15918 .update(cx, |map, cx| map.remove_creases(ids, cx));
15919 }
15920
15921 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15922 self.display_map
15923 .update(cx, |map, cx| map.snapshot(cx))
15924 .longest_row()
15925 }
15926
15927 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15928 self.display_map
15929 .update(cx, |map, cx| map.snapshot(cx))
15930 .max_point()
15931 }
15932
15933 pub fn text(&self, cx: &App) -> String {
15934 self.buffer.read(cx).read(cx).text()
15935 }
15936
15937 pub fn is_empty(&self, cx: &App) -> bool {
15938 self.buffer.read(cx).read(cx).is_empty()
15939 }
15940
15941 pub fn text_option(&self, cx: &App) -> Option<String> {
15942 let text = self.text(cx);
15943 let text = text.trim();
15944
15945 if text.is_empty() {
15946 return None;
15947 }
15948
15949 Some(text.to_string())
15950 }
15951
15952 pub fn set_text(
15953 &mut self,
15954 text: impl Into<Arc<str>>,
15955 window: &mut Window,
15956 cx: &mut Context<Self>,
15957 ) {
15958 self.transact(window, cx, |this, _, cx| {
15959 this.buffer
15960 .read(cx)
15961 .as_singleton()
15962 .expect("you can only call set_text on editors for singleton buffers")
15963 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15964 });
15965 }
15966
15967 pub fn display_text(&self, cx: &mut App) -> String {
15968 self.display_map
15969 .update(cx, |map, cx| map.snapshot(cx))
15970 .text()
15971 }
15972
15973 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15974 let mut wrap_guides = smallvec::smallvec![];
15975
15976 if self.show_wrap_guides == Some(false) {
15977 return wrap_guides;
15978 }
15979
15980 let settings = self.buffer.read(cx).language_settings(cx);
15981 if settings.show_wrap_guides {
15982 match self.soft_wrap_mode(cx) {
15983 SoftWrap::Column(soft_wrap) => {
15984 wrap_guides.push((soft_wrap as usize, true));
15985 }
15986 SoftWrap::Bounded(soft_wrap) => {
15987 wrap_guides.push((soft_wrap as usize, true));
15988 }
15989 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15990 }
15991 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15992 }
15993
15994 wrap_guides
15995 }
15996
15997 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15998 let settings = self.buffer.read(cx).language_settings(cx);
15999 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16000 match mode {
16001 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16002 SoftWrap::None
16003 }
16004 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16005 language_settings::SoftWrap::PreferredLineLength => {
16006 SoftWrap::Column(settings.preferred_line_length)
16007 }
16008 language_settings::SoftWrap::Bounded => {
16009 SoftWrap::Bounded(settings.preferred_line_length)
16010 }
16011 }
16012 }
16013
16014 pub fn set_soft_wrap_mode(
16015 &mut self,
16016 mode: language_settings::SoftWrap,
16017
16018 cx: &mut Context<Self>,
16019 ) {
16020 self.soft_wrap_mode_override = Some(mode);
16021 cx.notify();
16022 }
16023
16024 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16025 self.hard_wrap = hard_wrap;
16026 cx.notify();
16027 }
16028
16029 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16030 self.text_style_refinement = Some(style);
16031 }
16032
16033 /// called by the Element so we know what style we were most recently rendered with.
16034 pub(crate) fn set_style(
16035 &mut self,
16036 style: EditorStyle,
16037 window: &mut Window,
16038 cx: &mut Context<Self>,
16039 ) {
16040 let rem_size = window.rem_size();
16041 self.display_map.update(cx, |map, cx| {
16042 map.set_font(
16043 style.text.font(),
16044 style.text.font_size.to_pixels(rem_size),
16045 cx,
16046 )
16047 });
16048 self.style = Some(style);
16049 }
16050
16051 pub fn style(&self) -> Option<&EditorStyle> {
16052 self.style.as_ref()
16053 }
16054
16055 // Called by the element. This method is not designed to be called outside of the editor
16056 // element's layout code because it does not notify when rewrapping is computed synchronously.
16057 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16058 self.display_map
16059 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16060 }
16061
16062 pub fn set_soft_wrap(&mut self) {
16063 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16064 }
16065
16066 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16067 if self.soft_wrap_mode_override.is_some() {
16068 self.soft_wrap_mode_override.take();
16069 } else {
16070 let soft_wrap = match self.soft_wrap_mode(cx) {
16071 SoftWrap::GitDiff => return,
16072 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16073 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16074 language_settings::SoftWrap::None
16075 }
16076 };
16077 self.soft_wrap_mode_override = Some(soft_wrap);
16078 }
16079 cx.notify();
16080 }
16081
16082 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16083 let Some(workspace) = self.workspace() else {
16084 return;
16085 };
16086 let fs = workspace.read(cx).app_state().fs.clone();
16087 let current_show = TabBarSettings::get_global(cx).show;
16088 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16089 setting.show = Some(!current_show);
16090 });
16091 }
16092
16093 pub fn toggle_indent_guides(
16094 &mut self,
16095 _: &ToggleIndentGuides,
16096 _: &mut Window,
16097 cx: &mut Context<Self>,
16098 ) {
16099 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16100 self.buffer
16101 .read(cx)
16102 .language_settings(cx)
16103 .indent_guides
16104 .enabled
16105 });
16106 self.show_indent_guides = Some(!currently_enabled);
16107 cx.notify();
16108 }
16109
16110 fn should_show_indent_guides(&self) -> Option<bool> {
16111 self.show_indent_guides
16112 }
16113
16114 pub fn toggle_line_numbers(
16115 &mut self,
16116 _: &ToggleLineNumbers,
16117 _: &mut Window,
16118 cx: &mut Context<Self>,
16119 ) {
16120 let mut editor_settings = EditorSettings::get_global(cx).clone();
16121 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16122 EditorSettings::override_global(editor_settings, cx);
16123 }
16124
16125 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16126 if let Some(show_line_numbers) = self.show_line_numbers {
16127 return show_line_numbers;
16128 }
16129 EditorSettings::get_global(cx).gutter.line_numbers
16130 }
16131
16132 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16133 self.use_relative_line_numbers
16134 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16135 }
16136
16137 pub fn toggle_relative_line_numbers(
16138 &mut self,
16139 _: &ToggleRelativeLineNumbers,
16140 _: &mut Window,
16141 cx: &mut Context<Self>,
16142 ) {
16143 let is_relative = self.should_use_relative_line_numbers(cx);
16144 self.set_relative_line_number(Some(!is_relative), cx)
16145 }
16146
16147 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16148 self.use_relative_line_numbers = is_relative;
16149 cx.notify();
16150 }
16151
16152 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16153 self.show_gutter = show_gutter;
16154 cx.notify();
16155 }
16156
16157 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16158 self.show_scrollbars = show_scrollbars;
16159 cx.notify();
16160 }
16161
16162 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16163 self.show_line_numbers = Some(show_line_numbers);
16164 cx.notify();
16165 }
16166
16167 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16168 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16169 cx.notify();
16170 }
16171
16172 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16173 self.show_code_actions = Some(show_code_actions);
16174 cx.notify();
16175 }
16176
16177 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16178 self.show_runnables = Some(show_runnables);
16179 cx.notify();
16180 }
16181
16182 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16183 self.show_breakpoints = Some(show_breakpoints);
16184 cx.notify();
16185 }
16186
16187 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16188 if self.display_map.read(cx).masked != masked {
16189 self.display_map.update(cx, |map, _| map.masked = masked);
16190 }
16191 cx.notify()
16192 }
16193
16194 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16195 self.show_wrap_guides = Some(show_wrap_guides);
16196 cx.notify();
16197 }
16198
16199 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16200 self.show_indent_guides = Some(show_indent_guides);
16201 cx.notify();
16202 }
16203
16204 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16205 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16206 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16207 if let Some(dir) = file.abs_path(cx).parent() {
16208 return Some(dir.to_owned());
16209 }
16210 }
16211
16212 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16213 return Some(project_path.path.to_path_buf());
16214 }
16215 }
16216
16217 None
16218 }
16219
16220 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16221 self.active_excerpt(cx)?
16222 .1
16223 .read(cx)
16224 .file()
16225 .and_then(|f| f.as_local())
16226 }
16227
16228 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16229 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16230 let buffer = buffer.read(cx);
16231 if let Some(project_path) = buffer.project_path(cx) {
16232 let project = self.project.as_ref()?.read(cx);
16233 project.absolute_path(&project_path, cx)
16234 } else {
16235 buffer
16236 .file()
16237 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16238 }
16239 })
16240 }
16241
16242 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16243 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16244 let project_path = buffer.read(cx).project_path(cx)?;
16245 let project = self.project.as_ref()?.read(cx);
16246 let entry = project.entry_for_path(&project_path, cx)?;
16247 let path = entry.path.to_path_buf();
16248 Some(path)
16249 })
16250 }
16251
16252 pub fn reveal_in_finder(
16253 &mut self,
16254 _: &RevealInFileManager,
16255 _window: &mut Window,
16256 cx: &mut Context<Self>,
16257 ) {
16258 if let Some(target) = self.target_file(cx) {
16259 cx.reveal_path(&target.abs_path(cx));
16260 }
16261 }
16262
16263 pub fn copy_path(
16264 &mut self,
16265 _: &zed_actions::workspace::CopyPath,
16266 _window: &mut Window,
16267 cx: &mut Context<Self>,
16268 ) {
16269 if let Some(path) = self.target_file_abs_path(cx) {
16270 if let Some(path) = path.to_str() {
16271 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16272 }
16273 }
16274 }
16275
16276 pub fn copy_relative_path(
16277 &mut self,
16278 _: &zed_actions::workspace::CopyRelativePath,
16279 _window: &mut Window,
16280 cx: &mut Context<Self>,
16281 ) {
16282 if let Some(path) = self.target_file_path(cx) {
16283 if let Some(path) = path.to_str() {
16284 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16285 }
16286 }
16287 }
16288
16289 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16290 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16291 buffer.read(cx).project_path(cx)
16292 } else {
16293 None
16294 }
16295 }
16296
16297 // Returns true if the editor handled a go-to-line request
16298 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16299 maybe!({
16300 let breakpoint_store = self.breakpoint_store.as_ref()?;
16301
16302 let Some((_, _, active_position)) =
16303 breakpoint_store.read(cx).active_position().cloned()
16304 else {
16305 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16306 return None;
16307 };
16308
16309 let snapshot = self
16310 .project
16311 .as_ref()?
16312 .read(cx)
16313 .buffer_for_id(active_position.buffer_id?, cx)?
16314 .read(cx)
16315 .snapshot();
16316
16317 let mut handled = false;
16318 for (id, ExcerptRange { context, .. }) in self
16319 .buffer
16320 .read(cx)
16321 .excerpts_for_buffer(active_position.buffer_id?, cx)
16322 {
16323 if context.start.cmp(&active_position, &snapshot).is_ge()
16324 || context.end.cmp(&active_position, &snapshot).is_lt()
16325 {
16326 continue;
16327 }
16328 let snapshot = self.buffer.read(cx).snapshot(cx);
16329 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
16330
16331 handled = true;
16332 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16333 self.go_to_line::<DebugCurrentRowHighlight>(
16334 multibuffer_anchor,
16335 Some(cx.theme().colors().editor_debugger_active_line_background),
16336 window,
16337 cx,
16338 );
16339
16340 cx.notify();
16341 }
16342 handled.then_some(())
16343 })
16344 .is_some()
16345 }
16346
16347 pub fn copy_file_name_without_extension(
16348 &mut self,
16349 _: &CopyFileNameWithoutExtension,
16350 _: &mut Window,
16351 cx: &mut Context<Self>,
16352 ) {
16353 if let Some(file) = self.target_file(cx) {
16354 if let Some(file_stem) = file.path().file_stem() {
16355 if let Some(name) = file_stem.to_str() {
16356 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16357 }
16358 }
16359 }
16360 }
16361
16362 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16363 if let Some(file) = self.target_file(cx) {
16364 if let Some(file_name) = file.path().file_name() {
16365 if let Some(name) = file_name.to_str() {
16366 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16367 }
16368 }
16369 }
16370 }
16371
16372 pub fn toggle_git_blame(
16373 &mut self,
16374 _: &::git::Blame,
16375 window: &mut Window,
16376 cx: &mut Context<Self>,
16377 ) {
16378 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16379
16380 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16381 self.start_git_blame(true, window, cx);
16382 }
16383
16384 cx.notify();
16385 }
16386
16387 pub fn toggle_git_blame_inline(
16388 &mut self,
16389 _: &ToggleGitBlameInline,
16390 window: &mut Window,
16391 cx: &mut Context<Self>,
16392 ) {
16393 self.toggle_git_blame_inline_internal(true, window, cx);
16394 cx.notify();
16395 }
16396
16397 pub fn open_git_blame_commit(
16398 &mut self,
16399 _: &OpenGitBlameCommit,
16400 window: &mut Window,
16401 cx: &mut Context<Self>,
16402 ) {
16403 self.open_git_blame_commit_internal(window, cx);
16404 }
16405
16406 fn open_git_blame_commit_internal(
16407 &mut self,
16408 window: &mut Window,
16409 cx: &mut Context<Self>,
16410 ) -> Option<()> {
16411 let blame = self.blame.as_ref()?;
16412 let snapshot = self.snapshot(window, cx);
16413 let cursor = self.selections.newest::<Point>(cx).head();
16414 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16415 let blame_entry = blame
16416 .update(cx, |blame, cx| {
16417 blame
16418 .blame_for_rows(
16419 &[RowInfo {
16420 buffer_id: Some(buffer.remote_id()),
16421 buffer_row: Some(point.row),
16422 ..Default::default()
16423 }],
16424 cx,
16425 )
16426 .next()
16427 })
16428 .flatten()?;
16429 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16430 let repo = blame.read(cx).repository(cx)?;
16431 let workspace = self.workspace()?.downgrade();
16432 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16433 None
16434 }
16435
16436 pub fn git_blame_inline_enabled(&self) -> bool {
16437 self.git_blame_inline_enabled
16438 }
16439
16440 pub fn toggle_selection_menu(
16441 &mut self,
16442 _: &ToggleSelectionMenu,
16443 _: &mut Window,
16444 cx: &mut Context<Self>,
16445 ) {
16446 self.show_selection_menu = self
16447 .show_selection_menu
16448 .map(|show_selections_menu| !show_selections_menu)
16449 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16450
16451 cx.notify();
16452 }
16453
16454 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16455 self.show_selection_menu
16456 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16457 }
16458
16459 fn start_git_blame(
16460 &mut self,
16461 user_triggered: bool,
16462 window: &mut Window,
16463 cx: &mut Context<Self>,
16464 ) {
16465 if let Some(project) = self.project.as_ref() {
16466 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16467 return;
16468 };
16469
16470 if buffer.read(cx).file().is_none() {
16471 return;
16472 }
16473
16474 let focused = self.focus_handle(cx).contains_focused(window, cx);
16475
16476 let project = project.clone();
16477 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16478 self.blame_subscription =
16479 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16480 self.blame = Some(blame);
16481 }
16482 }
16483
16484 fn toggle_git_blame_inline_internal(
16485 &mut self,
16486 user_triggered: bool,
16487 window: &mut Window,
16488 cx: &mut Context<Self>,
16489 ) {
16490 if self.git_blame_inline_enabled {
16491 self.git_blame_inline_enabled = false;
16492 self.show_git_blame_inline = false;
16493 self.show_git_blame_inline_delay_task.take();
16494 } else {
16495 self.git_blame_inline_enabled = true;
16496 self.start_git_blame_inline(user_triggered, window, cx);
16497 }
16498
16499 cx.notify();
16500 }
16501
16502 fn start_git_blame_inline(
16503 &mut self,
16504 user_triggered: bool,
16505 window: &mut Window,
16506 cx: &mut Context<Self>,
16507 ) {
16508 self.start_git_blame(user_triggered, window, cx);
16509
16510 if ProjectSettings::get_global(cx)
16511 .git
16512 .inline_blame_delay()
16513 .is_some()
16514 {
16515 self.start_inline_blame_timer(window, cx);
16516 } else {
16517 self.show_git_blame_inline = true
16518 }
16519 }
16520
16521 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16522 self.blame.as_ref()
16523 }
16524
16525 pub fn show_git_blame_gutter(&self) -> bool {
16526 self.show_git_blame_gutter
16527 }
16528
16529 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16530 self.show_git_blame_gutter && self.has_blame_entries(cx)
16531 }
16532
16533 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16534 self.show_git_blame_inline
16535 && (self.focus_handle.is_focused(window)
16536 || self
16537 .git_blame_inline_tooltip
16538 .as_ref()
16539 .and_then(|t| t.upgrade())
16540 .is_some())
16541 && !self.newest_selection_head_on_empty_line(cx)
16542 && self.has_blame_entries(cx)
16543 }
16544
16545 fn has_blame_entries(&self, cx: &App) -> bool {
16546 self.blame()
16547 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16548 }
16549
16550 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16551 let cursor_anchor = self.selections.newest_anchor().head();
16552
16553 let snapshot = self.buffer.read(cx).snapshot(cx);
16554 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16555
16556 snapshot.line_len(buffer_row) == 0
16557 }
16558
16559 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16560 let buffer_and_selection = maybe!({
16561 let selection = self.selections.newest::<Point>(cx);
16562 let selection_range = selection.range();
16563
16564 let multi_buffer = self.buffer().read(cx);
16565 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16566 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16567
16568 let (buffer, range, _) = if selection.reversed {
16569 buffer_ranges.first()
16570 } else {
16571 buffer_ranges.last()
16572 }?;
16573
16574 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16575 ..text::ToPoint::to_point(&range.end, &buffer).row;
16576 Some((
16577 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16578 selection,
16579 ))
16580 });
16581
16582 let Some((buffer, selection)) = buffer_and_selection else {
16583 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16584 };
16585
16586 let Some(project) = self.project.as_ref() else {
16587 return Task::ready(Err(anyhow!("editor does not have project")));
16588 };
16589
16590 project.update(cx, |project, cx| {
16591 project.get_permalink_to_line(&buffer, selection, cx)
16592 })
16593 }
16594
16595 pub fn copy_permalink_to_line(
16596 &mut self,
16597 _: &CopyPermalinkToLine,
16598 window: &mut Window,
16599 cx: &mut Context<Self>,
16600 ) {
16601 let permalink_task = self.get_permalink_to_line(cx);
16602 let workspace = self.workspace();
16603
16604 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16605 Ok(permalink) => {
16606 cx.update(|_, cx| {
16607 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16608 })
16609 .ok();
16610 }
16611 Err(err) => {
16612 let message = format!("Failed to copy permalink: {err}");
16613
16614 Err::<(), anyhow::Error>(err).log_err();
16615
16616 if let Some(workspace) = workspace {
16617 workspace
16618 .update_in(cx, |workspace, _, cx| {
16619 struct CopyPermalinkToLine;
16620
16621 workspace.show_toast(
16622 Toast::new(
16623 NotificationId::unique::<CopyPermalinkToLine>(),
16624 message,
16625 ),
16626 cx,
16627 )
16628 })
16629 .ok();
16630 }
16631 }
16632 })
16633 .detach();
16634 }
16635
16636 pub fn copy_file_location(
16637 &mut self,
16638 _: &CopyFileLocation,
16639 _: &mut Window,
16640 cx: &mut Context<Self>,
16641 ) {
16642 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16643 if let Some(file) = self.target_file(cx) {
16644 if let Some(path) = file.path().to_str() {
16645 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16646 }
16647 }
16648 }
16649
16650 pub fn open_permalink_to_line(
16651 &mut self,
16652 _: &OpenPermalinkToLine,
16653 window: &mut Window,
16654 cx: &mut Context<Self>,
16655 ) {
16656 let permalink_task = self.get_permalink_to_line(cx);
16657 let workspace = self.workspace();
16658
16659 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16660 Ok(permalink) => {
16661 cx.update(|_, cx| {
16662 cx.open_url(permalink.as_ref());
16663 })
16664 .ok();
16665 }
16666 Err(err) => {
16667 let message = format!("Failed to open permalink: {err}");
16668
16669 Err::<(), anyhow::Error>(err).log_err();
16670
16671 if let Some(workspace) = workspace {
16672 workspace
16673 .update(cx, |workspace, cx| {
16674 struct OpenPermalinkToLine;
16675
16676 workspace.show_toast(
16677 Toast::new(
16678 NotificationId::unique::<OpenPermalinkToLine>(),
16679 message,
16680 ),
16681 cx,
16682 )
16683 })
16684 .ok();
16685 }
16686 }
16687 })
16688 .detach();
16689 }
16690
16691 pub fn insert_uuid_v4(
16692 &mut self,
16693 _: &InsertUuidV4,
16694 window: &mut Window,
16695 cx: &mut Context<Self>,
16696 ) {
16697 self.insert_uuid(UuidVersion::V4, window, cx);
16698 }
16699
16700 pub fn insert_uuid_v7(
16701 &mut self,
16702 _: &InsertUuidV7,
16703 window: &mut Window,
16704 cx: &mut Context<Self>,
16705 ) {
16706 self.insert_uuid(UuidVersion::V7, window, cx);
16707 }
16708
16709 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16710 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16711 self.transact(window, cx, |this, window, cx| {
16712 let edits = this
16713 .selections
16714 .all::<Point>(cx)
16715 .into_iter()
16716 .map(|selection| {
16717 let uuid = match version {
16718 UuidVersion::V4 => uuid::Uuid::new_v4(),
16719 UuidVersion::V7 => uuid::Uuid::now_v7(),
16720 };
16721
16722 (selection.range(), uuid.to_string())
16723 });
16724 this.edit(edits, cx);
16725 this.refresh_inline_completion(true, false, window, cx);
16726 });
16727 }
16728
16729 pub fn open_selections_in_multibuffer(
16730 &mut self,
16731 _: &OpenSelectionsInMultibuffer,
16732 window: &mut Window,
16733 cx: &mut Context<Self>,
16734 ) {
16735 let multibuffer = self.buffer.read(cx);
16736
16737 let Some(buffer) = multibuffer.as_singleton() else {
16738 return;
16739 };
16740
16741 let Some(workspace) = self.workspace() else {
16742 return;
16743 };
16744
16745 let locations = self
16746 .selections
16747 .disjoint_anchors()
16748 .iter()
16749 .map(|range| Location {
16750 buffer: buffer.clone(),
16751 range: range.start.text_anchor..range.end.text_anchor,
16752 })
16753 .collect::<Vec<_>>();
16754
16755 let title = multibuffer.title(cx).to_string();
16756
16757 cx.spawn_in(window, async move |_, cx| {
16758 workspace.update_in(cx, |workspace, window, cx| {
16759 Self::open_locations_in_multibuffer(
16760 workspace,
16761 locations,
16762 format!("Selections for '{title}'"),
16763 false,
16764 MultibufferSelectionMode::All,
16765 window,
16766 cx,
16767 );
16768 })
16769 })
16770 .detach();
16771 }
16772
16773 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16774 /// last highlight added will be used.
16775 ///
16776 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16777 pub fn highlight_rows<T: 'static>(
16778 &mut self,
16779 range: Range<Anchor>,
16780 color: Hsla,
16781 should_autoscroll: bool,
16782 cx: &mut Context<Self>,
16783 ) {
16784 let snapshot = self.buffer().read(cx).snapshot(cx);
16785 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16786 let ix = row_highlights.binary_search_by(|highlight| {
16787 Ordering::Equal
16788 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16789 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16790 });
16791
16792 if let Err(mut ix) = ix {
16793 let index = post_inc(&mut self.highlight_order);
16794
16795 // If this range intersects with the preceding highlight, then merge it with
16796 // the preceding highlight. Otherwise insert a new highlight.
16797 let mut merged = false;
16798 if ix > 0 {
16799 let prev_highlight = &mut row_highlights[ix - 1];
16800 if prev_highlight
16801 .range
16802 .end
16803 .cmp(&range.start, &snapshot)
16804 .is_ge()
16805 {
16806 ix -= 1;
16807 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16808 prev_highlight.range.end = range.end;
16809 }
16810 merged = true;
16811 prev_highlight.index = index;
16812 prev_highlight.color = color;
16813 prev_highlight.should_autoscroll = should_autoscroll;
16814 }
16815 }
16816
16817 if !merged {
16818 row_highlights.insert(
16819 ix,
16820 RowHighlight {
16821 range: range.clone(),
16822 index,
16823 color,
16824 should_autoscroll,
16825 },
16826 );
16827 }
16828
16829 // If any of the following highlights intersect with this one, merge them.
16830 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16831 let highlight = &row_highlights[ix];
16832 if next_highlight
16833 .range
16834 .start
16835 .cmp(&highlight.range.end, &snapshot)
16836 .is_le()
16837 {
16838 if next_highlight
16839 .range
16840 .end
16841 .cmp(&highlight.range.end, &snapshot)
16842 .is_gt()
16843 {
16844 row_highlights[ix].range.end = next_highlight.range.end;
16845 }
16846 row_highlights.remove(ix + 1);
16847 } else {
16848 break;
16849 }
16850 }
16851 }
16852 }
16853
16854 /// Remove any highlighted row ranges of the given type that intersect the
16855 /// given ranges.
16856 pub fn remove_highlighted_rows<T: 'static>(
16857 &mut self,
16858 ranges_to_remove: Vec<Range<Anchor>>,
16859 cx: &mut Context<Self>,
16860 ) {
16861 let snapshot = self.buffer().read(cx).snapshot(cx);
16862 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16863 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16864 row_highlights.retain(|highlight| {
16865 while let Some(range_to_remove) = ranges_to_remove.peek() {
16866 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16867 Ordering::Less | Ordering::Equal => {
16868 ranges_to_remove.next();
16869 }
16870 Ordering::Greater => {
16871 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16872 Ordering::Less | Ordering::Equal => {
16873 return false;
16874 }
16875 Ordering::Greater => break,
16876 }
16877 }
16878 }
16879 }
16880
16881 true
16882 })
16883 }
16884
16885 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16886 pub fn clear_row_highlights<T: 'static>(&mut self) {
16887 self.highlighted_rows.remove(&TypeId::of::<T>());
16888 }
16889
16890 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16891 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16892 self.highlighted_rows
16893 .get(&TypeId::of::<T>())
16894 .map_or(&[] as &[_], |vec| vec.as_slice())
16895 .iter()
16896 .map(|highlight| (highlight.range.clone(), highlight.color))
16897 }
16898
16899 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16900 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16901 /// Allows to ignore certain kinds of highlights.
16902 pub fn highlighted_display_rows(
16903 &self,
16904 window: &mut Window,
16905 cx: &mut App,
16906 ) -> BTreeMap<DisplayRow, LineHighlight> {
16907 let snapshot = self.snapshot(window, cx);
16908 let mut used_highlight_orders = HashMap::default();
16909 self.highlighted_rows
16910 .iter()
16911 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16912 .fold(
16913 BTreeMap::<DisplayRow, LineHighlight>::new(),
16914 |mut unique_rows, highlight| {
16915 let start = highlight.range.start.to_display_point(&snapshot);
16916 let end = highlight.range.end.to_display_point(&snapshot);
16917 let start_row = start.row().0;
16918 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16919 && end.column() == 0
16920 {
16921 end.row().0.saturating_sub(1)
16922 } else {
16923 end.row().0
16924 };
16925 for row in start_row..=end_row {
16926 let used_index =
16927 used_highlight_orders.entry(row).or_insert(highlight.index);
16928 if highlight.index >= *used_index {
16929 *used_index = highlight.index;
16930 unique_rows.insert(DisplayRow(row), highlight.color.into());
16931 }
16932 }
16933 unique_rows
16934 },
16935 )
16936 }
16937
16938 pub fn highlighted_display_row_for_autoscroll(
16939 &self,
16940 snapshot: &DisplaySnapshot,
16941 ) -> Option<DisplayRow> {
16942 self.highlighted_rows
16943 .values()
16944 .flat_map(|highlighted_rows| highlighted_rows.iter())
16945 .filter_map(|highlight| {
16946 if highlight.should_autoscroll {
16947 Some(highlight.range.start.to_display_point(snapshot).row())
16948 } else {
16949 None
16950 }
16951 })
16952 .min()
16953 }
16954
16955 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16956 self.highlight_background::<SearchWithinRange>(
16957 ranges,
16958 |colors| colors.editor_document_highlight_read_background,
16959 cx,
16960 )
16961 }
16962
16963 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16964 self.breadcrumb_header = Some(new_header);
16965 }
16966
16967 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16968 self.clear_background_highlights::<SearchWithinRange>(cx);
16969 }
16970
16971 pub fn highlight_background<T: 'static>(
16972 &mut self,
16973 ranges: &[Range<Anchor>],
16974 color_fetcher: fn(&ThemeColors) -> Hsla,
16975 cx: &mut Context<Self>,
16976 ) {
16977 self.background_highlights
16978 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16979 self.scrollbar_marker_state.dirty = true;
16980 cx.notify();
16981 }
16982
16983 pub fn clear_background_highlights<T: 'static>(
16984 &mut self,
16985 cx: &mut Context<Self>,
16986 ) -> Option<BackgroundHighlight> {
16987 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16988 if !text_highlights.1.is_empty() {
16989 self.scrollbar_marker_state.dirty = true;
16990 cx.notify();
16991 }
16992 Some(text_highlights)
16993 }
16994
16995 pub fn highlight_gutter<T: 'static>(
16996 &mut self,
16997 ranges: &[Range<Anchor>],
16998 color_fetcher: fn(&App) -> Hsla,
16999 cx: &mut Context<Self>,
17000 ) {
17001 self.gutter_highlights
17002 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17003 cx.notify();
17004 }
17005
17006 pub fn clear_gutter_highlights<T: 'static>(
17007 &mut self,
17008 cx: &mut Context<Self>,
17009 ) -> Option<GutterHighlight> {
17010 cx.notify();
17011 self.gutter_highlights.remove(&TypeId::of::<T>())
17012 }
17013
17014 #[cfg(feature = "test-support")]
17015 pub fn all_text_background_highlights(
17016 &self,
17017 window: &mut Window,
17018 cx: &mut Context<Self>,
17019 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17020 let snapshot = self.snapshot(window, cx);
17021 let buffer = &snapshot.buffer_snapshot;
17022 let start = buffer.anchor_before(0);
17023 let end = buffer.anchor_after(buffer.len());
17024 let theme = cx.theme().colors();
17025 self.background_highlights_in_range(start..end, &snapshot, theme)
17026 }
17027
17028 #[cfg(feature = "test-support")]
17029 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17030 let snapshot = self.buffer().read(cx).snapshot(cx);
17031
17032 let highlights = self
17033 .background_highlights
17034 .get(&TypeId::of::<items::BufferSearchHighlights>());
17035
17036 if let Some((_color, ranges)) = highlights {
17037 ranges
17038 .iter()
17039 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17040 .collect_vec()
17041 } else {
17042 vec![]
17043 }
17044 }
17045
17046 fn document_highlights_for_position<'a>(
17047 &'a self,
17048 position: Anchor,
17049 buffer: &'a MultiBufferSnapshot,
17050 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17051 let read_highlights = self
17052 .background_highlights
17053 .get(&TypeId::of::<DocumentHighlightRead>())
17054 .map(|h| &h.1);
17055 let write_highlights = self
17056 .background_highlights
17057 .get(&TypeId::of::<DocumentHighlightWrite>())
17058 .map(|h| &h.1);
17059 let left_position = position.bias_left(buffer);
17060 let right_position = position.bias_right(buffer);
17061 read_highlights
17062 .into_iter()
17063 .chain(write_highlights)
17064 .flat_map(move |ranges| {
17065 let start_ix = match ranges.binary_search_by(|probe| {
17066 let cmp = probe.end.cmp(&left_position, buffer);
17067 if cmp.is_ge() {
17068 Ordering::Greater
17069 } else {
17070 Ordering::Less
17071 }
17072 }) {
17073 Ok(i) | Err(i) => i,
17074 };
17075
17076 ranges[start_ix..]
17077 .iter()
17078 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17079 })
17080 }
17081
17082 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17083 self.background_highlights
17084 .get(&TypeId::of::<T>())
17085 .map_or(false, |(_, highlights)| !highlights.is_empty())
17086 }
17087
17088 pub fn background_highlights_in_range(
17089 &self,
17090 search_range: Range<Anchor>,
17091 display_snapshot: &DisplaySnapshot,
17092 theme: &ThemeColors,
17093 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17094 let mut results = Vec::new();
17095 for (color_fetcher, ranges) in self.background_highlights.values() {
17096 let color = color_fetcher(theme);
17097 let start_ix = match ranges.binary_search_by(|probe| {
17098 let cmp = probe
17099 .end
17100 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17101 if cmp.is_gt() {
17102 Ordering::Greater
17103 } else {
17104 Ordering::Less
17105 }
17106 }) {
17107 Ok(i) | Err(i) => i,
17108 };
17109 for range in &ranges[start_ix..] {
17110 if range
17111 .start
17112 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17113 .is_ge()
17114 {
17115 break;
17116 }
17117
17118 let start = range.start.to_display_point(display_snapshot);
17119 let end = range.end.to_display_point(display_snapshot);
17120 results.push((start..end, color))
17121 }
17122 }
17123 results
17124 }
17125
17126 pub fn background_highlight_row_ranges<T: 'static>(
17127 &self,
17128 search_range: Range<Anchor>,
17129 display_snapshot: &DisplaySnapshot,
17130 count: usize,
17131 ) -> Vec<RangeInclusive<DisplayPoint>> {
17132 let mut results = Vec::new();
17133 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17134 return vec![];
17135 };
17136
17137 let start_ix = match ranges.binary_search_by(|probe| {
17138 let cmp = probe
17139 .end
17140 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17141 if cmp.is_gt() {
17142 Ordering::Greater
17143 } else {
17144 Ordering::Less
17145 }
17146 }) {
17147 Ok(i) | Err(i) => i,
17148 };
17149 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17150 if let (Some(start_display), Some(end_display)) = (start, end) {
17151 results.push(
17152 start_display.to_display_point(display_snapshot)
17153 ..=end_display.to_display_point(display_snapshot),
17154 );
17155 }
17156 };
17157 let mut start_row: Option<Point> = None;
17158 let mut end_row: Option<Point> = None;
17159 if ranges.len() > count {
17160 return Vec::new();
17161 }
17162 for range in &ranges[start_ix..] {
17163 if range
17164 .start
17165 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17166 .is_ge()
17167 {
17168 break;
17169 }
17170 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17171 if let Some(current_row) = &end_row {
17172 if end.row == current_row.row {
17173 continue;
17174 }
17175 }
17176 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17177 if start_row.is_none() {
17178 assert_eq!(end_row, None);
17179 start_row = Some(start);
17180 end_row = Some(end);
17181 continue;
17182 }
17183 if let Some(current_end) = end_row.as_mut() {
17184 if start.row > current_end.row + 1 {
17185 push_region(start_row, end_row);
17186 start_row = Some(start);
17187 end_row = Some(end);
17188 } else {
17189 // Merge two hunks.
17190 *current_end = end;
17191 }
17192 } else {
17193 unreachable!();
17194 }
17195 }
17196 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17197 push_region(start_row, end_row);
17198 results
17199 }
17200
17201 pub fn gutter_highlights_in_range(
17202 &self,
17203 search_range: Range<Anchor>,
17204 display_snapshot: &DisplaySnapshot,
17205 cx: &App,
17206 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17207 let mut results = Vec::new();
17208 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17209 let color = color_fetcher(cx);
17210 let start_ix = match ranges.binary_search_by(|probe| {
17211 let cmp = probe
17212 .end
17213 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17214 if cmp.is_gt() {
17215 Ordering::Greater
17216 } else {
17217 Ordering::Less
17218 }
17219 }) {
17220 Ok(i) | Err(i) => i,
17221 };
17222 for range in &ranges[start_ix..] {
17223 if range
17224 .start
17225 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17226 .is_ge()
17227 {
17228 break;
17229 }
17230
17231 let start = range.start.to_display_point(display_snapshot);
17232 let end = range.end.to_display_point(display_snapshot);
17233 results.push((start..end, color))
17234 }
17235 }
17236 results
17237 }
17238
17239 /// Get the text ranges corresponding to the redaction query
17240 pub fn redacted_ranges(
17241 &self,
17242 search_range: Range<Anchor>,
17243 display_snapshot: &DisplaySnapshot,
17244 cx: &App,
17245 ) -> Vec<Range<DisplayPoint>> {
17246 display_snapshot
17247 .buffer_snapshot
17248 .redacted_ranges(search_range, |file| {
17249 if let Some(file) = file {
17250 file.is_private()
17251 && EditorSettings::get(
17252 Some(SettingsLocation {
17253 worktree_id: file.worktree_id(cx),
17254 path: file.path().as_ref(),
17255 }),
17256 cx,
17257 )
17258 .redact_private_values
17259 } else {
17260 false
17261 }
17262 })
17263 .map(|range| {
17264 range.start.to_display_point(display_snapshot)
17265 ..range.end.to_display_point(display_snapshot)
17266 })
17267 .collect()
17268 }
17269
17270 pub fn highlight_text<T: 'static>(
17271 &mut self,
17272 ranges: Vec<Range<Anchor>>,
17273 style: HighlightStyle,
17274 cx: &mut Context<Self>,
17275 ) {
17276 self.display_map.update(cx, |map, _| {
17277 map.highlight_text(TypeId::of::<T>(), ranges, style)
17278 });
17279 cx.notify();
17280 }
17281
17282 pub(crate) fn highlight_inlays<T: 'static>(
17283 &mut self,
17284 highlights: Vec<InlayHighlight>,
17285 style: HighlightStyle,
17286 cx: &mut Context<Self>,
17287 ) {
17288 self.display_map.update(cx, |map, _| {
17289 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17290 });
17291 cx.notify();
17292 }
17293
17294 pub fn text_highlights<'a, T: 'static>(
17295 &'a self,
17296 cx: &'a App,
17297 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17298 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17299 }
17300
17301 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17302 let cleared = self
17303 .display_map
17304 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17305 if cleared {
17306 cx.notify();
17307 }
17308 }
17309
17310 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17311 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17312 && self.focus_handle.is_focused(window)
17313 }
17314
17315 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17316 self.show_cursor_when_unfocused = is_enabled;
17317 cx.notify();
17318 }
17319
17320 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17321 cx.notify();
17322 }
17323
17324 fn on_buffer_event(
17325 &mut self,
17326 multibuffer: &Entity<MultiBuffer>,
17327 event: &multi_buffer::Event,
17328 window: &mut Window,
17329 cx: &mut Context<Self>,
17330 ) {
17331 match event {
17332 multi_buffer::Event::Edited {
17333 singleton_buffer_edited,
17334 edited_buffer: buffer_edited,
17335 } => {
17336 self.scrollbar_marker_state.dirty = true;
17337 self.active_indent_guides_state.dirty = true;
17338 self.refresh_active_diagnostics(cx);
17339 self.refresh_code_actions(window, cx);
17340 if self.has_active_inline_completion() {
17341 self.update_visible_inline_completion(window, cx);
17342 }
17343 if let Some(buffer) = buffer_edited {
17344 let buffer_id = buffer.read(cx).remote_id();
17345 if !self.registered_buffers.contains_key(&buffer_id) {
17346 if let Some(project) = self.project.as_ref() {
17347 project.update(cx, |project, cx| {
17348 self.registered_buffers.insert(
17349 buffer_id,
17350 project.register_buffer_with_language_servers(&buffer, cx),
17351 );
17352 })
17353 }
17354 }
17355 }
17356 cx.emit(EditorEvent::BufferEdited);
17357 cx.emit(SearchEvent::MatchesInvalidated);
17358 if *singleton_buffer_edited {
17359 if let Some(project) = &self.project {
17360 #[allow(clippy::mutable_key_type)]
17361 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17362 multibuffer
17363 .all_buffers()
17364 .into_iter()
17365 .filter_map(|buffer| {
17366 buffer.update(cx, |buffer, cx| {
17367 let language = buffer.language()?;
17368 let should_discard = project.update(cx, |project, cx| {
17369 project.is_local()
17370 && !project.has_language_servers_for(buffer, cx)
17371 });
17372 should_discard.not().then_some(language.clone())
17373 })
17374 })
17375 .collect::<HashSet<_>>()
17376 });
17377 if !languages_affected.is_empty() {
17378 self.refresh_inlay_hints(
17379 InlayHintRefreshReason::BufferEdited(languages_affected),
17380 cx,
17381 );
17382 }
17383 }
17384 }
17385
17386 let Some(project) = &self.project else { return };
17387 let (telemetry, is_via_ssh) = {
17388 let project = project.read(cx);
17389 let telemetry = project.client().telemetry().clone();
17390 let is_via_ssh = project.is_via_ssh();
17391 (telemetry, is_via_ssh)
17392 };
17393 refresh_linked_ranges(self, window, cx);
17394 telemetry.log_edit_event("editor", is_via_ssh);
17395 }
17396 multi_buffer::Event::ExcerptsAdded {
17397 buffer,
17398 predecessor,
17399 excerpts,
17400 } => {
17401 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17402 let buffer_id = buffer.read(cx).remote_id();
17403 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17404 if let Some(project) = &self.project {
17405 get_uncommitted_diff_for_buffer(
17406 project,
17407 [buffer.clone()],
17408 self.buffer.clone(),
17409 cx,
17410 )
17411 .detach();
17412 }
17413 }
17414 cx.emit(EditorEvent::ExcerptsAdded {
17415 buffer: buffer.clone(),
17416 predecessor: *predecessor,
17417 excerpts: excerpts.clone(),
17418 });
17419 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17420 }
17421 multi_buffer::Event::ExcerptsRemoved { ids } => {
17422 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17423 let buffer = self.buffer.read(cx);
17424 self.registered_buffers
17425 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17426 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17427 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
17428 }
17429 multi_buffer::Event::ExcerptsEdited {
17430 excerpt_ids,
17431 buffer_ids,
17432 } => {
17433 self.display_map.update(cx, |map, cx| {
17434 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17435 });
17436 cx.emit(EditorEvent::ExcerptsEdited {
17437 ids: excerpt_ids.clone(),
17438 })
17439 }
17440 multi_buffer::Event::ExcerptsExpanded { ids } => {
17441 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17442 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17443 }
17444 multi_buffer::Event::Reparsed(buffer_id) => {
17445 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17446 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17447
17448 cx.emit(EditorEvent::Reparsed(*buffer_id));
17449 }
17450 multi_buffer::Event::DiffHunksToggled => {
17451 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17452 }
17453 multi_buffer::Event::LanguageChanged(buffer_id) => {
17454 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17455 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17456 cx.emit(EditorEvent::Reparsed(*buffer_id));
17457 cx.notify();
17458 }
17459 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17460 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17461 multi_buffer::Event::FileHandleChanged
17462 | multi_buffer::Event::Reloaded
17463 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17464 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17465 multi_buffer::Event::DiagnosticsUpdated => {
17466 self.refresh_active_diagnostics(cx);
17467 self.refresh_inline_diagnostics(true, window, cx);
17468 self.scrollbar_marker_state.dirty = true;
17469 cx.notify();
17470 }
17471 _ => {}
17472 };
17473 }
17474
17475 fn on_display_map_changed(
17476 &mut self,
17477 _: Entity<DisplayMap>,
17478 _: &mut Window,
17479 cx: &mut Context<Self>,
17480 ) {
17481 cx.notify();
17482 }
17483
17484 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17485 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17486 self.update_edit_prediction_settings(cx);
17487 self.refresh_inline_completion(true, false, window, cx);
17488 self.refresh_inlay_hints(
17489 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17490 self.selections.newest_anchor().head(),
17491 &self.buffer.read(cx).snapshot(cx),
17492 cx,
17493 )),
17494 cx,
17495 );
17496
17497 let old_cursor_shape = self.cursor_shape;
17498
17499 {
17500 let editor_settings = EditorSettings::get_global(cx);
17501 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17502 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17503 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17504 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17505 }
17506
17507 if old_cursor_shape != self.cursor_shape {
17508 cx.emit(EditorEvent::CursorShapeChanged);
17509 }
17510
17511 let project_settings = ProjectSettings::get_global(cx);
17512 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17513
17514 if self.mode.is_full() {
17515 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17516 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17517 if self.show_inline_diagnostics != show_inline_diagnostics {
17518 self.show_inline_diagnostics = show_inline_diagnostics;
17519 self.refresh_inline_diagnostics(false, window, cx);
17520 }
17521
17522 if self.git_blame_inline_enabled != inline_blame_enabled {
17523 self.toggle_git_blame_inline_internal(false, window, cx);
17524 }
17525 }
17526
17527 cx.notify();
17528 }
17529
17530 pub fn set_searchable(&mut self, searchable: bool) {
17531 self.searchable = searchable;
17532 }
17533
17534 pub fn searchable(&self) -> bool {
17535 self.searchable
17536 }
17537
17538 fn open_proposed_changes_editor(
17539 &mut self,
17540 _: &OpenProposedChangesEditor,
17541 window: &mut Window,
17542 cx: &mut Context<Self>,
17543 ) {
17544 let Some(workspace) = self.workspace() else {
17545 cx.propagate();
17546 return;
17547 };
17548
17549 let selections = self.selections.all::<usize>(cx);
17550 let multi_buffer = self.buffer.read(cx);
17551 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17552 let mut new_selections_by_buffer = HashMap::default();
17553 for selection in selections {
17554 for (buffer, range, _) in
17555 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17556 {
17557 let mut range = range.to_point(buffer);
17558 range.start.column = 0;
17559 range.end.column = buffer.line_len(range.end.row);
17560 new_selections_by_buffer
17561 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17562 .or_insert(Vec::new())
17563 .push(range)
17564 }
17565 }
17566
17567 let proposed_changes_buffers = new_selections_by_buffer
17568 .into_iter()
17569 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17570 .collect::<Vec<_>>();
17571 let proposed_changes_editor = cx.new(|cx| {
17572 ProposedChangesEditor::new(
17573 "Proposed changes",
17574 proposed_changes_buffers,
17575 self.project.clone(),
17576 window,
17577 cx,
17578 )
17579 });
17580
17581 window.defer(cx, move |window, cx| {
17582 workspace.update(cx, |workspace, cx| {
17583 workspace.active_pane().update(cx, |pane, cx| {
17584 pane.add_item(
17585 Box::new(proposed_changes_editor),
17586 true,
17587 true,
17588 None,
17589 window,
17590 cx,
17591 );
17592 });
17593 });
17594 });
17595 }
17596
17597 pub fn open_excerpts_in_split(
17598 &mut self,
17599 _: &OpenExcerptsSplit,
17600 window: &mut Window,
17601 cx: &mut Context<Self>,
17602 ) {
17603 self.open_excerpts_common(None, true, window, cx)
17604 }
17605
17606 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17607 self.open_excerpts_common(None, false, window, cx)
17608 }
17609
17610 fn open_excerpts_common(
17611 &mut self,
17612 jump_data: Option<JumpData>,
17613 split: bool,
17614 window: &mut Window,
17615 cx: &mut Context<Self>,
17616 ) {
17617 let Some(workspace) = self.workspace() else {
17618 cx.propagate();
17619 return;
17620 };
17621
17622 if self.buffer.read(cx).is_singleton() {
17623 cx.propagate();
17624 return;
17625 }
17626
17627 let mut new_selections_by_buffer = HashMap::default();
17628 match &jump_data {
17629 Some(JumpData::MultiBufferPoint {
17630 excerpt_id,
17631 position,
17632 anchor,
17633 line_offset_from_top,
17634 }) => {
17635 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17636 if let Some(buffer) = multi_buffer_snapshot
17637 .buffer_id_for_excerpt(*excerpt_id)
17638 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17639 {
17640 let buffer_snapshot = buffer.read(cx).snapshot();
17641 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17642 language::ToPoint::to_point(anchor, &buffer_snapshot)
17643 } else {
17644 buffer_snapshot.clip_point(*position, Bias::Left)
17645 };
17646 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17647 new_selections_by_buffer.insert(
17648 buffer,
17649 (
17650 vec![jump_to_offset..jump_to_offset],
17651 Some(*line_offset_from_top),
17652 ),
17653 );
17654 }
17655 }
17656 Some(JumpData::MultiBufferRow {
17657 row,
17658 line_offset_from_top,
17659 }) => {
17660 let point = MultiBufferPoint::new(row.0, 0);
17661 if let Some((buffer, buffer_point, _)) =
17662 self.buffer.read(cx).point_to_buffer_point(point, cx)
17663 {
17664 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17665 new_selections_by_buffer
17666 .entry(buffer)
17667 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17668 .0
17669 .push(buffer_offset..buffer_offset)
17670 }
17671 }
17672 None => {
17673 let selections = self.selections.all::<usize>(cx);
17674 let multi_buffer = self.buffer.read(cx);
17675 for selection in selections {
17676 for (snapshot, range, _, anchor) in multi_buffer
17677 .snapshot(cx)
17678 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17679 {
17680 if let Some(anchor) = anchor {
17681 // selection is in a deleted hunk
17682 let Some(buffer_id) = anchor.buffer_id else {
17683 continue;
17684 };
17685 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17686 continue;
17687 };
17688 let offset = text::ToOffset::to_offset(
17689 &anchor.text_anchor,
17690 &buffer_handle.read(cx).snapshot(),
17691 );
17692 let range = offset..offset;
17693 new_selections_by_buffer
17694 .entry(buffer_handle)
17695 .or_insert((Vec::new(), None))
17696 .0
17697 .push(range)
17698 } else {
17699 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17700 else {
17701 continue;
17702 };
17703 new_selections_by_buffer
17704 .entry(buffer_handle)
17705 .or_insert((Vec::new(), None))
17706 .0
17707 .push(range)
17708 }
17709 }
17710 }
17711 }
17712 }
17713
17714 new_selections_by_buffer
17715 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
17716
17717 if new_selections_by_buffer.is_empty() {
17718 return;
17719 }
17720
17721 // We defer the pane interaction because we ourselves are a workspace item
17722 // and activating a new item causes the pane to call a method on us reentrantly,
17723 // which panics if we're on the stack.
17724 window.defer(cx, move |window, cx| {
17725 workspace.update(cx, |workspace, cx| {
17726 let pane = if split {
17727 workspace.adjacent_pane(window, cx)
17728 } else {
17729 workspace.active_pane().clone()
17730 };
17731
17732 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17733 let editor = buffer
17734 .read(cx)
17735 .file()
17736 .is_none()
17737 .then(|| {
17738 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17739 // so `workspace.open_project_item` will never find them, always opening a new editor.
17740 // Instead, we try to activate the existing editor in the pane first.
17741 let (editor, pane_item_index) =
17742 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17743 let editor = item.downcast::<Editor>()?;
17744 let singleton_buffer =
17745 editor.read(cx).buffer().read(cx).as_singleton()?;
17746 if singleton_buffer == buffer {
17747 Some((editor, i))
17748 } else {
17749 None
17750 }
17751 })?;
17752 pane.update(cx, |pane, cx| {
17753 pane.activate_item(pane_item_index, true, true, window, cx)
17754 });
17755 Some(editor)
17756 })
17757 .flatten()
17758 .unwrap_or_else(|| {
17759 workspace.open_project_item::<Self>(
17760 pane.clone(),
17761 buffer,
17762 true,
17763 true,
17764 window,
17765 cx,
17766 )
17767 });
17768
17769 editor.update(cx, |editor, cx| {
17770 let autoscroll = match scroll_offset {
17771 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17772 None => Autoscroll::newest(),
17773 };
17774 let nav_history = editor.nav_history.take();
17775 editor.change_selections(Some(autoscroll), window, cx, |s| {
17776 s.select_ranges(ranges);
17777 });
17778 editor.nav_history = nav_history;
17779 });
17780 }
17781 })
17782 });
17783 }
17784
17785 // For now, don't allow opening excerpts in buffers that aren't backed by
17786 // regular project files.
17787 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
17788 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
17789 }
17790
17791 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17792 let snapshot = self.buffer.read(cx).read(cx);
17793 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17794 Some(
17795 ranges
17796 .iter()
17797 .map(move |range| {
17798 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17799 })
17800 .collect(),
17801 )
17802 }
17803
17804 fn selection_replacement_ranges(
17805 &self,
17806 range: Range<OffsetUtf16>,
17807 cx: &mut App,
17808 ) -> Vec<Range<OffsetUtf16>> {
17809 let selections = self.selections.all::<OffsetUtf16>(cx);
17810 let newest_selection = selections
17811 .iter()
17812 .max_by_key(|selection| selection.id)
17813 .unwrap();
17814 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17815 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17816 let snapshot = self.buffer.read(cx).read(cx);
17817 selections
17818 .into_iter()
17819 .map(|mut selection| {
17820 selection.start.0 =
17821 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17822 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17823 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17824 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17825 })
17826 .collect()
17827 }
17828
17829 fn report_editor_event(
17830 &self,
17831 event_type: &'static str,
17832 file_extension: Option<String>,
17833 cx: &App,
17834 ) {
17835 if cfg!(any(test, feature = "test-support")) {
17836 return;
17837 }
17838
17839 let Some(project) = &self.project else { return };
17840
17841 // If None, we are in a file without an extension
17842 let file = self
17843 .buffer
17844 .read(cx)
17845 .as_singleton()
17846 .and_then(|b| b.read(cx).file());
17847 let file_extension = file_extension.or(file
17848 .as_ref()
17849 .and_then(|file| Path::new(file.file_name(cx)).extension())
17850 .and_then(|e| e.to_str())
17851 .map(|a| a.to_string()));
17852
17853 let vim_mode = vim_enabled(cx);
17854
17855 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17856 let copilot_enabled = edit_predictions_provider
17857 == language::language_settings::EditPredictionProvider::Copilot;
17858 let copilot_enabled_for_language = self
17859 .buffer
17860 .read(cx)
17861 .language_settings(cx)
17862 .show_edit_predictions;
17863
17864 let project = project.read(cx);
17865 telemetry::event!(
17866 event_type,
17867 file_extension,
17868 vim_mode,
17869 copilot_enabled,
17870 copilot_enabled_for_language,
17871 edit_predictions_provider,
17872 is_via_ssh = project.is_via_ssh(),
17873 );
17874 }
17875
17876 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17877 /// with each line being an array of {text, highlight} objects.
17878 fn copy_highlight_json(
17879 &mut self,
17880 _: &CopyHighlightJson,
17881 window: &mut Window,
17882 cx: &mut Context<Self>,
17883 ) {
17884 #[derive(Serialize)]
17885 struct Chunk<'a> {
17886 text: String,
17887 highlight: Option<&'a str>,
17888 }
17889
17890 let snapshot = self.buffer.read(cx).snapshot(cx);
17891 let range = self
17892 .selected_text_range(false, window, cx)
17893 .and_then(|selection| {
17894 if selection.range.is_empty() {
17895 None
17896 } else {
17897 Some(selection.range)
17898 }
17899 })
17900 .unwrap_or_else(|| 0..snapshot.len());
17901
17902 let chunks = snapshot.chunks(range, true);
17903 let mut lines = Vec::new();
17904 let mut line: VecDeque<Chunk> = VecDeque::new();
17905
17906 let Some(style) = self.style.as_ref() else {
17907 return;
17908 };
17909
17910 for chunk in chunks {
17911 let highlight = chunk
17912 .syntax_highlight_id
17913 .and_then(|id| id.name(&style.syntax));
17914 let mut chunk_lines = chunk.text.split('\n').peekable();
17915 while let Some(text) = chunk_lines.next() {
17916 let mut merged_with_last_token = false;
17917 if let Some(last_token) = line.back_mut() {
17918 if last_token.highlight == highlight {
17919 last_token.text.push_str(text);
17920 merged_with_last_token = true;
17921 }
17922 }
17923
17924 if !merged_with_last_token {
17925 line.push_back(Chunk {
17926 text: text.into(),
17927 highlight,
17928 });
17929 }
17930
17931 if chunk_lines.peek().is_some() {
17932 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17933 line.pop_front();
17934 }
17935 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17936 line.pop_back();
17937 }
17938
17939 lines.push(mem::take(&mut line));
17940 }
17941 }
17942 }
17943
17944 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17945 return;
17946 };
17947 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17948 }
17949
17950 pub fn open_context_menu(
17951 &mut self,
17952 _: &OpenContextMenu,
17953 window: &mut Window,
17954 cx: &mut Context<Self>,
17955 ) {
17956 self.request_autoscroll(Autoscroll::newest(), cx);
17957 let position = self.selections.newest_display(cx).start;
17958 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17959 }
17960
17961 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17962 &self.inlay_hint_cache
17963 }
17964
17965 pub fn replay_insert_event(
17966 &mut self,
17967 text: &str,
17968 relative_utf16_range: Option<Range<isize>>,
17969 window: &mut Window,
17970 cx: &mut Context<Self>,
17971 ) {
17972 if !self.input_enabled {
17973 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17974 return;
17975 }
17976 if let Some(relative_utf16_range) = relative_utf16_range {
17977 let selections = self.selections.all::<OffsetUtf16>(cx);
17978 self.change_selections(None, window, cx, |s| {
17979 let new_ranges = selections.into_iter().map(|range| {
17980 let start = OffsetUtf16(
17981 range
17982 .head()
17983 .0
17984 .saturating_add_signed(relative_utf16_range.start),
17985 );
17986 let end = OffsetUtf16(
17987 range
17988 .head()
17989 .0
17990 .saturating_add_signed(relative_utf16_range.end),
17991 );
17992 start..end
17993 });
17994 s.select_ranges(new_ranges);
17995 });
17996 }
17997
17998 self.handle_input(text, window, cx);
17999 }
18000
18001 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18002 let Some(provider) = self.semantics_provider.as_ref() else {
18003 return false;
18004 };
18005
18006 let mut supports = false;
18007 self.buffer().update(cx, |this, cx| {
18008 this.for_each_buffer(|buffer| {
18009 supports |= provider.supports_inlay_hints(buffer, cx);
18010 });
18011 });
18012
18013 supports
18014 }
18015
18016 pub fn is_focused(&self, window: &Window) -> bool {
18017 self.focus_handle.is_focused(window)
18018 }
18019
18020 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18021 cx.emit(EditorEvent::Focused);
18022
18023 if let Some(descendant) = self
18024 .last_focused_descendant
18025 .take()
18026 .and_then(|descendant| descendant.upgrade())
18027 {
18028 window.focus(&descendant);
18029 } else {
18030 if let Some(blame) = self.blame.as_ref() {
18031 blame.update(cx, GitBlame::focus)
18032 }
18033
18034 self.blink_manager.update(cx, BlinkManager::enable);
18035 self.show_cursor_names(window, cx);
18036 self.buffer.update(cx, |buffer, cx| {
18037 buffer.finalize_last_transaction(cx);
18038 if self.leader_peer_id.is_none() {
18039 buffer.set_active_selections(
18040 &self.selections.disjoint_anchors(),
18041 self.selections.line_mode,
18042 self.cursor_shape,
18043 cx,
18044 );
18045 }
18046 });
18047 }
18048 }
18049
18050 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18051 cx.emit(EditorEvent::FocusedIn)
18052 }
18053
18054 fn handle_focus_out(
18055 &mut self,
18056 event: FocusOutEvent,
18057 _window: &mut Window,
18058 cx: &mut Context<Self>,
18059 ) {
18060 if event.blurred != self.focus_handle {
18061 self.last_focused_descendant = Some(event.blurred);
18062 }
18063 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18064 }
18065
18066 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18067 self.blink_manager.update(cx, BlinkManager::disable);
18068 self.buffer
18069 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18070
18071 if let Some(blame) = self.blame.as_ref() {
18072 blame.update(cx, GitBlame::blur)
18073 }
18074 if !self.hover_state.focused(window, cx) {
18075 hide_hover(self, cx);
18076 }
18077 if !self
18078 .context_menu
18079 .borrow()
18080 .as_ref()
18081 .is_some_and(|context_menu| context_menu.focused(window, cx))
18082 {
18083 self.hide_context_menu(window, cx);
18084 }
18085 self.discard_inline_completion(false, cx);
18086 cx.emit(EditorEvent::Blurred);
18087 cx.notify();
18088 }
18089
18090 pub fn register_action<A: Action>(
18091 &mut self,
18092 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18093 ) -> Subscription {
18094 let id = self.next_editor_action_id.post_inc();
18095 let listener = Arc::new(listener);
18096 self.editor_actions.borrow_mut().insert(
18097 id,
18098 Box::new(move |window, _| {
18099 let listener = listener.clone();
18100 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18101 let action = action.downcast_ref().unwrap();
18102 if phase == DispatchPhase::Bubble {
18103 listener(action, window, cx)
18104 }
18105 })
18106 }),
18107 );
18108
18109 let editor_actions = self.editor_actions.clone();
18110 Subscription::new(move || {
18111 editor_actions.borrow_mut().remove(&id);
18112 })
18113 }
18114
18115 pub fn file_header_size(&self) -> u32 {
18116 FILE_HEADER_HEIGHT
18117 }
18118
18119 pub fn restore(
18120 &mut self,
18121 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18122 window: &mut Window,
18123 cx: &mut Context<Self>,
18124 ) {
18125 let workspace = self.workspace();
18126 let project = self.project.as_ref();
18127 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18128 let mut tasks = Vec::new();
18129 for (buffer_id, changes) in revert_changes {
18130 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18131 buffer.update(cx, |buffer, cx| {
18132 buffer.edit(
18133 changes
18134 .into_iter()
18135 .map(|(range, text)| (range, text.to_string())),
18136 None,
18137 cx,
18138 );
18139 });
18140
18141 if let Some(project) =
18142 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18143 {
18144 project.update(cx, |project, cx| {
18145 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18146 })
18147 }
18148 }
18149 }
18150 tasks
18151 });
18152 cx.spawn_in(window, async move |_, cx| {
18153 for (buffer, task) in save_tasks {
18154 let result = task.await;
18155 if result.is_err() {
18156 let Some(path) = buffer
18157 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18158 .ok()
18159 else {
18160 continue;
18161 };
18162 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18163 let Some(task) = cx
18164 .update_window_entity(&workspace, |workspace, window, cx| {
18165 workspace
18166 .open_path_preview(path, None, false, false, false, window, cx)
18167 })
18168 .ok()
18169 else {
18170 continue;
18171 };
18172 task.await.log_err();
18173 }
18174 }
18175 }
18176 })
18177 .detach();
18178 self.change_selections(None, window, cx, |selections| selections.refresh());
18179 }
18180
18181 pub fn to_pixel_point(
18182 &self,
18183 source: multi_buffer::Anchor,
18184 editor_snapshot: &EditorSnapshot,
18185 window: &mut Window,
18186 ) -> Option<gpui::Point<Pixels>> {
18187 let source_point = source.to_display_point(editor_snapshot);
18188 self.display_to_pixel_point(source_point, editor_snapshot, window)
18189 }
18190
18191 pub fn display_to_pixel_point(
18192 &self,
18193 source: DisplayPoint,
18194 editor_snapshot: &EditorSnapshot,
18195 window: &mut Window,
18196 ) -> Option<gpui::Point<Pixels>> {
18197 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18198 let text_layout_details = self.text_layout_details(window);
18199 let scroll_top = text_layout_details
18200 .scroll_anchor
18201 .scroll_position(editor_snapshot)
18202 .y;
18203
18204 if source.row().as_f32() < scroll_top.floor() {
18205 return None;
18206 }
18207 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18208 let source_y = line_height * (source.row().as_f32() - scroll_top);
18209 Some(gpui::Point::new(source_x, source_y))
18210 }
18211
18212 pub fn has_visible_completions_menu(&self) -> bool {
18213 !self.edit_prediction_preview_is_active()
18214 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18215 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18216 })
18217 }
18218
18219 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18220 self.addons
18221 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18222 }
18223
18224 pub fn unregister_addon<T: Addon>(&mut self) {
18225 self.addons.remove(&std::any::TypeId::of::<T>());
18226 }
18227
18228 pub fn addon<T: Addon>(&self) -> Option<&T> {
18229 let type_id = std::any::TypeId::of::<T>();
18230 self.addons
18231 .get(&type_id)
18232 .and_then(|item| item.to_any().downcast_ref::<T>())
18233 }
18234
18235 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18236 let text_layout_details = self.text_layout_details(window);
18237 let style = &text_layout_details.editor_style;
18238 let font_id = window.text_system().resolve_font(&style.text.font());
18239 let font_size = style.text.font_size.to_pixels(window.rem_size());
18240 let line_height = style.text.line_height_in_pixels(window.rem_size());
18241 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18242
18243 gpui::Size::new(em_width, line_height)
18244 }
18245
18246 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18247 self.load_diff_task.clone()
18248 }
18249
18250 fn read_metadata_from_db(
18251 &mut self,
18252 item_id: u64,
18253 workspace_id: WorkspaceId,
18254 window: &mut Window,
18255 cx: &mut Context<Editor>,
18256 ) {
18257 if self.is_singleton(cx)
18258 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18259 {
18260 let buffer_snapshot = OnceCell::new();
18261
18262 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18263 if !folds.is_empty() {
18264 let snapshot =
18265 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18266 self.fold_ranges(
18267 folds
18268 .into_iter()
18269 .map(|(start, end)| {
18270 snapshot.clip_offset(start, Bias::Left)
18271 ..snapshot.clip_offset(end, Bias::Right)
18272 })
18273 .collect(),
18274 false,
18275 window,
18276 cx,
18277 );
18278 }
18279 }
18280
18281 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18282 if !selections.is_empty() {
18283 let snapshot =
18284 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18285 self.change_selections(None, window, cx, |s| {
18286 s.select_ranges(selections.into_iter().map(|(start, end)| {
18287 snapshot.clip_offset(start, Bias::Left)
18288 ..snapshot.clip_offset(end, Bias::Right)
18289 }));
18290 });
18291 }
18292 };
18293 }
18294
18295 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18296 }
18297}
18298
18299fn vim_enabled(cx: &App) -> bool {
18300 cx.global::<SettingsStore>()
18301 .raw_user_settings()
18302 .get("vim_mode")
18303 == Some(&serde_json::Value::Bool(true))
18304}
18305
18306// Consider user intent and default settings
18307fn choose_completion_range(
18308 completion: &Completion,
18309 intent: CompletionIntent,
18310 buffer: &Entity<Buffer>,
18311 cx: &mut Context<Editor>,
18312) -> Range<usize> {
18313 fn should_replace(
18314 completion: &Completion,
18315 insert_range: &Range<text::Anchor>,
18316 intent: CompletionIntent,
18317 completion_mode_setting: LspInsertMode,
18318 buffer: &Buffer,
18319 ) -> bool {
18320 // specific actions take precedence over settings
18321 match intent {
18322 CompletionIntent::CompleteWithInsert => return false,
18323 CompletionIntent::CompleteWithReplace => return true,
18324 CompletionIntent::Complete | CompletionIntent::Compose => {}
18325 }
18326
18327 match completion_mode_setting {
18328 LspInsertMode::Insert => false,
18329 LspInsertMode::Replace => true,
18330 LspInsertMode::ReplaceSubsequence => {
18331 let mut text_to_replace = buffer.chars_for_range(
18332 buffer.anchor_before(completion.replace_range.start)
18333 ..buffer.anchor_after(completion.replace_range.end),
18334 );
18335 let mut completion_text = completion.new_text.chars();
18336
18337 // is `text_to_replace` a subsequence of `completion_text`
18338 text_to_replace
18339 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18340 }
18341 LspInsertMode::ReplaceSuffix => {
18342 let range_after_cursor = insert_range.end..completion.replace_range.end;
18343
18344 let text_after_cursor = buffer
18345 .text_for_range(
18346 buffer.anchor_before(range_after_cursor.start)
18347 ..buffer.anchor_after(range_after_cursor.end),
18348 )
18349 .collect::<String>();
18350 completion.new_text.ends_with(&text_after_cursor)
18351 }
18352 }
18353 }
18354
18355 let buffer = buffer.read(cx);
18356
18357 if let CompletionSource::Lsp {
18358 insert_range: Some(insert_range),
18359 ..
18360 } = &completion.source
18361 {
18362 let completion_mode_setting =
18363 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18364 .completions
18365 .lsp_insert_mode;
18366
18367 if !should_replace(
18368 completion,
18369 &insert_range,
18370 intent,
18371 completion_mode_setting,
18372 buffer,
18373 ) {
18374 return insert_range.to_offset(buffer);
18375 }
18376 }
18377
18378 completion.replace_range.to_offset(buffer)
18379}
18380
18381fn insert_extra_newline_brackets(
18382 buffer: &MultiBufferSnapshot,
18383 range: Range<usize>,
18384 language: &language::LanguageScope,
18385) -> bool {
18386 let leading_whitespace_len = buffer
18387 .reversed_chars_at(range.start)
18388 .take_while(|c| c.is_whitespace() && *c != '\n')
18389 .map(|c| c.len_utf8())
18390 .sum::<usize>();
18391 let trailing_whitespace_len = buffer
18392 .chars_at(range.end)
18393 .take_while(|c| c.is_whitespace() && *c != '\n')
18394 .map(|c| c.len_utf8())
18395 .sum::<usize>();
18396 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18397
18398 language.brackets().any(|(pair, enabled)| {
18399 let pair_start = pair.start.trim_end();
18400 let pair_end = pair.end.trim_start();
18401
18402 enabled
18403 && pair.newline
18404 && buffer.contains_str_at(range.end, pair_end)
18405 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18406 })
18407}
18408
18409fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18410 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18411 [(buffer, range, _)] => (*buffer, range.clone()),
18412 _ => return false,
18413 };
18414 let pair = {
18415 let mut result: Option<BracketMatch> = None;
18416
18417 for pair in buffer
18418 .all_bracket_ranges(range.clone())
18419 .filter(move |pair| {
18420 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18421 })
18422 {
18423 let len = pair.close_range.end - pair.open_range.start;
18424
18425 if let Some(existing) = &result {
18426 let existing_len = existing.close_range.end - existing.open_range.start;
18427 if len > existing_len {
18428 continue;
18429 }
18430 }
18431
18432 result = Some(pair);
18433 }
18434
18435 result
18436 };
18437 let Some(pair) = pair else {
18438 return false;
18439 };
18440 pair.newline_only
18441 && buffer
18442 .chars_for_range(pair.open_range.end..range.start)
18443 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18444 .all(|c| c.is_whitespace() && c != '\n')
18445}
18446
18447fn get_uncommitted_diff_for_buffer(
18448 project: &Entity<Project>,
18449 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18450 buffer: Entity<MultiBuffer>,
18451 cx: &mut App,
18452) -> Task<()> {
18453 let mut tasks = Vec::new();
18454 project.update(cx, |project, cx| {
18455 for buffer in buffers {
18456 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18457 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18458 }
18459 }
18460 });
18461 cx.spawn(async move |cx| {
18462 let diffs = future::join_all(tasks).await;
18463 buffer
18464 .update(cx, |buffer, cx| {
18465 for diff in diffs.into_iter().flatten() {
18466 buffer.add_diff(diff, cx);
18467 }
18468 })
18469 .ok();
18470 })
18471}
18472
18473fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18474 let tab_size = tab_size.get() as usize;
18475 let mut width = offset;
18476
18477 for ch in text.chars() {
18478 width += if ch == '\t' {
18479 tab_size - (width % tab_size)
18480 } else {
18481 1
18482 };
18483 }
18484
18485 width - offset
18486}
18487
18488#[cfg(test)]
18489mod tests {
18490 use super::*;
18491
18492 #[test]
18493 fn test_string_size_with_expanded_tabs() {
18494 let nz = |val| NonZeroU32::new(val).unwrap();
18495 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18496 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18497 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18498 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18499 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18500 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18501 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18502 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18503 }
18504}
18505
18506/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18507struct WordBreakingTokenizer<'a> {
18508 input: &'a str,
18509}
18510
18511impl<'a> WordBreakingTokenizer<'a> {
18512 fn new(input: &'a str) -> Self {
18513 Self { input }
18514 }
18515}
18516
18517fn is_char_ideographic(ch: char) -> bool {
18518 use unicode_script::Script::*;
18519 use unicode_script::UnicodeScript;
18520 matches!(ch.script(), Han | Tangut | Yi)
18521}
18522
18523fn is_grapheme_ideographic(text: &str) -> bool {
18524 text.chars().any(is_char_ideographic)
18525}
18526
18527fn is_grapheme_whitespace(text: &str) -> bool {
18528 text.chars().any(|x| x.is_whitespace())
18529}
18530
18531fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18532 text.chars().next().map_or(false, |ch| {
18533 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18534 })
18535}
18536
18537#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18538enum WordBreakToken<'a> {
18539 Word { token: &'a str, grapheme_len: usize },
18540 InlineWhitespace { token: &'a str, grapheme_len: usize },
18541 Newline,
18542}
18543
18544impl<'a> Iterator for WordBreakingTokenizer<'a> {
18545 /// Yields a span, the count of graphemes in the token, and whether it was
18546 /// whitespace. Note that it also breaks at word boundaries.
18547 type Item = WordBreakToken<'a>;
18548
18549 fn next(&mut self) -> Option<Self::Item> {
18550 use unicode_segmentation::UnicodeSegmentation;
18551 if self.input.is_empty() {
18552 return None;
18553 }
18554
18555 let mut iter = self.input.graphemes(true).peekable();
18556 let mut offset = 0;
18557 let mut grapheme_len = 0;
18558 if let Some(first_grapheme) = iter.next() {
18559 let is_newline = first_grapheme == "\n";
18560 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18561 offset += first_grapheme.len();
18562 grapheme_len += 1;
18563 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18564 if let Some(grapheme) = iter.peek().copied() {
18565 if should_stay_with_preceding_ideograph(grapheme) {
18566 offset += grapheme.len();
18567 grapheme_len += 1;
18568 }
18569 }
18570 } else {
18571 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18572 let mut next_word_bound = words.peek().copied();
18573 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18574 next_word_bound = words.next();
18575 }
18576 while let Some(grapheme) = iter.peek().copied() {
18577 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18578 break;
18579 };
18580 if is_grapheme_whitespace(grapheme) != is_whitespace
18581 || (grapheme == "\n") != is_newline
18582 {
18583 break;
18584 };
18585 offset += grapheme.len();
18586 grapheme_len += 1;
18587 iter.next();
18588 }
18589 }
18590 let token = &self.input[..offset];
18591 self.input = &self.input[offset..];
18592 if token == "\n" {
18593 Some(WordBreakToken::Newline)
18594 } else if is_whitespace {
18595 Some(WordBreakToken::InlineWhitespace {
18596 token,
18597 grapheme_len,
18598 })
18599 } else {
18600 Some(WordBreakToken::Word {
18601 token,
18602 grapheme_len,
18603 })
18604 }
18605 } else {
18606 None
18607 }
18608 }
18609}
18610
18611#[test]
18612fn test_word_breaking_tokenizer() {
18613 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18614 ("", &[]),
18615 (" ", &[whitespace(" ", 2)]),
18616 ("Ʒ", &[word("Ʒ", 1)]),
18617 ("Ǽ", &[word("Ǽ", 1)]),
18618 ("⋑", &[word("⋑", 1)]),
18619 ("⋑⋑", &[word("⋑⋑", 2)]),
18620 (
18621 "原理,进而",
18622 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18623 ),
18624 (
18625 "hello world",
18626 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18627 ),
18628 (
18629 "hello, world",
18630 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18631 ),
18632 (
18633 " hello world",
18634 &[
18635 whitespace(" ", 2),
18636 word("hello", 5),
18637 whitespace(" ", 1),
18638 word("world", 5),
18639 ],
18640 ),
18641 (
18642 "这是什么 \n 钢笔",
18643 &[
18644 word("这", 1),
18645 word("是", 1),
18646 word("什", 1),
18647 word("么", 1),
18648 whitespace(" ", 1),
18649 newline(),
18650 whitespace(" ", 1),
18651 word("钢", 1),
18652 word("笔", 1),
18653 ],
18654 ),
18655 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
18656 ];
18657
18658 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18659 WordBreakToken::Word {
18660 token,
18661 grapheme_len,
18662 }
18663 }
18664
18665 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18666 WordBreakToken::InlineWhitespace {
18667 token,
18668 grapheme_len,
18669 }
18670 }
18671
18672 fn newline() -> WordBreakToken<'static> {
18673 WordBreakToken::Newline
18674 }
18675
18676 for (input, result) in tests {
18677 assert_eq!(
18678 WordBreakingTokenizer::new(input)
18679 .collect::<Vec<_>>()
18680 .as_slice(),
18681 *result,
18682 );
18683 }
18684}
18685
18686fn wrap_with_prefix(
18687 line_prefix: String,
18688 unwrapped_text: String,
18689 wrap_column: usize,
18690 tab_size: NonZeroU32,
18691 preserve_existing_whitespace: bool,
18692) -> String {
18693 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18694 let mut wrapped_text = String::new();
18695 let mut current_line = line_prefix.clone();
18696
18697 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18698 let mut current_line_len = line_prefix_len;
18699 let mut in_whitespace = false;
18700 for token in tokenizer {
18701 let have_preceding_whitespace = in_whitespace;
18702 match token {
18703 WordBreakToken::Word {
18704 token,
18705 grapheme_len,
18706 } => {
18707 in_whitespace = false;
18708 if current_line_len + grapheme_len > wrap_column
18709 && current_line_len != line_prefix_len
18710 {
18711 wrapped_text.push_str(current_line.trim_end());
18712 wrapped_text.push('\n');
18713 current_line.truncate(line_prefix.len());
18714 current_line_len = line_prefix_len;
18715 }
18716 current_line.push_str(token);
18717 current_line_len += grapheme_len;
18718 }
18719 WordBreakToken::InlineWhitespace {
18720 mut token,
18721 mut grapheme_len,
18722 } => {
18723 in_whitespace = true;
18724 if have_preceding_whitespace && !preserve_existing_whitespace {
18725 continue;
18726 }
18727 if !preserve_existing_whitespace {
18728 token = " ";
18729 grapheme_len = 1;
18730 }
18731 if current_line_len + grapheme_len > wrap_column {
18732 wrapped_text.push_str(current_line.trim_end());
18733 wrapped_text.push('\n');
18734 current_line.truncate(line_prefix.len());
18735 current_line_len = line_prefix_len;
18736 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18737 current_line.push_str(token);
18738 current_line_len += grapheme_len;
18739 }
18740 }
18741 WordBreakToken::Newline => {
18742 in_whitespace = true;
18743 if preserve_existing_whitespace {
18744 wrapped_text.push_str(current_line.trim_end());
18745 wrapped_text.push('\n');
18746 current_line.truncate(line_prefix.len());
18747 current_line_len = line_prefix_len;
18748 } else if have_preceding_whitespace {
18749 continue;
18750 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18751 {
18752 wrapped_text.push_str(current_line.trim_end());
18753 wrapped_text.push('\n');
18754 current_line.truncate(line_prefix.len());
18755 current_line_len = line_prefix_len;
18756 } else if current_line_len != line_prefix_len {
18757 current_line.push(' ');
18758 current_line_len += 1;
18759 }
18760 }
18761 }
18762 }
18763
18764 if !current_line.is_empty() {
18765 wrapped_text.push_str(¤t_line);
18766 }
18767 wrapped_text
18768}
18769
18770#[test]
18771fn test_wrap_with_prefix() {
18772 assert_eq!(
18773 wrap_with_prefix(
18774 "# ".to_string(),
18775 "abcdefg".to_string(),
18776 4,
18777 NonZeroU32::new(4).unwrap(),
18778 false,
18779 ),
18780 "# abcdefg"
18781 );
18782 assert_eq!(
18783 wrap_with_prefix(
18784 "".to_string(),
18785 "\thello world".to_string(),
18786 8,
18787 NonZeroU32::new(4).unwrap(),
18788 false,
18789 ),
18790 "hello\nworld"
18791 );
18792 assert_eq!(
18793 wrap_with_prefix(
18794 "// ".to_string(),
18795 "xx \nyy zz aa bb cc".to_string(),
18796 12,
18797 NonZeroU32::new(4).unwrap(),
18798 false,
18799 ),
18800 "// xx yy zz\n// aa bb cc"
18801 );
18802 assert_eq!(
18803 wrap_with_prefix(
18804 String::new(),
18805 "这是什么 \n 钢笔".to_string(),
18806 3,
18807 NonZeroU32::new(4).unwrap(),
18808 false,
18809 ),
18810 "这是什\n么 钢\n笔"
18811 );
18812}
18813
18814pub trait CollaborationHub {
18815 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18816 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18817 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18818}
18819
18820impl CollaborationHub for Entity<Project> {
18821 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18822 self.read(cx).collaborators()
18823 }
18824
18825 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18826 self.read(cx).user_store().read(cx).participant_indices()
18827 }
18828
18829 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18830 let this = self.read(cx);
18831 let user_ids = this.collaborators().values().map(|c| c.user_id);
18832 this.user_store().read_with(cx, |user_store, cx| {
18833 user_store.participant_names(user_ids, cx)
18834 })
18835 }
18836}
18837
18838pub trait SemanticsProvider {
18839 fn hover(
18840 &self,
18841 buffer: &Entity<Buffer>,
18842 position: text::Anchor,
18843 cx: &mut App,
18844 ) -> Option<Task<Vec<project::Hover>>>;
18845
18846 fn inlay_hints(
18847 &self,
18848 buffer_handle: Entity<Buffer>,
18849 range: Range<text::Anchor>,
18850 cx: &mut App,
18851 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18852
18853 fn resolve_inlay_hint(
18854 &self,
18855 hint: InlayHint,
18856 buffer_handle: Entity<Buffer>,
18857 server_id: LanguageServerId,
18858 cx: &mut App,
18859 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18860
18861 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18862
18863 fn document_highlights(
18864 &self,
18865 buffer: &Entity<Buffer>,
18866 position: text::Anchor,
18867 cx: &mut App,
18868 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18869
18870 fn definitions(
18871 &self,
18872 buffer: &Entity<Buffer>,
18873 position: text::Anchor,
18874 kind: GotoDefinitionKind,
18875 cx: &mut App,
18876 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18877
18878 fn range_for_rename(
18879 &self,
18880 buffer: &Entity<Buffer>,
18881 position: text::Anchor,
18882 cx: &mut App,
18883 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18884
18885 fn perform_rename(
18886 &self,
18887 buffer: &Entity<Buffer>,
18888 position: text::Anchor,
18889 new_name: String,
18890 cx: &mut App,
18891 ) -> Option<Task<Result<ProjectTransaction>>>;
18892}
18893
18894pub trait CompletionProvider {
18895 fn completions(
18896 &self,
18897 excerpt_id: ExcerptId,
18898 buffer: &Entity<Buffer>,
18899 buffer_position: text::Anchor,
18900 trigger: CompletionContext,
18901 window: &mut Window,
18902 cx: &mut Context<Editor>,
18903 ) -> Task<Result<Option<Vec<Completion>>>>;
18904
18905 fn resolve_completions(
18906 &self,
18907 buffer: Entity<Buffer>,
18908 completion_indices: Vec<usize>,
18909 completions: Rc<RefCell<Box<[Completion]>>>,
18910 cx: &mut Context<Editor>,
18911 ) -> Task<Result<bool>>;
18912
18913 fn apply_additional_edits_for_completion(
18914 &self,
18915 _buffer: Entity<Buffer>,
18916 _completions: Rc<RefCell<Box<[Completion]>>>,
18917 _completion_index: usize,
18918 _push_to_history: bool,
18919 _cx: &mut Context<Editor>,
18920 ) -> Task<Result<Option<language::Transaction>>> {
18921 Task::ready(Ok(None))
18922 }
18923
18924 fn is_completion_trigger(
18925 &self,
18926 buffer: &Entity<Buffer>,
18927 position: language::Anchor,
18928 text: &str,
18929 trigger_in_words: bool,
18930 cx: &mut Context<Editor>,
18931 ) -> bool;
18932
18933 fn sort_completions(&self) -> bool {
18934 true
18935 }
18936
18937 fn filter_completions(&self) -> bool {
18938 true
18939 }
18940}
18941
18942pub trait CodeActionProvider {
18943 fn id(&self) -> Arc<str>;
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
18953 fn apply_code_action(
18954 &self,
18955 buffer_handle: Entity<Buffer>,
18956 action: CodeAction,
18957 excerpt_id: ExcerptId,
18958 push_to_history: bool,
18959 window: &mut Window,
18960 cx: &mut App,
18961 ) -> Task<Result<ProjectTransaction>>;
18962}
18963
18964impl CodeActionProvider for Entity<Project> {
18965 fn id(&self) -> Arc<str> {
18966 "project".into()
18967 }
18968
18969 fn code_actions(
18970 &self,
18971 buffer: &Entity<Buffer>,
18972 range: Range<text::Anchor>,
18973 _window: &mut Window,
18974 cx: &mut App,
18975 ) -> Task<Result<Vec<CodeAction>>> {
18976 self.update(cx, |project, cx| {
18977 let code_lens = project.code_lens(buffer, range.clone(), cx);
18978 let code_actions = project.code_actions(buffer, range, None, cx);
18979 cx.background_spawn(async move {
18980 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18981 Ok(code_lens
18982 .context("code lens fetch")?
18983 .into_iter()
18984 .chain(code_actions.context("code action fetch")?)
18985 .collect())
18986 })
18987 })
18988 }
18989
18990 fn apply_code_action(
18991 &self,
18992 buffer_handle: Entity<Buffer>,
18993 action: CodeAction,
18994 _excerpt_id: ExcerptId,
18995 push_to_history: bool,
18996 _window: &mut Window,
18997 cx: &mut App,
18998 ) -> Task<Result<ProjectTransaction>> {
18999 self.update(cx, |project, cx| {
19000 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19001 })
19002 }
19003}
19004
19005fn snippet_completions(
19006 project: &Project,
19007 buffer: &Entity<Buffer>,
19008 buffer_position: text::Anchor,
19009 cx: &mut App,
19010) -> Task<Result<Vec<Completion>>> {
19011 let languages = buffer.read(cx).languages_at(buffer_position);
19012 let snippet_store = project.snippets().read(cx);
19013
19014 let scopes: Vec<_> = languages
19015 .iter()
19016 .filter_map(|language| {
19017 let language_name = language.lsp_id();
19018 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19019
19020 if snippets.is_empty() {
19021 None
19022 } else {
19023 Some((language.default_scope(), snippets))
19024 }
19025 })
19026 .collect();
19027
19028 if scopes.is_empty() {
19029 return Task::ready(Ok(vec![]));
19030 }
19031
19032 let snapshot = buffer.read(cx).text_snapshot();
19033 let chars: String = snapshot
19034 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19035 .collect();
19036 let executor = cx.background_executor().clone();
19037
19038 cx.background_spawn(async move {
19039 let mut all_results: Vec<Completion> = Vec::new();
19040 for (scope, snippets) in scopes.into_iter() {
19041 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19042 let mut last_word = chars
19043 .chars()
19044 .take_while(|c| classifier.is_word(*c))
19045 .collect::<String>();
19046 last_word = last_word.chars().rev().collect();
19047
19048 if last_word.is_empty() {
19049 return Ok(vec![]);
19050 }
19051
19052 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19053 let to_lsp = |point: &text::Anchor| {
19054 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19055 point_to_lsp(end)
19056 };
19057 let lsp_end = to_lsp(&buffer_position);
19058
19059 let candidates = snippets
19060 .iter()
19061 .enumerate()
19062 .flat_map(|(ix, snippet)| {
19063 snippet
19064 .prefix
19065 .iter()
19066 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19067 })
19068 .collect::<Vec<StringMatchCandidate>>();
19069
19070 let mut matches = fuzzy::match_strings(
19071 &candidates,
19072 &last_word,
19073 last_word.chars().any(|c| c.is_uppercase()),
19074 100,
19075 &Default::default(),
19076 executor.clone(),
19077 )
19078 .await;
19079
19080 // Remove all candidates where the query's start does not match the start of any word in the candidate
19081 if let Some(query_start) = last_word.chars().next() {
19082 matches.retain(|string_match| {
19083 split_words(&string_match.string).any(|word| {
19084 // Check that the first codepoint of the word as lowercase matches the first
19085 // codepoint of the query as lowercase
19086 word.chars()
19087 .flat_map(|codepoint| codepoint.to_lowercase())
19088 .zip(query_start.to_lowercase())
19089 .all(|(word_cp, query_cp)| word_cp == query_cp)
19090 })
19091 });
19092 }
19093
19094 let matched_strings = matches
19095 .into_iter()
19096 .map(|m| m.string)
19097 .collect::<HashSet<_>>();
19098
19099 let mut result: Vec<Completion> = snippets
19100 .iter()
19101 .filter_map(|snippet| {
19102 let matching_prefix = snippet
19103 .prefix
19104 .iter()
19105 .find(|prefix| matched_strings.contains(*prefix))?;
19106 let start = as_offset - last_word.len();
19107 let start = snapshot.anchor_before(start);
19108 let range = start..buffer_position;
19109 let lsp_start = to_lsp(&start);
19110 let lsp_range = lsp::Range {
19111 start: lsp_start,
19112 end: lsp_end,
19113 };
19114 Some(Completion {
19115 replace_range: range,
19116 new_text: snippet.body.clone(),
19117 source: CompletionSource::Lsp {
19118 insert_range: None,
19119 server_id: LanguageServerId(usize::MAX),
19120 resolved: true,
19121 lsp_completion: Box::new(lsp::CompletionItem {
19122 label: snippet.prefix.first().unwrap().clone(),
19123 kind: Some(CompletionItemKind::SNIPPET),
19124 label_details: snippet.description.as_ref().map(|description| {
19125 lsp::CompletionItemLabelDetails {
19126 detail: Some(description.clone()),
19127 description: None,
19128 }
19129 }),
19130 insert_text_format: Some(InsertTextFormat::SNIPPET),
19131 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19132 lsp::InsertReplaceEdit {
19133 new_text: snippet.body.clone(),
19134 insert: lsp_range,
19135 replace: lsp_range,
19136 },
19137 )),
19138 filter_text: Some(snippet.body.clone()),
19139 sort_text: Some(char::MAX.to_string()),
19140 ..lsp::CompletionItem::default()
19141 }),
19142 lsp_defaults: None,
19143 },
19144 label: CodeLabel {
19145 text: matching_prefix.clone(),
19146 runs: Vec::new(),
19147 filter_range: 0..matching_prefix.len(),
19148 },
19149 icon_path: None,
19150 documentation: snippet.description.clone().map(|description| {
19151 CompletionDocumentation::SingleLine(description.into())
19152 }),
19153 insert_text_mode: None,
19154 confirm: None,
19155 })
19156 })
19157 .collect();
19158
19159 all_results.append(&mut result);
19160 }
19161
19162 Ok(all_results)
19163 })
19164}
19165
19166impl CompletionProvider for Entity<Project> {
19167 fn completions(
19168 &self,
19169 _excerpt_id: ExcerptId,
19170 buffer: &Entity<Buffer>,
19171 buffer_position: text::Anchor,
19172 options: CompletionContext,
19173 _window: &mut Window,
19174 cx: &mut Context<Editor>,
19175 ) -> Task<Result<Option<Vec<Completion>>>> {
19176 self.update(cx, |project, cx| {
19177 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19178 let project_completions = project.completions(buffer, buffer_position, options, cx);
19179 cx.background_spawn(async move {
19180 let snippets_completions = snippets.await?;
19181 match project_completions.await? {
19182 Some(mut completions) => {
19183 completions.extend(snippets_completions);
19184 Ok(Some(completions))
19185 }
19186 None => {
19187 if snippets_completions.is_empty() {
19188 Ok(None)
19189 } else {
19190 Ok(Some(snippets_completions))
19191 }
19192 }
19193 }
19194 })
19195 })
19196 }
19197
19198 fn resolve_completions(
19199 &self,
19200 buffer: Entity<Buffer>,
19201 completion_indices: Vec<usize>,
19202 completions: Rc<RefCell<Box<[Completion]>>>,
19203 cx: &mut Context<Editor>,
19204 ) -> Task<Result<bool>> {
19205 self.update(cx, |project, cx| {
19206 project.lsp_store().update(cx, |lsp_store, cx| {
19207 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19208 })
19209 })
19210 }
19211
19212 fn apply_additional_edits_for_completion(
19213 &self,
19214 buffer: Entity<Buffer>,
19215 completions: Rc<RefCell<Box<[Completion]>>>,
19216 completion_index: usize,
19217 push_to_history: bool,
19218 cx: &mut Context<Editor>,
19219 ) -> Task<Result<Option<language::Transaction>>> {
19220 self.update(cx, |project, cx| {
19221 project.lsp_store().update(cx, |lsp_store, cx| {
19222 lsp_store.apply_additional_edits_for_completion(
19223 buffer,
19224 completions,
19225 completion_index,
19226 push_to_history,
19227 cx,
19228 )
19229 })
19230 })
19231 }
19232
19233 fn is_completion_trigger(
19234 &self,
19235 buffer: &Entity<Buffer>,
19236 position: language::Anchor,
19237 text: &str,
19238 trigger_in_words: bool,
19239 cx: &mut Context<Editor>,
19240 ) -> bool {
19241 let mut chars = text.chars();
19242 let char = if let Some(char) = chars.next() {
19243 char
19244 } else {
19245 return false;
19246 };
19247 if chars.next().is_some() {
19248 return false;
19249 }
19250
19251 let buffer = buffer.read(cx);
19252 let snapshot = buffer.snapshot();
19253 if !snapshot.settings_at(position, cx).show_completions_on_input {
19254 return false;
19255 }
19256 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19257 if trigger_in_words && classifier.is_word(char) {
19258 return true;
19259 }
19260
19261 buffer.completion_triggers().contains(text)
19262 }
19263}
19264
19265impl SemanticsProvider for Entity<Project> {
19266 fn hover(
19267 &self,
19268 buffer: &Entity<Buffer>,
19269 position: text::Anchor,
19270 cx: &mut App,
19271 ) -> Option<Task<Vec<project::Hover>>> {
19272 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19273 }
19274
19275 fn document_highlights(
19276 &self,
19277 buffer: &Entity<Buffer>,
19278 position: text::Anchor,
19279 cx: &mut App,
19280 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19281 Some(self.update(cx, |project, cx| {
19282 project.document_highlights(buffer, position, cx)
19283 }))
19284 }
19285
19286 fn definitions(
19287 &self,
19288 buffer: &Entity<Buffer>,
19289 position: text::Anchor,
19290 kind: GotoDefinitionKind,
19291 cx: &mut App,
19292 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19293 Some(self.update(cx, |project, cx| match kind {
19294 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19295 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19296 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19297 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19298 }))
19299 }
19300
19301 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19302 // TODO: make this work for remote projects
19303 self.update(cx, |this, cx| {
19304 buffer.update(cx, |buffer, cx| {
19305 this.any_language_server_supports_inlay_hints(buffer, cx)
19306 })
19307 })
19308 }
19309
19310 fn inlay_hints(
19311 &self,
19312 buffer_handle: Entity<Buffer>,
19313 range: Range<text::Anchor>,
19314 cx: &mut App,
19315 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19316 Some(self.update(cx, |project, cx| {
19317 project.inlay_hints(buffer_handle, range, cx)
19318 }))
19319 }
19320
19321 fn resolve_inlay_hint(
19322 &self,
19323 hint: InlayHint,
19324 buffer_handle: Entity<Buffer>,
19325 server_id: LanguageServerId,
19326 cx: &mut App,
19327 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19328 Some(self.update(cx, |project, cx| {
19329 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19330 }))
19331 }
19332
19333 fn range_for_rename(
19334 &self,
19335 buffer: &Entity<Buffer>,
19336 position: text::Anchor,
19337 cx: &mut App,
19338 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19339 Some(self.update(cx, |project, cx| {
19340 let buffer = buffer.clone();
19341 let task = project.prepare_rename(buffer.clone(), position, cx);
19342 cx.spawn(async move |_, cx| {
19343 Ok(match task.await? {
19344 PrepareRenameResponse::Success(range) => Some(range),
19345 PrepareRenameResponse::InvalidPosition => None,
19346 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19347 // Fallback on using TreeSitter info to determine identifier range
19348 buffer.update(cx, |buffer, _| {
19349 let snapshot = buffer.snapshot();
19350 let (range, kind) = snapshot.surrounding_word(position);
19351 if kind != Some(CharKind::Word) {
19352 return None;
19353 }
19354 Some(
19355 snapshot.anchor_before(range.start)
19356 ..snapshot.anchor_after(range.end),
19357 )
19358 })?
19359 }
19360 })
19361 })
19362 }))
19363 }
19364
19365 fn perform_rename(
19366 &self,
19367 buffer: &Entity<Buffer>,
19368 position: text::Anchor,
19369 new_name: String,
19370 cx: &mut App,
19371 ) -> Option<Task<Result<ProjectTransaction>>> {
19372 Some(self.update(cx, |project, cx| {
19373 project.perform_rename(buffer.clone(), position, new_name, cx)
19374 }))
19375 }
19376}
19377
19378fn inlay_hint_settings(
19379 location: Anchor,
19380 snapshot: &MultiBufferSnapshot,
19381 cx: &mut Context<Editor>,
19382) -> InlayHintSettings {
19383 let file = snapshot.file_at(location);
19384 let language = snapshot.language_at(location).map(|l| l.name());
19385 language_settings(language, file, cx).inlay_hints
19386}
19387
19388fn consume_contiguous_rows(
19389 contiguous_row_selections: &mut Vec<Selection<Point>>,
19390 selection: &Selection<Point>,
19391 display_map: &DisplaySnapshot,
19392 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19393) -> (MultiBufferRow, MultiBufferRow) {
19394 contiguous_row_selections.push(selection.clone());
19395 let start_row = MultiBufferRow(selection.start.row);
19396 let mut end_row = ending_row(selection, display_map);
19397
19398 while let Some(next_selection) = selections.peek() {
19399 if next_selection.start.row <= end_row.0 {
19400 end_row = ending_row(next_selection, display_map);
19401 contiguous_row_selections.push(selections.next().unwrap().clone());
19402 } else {
19403 break;
19404 }
19405 }
19406 (start_row, end_row)
19407}
19408
19409fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19410 if next_selection.end.column > 0 || next_selection.is_empty() {
19411 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19412 } else {
19413 MultiBufferRow(next_selection.end.row)
19414 }
19415}
19416
19417impl EditorSnapshot {
19418 pub fn remote_selections_in_range<'a>(
19419 &'a self,
19420 range: &'a Range<Anchor>,
19421 collaboration_hub: &dyn CollaborationHub,
19422 cx: &'a App,
19423 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19424 let participant_names = collaboration_hub.user_names(cx);
19425 let participant_indices = collaboration_hub.user_participant_indices(cx);
19426 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19427 let collaborators_by_replica_id = collaborators_by_peer_id
19428 .iter()
19429 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19430 .collect::<HashMap<_, _>>();
19431 self.buffer_snapshot
19432 .selections_in_range(range, false)
19433 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19434 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19435 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19436 let user_name = participant_names.get(&collaborator.user_id).cloned();
19437 Some(RemoteSelection {
19438 replica_id,
19439 selection,
19440 cursor_shape,
19441 line_mode,
19442 participant_index,
19443 peer_id: collaborator.peer_id,
19444 user_name,
19445 })
19446 })
19447 }
19448
19449 pub fn hunks_for_ranges(
19450 &self,
19451 ranges: impl IntoIterator<Item = Range<Point>>,
19452 ) -> Vec<MultiBufferDiffHunk> {
19453 let mut hunks = Vec::new();
19454 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19455 HashMap::default();
19456 for query_range in ranges {
19457 let query_rows =
19458 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19459 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19460 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19461 ) {
19462 // Include deleted hunks that are adjacent to the query range, because
19463 // otherwise they would be missed.
19464 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19465 if hunk.status().is_deleted() {
19466 intersects_range |= hunk.row_range.start == query_rows.end;
19467 intersects_range |= hunk.row_range.end == query_rows.start;
19468 }
19469 if intersects_range {
19470 if !processed_buffer_rows
19471 .entry(hunk.buffer_id)
19472 .or_default()
19473 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19474 {
19475 continue;
19476 }
19477 hunks.push(hunk);
19478 }
19479 }
19480 }
19481
19482 hunks
19483 }
19484
19485 fn display_diff_hunks_for_rows<'a>(
19486 &'a self,
19487 display_rows: Range<DisplayRow>,
19488 folded_buffers: &'a HashSet<BufferId>,
19489 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19490 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19491 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19492
19493 self.buffer_snapshot
19494 .diff_hunks_in_range(buffer_start..buffer_end)
19495 .filter_map(|hunk| {
19496 if folded_buffers.contains(&hunk.buffer_id) {
19497 return None;
19498 }
19499
19500 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19501 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19502
19503 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19504 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19505
19506 let display_hunk = if hunk_display_start.column() != 0 {
19507 DisplayDiffHunk::Folded {
19508 display_row: hunk_display_start.row(),
19509 }
19510 } else {
19511 let mut end_row = hunk_display_end.row();
19512 if hunk_display_end.column() > 0 {
19513 end_row.0 += 1;
19514 }
19515 let is_created_file = hunk.is_created_file();
19516 DisplayDiffHunk::Unfolded {
19517 status: hunk.status(),
19518 diff_base_byte_range: hunk.diff_base_byte_range,
19519 display_row_range: hunk_display_start.row()..end_row,
19520 multi_buffer_range: Anchor::range_in_buffer(
19521 hunk.excerpt_id,
19522 hunk.buffer_id,
19523 hunk.buffer_range,
19524 ),
19525 is_created_file,
19526 }
19527 };
19528
19529 Some(display_hunk)
19530 })
19531 }
19532
19533 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19534 self.display_snapshot.buffer_snapshot.language_at(position)
19535 }
19536
19537 pub fn is_focused(&self) -> bool {
19538 self.is_focused
19539 }
19540
19541 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19542 self.placeholder_text.as_ref()
19543 }
19544
19545 pub fn scroll_position(&self) -> gpui::Point<f32> {
19546 self.scroll_anchor.scroll_position(&self.display_snapshot)
19547 }
19548
19549 fn gutter_dimensions(
19550 &self,
19551 font_id: FontId,
19552 font_size: Pixels,
19553 max_line_number_width: Pixels,
19554 cx: &App,
19555 ) -> Option<GutterDimensions> {
19556 if !self.show_gutter {
19557 return None;
19558 }
19559
19560 let descent = cx.text_system().descent(font_id, font_size);
19561 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19562 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19563
19564 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19565 matches!(
19566 ProjectSettings::get_global(cx).git.git_gutter,
19567 Some(GitGutterSetting::TrackedFiles)
19568 )
19569 });
19570 let gutter_settings = EditorSettings::get_global(cx).gutter;
19571 let show_line_numbers = self
19572 .show_line_numbers
19573 .unwrap_or(gutter_settings.line_numbers);
19574 let line_gutter_width = if show_line_numbers {
19575 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19576 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19577 max_line_number_width.max(min_width_for_number_on_gutter)
19578 } else {
19579 0.0.into()
19580 };
19581
19582 let show_code_actions = self
19583 .show_code_actions
19584 .unwrap_or(gutter_settings.code_actions);
19585
19586 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19587 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19588
19589 let git_blame_entries_width =
19590 self.git_blame_gutter_max_author_length
19591 .map(|max_author_length| {
19592 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19593 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19594
19595 /// The number of characters to dedicate to gaps and margins.
19596 const SPACING_WIDTH: usize = 4;
19597
19598 let max_char_count = max_author_length.min(renderer.max_author_length())
19599 + ::git::SHORT_SHA_LENGTH
19600 + MAX_RELATIVE_TIMESTAMP.len()
19601 + SPACING_WIDTH;
19602
19603 em_advance * max_char_count
19604 });
19605
19606 let is_singleton = self.buffer_snapshot.is_singleton();
19607
19608 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19609 left_padding += if !is_singleton {
19610 em_width * 4.0
19611 } else if show_code_actions || show_runnables || show_breakpoints {
19612 em_width * 3.0
19613 } else if show_git_gutter && show_line_numbers {
19614 em_width * 2.0
19615 } else if show_git_gutter || show_line_numbers {
19616 em_width
19617 } else {
19618 px(0.)
19619 };
19620
19621 let shows_folds = is_singleton && gutter_settings.folds;
19622
19623 let right_padding = if shows_folds && show_line_numbers {
19624 em_width * 4.0
19625 } else if shows_folds || (!is_singleton && show_line_numbers) {
19626 em_width * 3.0
19627 } else if show_line_numbers {
19628 em_width
19629 } else {
19630 px(0.)
19631 };
19632
19633 Some(GutterDimensions {
19634 left_padding,
19635 right_padding,
19636 width: line_gutter_width + left_padding + right_padding,
19637 margin: -descent,
19638 git_blame_entries_width,
19639 })
19640 }
19641
19642 pub fn render_crease_toggle(
19643 &self,
19644 buffer_row: MultiBufferRow,
19645 row_contains_cursor: bool,
19646 editor: Entity<Editor>,
19647 window: &mut Window,
19648 cx: &mut App,
19649 ) -> Option<AnyElement> {
19650 let folded = self.is_line_folded(buffer_row);
19651 let mut is_foldable = false;
19652
19653 if let Some(crease) = self
19654 .crease_snapshot
19655 .query_row(buffer_row, &self.buffer_snapshot)
19656 {
19657 is_foldable = true;
19658 match crease {
19659 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
19660 if let Some(render_toggle) = render_toggle {
19661 let toggle_callback =
19662 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
19663 if folded {
19664 editor.update(cx, |editor, cx| {
19665 editor.fold_at(buffer_row, window, cx)
19666 });
19667 } else {
19668 editor.update(cx, |editor, cx| {
19669 editor.unfold_at(buffer_row, window, cx)
19670 });
19671 }
19672 });
19673 return Some((render_toggle)(
19674 buffer_row,
19675 folded,
19676 toggle_callback,
19677 window,
19678 cx,
19679 ));
19680 }
19681 }
19682 }
19683 }
19684
19685 is_foldable |= self.starts_indent(buffer_row);
19686
19687 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19688 Some(
19689 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19690 .toggle_state(folded)
19691 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19692 if folded {
19693 this.unfold_at(buffer_row, window, cx);
19694 } else {
19695 this.fold_at(buffer_row, window, cx);
19696 }
19697 }))
19698 .into_any_element(),
19699 )
19700 } else {
19701 None
19702 }
19703 }
19704
19705 pub fn render_crease_trailer(
19706 &self,
19707 buffer_row: MultiBufferRow,
19708 window: &mut Window,
19709 cx: &mut App,
19710 ) -> Option<AnyElement> {
19711 let folded = self.is_line_folded(buffer_row);
19712 if let Crease::Inline { render_trailer, .. } = self
19713 .crease_snapshot
19714 .query_row(buffer_row, &self.buffer_snapshot)?
19715 {
19716 let render_trailer = render_trailer.as_ref()?;
19717 Some(render_trailer(buffer_row, folded, window, cx))
19718 } else {
19719 None
19720 }
19721 }
19722}
19723
19724impl Deref for EditorSnapshot {
19725 type Target = DisplaySnapshot;
19726
19727 fn deref(&self) -> &Self::Target {
19728 &self.display_snapshot
19729 }
19730}
19731
19732#[derive(Clone, Debug, PartialEq, Eq)]
19733pub enum EditorEvent {
19734 InputIgnored {
19735 text: Arc<str>,
19736 },
19737 InputHandled {
19738 utf16_range_to_replace: Option<Range<isize>>,
19739 text: Arc<str>,
19740 },
19741 ExcerptsAdded {
19742 buffer: Entity<Buffer>,
19743 predecessor: ExcerptId,
19744 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19745 },
19746 ExcerptsRemoved {
19747 ids: Vec<ExcerptId>,
19748 },
19749 BufferFoldToggled {
19750 ids: Vec<ExcerptId>,
19751 folded: bool,
19752 },
19753 ExcerptsEdited {
19754 ids: Vec<ExcerptId>,
19755 },
19756 ExcerptsExpanded {
19757 ids: Vec<ExcerptId>,
19758 },
19759 BufferEdited,
19760 Edited {
19761 transaction_id: clock::Lamport,
19762 },
19763 Reparsed(BufferId),
19764 Focused,
19765 FocusedIn,
19766 Blurred,
19767 DirtyChanged,
19768 Saved,
19769 TitleChanged,
19770 DiffBaseChanged,
19771 SelectionsChanged {
19772 local: bool,
19773 },
19774 ScrollPositionChanged {
19775 local: bool,
19776 autoscroll: bool,
19777 },
19778 Closed,
19779 TransactionUndone {
19780 transaction_id: clock::Lamport,
19781 },
19782 TransactionBegun {
19783 transaction_id: clock::Lamport,
19784 },
19785 Reloaded,
19786 CursorShapeChanged,
19787 PushedToNavHistory {
19788 anchor: Anchor,
19789 is_deactivate: bool,
19790 },
19791}
19792
19793impl EventEmitter<EditorEvent> for Editor {}
19794
19795impl Focusable for Editor {
19796 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19797 self.focus_handle.clone()
19798 }
19799}
19800
19801impl Render for Editor {
19802 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19803 let settings = ThemeSettings::get_global(cx);
19804
19805 let mut text_style = match self.mode {
19806 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19807 color: cx.theme().colors().editor_foreground,
19808 font_family: settings.ui_font.family.clone(),
19809 font_features: settings.ui_font.features.clone(),
19810 font_fallbacks: settings.ui_font.fallbacks.clone(),
19811 font_size: rems(0.875).into(),
19812 font_weight: settings.ui_font.weight,
19813 line_height: relative(settings.buffer_line_height.value()),
19814 ..Default::default()
19815 },
19816 EditorMode::Full { .. } => TextStyle {
19817 color: cx.theme().colors().editor_foreground,
19818 font_family: settings.buffer_font.family.clone(),
19819 font_features: settings.buffer_font.features.clone(),
19820 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19821 font_size: settings.buffer_font_size(cx).into(),
19822 font_weight: settings.buffer_font.weight,
19823 line_height: relative(settings.buffer_line_height.value()),
19824 ..Default::default()
19825 },
19826 };
19827 if let Some(text_style_refinement) = &self.text_style_refinement {
19828 text_style.refine(text_style_refinement)
19829 }
19830
19831 let background = match self.mode {
19832 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19833 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19834 EditorMode::Full { .. } => cx.theme().colors().editor_background,
19835 };
19836
19837 EditorElement::new(
19838 &cx.entity(),
19839 EditorStyle {
19840 background,
19841 local_player: cx.theme().players().local(),
19842 text: text_style,
19843 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19844 syntax: cx.theme().syntax().clone(),
19845 status: cx.theme().status().clone(),
19846 inlay_hints_style: make_inlay_hints_style(cx),
19847 inline_completion_styles: make_suggestion_styles(cx),
19848 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19849 },
19850 )
19851 }
19852}
19853
19854impl EntityInputHandler for Editor {
19855 fn text_for_range(
19856 &mut self,
19857 range_utf16: Range<usize>,
19858 adjusted_range: &mut Option<Range<usize>>,
19859 _: &mut Window,
19860 cx: &mut Context<Self>,
19861 ) -> Option<String> {
19862 let snapshot = self.buffer.read(cx).read(cx);
19863 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19864 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19865 if (start.0..end.0) != range_utf16 {
19866 adjusted_range.replace(start.0..end.0);
19867 }
19868 Some(snapshot.text_for_range(start..end).collect())
19869 }
19870
19871 fn selected_text_range(
19872 &mut self,
19873 ignore_disabled_input: bool,
19874 _: &mut Window,
19875 cx: &mut Context<Self>,
19876 ) -> Option<UTF16Selection> {
19877 // Prevent the IME menu from appearing when holding down an alphabetic key
19878 // while input is disabled.
19879 if !ignore_disabled_input && !self.input_enabled {
19880 return None;
19881 }
19882
19883 let selection = self.selections.newest::<OffsetUtf16>(cx);
19884 let range = selection.range();
19885
19886 Some(UTF16Selection {
19887 range: range.start.0..range.end.0,
19888 reversed: selection.reversed,
19889 })
19890 }
19891
19892 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19893 let snapshot = self.buffer.read(cx).read(cx);
19894 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19895 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19896 }
19897
19898 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19899 self.clear_highlights::<InputComposition>(cx);
19900 self.ime_transaction.take();
19901 }
19902
19903 fn replace_text_in_range(
19904 &mut self,
19905 range_utf16: Option<Range<usize>>,
19906 text: &str,
19907 window: &mut Window,
19908 cx: &mut Context<Self>,
19909 ) {
19910 if !self.input_enabled {
19911 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19912 return;
19913 }
19914
19915 self.transact(window, cx, |this, window, cx| {
19916 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19917 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19918 Some(this.selection_replacement_ranges(range_utf16, cx))
19919 } else {
19920 this.marked_text_ranges(cx)
19921 };
19922
19923 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19924 let newest_selection_id = this.selections.newest_anchor().id;
19925 this.selections
19926 .all::<OffsetUtf16>(cx)
19927 .iter()
19928 .zip(ranges_to_replace.iter())
19929 .find_map(|(selection, range)| {
19930 if selection.id == newest_selection_id {
19931 Some(
19932 (range.start.0 as isize - selection.head().0 as isize)
19933 ..(range.end.0 as isize - selection.head().0 as isize),
19934 )
19935 } else {
19936 None
19937 }
19938 })
19939 });
19940
19941 cx.emit(EditorEvent::InputHandled {
19942 utf16_range_to_replace: range_to_replace,
19943 text: text.into(),
19944 });
19945
19946 if let Some(new_selected_ranges) = new_selected_ranges {
19947 this.change_selections(None, window, cx, |selections| {
19948 selections.select_ranges(new_selected_ranges)
19949 });
19950 this.backspace(&Default::default(), window, cx);
19951 }
19952
19953 this.handle_input(text, window, cx);
19954 });
19955
19956 if let Some(transaction) = self.ime_transaction {
19957 self.buffer.update(cx, |buffer, cx| {
19958 buffer.group_until_transaction(transaction, cx);
19959 });
19960 }
19961
19962 self.unmark_text(window, cx);
19963 }
19964
19965 fn replace_and_mark_text_in_range(
19966 &mut self,
19967 range_utf16: Option<Range<usize>>,
19968 text: &str,
19969 new_selected_range_utf16: Option<Range<usize>>,
19970 window: &mut Window,
19971 cx: &mut Context<Self>,
19972 ) {
19973 if !self.input_enabled {
19974 return;
19975 }
19976
19977 let transaction = self.transact(window, cx, |this, window, cx| {
19978 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19979 let snapshot = this.buffer.read(cx).read(cx);
19980 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19981 for marked_range in &mut marked_ranges {
19982 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19983 marked_range.start.0 += relative_range_utf16.start;
19984 marked_range.start =
19985 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19986 marked_range.end =
19987 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19988 }
19989 }
19990 Some(marked_ranges)
19991 } else if let Some(range_utf16) = range_utf16 {
19992 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19993 Some(this.selection_replacement_ranges(range_utf16, cx))
19994 } else {
19995 None
19996 };
19997
19998 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19999 let newest_selection_id = this.selections.newest_anchor().id;
20000 this.selections
20001 .all::<OffsetUtf16>(cx)
20002 .iter()
20003 .zip(ranges_to_replace.iter())
20004 .find_map(|(selection, range)| {
20005 if selection.id == newest_selection_id {
20006 Some(
20007 (range.start.0 as isize - selection.head().0 as isize)
20008 ..(range.end.0 as isize - selection.head().0 as isize),
20009 )
20010 } else {
20011 None
20012 }
20013 })
20014 });
20015
20016 cx.emit(EditorEvent::InputHandled {
20017 utf16_range_to_replace: range_to_replace,
20018 text: text.into(),
20019 });
20020
20021 if let Some(ranges) = ranges_to_replace {
20022 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20023 }
20024
20025 let marked_ranges = {
20026 let snapshot = this.buffer.read(cx).read(cx);
20027 this.selections
20028 .disjoint_anchors()
20029 .iter()
20030 .map(|selection| {
20031 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20032 })
20033 .collect::<Vec<_>>()
20034 };
20035
20036 if text.is_empty() {
20037 this.unmark_text(window, cx);
20038 } else {
20039 this.highlight_text::<InputComposition>(
20040 marked_ranges.clone(),
20041 HighlightStyle {
20042 underline: Some(UnderlineStyle {
20043 thickness: px(1.),
20044 color: None,
20045 wavy: false,
20046 }),
20047 ..Default::default()
20048 },
20049 cx,
20050 );
20051 }
20052
20053 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20054 let use_autoclose = this.use_autoclose;
20055 let use_auto_surround = this.use_auto_surround;
20056 this.set_use_autoclose(false);
20057 this.set_use_auto_surround(false);
20058 this.handle_input(text, window, cx);
20059 this.set_use_autoclose(use_autoclose);
20060 this.set_use_auto_surround(use_auto_surround);
20061
20062 if let Some(new_selected_range) = new_selected_range_utf16 {
20063 let snapshot = this.buffer.read(cx).read(cx);
20064 let new_selected_ranges = marked_ranges
20065 .into_iter()
20066 .map(|marked_range| {
20067 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20068 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20069 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20070 snapshot.clip_offset_utf16(new_start, Bias::Left)
20071 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20072 })
20073 .collect::<Vec<_>>();
20074
20075 drop(snapshot);
20076 this.change_selections(None, window, cx, |selections| {
20077 selections.select_ranges(new_selected_ranges)
20078 });
20079 }
20080 });
20081
20082 self.ime_transaction = self.ime_transaction.or(transaction);
20083 if let Some(transaction) = self.ime_transaction {
20084 self.buffer.update(cx, |buffer, cx| {
20085 buffer.group_until_transaction(transaction, cx);
20086 });
20087 }
20088
20089 if self.text_highlights::<InputComposition>(cx).is_none() {
20090 self.ime_transaction.take();
20091 }
20092 }
20093
20094 fn bounds_for_range(
20095 &mut self,
20096 range_utf16: Range<usize>,
20097 element_bounds: gpui::Bounds<Pixels>,
20098 window: &mut Window,
20099 cx: &mut Context<Self>,
20100 ) -> Option<gpui::Bounds<Pixels>> {
20101 let text_layout_details = self.text_layout_details(window);
20102 let gpui::Size {
20103 width: em_width,
20104 height: line_height,
20105 } = self.character_size(window);
20106
20107 let snapshot = self.snapshot(window, cx);
20108 let scroll_position = snapshot.scroll_position();
20109 let scroll_left = scroll_position.x * em_width;
20110
20111 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20112 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20113 + self.gutter_dimensions.width
20114 + self.gutter_dimensions.margin;
20115 let y = line_height * (start.row().as_f32() - scroll_position.y);
20116
20117 Some(Bounds {
20118 origin: element_bounds.origin + point(x, y),
20119 size: size(em_width, line_height),
20120 })
20121 }
20122
20123 fn character_index_for_point(
20124 &mut self,
20125 point: gpui::Point<Pixels>,
20126 _window: &mut Window,
20127 _cx: &mut Context<Self>,
20128 ) -> Option<usize> {
20129 let position_map = self.last_position_map.as_ref()?;
20130 if !position_map.text_hitbox.contains(&point) {
20131 return None;
20132 }
20133 let display_point = position_map.point_for_position(point).previous_valid;
20134 let anchor = position_map
20135 .snapshot
20136 .display_point_to_anchor(display_point, Bias::Left);
20137 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20138 Some(utf16_offset.0)
20139 }
20140}
20141
20142trait SelectionExt {
20143 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20144 fn spanned_rows(
20145 &self,
20146 include_end_if_at_line_start: bool,
20147 map: &DisplaySnapshot,
20148 ) -> Range<MultiBufferRow>;
20149}
20150
20151impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20152 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20153 let start = self
20154 .start
20155 .to_point(&map.buffer_snapshot)
20156 .to_display_point(map);
20157 let end = self
20158 .end
20159 .to_point(&map.buffer_snapshot)
20160 .to_display_point(map);
20161 if self.reversed {
20162 end..start
20163 } else {
20164 start..end
20165 }
20166 }
20167
20168 fn spanned_rows(
20169 &self,
20170 include_end_if_at_line_start: bool,
20171 map: &DisplaySnapshot,
20172 ) -> Range<MultiBufferRow> {
20173 let start = self.start.to_point(&map.buffer_snapshot);
20174 let mut end = self.end.to_point(&map.buffer_snapshot);
20175 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20176 end.row -= 1;
20177 }
20178
20179 let buffer_start = map.prev_line_boundary(start).0;
20180 let buffer_end = map.next_line_boundary(end).0;
20181 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20182 }
20183}
20184
20185impl<T: InvalidationRegion> InvalidationStack<T> {
20186 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20187 where
20188 S: Clone + ToOffset,
20189 {
20190 while let Some(region) = self.last() {
20191 let all_selections_inside_invalidation_ranges =
20192 if selections.len() == region.ranges().len() {
20193 selections
20194 .iter()
20195 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20196 .all(|(selection, invalidation_range)| {
20197 let head = selection.head().to_offset(buffer);
20198 invalidation_range.start <= head && invalidation_range.end >= head
20199 })
20200 } else {
20201 false
20202 };
20203
20204 if all_selections_inside_invalidation_ranges {
20205 break;
20206 } else {
20207 self.pop();
20208 }
20209 }
20210 }
20211}
20212
20213impl<T> Default for InvalidationStack<T> {
20214 fn default() -> Self {
20215 Self(Default::default())
20216 }
20217}
20218
20219impl<T> Deref for InvalidationStack<T> {
20220 type Target = Vec<T>;
20221
20222 fn deref(&self) -> &Self::Target {
20223 &self.0
20224 }
20225}
20226
20227impl<T> DerefMut for InvalidationStack<T> {
20228 fn deref_mut(&mut self) -> &mut Self::Target {
20229 &mut self.0
20230 }
20231}
20232
20233impl InvalidationRegion for SnippetState {
20234 fn ranges(&self) -> &[Range<Anchor>] {
20235 &self.ranges[self.active_index]
20236 }
20237}
20238
20239fn inline_completion_edit_text(
20240 current_snapshot: &BufferSnapshot,
20241 edits: &[(Range<Anchor>, String)],
20242 edit_preview: &EditPreview,
20243 include_deletions: bool,
20244 cx: &App,
20245) -> HighlightedText {
20246 let edits = edits
20247 .iter()
20248 .map(|(anchor, text)| {
20249 (
20250 anchor.start.text_anchor..anchor.end.text_anchor,
20251 text.clone(),
20252 )
20253 })
20254 .collect::<Vec<_>>();
20255
20256 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20257}
20258
20259pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20260 match severity {
20261 DiagnosticSeverity::ERROR => colors.error,
20262 DiagnosticSeverity::WARNING => colors.warning,
20263 DiagnosticSeverity::INFORMATION => colors.info,
20264 DiagnosticSeverity::HINT => colors.info,
20265 _ => colors.ignored,
20266 }
20267}
20268
20269pub fn styled_runs_for_code_label<'a>(
20270 label: &'a CodeLabel,
20271 syntax_theme: &'a theme::SyntaxTheme,
20272) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20273 let fade_out = HighlightStyle {
20274 fade_out: Some(0.35),
20275 ..Default::default()
20276 };
20277
20278 let mut prev_end = label.filter_range.end;
20279 label
20280 .runs
20281 .iter()
20282 .enumerate()
20283 .flat_map(move |(ix, (range, highlight_id))| {
20284 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20285 style
20286 } else {
20287 return Default::default();
20288 };
20289 let mut muted_style = style;
20290 muted_style.highlight(fade_out);
20291
20292 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20293 if range.start >= label.filter_range.end {
20294 if range.start > prev_end {
20295 runs.push((prev_end..range.start, fade_out));
20296 }
20297 runs.push((range.clone(), muted_style));
20298 } else if range.end <= label.filter_range.end {
20299 runs.push((range.clone(), style));
20300 } else {
20301 runs.push((range.start..label.filter_range.end, style));
20302 runs.push((label.filter_range.end..range.end, muted_style));
20303 }
20304 prev_end = cmp::max(prev_end, range.end);
20305
20306 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20307 runs.push((prev_end..label.text.len(), fade_out));
20308 }
20309
20310 runs
20311 })
20312}
20313
20314pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20315 let mut prev_index = 0;
20316 let mut prev_codepoint: Option<char> = None;
20317 text.char_indices()
20318 .chain([(text.len(), '\0')])
20319 .filter_map(move |(index, codepoint)| {
20320 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20321 let is_boundary = index == text.len()
20322 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20323 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20324 if is_boundary {
20325 let chunk = &text[prev_index..index];
20326 prev_index = index;
20327 Some(chunk)
20328 } else {
20329 None
20330 }
20331 })
20332}
20333
20334pub trait RangeToAnchorExt: Sized {
20335 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20336
20337 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20338 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20339 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20340 }
20341}
20342
20343impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20344 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20345 let start_offset = self.start.to_offset(snapshot);
20346 let end_offset = self.end.to_offset(snapshot);
20347 if start_offset == end_offset {
20348 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20349 } else {
20350 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20351 }
20352 }
20353}
20354
20355pub trait RowExt {
20356 fn as_f32(&self) -> f32;
20357
20358 fn next_row(&self) -> Self;
20359
20360 fn previous_row(&self) -> Self;
20361
20362 fn minus(&self, other: Self) -> u32;
20363}
20364
20365impl RowExt for DisplayRow {
20366 fn as_f32(&self) -> f32 {
20367 self.0 as f32
20368 }
20369
20370 fn next_row(&self) -> Self {
20371 Self(self.0 + 1)
20372 }
20373
20374 fn previous_row(&self) -> Self {
20375 Self(self.0.saturating_sub(1))
20376 }
20377
20378 fn minus(&self, other: Self) -> u32 {
20379 self.0 - other.0
20380 }
20381}
20382
20383impl RowExt for MultiBufferRow {
20384 fn as_f32(&self) -> f32 {
20385 self.0 as f32
20386 }
20387
20388 fn next_row(&self) -> Self {
20389 Self(self.0 + 1)
20390 }
20391
20392 fn previous_row(&self) -> Self {
20393 Self(self.0.saturating_sub(1))
20394 }
20395
20396 fn minus(&self, other: Self) -> u32 {
20397 self.0 - other.0
20398 }
20399}
20400
20401trait RowRangeExt {
20402 type Row;
20403
20404 fn len(&self) -> usize;
20405
20406 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20407}
20408
20409impl RowRangeExt for Range<MultiBufferRow> {
20410 type Row = MultiBufferRow;
20411
20412 fn len(&self) -> usize {
20413 (self.end.0 - self.start.0) as usize
20414 }
20415
20416 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20417 (self.start.0..self.end.0).map(MultiBufferRow)
20418 }
20419}
20420
20421impl RowRangeExt for Range<DisplayRow> {
20422 type Row = DisplayRow;
20423
20424 fn len(&self) -> usize {
20425 (self.end.0 - self.start.0) as usize
20426 }
20427
20428 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20429 (self.start.0..self.end.0).map(DisplayRow)
20430 }
20431}
20432
20433/// If select range has more than one line, we
20434/// just point the cursor to range.start.
20435fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20436 if range.start.row == range.end.row {
20437 range
20438 } else {
20439 range.start..range.start
20440 }
20441}
20442pub struct KillRing(ClipboardItem);
20443impl Global for KillRing {}
20444
20445const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20446
20447enum BreakpointPromptEditAction {
20448 Log,
20449 Condition,
20450 HitCondition,
20451}
20452
20453struct BreakpointPromptEditor {
20454 pub(crate) prompt: Entity<Editor>,
20455 editor: WeakEntity<Editor>,
20456 breakpoint_anchor: Anchor,
20457 breakpoint: Breakpoint,
20458 edit_action: BreakpointPromptEditAction,
20459 block_ids: HashSet<CustomBlockId>,
20460 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20461 _subscriptions: Vec<Subscription>,
20462}
20463
20464impl BreakpointPromptEditor {
20465 const MAX_LINES: u8 = 4;
20466
20467 fn new(
20468 editor: WeakEntity<Editor>,
20469 breakpoint_anchor: Anchor,
20470 breakpoint: Breakpoint,
20471 edit_action: BreakpointPromptEditAction,
20472 window: &mut Window,
20473 cx: &mut Context<Self>,
20474 ) -> Self {
20475 let base_text = match edit_action {
20476 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20477 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20478 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20479 }
20480 .map(|msg| msg.to_string())
20481 .unwrap_or_default();
20482
20483 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20484 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20485
20486 let prompt = cx.new(|cx| {
20487 let mut prompt = Editor::new(
20488 EditorMode::AutoHeight {
20489 max_lines: Self::MAX_LINES as usize,
20490 },
20491 buffer,
20492 None,
20493 window,
20494 cx,
20495 );
20496 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20497 prompt.set_show_cursor_when_unfocused(false, cx);
20498 prompt.set_placeholder_text(
20499 match edit_action {
20500 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20501 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20502 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20503 },
20504 cx,
20505 );
20506
20507 prompt
20508 });
20509
20510 Self {
20511 prompt,
20512 editor,
20513 breakpoint_anchor,
20514 breakpoint,
20515 edit_action,
20516 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20517 block_ids: Default::default(),
20518 _subscriptions: vec![],
20519 }
20520 }
20521
20522 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20523 self.block_ids.extend(block_ids)
20524 }
20525
20526 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20527 if let Some(editor) = self.editor.upgrade() {
20528 let message = self
20529 .prompt
20530 .read(cx)
20531 .buffer
20532 .read(cx)
20533 .as_singleton()
20534 .expect("A multi buffer in breakpoint prompt isn't possible")
20535 .read(cx)
20536 .as_rope()
20537 .to_string();
20538
20539 editor.update(cx, |editor, cx| {
20540 editor.edit_breakpoint_at_anchor(
20541 self.breakpoint_anchor,
20542 self.breakpoint.clone(),
20543 match self.edit_action {
20544 BreakpointPromptEditAction::Log => {
20545 BreakpointEditAction::EditLogMessage(message.into())
20546 }
20547 BreakpointPromptEditAction::Condition => {
20548 BreakpointEditAction::EditCondition(message.into())
20549 }
20550 BreakpointPromptEditAction::HitCondition => {
20551 BreakpointEditAction::EditHitCondition(message.into())
20552 }
20553 },
20554 cx,
20555 );
20556
20557 editor.remove_blocks(self.block_ids.clone(), None, cx);
20558 cx.focus_self(window);
20559 });
20560 }
20561 }
20562
20563 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20564 self.editor
20565 .update(cx, |editor, cx| {
20566 editor.remove_blocks(self.block_ids.clone(), None, cx);
20567 window.focus(&editor.focus_handle);
20568 })
20569 .log_err();
20570 }
20571
20572 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20573 let settings = ThemeSettings::get_global(cx);
20574 let text_style = TextStyle {
20575 color: if self.prompt.read(cx).read_only(cx) {
20576 cx.theme().colors().text_disabled
20577 } else {
20578 cx.theme().colors().text
20579 },
20580 font_family: settings.buffer_font.family.clone(),
20581 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20582 font_size: settings.buffer_font_size(cx).into(),
20583 font_weight: settings.buffer_font.weight,
20584 line_height: relative(settings.buffer_line_height.value()),
20585 ..Default::default()
20586 };
20587 EditorElement::new(
20588 &self.prompt,
20589 EditorStyle {
20590 background: cx.theme().colors().editor_background,
20591 local_player: cx.theme().players().local(),
20592 text: text_style,
20593 ..Default::default()
20594 },
20595 )
20596 }
20597}
20598
20599impl Render for BreakpointPromptEditor {
20600 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20601 let gutter_dimensions = *self.gutter_dimensions.lock();
20602 h_flex()
20603 .key_context("Editor")
20604 .bg(cx.theme().colors().editor_background)
20605 .border_y_1()
20606 .border_color(cx.theme().status().info_border)
20607 .size_full()
20608 .py(window.line_height() / 2.5)
20609 .on_action(cx.listener(Self::confirm))
20610 .on_action(cx.listener(Self::cancel))
20611 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20612 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20613 }
20614}
20615
20616impl Focusable for BreakpointPromptEditor {
20617 fn focus_handle(&self, cx: &App) -> FocusHandle {
20618 self.prompt.focus_handle(cx)
20619 }
20620}
20621
20622fn all_edits_insertions_or_deletions(
20623 edits: &Vec<(Range<Anchor>, String)>,
20624 snapshot: &MultiBufferSnapshot,
20625) -> bool {
20626 let mut all_insertions = true;
20627 let mut all_deletions = true;
20628
20629 for (range, new_text) in edits.iter() {
20630 let range_is_empty = range.to_offset(&snapshot).is_empty();
20631 let text_is_empty = new_text.is_empty();
20632
20633 if range_is_empty != text_is_empty {
20634 if range_is_empty {
20635 all_deletions = false;
20636 } else {
20637 all_insertions = false;
20638 }
20639 } else {
20640 return false;
20641 }
20642
20643 if !all_insertions && !all_deletions {
20644 return false;
20645 }
20646 }
20647 all_insertions || all_deletions
20648}
20649
20650struct MissingEditPredictionKeybindingTooltip;
20651
20652impl Render for MissingEditPredictionKeybindingTooltip {
20653 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20654 ui::tooltip_container(window, cx, |container, _, cx| {
20655 container
20656 .flex_shrink_0()
20657 .max_w_80()
20658 .min_h(rems_from_px(124.))
20659 .justify_between()
20660 .child(
20661 v_flex()
20662 .flex_1()
20663 .text_ui_sm(cx)
20664 .child(Label::new("Conflict with Accept Keybinding"))
20665 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20666 )
20667 .child(
20668 h_flex()
20669 .pb_1()
20670 .gap_1()
20671 .items_end()
20672 .w_full()
20673 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20674 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20675 }))
20676 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20677 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20678 })),
20679 )
20680 })
20681 }
20682}
20683
20684#[derive(Debug, Clone, Copy, PartialEq)]
20685pub struct LineHighlight {
20686 pub background: Background,
20687 pub border: Option<gpui::Hsla>,
20688}
20689
20690impl From<Hsla> for LineHighlight {
20691 fn from(hsla: Hsla) -> Self {
20692 Self {
20693 background: hsla.into(),
20694 border: None,
20695 }
20696 }
20697}
20698
20699impl From<Background> for LineHighlight {
20700 fn from(background: Background) -> Self {
20701 Self {
20702 background,
20703 border: None,
20704 }
20705 }
20706}
20707
20708fn render_diff_hunk_controls(
20709 row: u32,
20710 status: &DiffHunkStatus,
20711 hunk_range: Range<Anchor>,
20712 is_created_file: bool,
20713 line_height: Pixels,
20714 editor: &Entity<Editor>,
20715 _window: &mut Window,
20716 cx: &mut App,
20717) -> AnyElement {
20718 h_flex()
20719 .h(line_height)
20720 .mr_1()
20721 .gap_1()
20722 .px_0p5()
20723 .pb_1()
20724 .border_x_1()
20725 .border_b_1()
20726 .border_color(cx.theme().colors().border_variant)
20727 .rounded_b_lg()
20728 .bg(cx.theme().colors().editor_background)
20729 .gap_1()
20730 .occlude()
20731 .shadow_md()
20732 .child(if status.has_secondary_hunk() {
20733 Button::new(("stage", row as u64), "Stage")
20734 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20735 .tooltip({
20736 let focus_handle = editor.focus_handle(cx);
20737 move |window, cx| {
20738 Tooltip::for_action_in(
20739 "Stage Hunk",
20740 &::git::ToggleStaged,
20741 &focus_handle,
20742 window,
20743 cx,
20744 )
20745 }
20746 })
20747 .on_click({
20748 let editor = editor.clone();
20749 move |_event, _window, cx| {
20750 editor.update(cx, |editor, cx| {
20751 editor.stage_or_unstage_diff_hunks(
20752 true,
20753 vec![hunk_range.start..hunk_range.start],
20754 cx,
20755 );
20756 });
20757 }
20758 })
20759 } else {
20760 Button::new(("unstage", row as u64), "Unstage")
20761 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20762 .tooltip({
20763 let focus_handle = editor.focus_handle(cx);
20764 move |window, cx| {
20765 Tooltip::for_action_in(
20766 "Unstage Hunk",
20767 &::git::ToggleStaged,
20768 &focus_handle,
20769 window,
20770 cx,
20771 )
20772 }
20773 })
20774 .on_click({
20775 let editor = editor.clone();
20776 move |_event, _window, cx| {
20777 editor.update(cx, |editor, cx| {
20778 editor.stage_or_unstage_diff_hunks(
20779 false,
20780 vec![hunk_range.start..hunk_range.start],
20781 cx,
20782 );
20783 });
20784 }
20785 })
20786 })
20787 .child(
20788 Button::new(("restore", row as u64), "Restore")
20789 .tooltip({
20790 let focus_handle = editor.focus_handle(cx);
20791 move |window, cx| {
20792 Tooltip::for_action_in(
20793 "Restore Hunk",
20794 &::git::Restore,
20795 &focus_handle,
20796 window,
20797 cx,
20798 )
20799 }
20800 })
20801 .on_click({
20802 let editor = editor.clone();
20803 move |_event, window, cx| {
20804 editor.update(cx, |editor, cx| {
20805 let snapshot = editor.snapshot(window, cx);
20806 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20807 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20808 });
20809 }
20810 })
20811 .disabled(is_created_file),
20812 )
20813 .when(
20814 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20815 |el| {
20816 el.child(
20817 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20818 .shape(IconButtonShape::Square)
20819 .icon_size(IconSize::Small)
20820 // .disabled(!has_multiple_hunks)
20821 .tooltip({
20822 let focus_handle = editor.focus_handle(cx);
20823 move |window, cx| {
20824 Tooltip::for_action_in(
20825 "Next Hunk",
20826 &GoToHunk,
20827 &focus_handle,
20828 window,
20829 cx,
20830 )
20831 }
20832 })
20833 .on_click({
20834 let editor = editor.clone();
20835 move |_event, window, cx| {
20836 editor.update(cx, |editor, cx| {
20837 let snapshot = editor.snapshot(window, cx);
20838 let position =
20839 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20840 editor.go_to_hunk_before_or_after_position(
20841 &snapshot,
20842 position,
20843 Direction::Next,
20844 window,
20845 cx,
20846 );
20847 editor.expand_selected_diff_hunks(cx);
20848 });
20849 }
20850 }),
20851 )
20852 .child(
20853 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20854 .shape(IconButtonShape::Square)
20855 .icon_size(IconSize::Small)
20856 // .disabled(!has_multiple_hunks)
20857 .tooltip({
20858 let focus_handle = editor.focus_handle(cx);
20859 move |window, cx| {
20860 Tooltip::for_action_in(
20861 "Previous Hunk",
20862 &GoToPreviousHunk,
20863 &focus_handle,
20864 window,
20865 cx,
20866 )
20867 }
20868 })
20869 .on_click({
20870 let editor = editor.clone();
20871 move |_event, window, cx| {
20872 editor.update(cx, |editor, cx| {
20873 let snapshot = editor.snapshot(window, cx);
20874 let point =
20875 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20876 editor.go_to_hunk_before_or_after_position(
20877 &snapshot,
20878 point,
20879 Direction::Prev,
20880 window,
20881 cx,
20882 );
20883 editor.expand_selected_diff_hunks(cx);
20884 });
20885 }
20886 }),
20887 )
20888 },
20889 )
20890 .into_any_element()
20891}