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//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlays;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39mod split;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction_types::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use inlays::Inlay;
65pub use items::MAX_TAB_TITLE_LEN;
66pub use lsp::CompletionContext;
67pub use lsp_ext::lsp_tasks;
68pub use multi_buffer::{
69 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
70 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
71 ToPoint,
72};
73pub use split::SplittableEditor;
74pub use text::Bias;
75
76use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
77use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
78use anyhow::{Context as _, Result, anyhow, bail};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction_types::{
92 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionGranularity,
93};
94use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
95use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
96use futures::{
97 FutureExt, StreamExt as _,
98 future::{self, Shared, join},
99 stream::FuturesUnordered,
100};
101use fuzzy::{StringMatch, StringMatchCandidate};
102use git::blame::{GitBlame, GlobalBlameRenderer};
103use gpui::{
104 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
105 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
106 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
107 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
108 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
109 Render, ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextRun,
110 TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
111 WeakEntity, WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative,
112 size,
113};
114use hover_links::{HoverLink, HoveredLinkState, find_file};
115use hover_popover::{HoverState, hide_hover};
116use indent_guides::ActiveIndentGuidesState;
117use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
118use itertools::{Either, Itertools};
119use language::{
120 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
121 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
122 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
123 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
124 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
125 TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143};
144use parking_lot::Mutex;
145use persistence::DB;
146use project::{
147 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
148 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
149 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
150 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
151 debugger::{
152 breakpoint_store::{
153 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
154 BreakpointStore, BreakpointStoreEvent,
155 },
156 session::{Session, SessionEvent},
157 },
158 git_store::GitStoreEvent,
159 lsp_store::{
160 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
161 OpenLspBufferHandle,
162 },
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use rpc::{ErrorCode, ErrorExt, proto::PeerId};
167use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
168use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
169use serde::{Deserialize, Serialize};
170use settings::{
171 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
172 update_settings_file,
173};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::{Any, TypeId},
178 borrow::Cow,
179 cell::{OnceCell, RefCell},
180 cmp::{self, Ordering, Reverse},
181 collections::hash_map,
182 iter::{self, Peekable},
183 mem,
184 num::NonZeroU32,
185 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
186 path::{Path, PathBuf},
187 rc::Rc,
188 sync::Arc,
189 time::{Duration, Instant},
190};
191use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
192use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
193use theme::{
194 AccentColors, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
195 observe_buffer_font_size_adjustment,
196};
197use ui::{
198 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
199 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
200};
201use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
202use workspace::{
203 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
204 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
205 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
206 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
207 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
208 searchable::SearchEvent,
209};
210
211use crate::{
212 code_context_menus::CompletionsMenuSource,
213 editor_settings::MultiCursorModifier,
214 hover_links::{find_url, find_url_from_range},
215 inlays::{
216 InlineValueCache,
217 inlay_hints::{LspInlayHintData, inlay_hint_settings},
218 },
219 scroll::{ScrollOffset, ScrollPixelOffset},
220 selections_collection::resolve_selections_wrapping_blocks,
221 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
222};
223
224pub const FILE_HEADER_HEIGHT: u32 = 2;
225pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
226const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
227const MAX_LINE_LEN: usize = 1024;
228const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
229const MAX_SELECTION_HISTORY_LEN: usize = 1024;
230pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
231#[doc(hidden)]
232pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
233pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
234
235pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
236pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
237pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
238pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
239
240pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
241pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
242pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
243
244pub type RenderDiffHunkControlsFn = Arc<
245 dyn Fn(
246 u32,
247 &DiffHunkStatus,
248 Range<Anchor>,
249 bool,
250 Pixels,
251 &Entity<Editor>,
252 &mut Window,
253 &mut App,
254 ) -> AnyElement,
255>;
256
257enum ReportEditorEvent {
258 Saved { auto_saved: bool },
259 EditorOpened,
260 Closed,
261}
262
263impl ReportEditorEvent {
264 pub fn event_type(&self) -> &'static str {
265 match self {
266 Self::Saved { .. } => "Editor Saved",
267 Self::EditorOpened => "Editor Opened",
268 Self::Closed => "Editor Closed",
269 }
270 }
271}
272
273pub enum ActiveDebugLine {}
274pub enum DebugStackFrameLine {}
275enum DocumentHighlightRead {}
276enum DocumentHighlightWrite {}
277enum InputComposition {}
278pub enum PendingInput {}
279enum SelectedTextHighlight {}
280
281pub enum ConflictsOuter {}
282pub enum ConflictsOurs {}
283pub enum ConflictsTheirs {}
284pub enum ConflictsOursMarker {}
285pub enum ConflictsTheirsMarker {}
286
287pub struct HunkAddedColor;
288pub struct HunkRemovedColor;
289
290#[derive(Debug, Copy, Clone, PartialEq, Eq)]
291pub enum Navigated {
292 Yes,
293 No,
294}
295
296impl Navigated {
297 pub fn from_bool(yes: bool) -> Navigated {
298 if yes { Navigated::Yes } else { Navigated::No }
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
303enum DisplayDiffHunk {
304 Folded {
305 display_row: DisplayRow,
306 },
307 Unfolded {
308 is_created_file: bool,
309 diff_base_byte_range: Range<usize>,
310 display_row_range: Range<DisplayRow>,
311 multi_buffer_range: Range<Anchor>,
312 status: DiffHunkStatus,
313 word_diffs: Vec<Range<MultiBufferOffset>>,
314 },
315}
316
317pub enum HideMouseCursorOrigin {
318 TypingAction,
319 MovementAction,
320}
321
322pub fn init(cx: &mut App) {
323 cx.set_global(GlobalBlameRenderer(Arc::new(())));
324
325 workspace::register_project_item::<Editor>(cx);
326 workspace::FollowableViewRegistry::register::<Editor>(cx);
327 workspace::register_serializable_item::<Editor>(cx);
328
329 cx.observe_new(
330 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
331 workspace.register_action(Editor::new_file);
332 workspace.register_action(Editor::new_file_split);
333 workspace.register_action(Editor::new_file_vertical);
334 workspace.register_action(Editor::new_file_horizontal);
335 workspace.register_action(Editor::cancel_language_server_work);
336 workspace.register_action(Editor::toggle_focus);
337 },
338 )
339 .detach();
340
341 cx.on_action(move |_: &workspace::NewFile, cx| {
342 let app_state = workspace::AppState::global(cx);
343 if let Some(app_state) = app_state.upgrade() {
344 workspace::open_new(
345 Default::default(),
346 app_state,
347 cx,
348 |workspace, window, cx| {
349 Editor::new_file(workspace, &Default::default(), window, cx)
350 },
351 )
352 .detach();
353 }
354 })
355 .on_action(move |_: &workspace::NewWindow, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 cx.activate(true);
364 Editor::new_file(workspace, &Default::default(), window, cx)
365 },
366 )
367 .detach();
368 }
369 });
370}
371
372pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
373 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
374}
375
376pub trait DiagnosticRenderer {
377 fn render_group(
378 &self,
379 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
380 buffer_id: BufferId,
381 snapshot: EditorSnapshot,
382 editor: WeakEntity<Editor>,
383 language_registry: Option<Arc<LanguageRegistry>>,
384 cx: &mut App,
385 ) -> Vec<BlockProperties<Anchor>>;
386
387 fn render_hover(
388 &self,
389 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
390 range: Range<Point>,
391 buffer_id: BufferId,
392 language_registry: Option<Arc<LanguageRegistry>>,
393 cx: &mut App,
394 ) -> Option<Entity<markdown::Markdown>>;
395
396 fn open_link(
397 &self,
398 editor: &mut Editor,
399 link: SharedString,
400 window: &mut Window,
401 cx: &mut Context<Editor>,
402 );
403}
404
405pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
406
407impl GlobalDiagnosticRenderer {
408 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
409 cx.try_global::<Self>().map(|g| g.0.clone())
410 }
411}
412
413impl gpui::Global for GlobalDiagnosticRenderer {}
414pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
415 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
416}
417
418pub struct SearchWithinRange;
419
420trait InvalidationRegion {
421 fn ranges(&self) -> &[Range<Anchor>];
422}
423
424#[derive(Clone, Debug, PartialEq)]
425pub enum SelectPhase {
426 Begin {
427 position: DisplayPoint,
428 add: bool,
429 click_count: usize,
430 },
431 BeginColumnar {
432 position: DisplayPoint,
433 reset: bool,
434 mode: ColumnarMode,
435 goal_column: u32,
436 },
437 Extend {
438 position: DisplayPoint,
439 click_count: usize,
440 },
441 Update {
442 position: DisplayPoint,
443 goal_column: u32,
444 scroll_delta: gpui::Point<f32>,
445 },
446 End,
447}
448
449#[derive(Clone, Debug, PartialEq)]
450pub enum ColumnarMode {
451 FromMouse,
452 FromSelection,
453}
454
455#[derive(Clone, Debug)]
456pub enum SelectMode {
457 Character,
458 Word(Range<Anchor>),
459 Line(Range<Anchor>),
460 All,
461}
462
463#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
464pub enum SizingBehavior {
465 /// The editor will layout itself using `size_full` and will include the vertical
466 /// scroll margin as requested by user settings.
467 #[default]
468 Default,
469 /// The editor will layout itself using `size_full`, but will not have any
470 /// vertical overscroll.
471 ExcludeOverscrollMargin,
472 /// The editor will request a vertical size according to its content and will be
473 /// layouted without a vertical scroll margin.
474 SizeByContent,
475}
476
477#[derive(Clone, PartialEq, Eq, Debug)]
478pub enum EditorMode {
479 SingleLine,
480 AutoHeight {
481 min_lines: usize,
482 max_lines: Option<usize>,
483 },
484 Full {
485 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
486 scale_ui_elements_with_buffer_font_size: bool,
487 /// When set to `true`, the editor will render a background for the active line.
488 show_active_line_background: bool,
489 /// Determines the sizing behavior for this editor
490 sizing_behavior: SizingBehavior,
491 },
492 Minimap {
493 parent: WeakEntity<Editor>,
494 },
495}
496
497impl EditorMode {
498 pub fn full() -> Self {
499 Self::Full {
500 scale_ui_elements_with_buffer_font_size: true,
501 show_active_line_background: true,
502 sizing_behavior: SizingBehavior::Default,
503 }
504 }
505
506 #[inline]
507 pub fn is_full(&self) -> bool {
508 matches!(self, Self::Full { .. })
509 }
510
511 #[inline]
512 pub fn is_single_line(&self) -> bool {
513 matches!(self, Self::SingleLine { .. })
514 }
515
516 #[inline]
517 fn is_minimap(&self) -> bool {
518 matches!(self, Self::Minimap { .. })
519 }
520}
521
522#[derive(Copy, Clone, Debug)]
523pub enum SoftWrap {
524 /// Prefer not to wrap at all.
525 ///
526 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
527 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
528 GitDiff,
529 /// Prefer a single line generally, unless an overly long line is encountered.
530 None,
531 /// Soft wrap lines that exceed the editor width.
532 EditorWidth,
533 /// Soft wrap lines at the preferred line length.
534 Column(u32),
535 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
536 Bounded(u32),
537}
538
539#[derive(Clone)]
540pub struct EditorStyle {
541 pub background: Hsla,
542 pub border: Hsla,
543 pub local_player: PlayerColor,
544 pub text: TextStyle,
545 pub scrollbar_width: Pixels,
546 pub syntax: Arc<SyntaxTheme>,
547 pub status: StatusColors,
548 pub inlay_hints_style: HighlightStyle,
549 pub edit_prediction_styles: EditPredictionStyles,
550 pub unnecessary_code_fade: f32,
551 pub show_underlines: bool,
552}
553
554impl Default for EditorStyle {
555 fn default() -> Self {
556 Self {
557 background: Hsla::default(),
558 border: Hsla::default(),
559 local_player: PlayerColor::default(),
560 text: TextStyle::default(),
561 scrollbar_width: Pixels::default(),
562 syntax: Default::default(),
563 // HACK: Status colors don't have a real default.
564 // We should look into removing the status colors from the editor
565 // style and retrieve them directly from the theme.
566 status: StatusColors::dark(),
567 inlay_hints_style: HighlightStyle::default(),
568 edit_prediction_styles: EditPredictionStyles {
569 insertion: HighlightStyle::default(),
570 whitespace: HighlightStyle::default(),
571 },
572 unnecessary_code_fade: Default::default(),
573 show_underlines: true,
574 }
575 }
576}
577
578pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
579 let show_background = language_settings::language_settings(None, None, cx)
580 .inlay_hints
581 .show_background;
582
583 let mut style = cx.theme().syntax().get("hint");
584
585 if style.color.is_none() {
586 style.color = Some(cx.theme().status().hint);
587 }
588
589 if !show_background {
590 style.background_color = None;
591 return style;
592 }
593
594 if style.background_color.is_none() {
595 style.background_color = Some(cx.theme().status().hint_background);
596 }
597
598 style
599}
600
601pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
602 EditPredictionStyles {
603 insertion: HighlightStyle {
604 color: Some(cx.theme().status().predictive),
605 ..HighlightStyle::default()
606 },
607 whitespace: HighlightStyle {
608 background_color: Some(cx.theme().status().created_background),
609 ..HighlightStyle::default()
610 },
611 }
612}
613
614type CompletionId = usize;
615
616pub(crate) enum EditDisplayMode {
617 TabAccept,
618 DiffPopover,
619 Inline,
620}
621
622enum EditPrediction {
623 Edit {
624 edits: Vec<(Range<Anchor>, Arc<str>)>,
625 edit_preview: Option<EditPreview>,
626 display_mode: EditDisplayMode,
627 snapshot: BufferSnapshot,
628 },
629 /// Move to a specific location in the active editor
630 MoveWithin {
631 target: Anchor,
632 snapshot: BufferSnapshot,
633 },
634 /// Move to a specific location in a different editor (not the active one)
635 MoveOutside {
636 target: language::Anchor,
637 snapshot: BufferSnapshot,
638 },
639}
640
641struct EditPredictionState {
642 inlay_ids: Vec<InlayId>,
643 completion: EditPrediction,
644 completion_id: Option<SharedString>,
645 invalidation_range: Option<Range<Anchor>>,
646}
647
648enum EditPredictionSettings {
649 Disabled,
650 Enabled {
651 show_in_menu: bool,
652 preview_requires_modifier: bool,
653 },
654}
655
656enum EditPredictionHighlight {}
657
658#[derive(Debug, Clone)]
659struct InlineDiagnostic {
660 message: SharedString,
661 group_id: usize,
662 is_primary: bool,
663 start: Point,
664 severity: lsp::DiagnosticSeverity,
665}
666
667pub enum MenuEditPredictionsPolicy {
668 Never,
669 ByProvider,
670}
671
672pub enum EditPredictionPreview {
673 /// Modifier is not pressed
674 Inactive { released_too_fast: bool },
675 /// Modifier pressed
676 Active {
677 since: Instant,
678 previous_scroll_position: Option<ScrollAnchor>,
679 },
680}
681
682impl EditPredictionPreview {
683 pub fn released_too_fast(&self) -> bool {
684 match self {
685 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
686 EditPredictionPreview::Active { .. } => false,
687 }
688 }
689
690 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
691 if let EditPredictionPreview::Active {
692 previous_scroll_position,
693 ..
694 } = self
695 {
696 *previous_scroll_position = scroll_position;
697 }
698 }
699}
700
701pub struct ContextMenuOptions {
702 pub min_entries_visible: usize,
703 pub max_entries_visible: usize,
704 pub placement: Option<ContextMenuPlacement>,
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
708pub enum ContextMenuPlacement {
709 Above,
710 Below,
711}
712
713#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
714struct EditorActionId(usize);
715
716impl EditorActionId {
717 pub fn post_inc(&mut self) -> Self {
718 let answer = self.0;
719
720 *self = Self(answer + 1);
721
722 Self(answer)
723 }
724}
725
726// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
727// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
728
729type BackgroundHighlight = (
730 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
731 Arc<[Range<Anchor>]>,
732);
733type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
734
735#[derive(Default)]
736struct ScrollbarMarkerState {
737 scrollbar_size: Size<Pixels>,
738 dirty: bool,
739 markers: Arc<[PaintQuad]>,
740 pending_refresh: Option<Task<Result<()>>>,
741}
742
743impl ScrollbarMarkerState {
744 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
745 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
746 }
747}
748
749#[derive(Clone, Copy, PartialEq, Eq)]
750pub enum MinimapVisibility {
751 Disabled,
752 Enabled {
753 /// The configuration currently present in the users settings.
754 setting_configuration: bool,
755 /// Whether to override the currently set visibility from the users setting.
756 toggle_override: bool,
757 },
758}
759
760impl MinimapVisibility {
761 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
762 if mode.is_full() {
763 Self::Enabled {
764 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
765 toggle_override: false,
766 }
767 } else {
768 Self::Disabled
769 }
770 }
771
772 fn hidden(&self) -> Self {
773 match *self {
774 Self::Enabled {
775 setting_configuration,
776 ..
777 } => Self::Enabled {
778 setting_configuration,
779 toggle_override: setting_configuration,
780 },
781 Self::Disabled => Self::Disabled,
782 }
783 }
784
785 fn disabled(&self) -> bool {
786 matches!(*self, Self::Disabled)
787 }
788
789 fn settings_visibility(&self) -> bool {
790 match *self {
791 Self::Enabled {
792 setting_configuration,
793 ..
794 } => setting_configuration,
795 _ => false,
796 }
797 }
798
799 fn visible(&self) -> bool {
800 match *self {
801 Self::Enabled {
802 setting_configuration,
803 toggle_override,
804 } => setting_configuration ^ toggle_override,
805 _ => false,
806 }
807 }
808
809 fn toggle_visibility(&self) -> Self {
810 match *self {
811 Self::Enabled {
812 toggle_override,
813 setting_configuration,
814 } => Self::Enabled {
815 setting_configuration,
816 toggle_override: !toggle_override,
817 },
818 Self::Disabled => Self::Disabled,
819 }
820 }
821}
822
823#[derive(Debug, Clone, Copy, PartialEq, Eq)]
824pub enum BufferSerialization {
825 All,
826 NonDirtyBuffers,
827}
828
829impl BufferSerialization {
830 fn new(restore_unsaved_buffers: bool) -> Self {
831 if restore_unsaved_buffers {
832 Self::All
833 } else {
834 Self::NonDirtyBuffers
835 }
836 }
837}
838
839#[derive(Clone, Debug)]
840struct RunnableTasks {
841 templates: Vec<(TaskSourceKind, TaskTemplate)>,
842 offset: multi_buffer::Anchor,
843 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
844 column: u32,
845 // Values of all named captures, including those starting with '_'
846 extra_variables: HashMap<String, String>,
847 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
848 context_range: Range<BufferOffset>,
849}
850
851impl RunnableTasks {
852 fn resolve<'a>(
853 &'a self,
854 cx: &'a task::TaskContext,
855 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
856 self.templates.iter().filter_map(|(kind, template)| {
857 template
858 .resolve_task(&kind.to_id_base(), cx)
859 .map(|task| (kind.clone(), task))
860 })
861 }
862}
863
864#[derive(Clone)]
865pub struct ResolvedTasks {
866 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
867 position: Anchor,
868}
869
870/// Addons allow storing per-editor state in other crates (e.g. Vim)
871pub trait Addon: 'static {
872 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
873
874 fn render_buffer_header_controls(
875 &self,
876 _: &ExcerptInfo,
877 _: &Window,
878 _: &App,
879 ) -> Option<AnyElement> {
880 None
881 }
882
883 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
884 None
885 }
886
887 fn to_any(&self) -> &dyn std::any::Any;
888
889 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
890 None
891 }
892}
893
894struct ChangeLocation {
895 current: Option<Vec<Anchor>>,
896 original: Vec<Anchor>,
897}
898impl ChangeLocation {
899 fn locations(&self) -> &[Anchor] {
900 self.current.as_ref().unwrap_or(&self.original)
901 }
902}
903
904/// A set of caret positions, registered when the editor was edited.
905pub struct ChangeList {
906 changes: Vec<ChangeLocation>,
907 /// Currently "selected" change.
908 position: Option<usize>,
909}
910
911impl ChangeList {
912 pub fn new() -> Self {
913 Self {
914 changes: Vec::new(),
915 position: None,
916 }
917 }
918
919 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
920 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
921 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
922 if self.changes.is_empty() {
923 return None;
924 }
925
926 let prev = self.position.unwrap_or(self.changes.len());
927 let next = if direction == Direction::Prev {
928 prev.saturating_sub(count)
929 } else {
930 (prev + count).min(self.changes.len() - 1)
931 };
932 self.position = Some(next);
933 self.changes.get(next).map(|change| change.locations())
934 }
935
936 /// Adds a new change to the list, resetting the change list position.
937 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
938 self.position.take();
939 if let Some(last) = self.changes.last_mut()
940 && group
941 {
942 last.current = Some(new_positions)
943 } else {
944 self.changes.push(ChangeLocation {
945 original: new_positions,
946 current: None,
947 });
948 }
949 }
950
951 pub fn last(&self) -> Option<&[Anchor]> {
952 self.changes.last().map(|change| change.locations())
953 }
954
955 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
956 self.changes.last().map(|change| change.original.as_slice())
957 }
958
959 pub fn invert_last_group(&mut self) {
960 if let Some(last) = self.changes.last_mut()
961 && let Some(current) = last.current.as_mut()
962 {
963 mem::swap(&mut last.original, current);
964 }
965 }
966}
967
968#[derive(Clone)]
969struct InlineBlamePopoverState {
970 scroll_handle: ScrollHandle,
971 commit_message: Option<ParsedCommitMessage>,
972 markdown: Entity<Markdown>,
973}
974
975struct InlineBlamePopover {
976 position: gpui::Point<Pixels>,
977 hide_task: Option<Task<()>>,
978 popover_bounds: Option<Bounds<Pixels>>,
979 popover_state: InlineBlamePopoverState,
980 keyboard_grace: bool,
981}
982
983enum SelectionDragState {
984 /// State when no drag related activity is detected.
985 None,
986 /// State when the mouse is down on a selection that is about to be dragged.
987 ReadyToDrag {
988 selection: Selection<Anchor>,
989 click_position: gpui::Point<Pixels>,
990 mouse_down_time: Instant,
991 },
992 /// State when the mouse is dragging the selection in the editor.
993 Dragging {
994 selection: Selection<Anchor>,
995 drop_cursor: Selection<Anchor>,
996 hide_drop_cursor: bool,
997 },
998}
999
1000enum ColumnarSelectionState {
1001 FromMouse {
1002 selection_tail: Anchor,
1003 display_point: Option<DisplayPoint>,
1004 },
1005 FromSelection {
1006 selection_tail: Anchor,
1007 },
1008}
1009
1010/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1011/// a breakpoint on them.
1012#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1013struct PhantomBreakpointIndicator {
1014 display_row: DisplayRow,
1015 /// There's a small debounce between hovering over the line and showing the indicator.
1016 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1017 is_active: bool,
1018 collides_with_existing_breakpoint: bool,
1019}
1020
1021/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1022///
1023/// See the [module level documentation](self) for more information.
1024pub struct Editor {
1025 focus_handle: FocusHandle,
1026 last_focused_descendant: Option<WeakFocusHandle>,
1027 /// The text buffer being edited
1028 buffer: Entity<MultiBuffer>,
1029 /// Map of how text in the buffer should be displayed.
1030 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1031 pub display_map: Entity<DisplayMap>,
1032 placeholder_display_map: Option<Entity<DisplayMap>>,
1033 pub selections: SelectionsCollection,
1034 pub scroll_manager: ScrollManager,
1035 /// When inline assist editors are linked, they all render cursors because
1036 /// typing enters text into each of them, even the ones that aren't focused.
1037 pub(crate) show_cursor_when_unfocused: bool,
1038 columnar_selection_state: Option<ColumnarSelectionState>,
1039 add_selections_state: Option<AddSelectionsState>,
1040 select_next_state: Option<SelectNextState>,
1041 select_prev_state: Option<SelectNextState>,
1042 selection_history: SelectionHistory,
1043 defer_selection_effects: bool,
1044 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1045 autoclose_regions: Vec<AutocloseRegion>,
1046 snippet_stack: InvalidationStack<SnippetState>,
1047 select_syntax_node_history: SelectSyntaxNodeHistory,
1048 ime_transaction: Option<TransactionId>,
1049 pub diagnostics_max_severity: DiagnosticSeverity,
1050 active_diagnostics: ActiveDiagnostic,
1051 show_inline_diagnostics: bool,
1052 inline_diagnostics_update: Task<()>,
1053 inline_diagnostics_enabled: bool,
1054 diagnostics_enabled: bool,
1055 word_completions_enabled: bool,
1056 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1057 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1058 hard_wrap: Option<usize>,
1059 project: Option<Entity<Project>>,
1060 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1061 completion_provider: Option<Rc<dyn CompletionProvider>>,
1062 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1063 blink_manager: Entity<BlinkManager>,
1064 show_cursor_names: bool,
1065 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1066 pub show_local_selections: bool,
1067 mode: EditorMode,
1068 show_breadcrumbs: bool,
1069 show_gutter: bool,
1070 show_scrollbars: ScrollbarAxes,
1071 minimap_visibility: MinimapVisibility,
1072 offset_content: bool,
1073 disable_expand_excerpt_buttons: bool,
1074 show_line_numbers: Option<bool>,
1075 use_relative_line_numbers: Option<bool>,
1076 show_git_diff_gutter: Option<bool>,
1077 show_code_actions: Option<bool>,
1078 show_runnables: Option<bool>,
1079 show_breakpoints: Option<bool>,
1080 show_wrap_guides: Option<bool>,
1081 show_indent_guides: Option<bool>,
1082 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1083 highlight_order: usize,
1084 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1085 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1086 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1087 scrollbar_marker_state: ScrollbarMarkerState,
1088 active_indent_guides_state: ActiveIndentGuidesState,
1089 nav_history: Option<ItemNavHistory>,
1090 context_menu: RefCell<Option<CodeContextMenu>>,
1091 context_menu_options: Option<ContextMenuOptions>,
1092 mouse_context_menu: Option<MouseContextMenu>,
1093 completion_tasks: Vec<(CompletionId, Task<()>)>,
1094 inline_blame_popover: Option<InlineBlamePopover>,
1095 inline_blame_popover_show_task: Option<Task<()>>,
1096 signature_help_state: SignatureHelpState,
1097 auto_signature_help: Option<bool>,
1098 find_all_references_task_sources: Vec<Anchor>,
1099 next_completion_id: CompletionId,
1100 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1101 code_actions_task: Option<Task<Result<()>>>,
1102 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1103 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1104 document_highlights_task: Option<Task<()>>,
1105 linked_editing_range_task: Option<Task<Option<()>>>,
1106 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1107 pending_rename: Option<RenameState>,
1108 searchable: bool,
1109 cursor_shape: CursorShape,
1110 /// Whether the cursor is offset one character to the left when something is
1111 /// selected (needed for vim visual mode)
1112 cursor_offset_on_selection: bool,
1113 current_line_highlight: Option<CurrentLineHighlight>,
1114 pub collapse_matches: bool,
1115 autoindent_mode: Option<AutoindentMode>,
1116 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1117 input_enabled: bool,
1118 use_modal_editing: bool,
1119 read_only: bool,
1120 leader_id: Option<CollaboratorId>,
1121 remote_id: Option<ViewId>,
1122 pub hover_state: HoverState,
1123 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1124 prev_pressure_stage: Option<PressureStage>,
1125 gutter_hovered: bool,
1126 hovered_link_state: Option<HoveredLinkState>,
1127 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1128 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1129 active_edit_prediction: Option<EditPredictionState>,
1130 /// Used to prevent flickering as the user types while the menu is open
1131 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1132 edit_prediction_settings: EditPredictionSettings,
1133 edit_predictions_hidden_for_vim_mode: bool,
1134 show_edit_predictions_override: Option<bool>,
1135 show_completions_on_input_override: Option<bool>,
1136 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1137 edit_prediction_preview: EditPredictionPreview,
1138 edit_prediction_indent_conflict: bool,
1139 edit_prediction_requires_modifier_in_indent_conflict: bool,
1140 next_inlay_id: usize,
1141 next_color_inlay_id: usize,
1142 _subscriptions: Vec<Subscription>,
1143 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1144 gutter_dimensions: GutterDimensions,
1145 style: Option<EditorStyle>,
1146 text_style_refinement: Option<TextStyleRefinement>,
1147 next_editor_action_id: EditorActionId,
1148 editor_actions: Rc<
1149 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1150 >,
1151 use_autoclose: bool,
1152 use_auto_surround: bool,
1153 auto_replace_emoji_shortcode: bool,
1154 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1155 show_git_blame_gutter: bool,
1156 show_git_blame_inline: bool,
1157 show_git_blame_inline_delay_task: Option<Task<()>>,
1158 git_blame_inline_enabled: bool,
1159 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1160 buffer_serialization: Option<BufferSerialization>,
1161 show_selection_menu: Option<bool>,
1162 blame: Option<Entity<GitBlame>>,
1163 blame_subscription: Option<Subscription>,
1164 custom_context_menu: Option<
1165 Box<
1166 dyn 'static
1167 + Fn(
1168 &mut Self,
1169 DisplayPoint,
1170 &mut Window,
1171 &mut Context<Self>,
1172 ) -> Option<Entity<ui::ContextMenu>>,
1173 >,
1174 >,
1175 last_bounds: Option<Bounds<Pixels>>,
1176 last_position_map: Option<Rc<PositionMap>>,
1177 expect_bounds_change: Option<Bounds<Pixels>>,
1178 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1179 tasks_update_task: Option<Task<()>>,
1180 breakpoint_store: Option<Entity<BreakpointStore>>,
1181 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1182 hovered_diff_hunk_row: Option<DisplayRow>,
1183 pull_diagnostics_task: Task<()>,
1184 pull_diagnostics_background_task: Task<()>,
1185 in_project_search: bool,
1186 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1187 breadcrumb_header: Option<String>,
1188 focused_block: Option<FocusedBlock>,
1189 next_scroll_position: NextScrollCursorCenterTopBottom,
1190 addons: HashMap<TypeId, Box<dyn Addon>>,
1191 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1192 load_diff_task: Option<Shared<Task<()>>>,
1193 /// Whether we are temporarily displaying a diff other than git's
1194 temporary_diff_override: bool,
1195 selection_mark_mode: bool,
1196 toggle_fold_multiple_buffers: Task<()>,
1197 _scroll_cursor_center_top_bottom_task: Task<()>,
1198 serialize_selections: Task<()>,
1199 serialize_folds: Task<()>,
1200 mouse_cursor_hidden: bool,
1201 minimap: Option<Entity<Self>>,
1202 hide_mouse_mode: HideMouseMode,
1203 pub change_list: ChangeList,
1204 inline_value_cache: InlineValueCache,
1205
1206 selection_drag_state: SelectionDragState,
1207 colors: Option<LspColorData>,
1208 post_scroll_update: Task<()>,
1209 refresh_colors_task: Task<()>,
1210 inlay_hints: Option<LspInlayHintData>,
1211 folding_newlines: Task<()>,
1212 select_next_is_case_sensitive: Option<bool>,
1213 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1214 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1215 accent_data: Option<AccentData>,
1216 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1217 use_base_text_line_numbers: bool,
1218}
1219
1220#[derive(Debug, PartialEq)]
1221struct AccentData {
1222 colors: AccentColors,
1223 overrides: Vec<SharedString>,
1224}
1225
1226fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1227 if debounce_ms > 0 {
1228 Some(Duration::from_millis(debounce_ms))
1229 } else {
1230 None
1231 }
1232}
1233
1234#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1235enum NextScrollCursorCenterTopBottom {
1236 #[default]
1237 Center,
1238 Top,
1239 Bottom,
1240}
1241
1242impl NextScrollCursorCenterTopBottom {
1243 fn next(&self) -> Self {
1244 match self {
1245 Self::Center => Self::Top,
1246 Self::Top => Self::Bottom,
1247 Self::Bottom => Self::Center,
1248 }
1249 }
1250}
1251
1252#[derive(Clone)]
1253pub struct EditorSnapshot {
1254 pub mode: EditorMode,
1255 show_gutter: bool,
1256 offset_content: bool,
1257 show_line_numbers: Option<bool>,
1258 show_git_diff_gutter: Option<bool>,
1259 show_code_actions: Option<bool>,
1260 show_runnables: Option<bool>,
1261 show_breakpoints: Option<bool>,
1262 git_blame_gutter_max_author_length: Option<usize>,
1263 pub display_snapshot: DisplaySnapshot,
1264 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1265 is_focused: bool,
1266 scroll_anchor: ScrollAnchor,
1267 ongoing_scroll: OngoingScroll,
1268 current_line_highlight: CurrentLineHighlight,
1269 gutter_hovered: bool,
1270}
1271
1272#[derive(Default, Debug, Clone, Copy)]
1273pub struct GutterDimensions {
1274 pub left_padding: Pixels,
1275 pub right_padding: Pixels,
1276 pub width: Pixels,
1277 pub margin: Pixels,
1278 pub git_blame_entries_width: Option<Pixels>,
1279}
1280
1281impl GutterDimensions {
1282 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1283 Self {
1284 margin: Self::default_gutter_margin(font_id, font_size, cx),
1285 ..Default::default()
1286 }
1287 }
1288
1289 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1290 -cx.text_system().descent(font_id, font_size)
1291 }
1292 /// The full width of the space taken up by the gutter.
1293 pub fn full_width(&self) -> Pixels {
1294 self.margin + self.width
1295 }
1296
1297 /// The width of the space reserved for the fold indicators,
1298 /// use alongside 'justify_end' and `gutter_width` to
1299 /// right align content with the line numbers
1300 pub fn fold_area_width(&self) -> Pixels {
1301 self.margin + self.right_padding
1302 }
1303}
1304
1305struct CharacterDimensions {
1306 em_width: Pixels,
1307 em_advance: Pixels,
1308 line_height: Pixels,
1309}
1310
1311#[derive(Debug)]
1312pub struct RemoteSelection {
1313 pub replica_id: ReplicaId,
1314 pub selection: Selection<Anchor>,
1315 pub cursor_shape: CursorShape,
1316 pub collaborator_id: CollaboratorId,
1317 pub line_mode: bool,
1318 pub user_name: Option<SharedString>,
1319 pub color: PlayerColor,
1320}
1321
1322#[derive(Clone, Debug)]
1323struct SelectionHistoryEntry {
1324 selections: Arc<[Selection<Anchor>]>,
1325 select_next_state: Option<SelectNextState>,
1326 select_prev_state: Option<SelectNextState>,
1327 add_selections_state: Option<AddSelectionsState>,
1328}
1329
1330#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1331enum SelectionHistoryMode {
1332 #[default]
1333 Normal,
1334 Undoing,
1335 Redoing,
1336 Skipping,
1337}
1338
1339#[derive(Clone, PartialEq, Eq, Hash)]
1340struct HoveredCursor {
1341 replica_id: ReplicaId,
1342 selection_id: usize,
1343}
1344
1345#[derive(Debug)]
1346/// SelectionEffects controls the side-effects of updating the selection.
1347///
1348/// The default behaviour does "what you mostly want":
1349/// - it pushes to the nav history if the cursor moved by >10 lines
1350/// - it re-triggers completion requests
1351/// - it scrolls to fit
1352///
1353/// You might want to modify these behaviours. For example when doing a "jump"
1354/// like go to definition, we always want to add to nav history; but when scrolling
1355/// in vim mode we never do.
1356///
1357/// Similarly, you might want to disable scrolling if you don't want the viewport to
1358/// move.
1359#[derive(Clone)]
1360pub struct SelectionEffects {
1361 nav_history: Option<bool>,
1362 completions: bool,
1363 scroll: Option<Autoscroll>,
1364}
1365
1366impl Default for SelectionEffects {
1367 fn default() -> Self {
1368 Self {
1369 nav_history: None,
1370 completions: true,
1371 scroll: Some(Autoscroll::fit()),
1372 }
1373 }
1374}
1375impl SelectionEffects {
1376 pub fn scroll(scroll: Autoscroll) -> Self {
1377 Self {
1378 scroll: Some(scroll),
1379 ..Default::default()
1380 }
1381 }
1382
1383 pub fn no_scroll() -> Self {
1384 Self {
1385 scroll: None,
1386 ..Default::default()
1387 }
1388 }
1389
1390 pub fn completions(self, completions: bool) -> Self {
1391 Self {
1392 completions,
1393 ..self
1394 }
1395 }
1396
1397 pub fn nav_history(self, nav_history: bool) -> Self {
1398 Self {
1399 nav_history: Some(nav_history),
1400 ..self
1401 }
1402 }
1403}
1404
1405struct DeferredSelectionEffectsState {
1406 changed: bool,
1407 effects: SelectionEffects,
1408 old_cursor_position: Anchor,
1409 history_entry: SelectionHistoryEntry,
1410}
1411
1412#[derive(Default)]
1413struct SelectionHistory {
1414 #[allow(clippy::type_complexity)]
1415 selections_by_transaction:
1416 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1417 mode: SelectionHistoryMode,
1418 undo_stack: VecDeque<SelectionHistoryEntry>,
1419 redo_stack: VecDeque<SelectionHistoryEntry>,
1420}
1421
1422impl SelectionHistory {
1423 #[track_caller]
1424 fn insert_transaction(
1425 &mut self,
1426 transaction_id: TransactionId,
1427 selections: Arc<[Selection<Anchor>]>,
1428 ) {
1429 if selections.is_empty() {
1430 log::error!(
1431 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1432 std::panic::Location::caller()
1433 );
1434 return;
1435 }
1436 self.selections_by_transaction
1437 .insert(transaction_id, (selections, None));
1438 }
1439
1440 #[allow(clippy::type_complexity)]
1441 fn transaction(
1442 &self,
1443 transaction_id: TransactionId,
1444 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1445 self.selections_by_transaction.get(&transaction_id)
1446 }
1447
1448 #[allow(clippy::type_complexity)]
1449 fn transaction_mut(
1450 &mut self,
1451 transaction_id: TransactionId,
1452 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1453 self.selections_by_transaction.get_mut(&transaction_id)
1454 }
1455
1456 fn push(&mut self, entry: SelectionHistoryEntry) {
1457 if !entry.selections.is_empty() {
1458 match self.mode {
1459 SelectionHistoryMode::Normal => {
1460 self.push_undo(entry);
1461 self.redo_stack.clear();
1462 }
1463 SelectionHistoryMode::Undoing => self.push_redo(entry),
1464 SelectionHistoryMode::Redoing => self.push_undo(entry),
1465 SelectionHistoryMode::Skipping => {}
1466 }
1467 }
1468 }
1469
1470 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1471 if self
1472 .undo_stack
1473 .back()
1474 .is_none_or(|e| e.selections != entry.selections)
1475 {
1476 self.undo_stack.push_back(entry);
1477 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1478 self.undo_stack.pop_front();
1479 }
1480 }
1481 }
1482
1483 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1484 if self
1485 .redo_stack
1486 .back()
1487 .is_none_or(|e| e.selections != entry.selections)
1488 {
1489 self.redo_stack.push_back(entry);
1490 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1491 self.redo_stack.pop_front();
1492 }
1493 }
1494 }
1495}
1496
1497#[derive(Clone, Copy)]
1498pub struct RowHighlightOptions {
1499 pub autoscroll: bool,
1500 pub include_gutter: bool,
1501}
1502
1503impl Default for RowHighlightOptions {
1504 fn default() -> Self {
1505 Self {
1506 autoscroll: Default::default(),
1507 include_gutter: true,
1508 }
1509 }
1510}
1511
1512struct RowHighlight {
1513 index: usize,
1514 range: Range<Anchor>,
1515 color: Hsla,
1516 options: RowHighlightOptions,
1517 type_id: TypeId,
1518}
1519
1520#[derive(Clone, Debug)]
1521struct AddSelectionsState {
1522 groups: Vec<AddSelectionsGroup>,
1523}
1524
1525#[derive(Clone, Debug)]
1526struct AddSelectionsGroup {
1527 above: bool,
1528 stack: Vec<usize>,
1529}
1530
1531#[derive(Clone)]
1532struct SelectNextState {
1533 query: AhoCorasick,
1534 wordwise: bool,
1535 done: bool,
1536}
1537
1538impl std::fmt::Debug for SelectNextState {
1539 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1540 f.debug_struct(std::any::type_name::<Self>())
1541 .field("wordwise", &self.wordwise)
1542 .field("done", &self.done)
1543 .finish()
1544 }
1545}
1546
1547#[derive(Debug)]
1548struct AutocloseRegion {
1549 selection_id: usize,
1550 range: Range<Anchor>,
1551 pair: BracketPair,
1552}
1553
1554#[derive(Debug)]
1555struct SnippetState {
1556 ranges: Vec<Vec<Range<Anchor>>>,
1557 active_index: usize,
1558 choices: Vec<Option<Vec<String>>>,
1559}
1560
1561#[doc(hidden)]
1562pub struct RenameState {
1563 pub range: Range<Anchor>,
1564 pub old_name: Arc<str>,
1565 pub editor: Entity<Editor>,
1566 block_id: CustomBlockId,
1567}
1568
1569struct InvalidationStack<T>(Vec<T>);
1570
1571struct RegisteredEditPredictionDelegate {
1572 provider: Arc<dyn EditPredictionDelegateHandle>,
1573 _subscription: Subscription,
1574}
1575
1576#[derive(Debug, PartialEq, Eq)]
1577pub struct ActiveDiagnosticGroup {
1578 pub active_range: Range<Anchor>,
1579 pub active_message: String,
1580 pub group_id: usize,
1581 pub blocks: HashSet<CustomBlockId>,
1582}
1583
1584#[derive(Debug, PartialEq, Eq)]
1585
1586pub(crate) enum ActiveDiagnostic {
1587 None,
1588 All,
1589 Group(ActiveDiagnosticGroup),
1590}
1591
1592#[derive(Serialize, Deserialize, Clone, Debug)]
1593pub struct ClipboardSelection {
1594 /// The number of bytes in this selection.
1595 pub len: usize,
1596 /// Whether this was a full-line selection.
1597 pub is_entire_line: bool,
1598 /// The indentation of the first line when this content was originally copied.
1599 pub first_line_indent: u32,
1600 #[serde(default)]
1601 pub file_path: Option<PathBuf>,
1602 #[serde(default)]
1603 pub line_range: Option<RangeInclusive<u32>>,
1604}
1605
1606impl ClipboardSelection {
1607 pub fn for_buffer(
1608 len: usize,
1609 is_entire_line: bool,
1610 range: Range<Point>,
1611 buffer: &MultiBufferSnapshot,
1612 project: Option<&Entity<Project>>,
1613 cx: &App,
1614 ) -> Self {
1615 let first_line_indent = buffer
1616 .indent_size_for_line(MultiBufferRow(range.start.row))
1617 .len;
1618
1619 let file_path = util::maybe!({
1620 let project = project?.read(cx);
1621 let file = buffer.file_at(range.start)?;
1622 let project_path = ProjectPath {
1623 worktree_id: file.worktree_id(cx),
1624 path: file.path().clone(),
1625 };
1626 project.absolute_path(&project_path, cx)
1627 });
1628
1629 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1630
1631 Self {
1632 len,
1633 is_entire_line,
1634 first_line_indent,
1635 file_path,
1636 line_range,
1637 }
1638 }
1639}
1640
1641// selections, scroll behavior, was newest selection reversed
1642type SelectSyntaxNodeHistoryState = (
1643 Box<[Selection<MultiBufferOffset>]>,
1644 SelectSyntaxNodeScrollBehavior,
1645 bool,
1646);
1647
1648#[derive(Default)]
1649struct SelectSyntaxNodeHistory {
1650 stack: Vec<SelectSyntaxNodeHistoryState>,
1651 // disable temporarily to allow changing selections without losing the stack
1652 pub disable_clearing: bool,
1653}
1654
1655impl SelectSyntaxNodeHistory {
1656 pub fn try_clear(&mut self) {
1657 if !self.disable_clearing {
1658 self.stack.clear();
1659 }
1660 }
1661
1662 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1663 self.stack.push(selection);
1664 }
1665
1666 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1667 self.stack.pop()
1668 }
1669}
1670
1671enum SelectSyntaxNodeScrollBehavior {
1672 CursorTop,
1673 FitSelection,
1674 CursorBottom,
1675}
1676
1677#[derive(Debug)]
1678pub(crate) struct NavigationData {
1679 cursor_anchor: Anchor,
1680 cursor_position: Point,
1681 scroll_anchor: ScrollAnchor,
1682 scroll_top_row: u32,
1683}
1684
1685#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1686pub enum GotoDefinitionKind {
1687 Symbol,
1688 Declaration,
1689 Type,
1690 Implementation,
1691}
1692
1693pub enum FormatTarget {
1694 Buffers(HashSet<Entity<Buffer>>),
1695 Ranges(Vec<Range<MultiBufferPoint>>),
1696}
1697
1698pub(crate) struct FocusedBlock {
1699 id: BlockId,
1700 focus_handle: WeakFocusHandle,
1701}
1702
1703#[derive(Clone, Debug)]
1704enum JumpData {
1705 MultiBufferRow {
1706 row: MultiBufferRow,
1707 line_offset_from_top: u32,
1708 },
1709 MultiBufferPoint {
1710 excerpt_id: ExcerptId,
1711 position: Point,
1712 anchor: text::Anchor,
1713 line_offset_from_top: u32,
1714 },
1715}
1716
1717pub enum MultibufferSelectionMode {
1718 First,
1719 All,
1720}
1721
1722#[derive(Clone, Copy, Debug, Default)]
1723pub struct RewrapOptions {
1724 pub override_language_settings: bool,
1725 pub preserve_existing_whitespace: bool,
1726}
1727
1728impl Editor {
1729 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1730 let buffer = cx.new(|cx| Buffer::local("", cx));
1731 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1732 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1733 }
1734
1735 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1736 let buffer = cx.new(|cx| Buffer::local("", cx));
1737 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1738 Self::new(EditorMode::full(), buffer, None, window, cx)
1739 }
1740
1741 pub fn auto_height(
1742 min_lines: usize,
1743 max_lines: usize,
1744 window: &mut Window,
1745 cx: &mut Context<Self>,
1746 ) -> Self {
1747 let buffer = cx.new(|cx| Buffer::local("", cx));
1748 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1749 Self::new(
1750 EditorMode::AutoHeight {
1751 min_lines,
1752 max_lines: Some(max_lines),
1753 },
1754 buffer,
1755 None,
1756 window,
1757 cx,
1758 )
1759 }
1760
1761 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1762 /// The editor grows as tall as needed to fit its content.
1763 pub fn auto_height_unbounded(
1764 min_lines: usize,
1765 window: &mut Window,
1766 cx: &mut Context<Self>,
1767 ) -> Self {
1768 let buffer = cx.new(|cx| Buffer::local("", cx));
1769 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1770 Self::new(
1771 EditorMode::AutoHeight {
1772 min_lines,
1773 max_lines: None,
1774 },
1775 buffer,
1776 None,
1777 window,
1778 cx,
1779 )
1780 }
1781
1782 pub fn for_buffer(
1783 buffer: Entity<Buffer>,
1784 project: Option<Entity<Project>>,
1785 window: &mut Window,
1786 cx: &mut Context<Self>,
1787 ) -> Self {
1788 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1789 Self::new(EditorMode::full(), buffer, project, window, cx)
1790 }
1791
1792 pub fn for_multibuffer(
1793 buffer: Entity<MultiBuffer>,
1794 project: Option<Entity<Project>>,
1795 window: &mut Window,
1796 cx: &mut Context<Self>,
1797 ) -> Self {
1798 Self::new(EditorMode::full(), buffer, project, window, cx)
1799 }
1800
1801 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1802 let mut clone = Self::new(
1803 self.mode.clone(),
1804 self.buffer.clone(),
1805 self.project.clone(),
1806 window,
1807 cx,
1808 );
1809 self.display_map.update(cx, |display_map, cx| {
1810 let snapshot = display_map.snapshot(cx);
1811 clone.display_map.update(cx, |display_map, cx| {
1812 display_map.set_state(&snapshot, cx);
1813 });
1814 });
1815 clone.folds_did_change(cx);
1816 clone.selections.clone_state(&self.selections);
1817 clone.scroll_manager.clone_state(&self.scroll_manager);
1818 clone.searchable = self.searchable;
1819 clone.read_only = self.read_only;
1820 clone
1821 }
1822
1823 pub fn new(
1824 mode: EditorMode,
1825 buffer: Entity<MultiBuffer>,
1826 project: Option<Entity<Project>>,
1827 window: &mut Window,
1828 cx: &mut Context<Self>,
1829 ) -> Self {
1830 Editor::new_internal(mode, buffer, project, None, window, cx)
1831 }
1832
1833 pub fn sticky_headers(
1834 &self,
1835 style: &EditorStyle,
1836 cx: &App,
1837 ) -> Option<Vec<OutlineItem<Anchor>>> {
1838 let multi_buffer = self.buffer().read(cx);
1839 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1840 let multi_buffer_visible_start = self
1841 .scroll_manager
1842 .anchor()
1843 .anchor
1844 .to_point(&multi_buffer_snapshot);
1845 let max_row = multi_buffer_snapshot.max_point().row;
1846
1847 let start_row = (multi_buffer_visible_start.row).min(max_row);
1848 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1849
1850 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1851 let outline_items = buffer
1852 .outline_items_containing(
1853 Point::new(start_row, 0)..Point::new(end_row, 0),
1854 true,
1855 Some(style.syntax.as_ref()),
1856 )
1857 .into_iter()
1858 .map(|outline_item| OutlineItem {
1859 depth: outline_item.depth,
1860 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1861 source_range_for_text: Anchor::range_in_buffer(
1862 *excerpt_id,
1863 outline_item.source_range_for_text,
1864 ),
1865 text: outline_item.text,
1866 highlight_ranges: outline_item.highlight_ranges,
1867 name_ranges: outline_item.name_ranges,
1868 body_range: outline_item
1869 .body_range
1870 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1871 annotation_range: outline_item
1872 .annotation_range
1873 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1874 });
1875 return Some(outline_items.collect());
1876 }
1877
1878 None
1879 }
1880
1881 fn new_internal(
1882 mode: EditorMode,
1883 multi_buffer: Entity<MultiBuffer>,
1884 project: Option<Entity<Project>>,
1885 display_map: Option<Entity<DisplayMap>>,
1886 window: &mut Window,
1887 cx: &mut Context<Self>,
1888 ) -> Self {
1889 debug_assert!(
1890 display_map.is_none() || mode.is_minimap(),
1891 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1892 );
1893
1894 let full_mode = mode.is_full();
1895 let is_minimap = mode.is_minimap();
1896 let diagnostics_max_severity = if full_mode {
1897 EditorSettings::get_global(cx)
1898 .diagnostics_max_severity
1899 .unwrap_or(DiagnosticSeverity::Hint)
1900 } else {
1901 DiagnosticSeverity::Off
1902 };
1903 let style = window.text_style();
1904 let font_size = style.font_size.to_pixels(window.rem_size());
1905 let editor = cx.entity().downgrade();
1906 let fold_placeholder = FoldPlaceholder {
1907 constrain_width: false,
1908 render: Arc::new(move |fold_id, fold_range, cx| {
1909 let editor = editor.clone();
1910 div()
1911 .id(fold_id)
1912 .bg(cx.theme().colors().ghost_element_background)
1913 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1914 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1915 .rounded_xs()
1916 .size_full()
1917 .cursor_pointer()
1918 .child("⋯")
1919 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1920 .on_click(move |_, _window, cx| {
1921 editor
1922 .update(cx, |editor, cx| {
1923 editor.unfold_ranges(
1924 &[fold_range.start..fold_range.end],
1925 true,
1926 false,
1927 cx,
1928 );
1929 cx.stop_propagation();
1930 })
1931 .ok();
1932 })
1933 .into_any()
1934 }),
1935 merge_adjacent: true,
1936 ..FoldPlaceholder::default()
1937 };
1938 let display_map = display_map.unwrap_or_else(|| {
1939 cx.new(|cx| {
1940 DisplayMap::new(
1941 multi_buffer.clone(),
1942 style.font(),
1943 font_size,
1944 None,
1945 FILE_HEADER_HEIGHT,
1946 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1947 fold_placeholder,
1948 diagnostics_max_severity,
1949 cx,
1950 )
1951 })
1952 });
1953
1954 let selections = SelectionsCollection::new();
1955
1956 let blink_manager = cx.new(|cx| {
1957 let mut blink_manager = BlinkManager::new(
1958 CURSOR_BLINK_INTERVAL,
1959 |cx| EditorSettings::get_global(cx).cursor_blink,
1960 cx,
1961 );
1962 if is_minimap {
1963 blink_manager.disable(cx);
1964 }
1965 blink_manager
1966 });
1967
1968 let soft_wrap_mode_override =
1969 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1970
1971 let mut project_subscriptions = Vec::new();
1972 if full_mode && let Some(project) = project.as_ref() {
1973 project_subscriptions.push(cx.subscribe_in(
1974 project,
1975 window,
1976 |editor, _, event, window, cx| match event {
1977 project::Event::RefreshCodeLens => {
1978 // we always query lens with actions, without storing them, always refreshing them
1979 }
1980 project::Event::RefreshInlayHints {
1981 server_id,
1982 request_id,
1983 } => {
1984 editor.refresh_inlay_hints(
1985 InlayHintRefreshReason::RefreshRequested {
1986 server_id: *server_id,
1987 request_id: *request_id,
1988 },
1989 cx,
1990 );
1991 }
1992 project::Event::LanguageServerRemoved(..) => {
1993 if editor.tasks_update_task.is_none() {
1994 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1995 }
1996 editor.registered_buffers.clear();
1997 editor.register_visible_buffers(cx);
1998 }
1999 project::Event::LanguageServerAdded(..) => {
2000 if editor.tasks_update_task.is_none() {
2001 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2002 }
2003 }
2004 project::Event::SnippetEdit(id, snippet_edits) => {
2005 // todo(lw): Non singletons
2006 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2007 let snapshot = buffer.read(cx).snapshot();
2008 let focus_handle = editor.focus_handle(cx);
2009 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2010 for (range, snippet) in snippet_edits {
2011 let buffer_range =
2012 language::range_from_lsp(*range).to_offset(&snapshot);
2013 editor
2014 .insert_snippet(
2015 &[MultiBufferOffset(buffer_range.start)
2016 ..MultiBufferOffset(buffer_range.end)],
2017 snippet.clone(),
2018 window,
2019 cx,
2020 )
2021 .ok();
2022 }
2023 }
2024 }
2025 }
2026 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2027 let buffer_id = *buffer_id;
2028 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2029 editor.register_buffer(buffer_id, cx);
2030 editor.update_lsp_data(Some(buffer_id), window, cx);
2031 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2032 refresh_linked_ranges(editor, window, cx);
2033 editor.refresh_code_actions(window, cx);
2034 editor.refresh_document_highlights(cx);
2035 }
2036 }
2037
2038 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2039 let Some(workspace) = editor.workspace() else {
2040 return;
2041 };
2042 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2043 else {
2044 return;
2045 };
2046
2047 if active_editor.entity_id() == cx.entity_id() {
2048 let entity_id = cx.entity_id();
2049 workspace.update(cx, |this, cx| {
2050 this.panes_mut()
2051 .iter_mut()
2052 .filter(|pane| pane.entity_id() != entity_id)
2053 .for_each(|p| {
2054 p.update(cx, |pane, _| {
2055 pane.nav_history_mut().rename_item(
2056 entity_id,
2057 project_path.clone(),
2058 abs_path.clone().into(),
2059 );
2060 })
2061 });
2062 });
2063
2064 Self::open_transaction_for_hidden_buffers(
2065 workspace,
2066 transaction.clone(),
2067 "Rename".to_string(),
2068 window,
2069 cx,
2070 );
2071 }
2072 }
2073
2074 project::Event::WorkspaceEditApplied(transaction) => {
2075 let Some(workspace) = editor.workspace() else {
2076 return;
2077 };
2078 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2079 else {
2080 return;
2081 };
2082
2083 if active_editor.entity_id() == cx.entity_id() {
2084 Self::open_transaction_for_hidden_buffers(
2085 workspace,
2086 transaction.clone(),
2087 "LSP Edit".to_string(),
2088 window,
2089 cx,
2090 );
2091 }
2092 }
2093
2094 _ => {}
2095 },
2096 ));
2097 if let Some(task_inventory) = project
2098 .read(cx)
2099 .task_store()
2100 .read(cx)
2101 .task_inventory()
2102 .cloned()
2103 {
2104 project_subscriptions.push(cx.observe_in(
2105 &task_inventory,
2106 window,
2107 |editor, _, window, cx| {
2108 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2109 },
2110 ));
2111 };
2112
2113 project_subscriptions.push(cx.subscribe_in(
2114 &project.read(cx).breakpoint_store(),
2115 window,
2116 |editor, _, event, window, cx| match event {
2117 BreakpointStoreEvent::ClearDebugLines => {
2118 editor.clear_row_highlights::<ActiveDebugLine>();
2119 editor.refresh_inline_values(cx);
2120 }
2121 BreakpointStoreEvent::SetDebugLine => {
2122 if editor.go_to_active_debug_line(window, cx) {
2123 cx.stop_propagation();
2124 }
2125
2126 editor.refresh_inline_values(cx);
2127 }
2128 _ => {}
2129 },
2130 ));
2131 let git_store = project.read(cx).git_store().clone();
2132 let project = project.clone();
2133 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2134 if let GitStoreEvent::RepositoryAdded = event {
2135 this.load_diff_task = Some(
2136 update_uncommitted_diff_for_buffer(
2137 cx.entity(),
2138 &project,
2139 this.buffer.read(cx).all_buffers(),
2140 this.buffer.clone(),
2141 cx,
2142 )
2143 .shared(),
2144 );
2145 }
2146 }));
2147 }
2148
2149 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2150
2151 let inlay_hint_settings =
2152 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2153 let focus_handle = cx.focus_handle();
2154 if !is_minimap {
2155 cx.on_focus(&focus_handle, window, Self::handle_focus)
2156 .detach();
2157 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2158 .detach();
2159 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2160 .detach();
2161 cx.on_blur(&focus_handle, window, Self::handle_blur)
2162 .detach();
2163 cx.observe_pending_input(window, Self::observe_pending_input)
2164 .detach();
2165 }
2166
2167 let show_indent_guides =
2168 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2169 Some(false)
2170 } else {
2171 None
2172 };
2173
2174 let breakpoint_store = match (&mode, project.as_ref()) {
2175 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2176 _ => None,
2177 };
2178
2179 let mut code_action_providers = Vec::new();
2180 let mut load_uncommitted_diff = None;
2181 if let Some(project) = project.clone() {
2182 load_uncommitted_diff = Some(
2183 update_uncommitted_diff_for_buffer(
2184 cx.entity(),
2185 &project,
2186 multi_buffer.read(cx).all_buffers(),
2187 multi_buffer.clone(),
2188 cx,
2189 )
2190 .shared(),
2191 );
2192 code_action_providers.push(Rc::new(project) as Rc<_>);
2193 }
2194
2195 let mut editor = Self {
2196 focus_handle,
2197 show_cursor_when_unfocused: false,
2198 last_focused_descendant: None,
2199 buffer: multi_buffer.clone(),
2200 display_map: display_map.clone(),
2201 placeholder_display_map: None,
2202 selections,
2203 scroll_manager: ScrollManager::new(cx),
2204 columnar_selection_state: None,
2205 add_selections_state: None,
2206 select_next_state: None,
2207 select_prev_state: None,
2208 selection_history: SelectionHistory::default(),
2209 defer_selection_effects: false,
2210 deferred_selection_effects_state: None,
2211 autoclose_regions: Vec::new(),
2212 snippet_stack: InvalidationStack::default(),
2213 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2214 ime_transaction: None,
2215 active_diagnostics: ActiveDiagnostic::None,
2216 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2217 inline_diagnostics_update: Task::ready(()),
2218 inline_diagnostics: Vec::new(),
2219 soft_wrap_mode_override,
2220 diagnostics_max_severity,
2221 hard_wrap: None,
2222 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2223 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2224 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2225 project,
2226 blink_manager: blink_manager.clone(),
2227 show_local_selections: true,
2228 show_scrollbars: ScrollbarAxes {
2229 horizontal: full_mode,
2230 vertical: full_mode,
2231 },
2232 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2233 offset_content: !matches!(mode, EditorMode::SingleLine),
2234 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2235 show_gutter: full_mode,
2236 show_line_numbers: (!full_mode).then_some(false),
2237 use_relative_line_numbers: None,
2238 disable_expand_excerpt_buttons: !full_mode,
2239 show_git_diff_gutter: None,
2240 show_code_actions: None,
2241 show_runnables: None,
2242 show_breakpoints: None,
2243 show_wrap_guides: None,
2244 show_indent_guides,
2245 buffers_with_disabled_indent_guides: HashSet::default(),
2246 highlight_order: 0,
2247 highlighted_rows: HashMap::default(),
2248 background_highlights: HashMap::default(),
2249 gutter_highlights: HashMap::default(),
2250 scrollbar_marker_state: ScrollbarMarkerState::default(),
2251 active_indent_guides_state: ActiveIndentGuidesState::default(),
2252 nav_history: None,
2253 context_menu: RefCell::new(None),
2254 context_menu_options: None,
2255 mouse_context_menu: None,
2256 completion_tasks: Vec::new(),
2257 inline_blame_popover: None,
2258 inline_blame_popover_show_task: None,
2259 signature_help_state: SignatureHelpState::default(),
2260 auto_signature_help: None,
2261 find_all_references_task_sources: Vec::new(),
2262 next_completion_id: 0,
2263 next_inlay_id: 0,
2264 code_action_providers,
2265 available_code_actions: None,
2266 code_actions_task: None,
2267 quick_selection_highlight_task: None,
2268 debounced_selection_highlight_task: None,
2269 document_highlights_task: None,
2270 linked_editing_range_task: None,
2271 pending_rename: None,
2272 searchable: !is_minimap,
2273 cursor_shape: EditorSettings::get_global(cx)
2274 .cursor_shape
2275 .unwrap_or_default(),
2276 cursor_offset_on_selection: false,
2277 current_line_highlight: None,
2278 autoindent_mode: Some(AutoindentMode::EachLine),
2279 collapse_matches: false,
2280 workspace: None,
2281 input_enabled: !is_minimap,
2282 use_modal_editing: full_mode,
2283 read_only: is_minimap,
2284 use_autoclose: true,
2285 use_auto_surround: true,
2286 auto_replace_emoji_shortcode: false,
2287 jsx_tag_auto_close_enabled_in_any_buffer: false,
2288 leader_id: None,
2289 remote_id: None,
2290 hover_state: HoverState::default(),
2291 pending_mouse_down: None,
2292 prev_pressure_stage: None,
2293 hovered_link_state: None,
2294 edit_prediction_provider: None,
2295 active_edit_prediction: None,
2296 stale_edit_prediction_in_menu: None,
2297 edit_prediction_preview: EditPredictionPreview::Inactive {
2298 released_too_fast: false,
2299 },
2300 inline_diagnostics_enabled: full_mode,
2301 diagnostics_enabled: full_mode,
2302 word_completions_enabled: full_mode,
2303 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2304 gutter_hovered: false,
2305 pixel_position_of_newest_cursor: None,
2306 last_bounds: None,
2307 last_position_map: None,
2308 expect_bounds_change: None,
2309 gutter_dimensions: GutterDimensions::default(),
2310 style: None,
2311 show_cursor_names: false,
2312 hovered_cursors: HashMap::default(),
2313 next_editor_action_id: EditorActionId::default(),
2314 editor_actions: Rc::default(),
2315 edit_predictions_hidden_for_vim_mode: false,
2316 show_edit_predictions_override: None,
2317 show_completions_on_input_override: None,
2318 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2319 edit_prediction_settings: EditPredictionSettings::Disabled,
2320 edit_prediction_indent_conflict: false,
2321 edit_prediction_requires_modifier_in_indent_conflict: true,
2322 custom_context_menu: None,
2323 show_git_blame_gutter: false,
2324 show_git_blame_inline: false,
2325 show_selection_menu: None,
2326 show_git_blame_inline_delay_task: None,
2327 git_blame_inline_enabled: full_mode
2328 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2329 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2330 buffer_serialization: is_minimap.not().then(|| {
2331 BufferSerialization::new(
2332 ProjectSettings::get_global(cx)
2333 .session
2334 .restore_unsaved_buffers,
2335 )
2336 }),
2337 blame: None,
2338 blame_subscription: None,
2339 tasks: BTreeMap::default(),
2340
2341 breakpoint_store,
2342 gutter_breakpoint_indicator: (None, None),
2343 hovered_diff_hunk_row: None,
2344 _subscriptions: (!is_minimap)
2345 .then(|| {
2346 vec![
2347 cx.observe(&multi_buffer, Self::on_buffer_changed),
2348 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2349 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2350 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2351 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2352 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2353 cx.observe_window_activation(window, |editor, window, cx| {
2354 let active = window.is_window_active();
2355 editor.blink_manager.update(cx, |blink_manager, cx| {
2356 if active {
2357 blink_manager.enable(cx);
2358 } else {
2359 blink_manager.disable(cx);
2360 }
2361 });
2362 if active {
2363 editor.show_mouse_cursor(cx);
2364 }
2365 }),
2366 ]
2367 })
2368 .unwrap_or_default(),
2369 tasks_update_task: None,
2370 pull_diagnostics_task: Task::ready(()),
2371 pull_diagnostics_background_task: Task::ready(()),
2372 colors: None,
2373 refresh_colors_task: Task::ready(()),
2374 inlay_hints: None,
2375 next_color_inlay_id: 0,
2376 post_scroll_update: Task::ready(()),
2377 linked_edit_ranges: Default::default(),
2378 in_project_search: false,
2379 previous_search_ranges: None,
2380 breadcrumb_header: None,
2381 focused_block: None,
2382 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2383 addons: HashMap::default(),
2384 registered_buffers: HashMap::default(),
2385 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2386 selection_mark_mode: false,
2387 toggle_fold_multiple_buffers: Task::ready(()),
2388 serialize_selections: Task::ready(()),
2389 serialize_folds: Task::ready(()),
2390 text_style_refinement: None,
2391 load_diff_task: load_uncommitted_diff,
2392 temporary_diff_override: false,
2393 mouse_cursor_hidden: false,
2394 minimap: None,
2395 hide_mouse_mode: EditorSettings::get_global(cx)
2396 .hide_mouse
2397 .unwrap_or_default(),
2398 change_list: ChangeList::new(),
2399 mode,
2400 selection_drag_state: SelectionDragState::None,
2401 folding_newlines: Task::ready(()),
2402 lookup_key: None,
2403 select_next_is_case_sensitive: None,
2404 applicable_language_settings: HashMap::default(),
2405 accent_data: None,
2406 fetched_tree_sitter_chunks: HashMap::default(),
2407 use_base_text_line_numbers: false,
2408 };
2409
2410 if is_minimap {
2411 return editor;
2412 }
2413
2414 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2415 editor.accent_data = editor.fetch_accent_data(cx);
2416
2417 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2418 editor
2419 ._subscriptions
2420 .push(cx.observe(breakpoints, |_, _, cx| {
2421 cx.notify();
2422 }));
2423 }
2424 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2425 editor._subscriptions.extend(project_subscriptions);
2426
2427 editor._subscriptions.push(cx.subscribe_in(
2428 &cx.entity(),
2429 window,
2430 |editor, _, e: &EditorEvent, window, cx| match e {
2431 EditorEvent::ScrollPositionChanged { local, .. } => {
2432 if *local {
2433 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2434 editor.inline_blame_popover.take();
2435 let new_anchor = editor.scroll_manager.anchor();
2436 let snapshot = editor.snapshot(window, cx);
2437 editor.update_restoration_data(cx, move |data| {
2438 data.scroll_position = (
2439 new_anchor.top_row(snapshot.buffer_snapshot()),
2440 new_anchor.offset,
2441 );
2442 });
2443
2444 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2445 cx.background_executor()
2446 .timer(Duration::from_millis(50))
2447 .await;
2448 editor
2449 .update_in(cx, |editor, window, cx| {
2450 editor.register_visible_buffers(cx);
2451 editor.refresh_colors_for_visible_range(None, window, cx);
2452 editor.refresh_inlay_hints(
2453 InlayHintRefreshReason::NewLinesShown,
2454 cx,
2455 );
2456 editor.colorize_brackets(false, cx);
2457 })
2458 .ok();
2459 });
2460 }
2461 }
2462 EditorEvent::Edited { .. } => {
2463 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2464 .map(|vim_mode| vim_mode.0)
2465 .unwrap_or(false);
2466 if !vim_mode {
2467 let display_map = editor.display_snapshot(cx);
2468 let selections = editor.selections.all_adjusted_display(&display_map);
2469 let pop_state = editor
2470 .change_list
2471 .last()
2472 .map(|previous| {
2473 previous.len() == selections.len()
2474 && previous.iter().enumerate().all(|(ix, p)| {
2475 p.to_display_point(&display_map).row()
2476 == selections[ix].head().row()
2477 })
2478 })
2479 .unwrap_or(false);
2480 let new_positions = selections
2481 .into_iter()
2482 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2483 .collect();
2484 editor
2485 .change_list
2486 .push_to_change_list(pop_state, new_positions);
2487 }
2488 }
2489 _ => (),
2490 },
2491 ));
2492
2493 if let Some(dap_store) = editor
2494 .project
2495 .as_ref()
2496 .map(|project| project.read(cx).dap_store())
2497 {
2498 let weak_editor = cx.weak_entity();
2499
2500 editor
2501 ._subscriptions
2502 .push(
2503 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2504 let session_entity = cx.entity();
2505 weak_editor
2506 .update(cx, |editor, cx| {
2507 editor._subscriptions.push(
2508 cx.subscribe(&session_entity, Self::on_debug_session_event),
2509 );
2510 })
2511 .ok();
2512 }),
2513 );
2514
2515 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2516 editor
2517 ._subscriptions
2518 .push(cx.subscribe(&session, Self::on_debug_session_event));
2519 }
2520 }
2521
2522 // skip adding the initial selection to selection history
2523 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2524 editor.end_selection(window, cx);
2525 editor.selection_history.mode = SelectionHistoryMode::Normal;
2526
2527 editor.scroll_manager.show_scrollbars(window, cx);
2528 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2529
2530 if full_mode {
2531 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2532 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2533
2534 if editor.git_blame_inline_enabled {
2535 editor.start_git_blame_inline(false, window, cx);
2536 }
2537
2538 editor.go_to_active_debug_line(window, cx);
2539
2540 editor.minimap =
2541 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2542 editor.colors = Some(LspColorData::new(cx));
2543 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2544
2545 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2546 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2547 }
2548 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2549 }
2550
2551 editor
2552 }
2553
2554 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2555 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2556 }
2557
2558 pub fn deploy_mouse_context_menu(
2559 &mut self,
2560 position: gpui::Point<Pixels>,
2561 context_menu: Entity<ContextMenu>,
2562 window: &mut Window,
2563 cx: &mut Context<Self>,
2564 ) {
2565 self.mouse_context_menu = Some(MouseContextMenu::new(
2566 self,
2567 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2568 context_menu,
2569 window,
2570 cx,
2571 ));
2572 }
2573
2574 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2575 self.mouse_context_menu
2576 .as_ref()
2577 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2578 }
2579
2580 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2581 if self
2582 .selections
2583 .pending_anchor()
2584 .is_some_and(|pending_selection| {
2585 let snapshot = self.buffer().read(cx).snapshot(cx);
2586 pending_selection.range().includes(range, &snapshot)
2587 })
2588 {
2589 return true;
2590 }
2591
2592 self.selections
2593 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2594 .into_iter()
2595 .any(|selection| {
2596 // This is needed to cover a corner case, if we just check for an existing
2597 // selection in the fold range, having a cursor at the start of the fold
2598 // marks it as selected. Non-empty selections don't cause this.
2599 let length = selection.end - selection.start;
2600 length > 0
2601 })
2602 }
2603
2604 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2605 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2606 }
2607
2608 fn key_context_internal(
2609 &self,
2610 has_active_edit_prediction: bool,
2611 window: &mut Window,
2612 cx: &mut App,
2613 ) -> KeyContext {
2614 let mut key_context = KeyContext::new_with_defaults();
2615 key_context.add("Editor");
2616 let mode = match self.mode {
2617 EditorMode::SingleLine => "single_line",
2618 EditorMode::AutoHeight { .. } => "auto_height",
2619 EditorMode::Minimap { .. } => "minimap",
2620 EditorMode::Full { .. } => "full",
2621 };
2622
2623 if EditorSettings::jupyter_enabled(cx) {
2624 key_context.add("jupyter");
2625 }
2626
2627 key_context.set("mode", mode);
2628 if self.pending_rename.is_some() {
2629 key_context.add("renaming");
2630 }
2631
2632 if let Some(snippet_stack) = self.snippet_stack.last() {
2633 key_context.add("in_snippet");
2634
2635 if snippet_stack.active_index > 0 {
2636 key_context.add("has_previous_tabstop");
2637 }
2638
2639 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2640 key_context.add("has_next_tabstop");
2641 }
2642 }
2643
2644 match self.context_menu.borrow().as_ref() {
2645 Some(CodeContextMenu::Completions(menu)) => {
2646 if menu.visible() {
2647 key_context.add("menu");
2648 key_context.add("showing_completions");
2649 }
2650 }
2651 Some(CodeContextMenu::CodeActions(menu)) => {
2652 if menu.visible() {
2653 key_context.add("menu");
2654 key_context.add("showing_code_actions")
2655 }
2656 }
2657 None => {}
2658 }
2659
2660 if self.signature_help_state.has_multiple_signatures() {
2661 key_context.add("showing_signature_help");
2662 }
2663
2664 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2665 if !self.focus_handle(cx).contains_focused(window, cx)
2666 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2667 {
2668 for addon in self.addons.values() {
2669 addon.extend_key_context(&mut key_context, cx)
2670 }
2671 }
2672
2673 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2674 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2675 Some(
2676 file.full_path(cx)
2677 .extension()?
2678 .to_string_lossy()
2679 .into_owned(),
2680 )
2681 }) {
2682 key_context.set("extension", extension);
2683 }
2684 } else {
2685 key_context.add("multibuffer");
2686 }
2687
2688 if has_active_edit_prediction {
2689 if self.edit_prediction_in_conflict() {
2690 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2691 } else {
2692 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2693 key_context.add("copilot_suggestion");
2694 }
2695 }
2696
2697 if self.selection_mark_mode {
2698 key_context.add("selection_mode");
2699 }
2700
2701 let disjoint = self.selections.disjoint_anchors();
2702 let snapshot = self.snapshot(window, cx);
2703 let snapshot = snapshot.buffer_snapshot();
2704 if self.mode == EditorMode::SingleLine
2705 && let [selection] = disjoint
2706 && selection.start == selection.end
2707 && selection.end.to_offset(snapshot) == snapshot.len()
2708 {
2709 key_context.add("end_of_input");
2710 }
2711
2712 if self.has_any_expanded_diff_hunks(cx) {
2713 key_context.add("diffs_expanded");
2714 }
2715
2716 key_context
2717 }
2718
2719 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2720 self.last_bounds.as_ref()
2721 }
2722
2723 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2724 if self.mouse_cursor_hidden {
2725 self.mouse_cursor_hidden = false;
2726 cx.notify();
2727 }
2728 }
2729
2730 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2731 let hide_mouse_cursor = match origin {
2732 HideMouseCursorOrigin::TypingAction => {
2733 matches!(
2734 self.hide_mouse_mode,
2735 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2736 )
2737 }
2738 HideMouseCursorOrigin::MovementAction => {
2739 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2740 }
2741 };
2742 if self.mouse_cursor_hidden != hide_mouse_cursor {
2743 self.mouse_cursor_hidden = hide_mouse_cursor;
2744 cx.notify();
2745 }
2746 }
2747
2748 pub fn edit_prediction_in_conflict(&self) -> bool {
2749 if !self.show_edit_predictions_in_menu() {
2750 return false;
2751 }
2752
2753 let showing_completions = self
2754 .context_menu
2755 .borrow()
2756 .as_ref()
2757 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2758
2759 showing_completions
2760 || self.edit_prediction_requires_modifier()
2761 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2762 // bindings to insert tab characters.
2763 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2764 }
2765
2766 pub fn accept_edit_prediction_keybind(
2767 &self,
2768 granularity: EditPredictionGranularity,
2769 window: &mut Window,
2770 cx: &mut App,
2771 ) -> AcceptEditPredictionBinding {
2772 let key_context = self.key_context_internal(true, window, cx);
2773 let in_conflict = self.edit_prediction_in_conflict();
2774
2775 let bindings =
2776 match granularity {
2777 EditPredictionGranularity::Word => window
2778 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2779 EditPredictionGranularity::Line => window
2780 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2781 EditPredictionGranularity::Full => {
2782 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2783 }
2784 };
2785
2786 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2787 !in_conflict
2788 || binding
2789 .keystrokes()
2790 .first()
2791 .is_some_and(|keystroke| keystroke.modifiers().modified())
2792 }))
2793 }
2794
2795 pub fn new_file(
2796 workspace: &mut Workspace,
2797 _: &workspace::NewFile,
2798 window: &mut Window,
2799 cx: &mut Context<Workspace>,
2800 ) {
2801 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2802 "Failed to create buffer",
2803 window,
2804 cx,
2805 |e, _, _| match e.error_code() {
2806 ErrorCode::RemoteUpgradeRequired => Some(format!(
2807 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2808 e.error_tag("required").unwrap_or("the latest version")
2809 )),
2810 _ => None,
2811 },
2812 );
2813 }
2814
2815 pub fn new_in_workspace(
2816 workspace: &mut Workspace,
2817 window: &mut Window,
2818 cx: &mut Context<Workspace>,
2819 ) -> Task<Result<Entity<Editor>>> {
2820 let project = workspace.project().clone();
2821 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2822
2823 cx.spawn_in(window, async move |workspace, cx| {
2824 let buffer = create.await?;
2825 workspace.update_in(cx, |workspace, window, cx| {
2826 let editor =
2827 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2828 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2829 editor
2830 })
2831 })
2832 }
2833
2834 fn new_file_vertical(
2835 workspace: &mut Workspace,
2836 _: &workspace::NewFileSplitVertical,
2837 window: &mut Window,
2838 cx: &mut Context<Workspace>,
2839 ) {
2840 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2841 }
2842
2843 fn new_file_horizontal(
2844 workspace: &mut Workspace,
2845 _: &workspace::NewFileSplitHorizontal,
2846 window: &mut Window,
2847 cx: &mut Context<Workspace>,
2848 ) {
2849 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2850 }
2851
2852 fn new_file_split(
2853 workspace: &mut Workspace,
2854 action: &workspace::NewFileSplit,
2855 window: &mut Window,
2856 cx: &mut Context<Workspace>,
2857 ) {
2858 Self::new_file_in_direction(workspace, action.0, window, cx)
2859 }
2860
2861 fn new_file_in_direction(
2862 workspace: &mut Workspace,
2863 direction: SplitDirection,
2864 window: &mut Window,
2865 cx: &mut Context<Workspace>,
2866 ) {
2867 let project = workspace.project().clone();
2868 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2869
2870 cx.spawn_in(window, async move |workspace, cx| {
2871 let buffer = create.await?;
2872 workspace.update_in(cx, move |workspace, window, cx| {
2873 workspace.split_item(
2874 direction,
2875 Box::new(
2876 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2877 ),
2878 window,
2879 cx,
2880 )
2881 })?;
2882 anyhow::Ok(())
2883 })
2884 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2885 match e.error_code() {
2886 ErrorCode::RemoteUpgradeRequired => Some(format!(
2887 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2888 e.error_tag("required").unwrap_or("the latest version")
2889 )),
2890 _ => None,
2891 }
2892 });
2893 }
2894
2895 pub fn leader_id(&self) -> Option<CollaboratorId> {
2896 self.leader_id
2897 }
2898
2899 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2900 &self.buffer
2901 }
2902
2903 pub fn project(&self) -> Option<&Entity<Project>> {
2904 self.project.as_ref()
2905 }
2906
2907 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2908 self.workspace.as_ref()?.0.upgrade()
2909 }
2910
2911 /// Returns the workspace serialization ID if this editor should be serialized.
2912 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2913 self.workspace
2914 .as_ref()
2915 .filter(|_| self.should_serialize_buffer())
2916 .and_then(|workspace| workspace.1)
2917 }
2918
2919 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2920 self.buffer().read(cx).title(cx)
2921 }
2922
2923 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2924 let git_blame_gutter_max_author_length = self
2925 .render_git_blame_gutter(cx)
2926 .then(|| {
2927 if let Some(blame) = self.blame.as_ref() {
2928 let max_author_length =
2929 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2930 Some(max_author_length)
2931 } else {
2932 None
2933 }
2934 })
2935 .flatten();
2936
2937 EditorSnapshot {
2938 mode: self.mode.clone(),
2939 show_gutter: self.show_gutter,
2940 offset_content: self.offset_content,
2941 show_line_numbers: self.show_line_numbers,
2942 show_git_diff_gutter: self.show_git_diff_gutter,
2943 show_code_actions: self.show_code_actions,
2944 show_runnables: self.show_runnables,
2945 show_breakpoints: self.show_breakpoints,
2946 git_blame_gutter_max_author_length,
2947 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2948 placeholder_display_snapshot: self
2949 .placeholder_display_map
2950 .as_ref()
2951 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2952 scroll_anchor: self.scroll_manager.anchor(),
2953 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2954 is_focused: self.focus_handle.is_focused(window),
2955 current_line_highlight: self
2956 .current_line_highlight
2957 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2958 gutter_hovered: self.gutter_hovered,
2959 }
2960 }
2961
2962 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2963 self.buffer.read(cx).language_at(point, cx)
2964 }
2965
2966 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2967 self.buffer.read(cx).read(cx).file_at(point).cloned()
2968 }
2969
2970 pub fn active_excerpt(
2971 &self,
2972 cx: &App,
2973 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2974 self.buffer
2975 .read(cx)
2976 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2977 }
2978
2979 pub fn mode(&self) -> &EditorMode {
2980 &self.mode
2981 }
2982
2983 pub fn set_mode(&mut self, mode: EditorMode) {
2984 self.mode = mode;
2985 }
2986
2987 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2988 self.collaboration_hub.as_deref()
2989 }
2990
2991 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2992 self.collaboration_hub = Some(hub);
2993 }
2994
2995 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2996 self.in_project_search = in_project_search;
2997 }
2998
2999 pub fn set_custom_context_menu(
3000 &mut self,
3001 f: impl 'static
3002 + Fn(
3003 &mut Self,
3004 DisplayPoint,
3005 &mut Window,
3006 &mut Context<Self>,
3007 ) -> Option<Entity<ui::ContextMenu>>,
3008 ) {
3009 self.custom_context_menu = Some(Box::new(f))
3010 }
3011
3012 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3013 self.completion_provider = provider;
3014 }
3015
3016 #[cfg(any(test, feature = "test-support"))]
3017 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3018 self.completion_provider.clone()
3019 }
3020
3021 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3022 self.semantics_provider.clone()
3023 }
3024
3025 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3026 self.semantics_provider = provider;
3027 }
3028
3029 pub fn set_edit_prediction_provider<T>(
3030 &mut self,
3031 provider: Option<Entity<T>>,
3032 window: &mut Window,
3033 cx: &mut Context<Self>,
3034 ) where
3035 T: EditPredictionDelegate,
3036 {
3037 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3038 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3039 if this.focus_handle.is_focused(window) {
3040 this.update_visible_edit_prediction(window, cx);
3041 }
3042 }),
3043 provider: Arc::new(provider),
3044 });
3045 self.update_edit_prediction_settings(cx);
3046 self.refresh_edit_prediction(false, false, window, cx);
3047 }
3048
3049 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3050 self.placeholder_display_map
3051 .as_ref()
3052 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3053 }
3054
3055 pub fn set_placeholder_text(
3056 &mut self,
3057 placeholder_text: &str,
3058 window: &mut Window,
3059 cx: &mut Context<Self>,
3060 ) {
3061 let multibuffer = cx
3062 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3063
3064 let style = window.text_style();
3065
3066 self.placeholder_display_map = Some(cx.new(|cx| {
3067 DisplayMap::new(
3068 multibuffer,
3069 style.font(),
3070 style.font_size.to_pixels(window.rem_size()),
3071 None,
3072 FILE_HEADER_HEIGHT,
3073 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3074 Default::default(),
3075 DiagnosticSeverity::Off,
3076 cx,
3077 )
3078 }));
3079 cx.notify();
3080 }
3081
3082 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3083 self.cursor_shape = cursor_shape;
3084
3085 // Disrupt blink for immediate user feedback that the cursor shape has changed
3086 self.blink_manager.update(cx, BlinkManager::show_cursor);
3087
3088 cx.notify();
3089 }
3090
3091 pub fn cursor_shape(&self) -> CursorShape {
3092 self.cursor_shape
3093 }
3094
3095 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3096 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3097 }
3098
3099 pub fn set_current_line_highlight(
3100 &mut self,
3101 current_line_highlight: Option<CurrentLineHighlight>,
3102 ) {
3103 self.current_line_highlight = current_line_highlight;
3104 }
3105
3106 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3107 self.collapse_matches = collapse_matches;
3108 }
3109
3110 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3111 if self.collapse_matches {
3112 return range.start..range.start;
3113 }
3114 range.clone()
3115 }
3116
3117 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3118 self.display_map.read(cx).clip_at_line_ends
3119 }
3120
3121 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3122 if self.display_map.read(cx).clip_at_line_ends != clip {
3123 self.display_map
3124 .update(cx, |map, _| map.clip_at_line_ends = clip);
3125 }
3126 }
3127
3128 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3129 self.input_enabled = input_enabled;
3130 }
3131
3132 pub fn set_edit_predictions_hidden_for_vim_mode(
3133 &mut self,
3134 hidden: bool,
3135 window: &mut Window,
3136 cx: &mut Context<Self>,
3137 ) {
3138 if hidden != self.edit_predictions_hidden_for_vim_mode {
3139 self.edit_predictions_hidden_for_vim_mode = hidden;
3140 if hidden {
3141 self.update_visible_edit_prediction(window, cx);
3142 } else {
3143 self.refresh_edit_prediction(true, false, window, cx);
3144 }
3145 }
3146 }
3147
3148 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3149 self.menu_edit_predictions_policy = value;
3150 }
3151
3152 pub fn set_autoindent(&mut self, autoindent: bool) {
3153 if autoindent {
3154 self.autoindent_mode = Some(AutoindentMode::EachLine);
3155 } else {
3156 self.autoindent_mode = None;
3157 }
3158 }
3159
3160 pub fn read_only(&self, cx: &App) -> bool {
3161 self.read_only || self.buffer.read(cx).read_only()
3162 }
3163
3164 pub fn set_read_only(&mut self, read_only: bool) {
3165 self.read_only = read_only;
3166 }
3167
3168 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3169 self.use_autoclose = autoclose;
3170 }
3171
3172 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3173 self.use_auto_surround = auto_surround;
3174 }
3175
3176 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3177 self.auto_replace_emoji_shortcode = auto_replace;
3178 }
3179
3180 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3181 self.buffer_serialization = should_serialize.then(|| {
3182 BufferSerialization::new(
3183 ProjectSettings::get_global(cx)
3184 .session
3185 .restore_unsaved_buffers,
3186 )
3187 })
3188 }
3189
3190 fn should_serialize_buffer(&self) -> bool {
3191 self.buffer_serialization.is_some()
3192 }
3193
3194 pub fn toggle_edit_predictions(
3195 &mut self,
3196 _: &ToggleEditPrediction,
3197 window: &mut Window,
3198 cx: &mut Context<Self>,
3199 ) {
3200 if self.show_edit_predictions_override.is_some() {
3201 self.set_show_edit_predictions(None, window, cx);
3202 } else {
3203 let show_edit_predictions = !self.edit_predictions_enabled();
3204 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3205 }
3206 }
3207
3208 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3209 self.show_completions_on_input_override = show_completions_on_input;
3210 }
3211
3212 pub fn set_show_edit_predictions(
3213 &mut self,
3214 show_edit_predictions: Option<bool>,
3215 window: &mut Window,
3216 cx: &mut Context<Self>,
3217 ) {
3218 self.show_edit_predictions_override = show_edit_predictions;
3219 self.update_edit_prediction_settings(cx);
3220
3221 if let Some(false) = show_edit_predictions {
3222 self.discard_edit_prediction(false, cx);
3223 } else {
3224 self.refresh_edit_prediction(false, true, window, cx);
3225 }
3226 }
3227
3228 fn edit_predictions_disabled_in_scope(
3229 &self,
3230 buffer: &Entity<Buffer>,
3231 buffer_position: language::Anchor,
3232 cx: &App,
3233 ) -> bool {
3234 let snapshot = buffer.read(cx).snapshot();
3235 let settings = snapshot.settings_at(buffer_position, cx);
3236
3237 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3238 return false;
3239 };
3240
3241 scope.override_name().is_some_and(|scope_name| {
3242 settings
3243 .edit_predictions_disabled_in
3244 .iter()
3245 .any(|s| s == scope_name)
3246 })
3247 }
3248
3249 pub fn set_use_modal_editing(&mut self, to: bool) {
3250 self.use_modal_editing = to;
3251 }
3252
3253 pub fn use_modal_editing(&self) -> bool {
3254 self.use_modal_editing
3255 }
3256
3257 fn selections_did_change(
3258 &mut self,
3259 local: bool,
3260 old_cursor_position: &Anchor,
3261 effects: SelectionEffects,
3262 window: &mut Window,
3263 cx: &mut Context<Self>,
3264 ) {
3265 window.invalidate_character_coordinates();
3266
3267 // Copy selections to primary selection buffer
3268 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3269 if local {
3270 let selections = self
3271 .selections
3272 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3273 let buffer_handle = self.buffer.read(cx).read(cx);
3274
3275 let mut text = String::new();
3276 for (index, selection) in selections.iter().enumerate() {
3277 let text_for_selection = buffer_handle
3278 .text_for_range(selection.start..selection.end)
3279 .collect::<String>();
3280
3281 text.push_str(&text_for_selection);
3282 if index != selections.len() - 1 {
3283 text.push('\n');
3284 }
3285 }
3286
3287 if !text.is_empty() {
3288 cx.write_to_primary(ClipboardItem::new_string(text));
3289 }
3290 }
3291
3292 let selection_anchors = self.selections.disjoint_anchors_arc();
3293
3294 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3295 self.buffer.update(cx, |buffer, cx| {
3296 buffer.set_active_selections(
3297 &selection_anchors,
3298 self.selections.line_mode(),
3299 self.cursor_shape,
3300 cx,
3301 )
3302 });
3303 }
3304 let display_map = self
3305 .display_map
3306 .update(cx, |display_map, cx| display_map.snapshot(cx));
3307 let buffer = display_map.buffer_snapshot();
3308 if self.selections.count() == 1 {
3309 self.add_selections_state = None;
3310 }
3311 self.select_next_state = None;
3312 self.select_prev_state = None;
3313 self.select_syntax_node_history.try_clear();
3314 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3315 self.snippet_stack.invalidate(&selection_anchors, buffer);
3316 self.take_rename(false, window, cx);
3317
3318 let newest_selection = self.selections.newest_anchor();
3319 let new_cursor_position = newest_selection.head();
3320 let selection_start = newest_selection.start;
3321
3322 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3323 self.push_to_nav_history(
3324 *old_cursor_position,
3325 Some(new_cursor_position.to_point(buffer)),
3326 false,
3327 effects.nav_history == Some(true),
3328 cx,
3329 );
3330 }
3331
3332 if local {
3333 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3334 self.register_buffer(buffer_id, cx);
3335 }
3336
3337 let mut context_menu = self.context_menu.borrow_mut();
3338 let completion_menu = match context_menu.as_ref() {
3339 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3340 Some(CodeContextMenu::CodeActions(_)) => {
3341 *context_menu = None;
3342 None
3343 }
3344 None => None,
3345 };
3346 let completion_position = completion_menu.map(|menu| menu.initial_position);
3347 drop(context_menu);
3348
3349 if effects.completions
3350 && let Some(completion_position) = completion_position
3351 {
3352 let start_offset = selection_start.to_offset(buffer);
3353 let position_matches = start_offset == completion_position.to_offset(buffer);
3354 let continue_showing = if position_matches {
3355 if self.snippet_stack.is_empty() {
3356 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3357 == Some(CharKind::Word)
3358 } else {
3359 // Snippet choices can be shown even when the cursor is in whitespace.
3360 // Dismissing the menu with actions like backspace is handled by
3361 // invalidation regions.
3362 true
3363 }
3364 } else {
3365 false
3366 };
3367
3368 if continue_showing {
3369 self.open_or_update_completions_menu(None, None, false, window, cx);
3370 } else {
3371 self.hide_context_menu(window, cx);
3372 }
3373 }
3374
3375 hide_hover(self, cx);
3376
3377 if old_cursor_position.to_display_point(&display_map).row()
3378 != new_cursor_position.to_display_point(&display_map).row()
3379 {
3380 self.available_code_actions.take();
3381 }
3382 self.refresh_code_actions(window, cx);
3383 self.refresh_document_highlights(cx);
3384 refresh_linked_ranges(self, window, cx);
3385
3386 self.refresh_selected_text_highlights(false, window, cx);
3387 self.refresh_matching_bracket_highlights(window, cx);
3388 self.update_visible_edit_prediction(window, cx);
3389 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3390 self.inline_blame_popover.take();
3391 if self.git_blame_inline_enabled {
3392 self.start_inline_blame_timer(window, cx);
3393 }
3394 }
3395
3396 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3397 cx.emit(EditorEvent::SelectionsChanged { local });
3398
3399 let selections = &self.selections.disjoint_anchors_arc();
3400 if selections.len() == 1 {
3401 cx.emit(SearchEvent::ActiveMatchChanged)
3402 }
3403 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3404 let inmemory_selections = selections
3405 .iter()
3406 .map(|s| {
3407 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3408 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3409 })
3410 .collect();
3411 self.update_restoration_data(cx, |data| {
3412 data.selections = inmemory_selections;
3413 });
3414
3415 if WorkspaceSettings::get(None, cx).restore_on_startup
3416 != RestoreOnStartupBehavior::EmptyTab
3417 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3418 {
3419 let snapshot = self.buffer().read(cx).snapshot(cx);
3420 let selections = selections.clone();
3421 let background_executor = cx.background_executor().clone();
3422 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3423 self.serialize_selections = cx.background_spawn(async move {
3424 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3425 let db_selections = selections
3426 .iter()
3427 .map(|selection| {
3428 (
3429 selection.start.to_offset(&snapshot).0,
3430 selection.end.to_offset(&snapshot).0,
3431 )
3432 })
3433 .collect();
3434
3435 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3436 .await
3437 .with_context(|| {
3438 format!(
3439 "persisting editor selections for editor {editor_id}, \
3440 workspace {workspace_id:?}"
3441 )
3442 })
3443 .log_err();
3444 });
3445 }
3446 }
3447
3448 cx.notify();
3449 }
3450
3451 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3452 use text::ToOffset as _;
3453 use text::ToPoint as _;
3454
3455 if self.mode.is_minimap()
3456 || WorkspaceSettings::get(None, cx).restore_on_startup
3457 == RestoreOnStartupBehavior::EmptyTab
3458 {
3459 return;
3460 }
3461
3462 if !self.buffer().read(cx).is_singleton() {
3463 return;
3464 }
3465
3466 let display_snapshot = self
3467 .display_map
3468 .update(cx, |display_map, cx| display_map.snapshot(cx));
3469 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3470 return;
3471 };
3472 let inmemory_folds = display_snapshot
3473 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3474 .map(|fold| {
3475 fold.range.start.text_anchor.to_point(&snapshot)
3476 ..fold.range.end.text_anchor.to_point(&snapshot)
3477 })
3478 .collect();
3479 self.update_restoration_data(cx, |data| {
3480 data.folds = inmemory_folds;
3481 });
3482
3483 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3484 return;
3485 };
3486 let background_executor = cx.background_executor().clone();
3487 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3488 let db_folds = display_snapshot
3489 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3490 .map(|fold| {
3491 (
3492 fold.range.start.text_anchor.to_offset(&snapshot),
3493 fold.range.end.text_anchor.to_offset(&snapshot),
3494 )
3495 })
3496 .collect();
3497 self.serialize_folds = cx.background_spawn(async move {
3498 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3499 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3500 .await
3501 .with_context(|| {
3502 format!(
3503 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3504 )
3505 })
3506 .log_err();
3507 });
3508 }
3509
3510 pub fn sync_selections(
3511 &mut self,
3512 other: Entity<Editor>,
3513 cx: &mut Context<Self>,
3514 ) -> gpui::Subscription {
3515 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3516 if !other_selections.is_empty() {
3517 self.selections
3518 .change_with(&self.display_snapshot(cx), |selections| {
3519 selections.select_anchors(other_selections);
3520 });
3521 }
3522
3523 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3524 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3525 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3526 if other_selections.is_empty() {
3527 return;
3528 }
3529 let snapshot = this.display_snapshot(cx);
3530 this.selections.change_with(&snapshot, |selections| {
3531 selections.select_anchors(other_selections);
3532 });
3533 }
3534 });
3535
3536 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3537 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3538 let these_selections = this.selections.disjoint_anchors().to_vec();
3539 if these_selections.is_empty() {
3540 return;
3541 }
3542 other.update(cx, |other_editor, cx| {
3543 let snapshot = other_editor.display_snapshot(cx);
3544 other_editor
3545 .selections
3546 .change_with(&snapshot, |selections| {
3547 selections.select_anchors(these_selections);
3548 })
3549 });
3550 }
3551 });
3552
3553 Subscription::join(other_subscription, this_subscription)
3554 }
3555
3556 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3557 if self.buffer().read(cx).is_singleton() {
3558 return;
3559 }
3560 let snapshot = self.buffer.read(cx).snapshot(cx);
3561 let buffer_ids: HashSet<BufferId> = self
3562 .selections
3563 .disjoint_anchor_ranges()
3564 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3565 .collect();
3566 for buffer_id in buffer_ids {
3567 self.unfold_buffer(buffer_id, cx);
3568 }
3569 }
3570
3571 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3572 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3573 /// effects of selection change occur at the end of the transaction.
3574 pub fn change_selections<R>(
3575 &mut self,
3576 effects: SelectionEffects,
3577 window: &mut Window,
3578 cx: &mut Context<Self>,
3579 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3580 ) -> R {
3581 let snapshot = self.display_snapshot(cx);
3582 if let Some(state) = &mut self.deferred_selection_effects_state {
3583 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3584 state.effects.completions = effects.completions;
3585 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3586 let (changed, result) = self.selections.change_with(&snapshot, change);
3587 state.changed |= changed;
3588 return result;
3589 }
3590 let mut state = DeferredSelectionEffectsState {
3591 changed: false,
3592 effects,
3593 old_cursor_position: self.selections.newest_anchor().head(),
3594 history_entry: SelectionHistoryEntry {
3595 selections: self.selections.disjoint_anchors_arc(),
3596 select_next_state: self.select_next_state.clone(),
3597 select_prev_state: self.select_prev_state.clone(),
3598 add_selections_state: self.add_selections_state.clone(),
3599 },
3600 };
3601 let (changed, result) = self.selections.change_with(&snapshot, change);
3602 state.changed = state.changed || changed;
3603 if self.defer_selection_effects {
3604 self.deferred_selection_effects_state = Some(state);
3605 } else {
3606 self.apply_selection_effects(state, window, cx);
3607 }
3608 result
3609 }
3610
3611 /// Defers the effects of selection change, so that the effects of multiple calls to
3612 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3613 /// to selection history and the state of popovers based on selection position aren't
3614 /// erroneously updated.
3615 pub fn with_selection_effects_deferred<R>(
3616 &mut self,
3617 window: &mut Window,
3618 cx: &mut Context<Self>,
3619 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3620 ) -> R {
3621 let already_deferred = self.defer_selection_effects;
3622 self.defer_selection_effects = true;
3623 let result = update(self, window, cx);
3624 if !already_deferred {
3625 self.defer_selection_effects = false;
3626 if let Some(state) = self.deferred_selection_effects_state.take() {
3627 self.apply_selection_effects(state, window, cx);
3628 }
3629 }
3630 result
3631 }
3632
3633 fn apply_selection_effects(
3634 &mut self,
3635 state: DeferredSelectionEffectsState,
3636 window: &mut Window,
3637 cx: &mut Context<Self>,
3638 ) {
3639 if state.changed {
3640 self.selection_history.push(state.history_entry);
3641
3642 if let Some(autoscroll) = state.effects.scroll {
3643 self.request_autoscroll(autoscroll, cx);
3644 }
3645
3646 let old_cursor_position = &state.old_cursor_position;
3647
3648 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3649
3650 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3651 self.show_signature_help(&ShowSignatureHelp, window, cx);
3652 }
3653 }
3654 }
3655
3656 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3657 where
3658 I: IntoIterator<Item = (Range<S>, T)>,
3659 S: ToOffset,
3660 T: Into<Arc<str>>,
3661 {
3662 if self.read_only(cx) {
3663 return;
3664 }
3665
3666 self.buffer
3667 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3668 }
3669
3670 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3671 where
3672 I: IntoIterator<Item = (Range<S>, T)>,
3673 S: ToOffset,
3674 T: Into<Arc<str>>,
3675 {
3676 if self.read_only(cx) {
3677 return;
3678 }
3679
3680 self.buffer.update(cx, |buffer, cx| {
3681 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3682 });
3683 }
3684
3685 pub fn edit_with_block_indent<I, S, T>(
3686 &mut self,
3687 edits: I,
3688 original_indent_columns: Vec<Option<u32>>,
3689 cx: &mut Context<Self>,
3690 ) where
3691 I: IntoIterator<Item = (Range<S>, T)>,
3692 S: ToOffset,
3693 T: Into<Arc<str>>,
3694 {
3695 if self.read_only(cx) {
3696 return;
3697 }
3698
3699 self.buffer.update(cx, |buffer, cx| {
3700 buffer.edit(
3701 edits,
3702 Some(AutoindentMode::Block {
3703 original_indent_columns,
3704 }),
3705 cx,
3706 )
3707 });
3708 }
3709
3710 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3711 self.hide_context_menu(window, cx);
3712
3713 match phase {
3714 SelectPhase::Begin {
3715 position,
3716 add,
3717 click_count,
3718 } => self.begin_selection(position, add, click_count, window, cx),
3719 SelectPhase::BeginColumnar {
3720 position,
3721 goal_column,
3722 reset,
3723 mode,
3724 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3725 SelectPhase::Extend {
3726 position,
3727 click_count,
3728 } => self.extend_selection(position, click_count, window, cx),
3729 SelectPhase::Update {
3730 position,
3731 goal_column,
3732 scroll_delta,
3733 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3734 SelectPhase::End => self.end_selection(window, cx),
3735 }
3736 }
3737
3738 fn extend_selection(
3739 &mut self,
3740 position: DisplayPoint,
3741 click_count: usize,
3742 window: &mut Window,
3743 cx: &mut Context<Self>,
3744 ) {
3745 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3746 let tail = self
3747 .selections
3748 .newest::<MultiBufferOffset>(&display_map)
3749 .tail();
3750 let click_count = click_count.max(match self.selections.select_mode() {
3751 SelectMode::Character => 1,
3752 SelectMode::Word(_) => 2,
3753 SelectMode::Line(_) => 3,
3754 SelectMode::All => 4,
3755 });
3756 self.begin_selection(position, false, click_count, window, cx);
3757
3758 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3759
3760 let current_selection = match self.selections.select_mode() {
3761 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3762 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3763 };
3764
3765 let mut pending_selection = self
3766 .selections
3767 .pending_anchor()
3768 .cloned()
3769 .expect("extend_selection not called with pending selection");
3770
3771 if pending_selection
3772 .start
3773 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3774 == Ordering::Greater
3775 {
3776 pending_selection.start = current_selection.start;
3777 }
3778 if pending_selection
3779 .end
3780 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3781 == Ordering::Less
3782 {
3783 pending_selection.end = current_selection.end;
3784 pending_selection.reversed = true;
3785 }
3786
3787 let mut pending_mode = self.selections.pending_mode().unwrap();
3788 match &mut pending_mode {
3789 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3790 _ => {}
3791 }
3792
3793 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3794 SelectionEffects::scroll(Autoscroll::fit())
3795 } else {
3796 SelectionEffects::no_scroll()
3797 };
3798
3799 self.change_selections(effects, window, cx, |s| {
3800 s.set_pending(pending_selection.clone(), pending_mode);
3801 s.set_is_extending(true);
3802 });
3803 }
3804
3805 fn begin_selection(
3806 &mut self,
3807 position: DisplayPoint,
3808 add: bool,
3809 click_count: usize,
3810 window: &mut Window,
3811 cx: &mut Context<Self>,
3812 ) {
3813 if !self.focus_handle.is_focused(window) {
3814 self.last_focused_descendant = None;
3815 window.focus(&self.focus_handle, cx);
3816 }
3817
3818 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3819 let buffer = display_map.buffer_snapshot();
3820 let position = display_map.clip_point(position, Bias::Left);
3821
3822 let start;
3823 let end;
3824 let mode;
3825 let mut auto_scroll;
3826 match click_count {
3827 1 => {
3828 start = buffer.anchor_before(position.to_point(&display_map));
3829 end = start;
3830 mode = SelectMode::Character;
3831 auto_scroll = true;
3832 }
3833 2 => {
3834 let position = display_map
3835 .clip_point(position, Bias::Left)
3836 .to_offset(&display_map, Bias::Left);
3837 let (range, _) = buffer.surrounding_word(position, None);
3838 start = buffer.anchor_before(range.start);
3839 end = buffer.anchor_before(range.end);
3840 mode = SelectMode::Word(start..end);
3841 auto_scroll = true;
3842 }
3843 3 => {
3844 let position = display_map
3845 .clip_point(position, Bias::Left)
3846 .to_point(&display_map);
3847 let line_start = display_map.prev_line_boundary(position).0;
3848 let next_line_start = buffer.clip_point(
3849 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3850 Bias::Left,
3851 );
3852 start = buffer.anchor_before(line_start);
3853 end = buffer.anchor_before(next_line_start);
3854 mode = SelectMode::Line(start..end);
3855 auto_scroll = true;
3856 }
3857 _ => {
3858 start = buffer.anchor_before(MultiBufferOffset(0));
3859 end = buffer.anchor_before(buffer.len());
3860 mode = SelectMode::All;
3861 auto_scroll = false;
3862 }
3863 }
3864 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3865
3866 let point_to_delete: Option<usize> = {
3867 let selected_points: Vec<Selection<Point>> =
3868 self.selections.disjoint_in_range(start..end, &display_map);
3869
3870 if !add || click_count > 1 {
3871 None
3872 } else if !selected_points.is_empty() {
3873 Some(selected_points[0].id)
3874 } else {
3875 let clicked_point_already_selected =
3876 self.selections.disjoint_anchors().iter().find(|selection| {
3877 selection.start.to_point(buffer) == start.to_point(buffer)
3878 || selection.end.to_point(buffer) == end.to_point(buffer)
3879 });
3880
3881 clicked_point_already_selected.map(|selection| selection.id)
3882 }
3883 };
3884
3885 let selections_count = self.selections.count();
3886 let effects = if auto_scroll {
3887 SelectionEffects::default()
3888 } else {
3889 SelectionEffects::no_scroll()
3890 };
3891
3892 self.change_selections(effects, window, cx, |s| {
3893 if let Some(point_to_delete) = point_to_delete {
3894 s.delete(point_to_delete);
3895
3896 if selections_count == 1 {
3897 s.set_pending_anchor_range(start..end, mode);
3898 }
3899 } else {
3900 if !add {
3901 s.clear_disjoint();
3902 }
3903
3904 s.set_pending_anchor_range(start..end, mode);
3905 }
3906 });
3907 }
3908
3909 fn begin_columnar_selection(
3910 &mut self,
3911 position: DisplayPoint,
3912 goal_column: u32,
3913 reset: bool,
3914 mode: ColumnarMode,
3915 window: &mut Window,
3916 cx: &mut Context<Self>,
3917 ) {
3918 if !self.focus_handle.is_focused(window) {
3919 self.last_focused_descendant = None;
3920 window.focus(&self.focus_handle, cx);
3921 }
3922
3923 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3924
3925 if reset {
3926 let pointer_position = display_map
3927 .buffer_snapshot()
3928 .anchor_before(position.to_point(&display_map));
3929
3930 self.change_selections(
3931 SelectionEffects::scroll(Autoscroll::newest()),
3932 window,
3933 cx,
3934 |s| {
3935 s.clear_disjoint();
3936 s.set_pending_anchor_range(
3937 pointer_position..pointer_position,
3938 SelectMode::Character,
3939 );
3940 },
3941 );
3942 };
3943
3944 let tail = self.selections.newest::<Point>(&display_map).tail();
3945 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3946 self.columnar_selection_state = match mode {
3947 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3948 selection_tail: selection_anchor,
3949 display_point: if reset {
3950 if position.column() != goal_column {
3951 Some(DisplayPoint::new(position.row(), goal_column))
3952 } else {
3953 None
3954 }
3955 } else {
3956 None
3957 },
3958 }),
3959 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3960 selection_tail: selection_anchor,
3961 }),
3962 };
3963
3964 if !reset {
3965 self.select_columns(position, goal_column, &display_map, window, cx);
3966 }
3967 }
3968
3969 fn update_selection(
3970 &mut self,
3971 position: DisplayPoint,
3972 goal_column: u32,
3973 scroll_delta: gpui::Point<f32>,
3974 window: &mut Window,
3975 cx: &mut Context<Self>,
3976 ) {
3977 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3978
3979 if self.columnar_selection_state.is_some() {
3980 self.select_columns(position, goal_column, &display_map, window, cx);
3981 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3982 let buffer = display_map.buffer_snapshot();
3983 let head;
3984 let tail;
3985 let mode = self.selections.pending_mode().unwrap();
3986 match &mode {
3987 SelectMode::Character => {
3988 head = position.to_point(&display_map);
3989 tail = pending.tail().to_point(buffer);
3990 }
3991 SelectMode::Word(original_range) => {
3992 let offset = display_map
3993 .clip_point(position, Bias::Left)
3994 .to_offset(&display_map, Bias::Left);
3995 let original_range = original_range.to_offset(buffer);
3996
3997 let head_offset = if buffer.is_inside_word(offset, None)
3998 || original_range.contains(&offset)
3999 {
4000 let (word_range, _) = buffer.surrounding_word(offset, None);
4001 if word_range.start < original_range.start {
4002 word_range.start
4003 } else {
4004 word_range.end
4005 }
4006 } else {
4007 offset
4008 };
4009
4010 head = head_offset.to_point(buffer);
4011 if head_offset <= original_range.start {
4012 tail = original_range.end.to_point(buffer);
4013 } else {
4014 tail = original_range.start.to_point(buffer);
4015 }
4016 }
4017 SelectMode::Line(original_range) => {
4018 let original_range = original_range.to_point(display_map.buffer_snapshot());
4019
4020 let position = display_map
4021 .clip_point(position, Bias::Left)
4022 .to_point(&display_map);
4023 let line_start = display_map.prev_line_boundary(position).0;
4024 let next_line_start = buffer.clip_point(
4025 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4026 Bias::Left,
4027 );
4028
4029 if line_start < original_range.start {
4030 head = line_start
4031 } else {
4032 head = next_line_start
4033 }
4034
4035 if head <= original_range.start {
4036 tail = original_range.end;
4037 } else {
4038 tail = original_range.start;
4039 }
4040 }
4041 SelectMode::All => {
4042 return;
4043 }
4044 };
4045
4046 if head < tail {
4047 pending.start = buffer.anchor_before(head);
4048 pending.end = buffer.anchor_before(tail);
4049 pending.reversed = true;
4050 } else {
4051 pending.start = buffer.anchor_before(tail);
4052 pending.end = buffer.anchor_before(head);
4053 pending.reversed = false;
4054 }
4055
4056 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4057 s.set_pending(pending.clone(), mode);
4058 });
4059 } else {
4060 log::error!("update_selection dispatched with no pending selection");
4061 return;
4062 }
4063
4064 self.apply_scroll_delta(scroll_delta, window, cx);
4065 cx.notify();
4066 }
4067
4068 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4069 self.columnar_selection_state.take();
4070 if let Some(pending_mode) = self.selections.pending_mode() {
4071 let selections = self
4072 .selections
4073 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4074 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4075 s.select(selections);
4076 s.clear_pending();
4077 if s.is_extending() {
4078 s.set_is_extending(false);
4079 } else {
4080 s.set_select_mode(pending_mode);
4081 }
4082 });
4083 }
4084 }
4085
4086 fn select_columns(
4087 &mut self,
4088 head: DisplayPoint,
4089 goal_column: u32,
4090 display_map: &DisplaySnapshot,
4091 window: &mut Window,
4092 cx: &mut Context<Self>,
4093 ) {
4094 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4095 return;
4096 };
4097
4098 let tail = match columnar_state {
4099 ColumnarSelectionState::FromMouse {
4100 selection_tail,
4101 display_point,
4102 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4103 ColumnarSelectionState::FromSelection { selection_tail } => {
4104 selection_tail.to_display_point(display_map)
4105 }
4106 };
4107
4108 let start_row = cmp::min(tail.row(), head.row());
4109 let end_row = cmp::max(tail.row(), head.row());
4110 let start_column = cmp::min(tail.column(), goal_column);
4111 let end_column = cmp::max(tail.column(), goal_column);
4112 let reversed = start_column < tail.column();
4113
4114 let selection_ranges = (start_row.0..=end_row.0)
4115 .map(DisplayRow)
4116 .filter_map(|row| {
4117 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4118 || start_column <= display_map.line_len(row))
4119 && !display_map.is_block_line(row)
4120 {
4121 let start = display_map
4122 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4123 .to_point(display_map);
4124 let end = display_map
4125 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4126 .to_point(display_map);
4127 if reversed {
4128 Some(end..start)
4129 } else {
4130 Some(start..end)
4131 }
4132 } else {
4133 None
4134 }
4135 })
4136 .collect::<Vec<_>>();
4137 if selection_ranges.is_empty() {
4138 return;
4139 }
4140
4141 let ranges = match columnar_state {
4142 ColumnarSelectionState::FromMouse { .. } => {
4143 let mut non_empty_ranges = selection_ranges
4144 .iter()
4145 .filter(|selection_range| selection_range.start != selection_range.end)
4146 .peekable();
4147 if non_empty_ranges.peek().is_some() {
4148 non_empty_ranges.cloned().collect()
4149 } else {
4150 selection_ranges
4151 }
4152 }
4153 _ => selection_ranges,
4154 };
4155
4156 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4157 s.select_ranges(ranges);
4158 });
4159 cx.notify();
4160 }
4161
4162 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4163 self.selections
4164 .all_adjusted(snapshot)
4165 .iter()
4166 .any(|selection| !selection.is_empty())
4167 }
4168
4169 pub fn has_pending_nonempty_selection(&self) -> bool {
4170 let pending_nonempty_selection = match self.selections.pending_anchor() {
4171 Some(Selection { start, end, .. }) => start != end,
4172 None => false,
4173 };
4174
4175 pending_nonempty_selection
4176 || (self.columnar_selection_state.is_some()
4177 && self.selections.disjoint_anchors().len() > 1)
4178 }
4179
4180 pub fn has_pending_selection(&self) -> bool {
4181 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4182 }
4183
4184 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4185 self.selection_mark_mode = false;
4186 self.selection_drag_state = SelectionDragState::None;
4187
4188 if self.dismiss_menus_and_popups(true, window, cx) {
4189 cx.notify();
4190 return;
4191 }
4192 if self.clear_expanded_diff_hunks(cx) {
4193 cx.notify();
4194 return;
4195 }
4196 if self.show_git_blame_gutter {
4197 self.show_git_blame_gutter = false;
4198 cx.notify();
4199 return;
4200 }
4201
4202 if self.mode.is_full()
4203 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4204 {
4205 cx.notify();
4206 return;
4207 }
4208
4209 cx.propagate();
4210 }
4211
4212 pub fn dismiss_menus_and_popups(
4213 &mut self,
4214 is_user_requested: bool,
4215 window: &mut Window,
4216 cx: &mut Context<Self>,
4217 ) -> bool {
4218 let mut dismissed = false;
4219
4220 dismissed |= self.take_rename(false, window, cx).is_some();
4221 dismissed |= self.hide_blame_popover(true, cx);
4222 dismissed |= hide_hover(self, cx);
4223 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4224 dismissed |= self.hide_context_menu(window, cx).is_some();
4225 dismissed |= self.mouse_context_menu.take().is_some();
4226 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4227 dismissed |= self.snippet_stack.pop().is_some();
4228
4229 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4230 self.dismiss_diagnostics(cx);
4231 dismissed = true;
4232 }
4233
4234 dismissed
4235 }
4236
4237 fn linked_editing_ranges_for(
4238 &self,
4239 selection: Range<text::Anchor>,
4240 cx: &App,
4241 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4242 if self.linked_edit_ranges.is_empty() {
4243 return None;
4244 }
4245 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4246 selection.end.buffer_id.and_then(|end_buffer_id| {
4247 if selection.start.buffer_id != Some(end_buffer_id) {
4248 return None;
4249 }
4250 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4251 let snapshot = buffer.read(cx).snapshot();
4252 self.linked_edit_ranges
4253 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4254 .map(|ranges| (ranges, snapshot, buffer))
4255 })?;
4256 use text::ToOffset as TO;
4257 // find offset from the start of current range to current cursor position
4258 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4259
4260 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4261 let start_difference = start_offset - start_byte_offset;
4262 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4263 let end_difference = end_offset - start_byte_offset;
4264 // Current range has associated linked ranges.
4265 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4266 for range in linked_ranges.iter() {
4267 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4268 let end_offset = start_offset + end_difference;
4269 let start_offset = start_offset + start_difference;
4270 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4271 continue;
4272 }
4273 if self.selections.disjoint_anchor_ranges().any(|s| {
4274 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4275 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4276 {
4277 return false;
4278 }
4279 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4280 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4281 }) {
4282 continue;
4283 }
4284 let start = buffer_snapshot.anchor_after(start_offset);
4285 let end = buffer_snapshot.anchor_after(end_offset);
4286 linked_edits
4287 .entry(buffer.clone())
4288 .or_default()
4289 .push(start..end);
4290 }
4291 Some(linked_edits)
4292 }
4293
4294 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4295 let text: Arc<str> = text.into();
4296
4297 if self.read_only(cx) {
4298 return;
4299 }
4300
4301 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4302
4303 self.unfold_buffers_with_selections(cx);
4304
4305 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4306 let mut bracket_inserted = false;
4307 let mut edits = Vec::new();
4308 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4309 let mut new_selections = Vec::with_capacity(selections.len());
4310 let mut new_autoclose_regions = Vec::new();
4311 let snapshot = self.buffer.read(cx).read(cx);
4312 let mut clear_linked_edit_ranges = false;
4313
4314 for (selection, autoclose_region) in
4315 self.selections_with_autoclose_regions(selections, &snapshot)
4316 {
4317 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4318 // Determine if the inserted text matches the opening or closing
4319 // bracket of any of this language's bracket pairs.
4320 let mut bracket_pair = None;
4321 let mut is_bracket_pair_start = false;
4322 let mut is_bracket_pair_end = false;
4323 if !text.is_empty() {
4324 let mut bracket_pair_matching_end = None;
4325 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4326 // and they are removing the character that triggered IME popup.
4327 for (pair, enabled) in scope.brackets() {
4328 if !pair.close && !pair.surround {
4329 continue;
4330 }
4331
4332 if enabled && pair.start.ends_with(text.as_ref()) {
4333 let prefix_len = pair.start.len() - text.len();
4334 let preceding_text_matches_prefix = prefix_len == 0
4335 || (selection.start.column >= (prefix_len as u32)
4336 && snapshot.contains_str_at(
4337 Point::new(
4338 selection.start.row,
4339 selection.start.column - (prefix_len as u32),
4340 ),
4341 &pair.start[..prefix_len],
4342 ));
4343 if preceding_text_matches_prefix {
4344 bracket_pair = Some(pair.clone());
4345 is_bracket_pair_start = true;
4346 break;
4347 }
4348 }
4349 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4350 {
4351 // take first bracket pair matching end, but don't break in case a later bracket
4352 // pair matches start
4353 bracket_pair_matching_end = Some(pair.clone());
4354 }
4355 }
4356 if let Some(end) = bracket_pair_matching_end
4357 && bracket_pair.is_none()
4358 {
4359 bracket_pair = Some(end);
4360 is_bracket_pair_end = true;
4361 }
4362 }
4363
4364 if let Some(bracket_pair) = bracket_pair {
4365 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4366 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4367 let auto_surround =
4368 self.use_auto_surround && snapshot_settings.use_auto_surround;
4369 if selection.is_empty() {
4370 if is_bracket_pair_start {
4371 // If the inserted text is a suffix of an opening bracket and the
4372 // selection is preceded by the rest of the opening bracket, then
4373 // insert the closing bracket.
4374 let following_text_allows_autoclose = snapshot
4375 .chars_at(selection.start)
4376 .next()
4377 .is_none_or(|c| scope.should_autoclose_before(c));
4378
4379 let preceding_text_allows_autoclose = selection.start.column == 0
4380 || snapshot
4381 .reversed_chars_at(selection.start)
4382 .next()
4383 .is_none_or(|c| {
4384 bracket_pair.start != bracket_pair.end
4385 || !snapshot
4386 .char_classifier_at(selection.start)
4387 .is_word(c)
4388 });
4389
4390 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4391 && bracket_pair.start.len() == 1
4392 {
4393 let target = bracket_pair.start.chars().next().unwrap();
4394 let mut byte_offset = 0u32;
4395 let current_line_count = snapshot
4396 .reversed_chars_at(selection.start)
4397 .take_while(|&c| c != '\n')
4398 .filter(|c| {
4399 byte_offset += c.len_utf8() as u32;
4400 if *c != target {
4401 return false;
4402 }
4403
4404 let point = Point::new(
4405 selection.start.row,
4406 selection.start.column.saturating_sub(byte_offset),
4407 );
4408
4409 let is_enabled = snapshot
4410 .language_scope_at(point)
4411 .and_then(|scope| {
4412 scope
4413 .brackets()
4414 .find(|(pair, _)| {
4415 pair.start == bracket_pair.start
4416 })
4417 .map(|(_, enabled)| enabled)
4418 })
4419 .unwrap_or(true);
4420
4421 let is_delimiter = snapshot
4422 .language_scope_at(Point::new(
4423 point.row,
4424 point.column + 1,
4425 ))
4426 .and_then(|scope| {
4427 scope
4428 .brackets()
4429 .find(|(pair, _)| {
4430 pair.start == bracket_pair.start
4431 })
4432 .map(|(_, enabled)| !enabled)
4433 })
4434 .unwrap_or(false);
4435
4436 is_enabled && !is_delimiter
4437 })
4438 .count();
4439 current_line_count % 2 == 1
4440 } else {
4441 false
4442 };
4443
4444 if autoclose
4445 && bracket_pair.close
4446 && following_text_allows_autoclose
4447 && preceding_text_allows_autoclose
4448 && !is_closing_quote
4449 {
4450 let anchor = snapshot.anchor_before(selection.end);
4451 new_selections.push((selection.map(|_| anchor), text.len()));
4452 new_autoclose_regions.push((
4453 anchor,
4454 text.len(),
4455 selection.id,
4456 bracket_pair.clone(),
4457 ));
4458 edits.push((
4459 selection.range(),
4460 format!("{}{}", text, bracket_pair.end).into(),
4461 ));
4462 bracket_inserted = true;
4463 continue;
4464 }
4465 }
4466
4467 if let Some(region) = autoclose_region {
4468 // If the selection is followed by an auto-inserted closing bracket,
4469 // then don't insert that closing bracket again; just move the selection
4470 // past the closing bracket.
4471 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4472 && text.as_ref() == region.pair.end.as_str()
4473 && snapshot.contains_str_at(region.range.end, text.as_ref());
4474 if should_skip {
4475 let anchor = snapshot.anchor_after(selection.end);
4476 new_selections
4477 .push((selection.map(|_| anchor), region.pair.end.len()));
4478 continue;
4479 }
4480 }
4481
4482 let always_treat_brackets_as_autoclosed = snapshot
4483 .language_settings_at(selection.start, cx)
4484 .always_treat_brackets_as_autoclosed;
4485 if always_treat_brackets_as_autoclosed
4486 && is_bracket_pair_end
4487 && snapshot.contains_str_at(selection.end, text.as_ref())
4488 {
4489 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4490 // and the inserted text is a closing bracket and the selection is followed
4491 // by the closing bracket then move the selection past the closing bracket.
4492 let anchor = snapshot.anchor_after(selection.end);
4493 new_selections.push((selection.map(|_| anchor), text.len()));
4494 continue;
4495 }
4496 }
4497 // If an opening bracket is 1 character long and is typed while
4498 // text is selected, then surround that text with the bracket pair.
4499 else if auto_surround
4500 && bracket_pair.surround
4501 && is_bracket_pair_start
4502 && bracket_pair.start.chars().count() == 1
4503 {
4504 edits.push((selection.start..selection.start, text.clone()));
4505 edits.push((
4506 selection.end..selection.end,
4507 bracket_pair.end.as_str().into(),
4508 ));
4509 bracket_inserted = true;
4510 new_selections.push((
4511 Selection {
4512 id: selection.id,
4513 start: snapshot.anchor_after(selection.start),
4514 end: snapshot.anchor_before(selection.end),
4515 reversed: selection.reversed,
4516 goal: selection.goal,
4517 },
4518 0,
4519 ));
4520 continue;
4521 }
4522 }
4523 }
4524
4525 if self.auto_replace_emoji_shortcode
4526 && selection.is_empty()
4527 && text.as_ref().ends_with(':')
4528 && let Some(possible_emoji_short_code) =
4529 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4530 && !possible_emoji_short_code.is_empty()
4531 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4532 {
4533 let emoji_shortcode_start = Point::new(
4534 selection.start.row,
4535 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4536 );
4537
4538 // Remove shortcode from buffer
4539 edits.push((
4540 emoji_shortcode_start..selection.start,
4541 "".to_string().into(),
4542 ));
4543 new_selections.push((
4544 Selection {
4545 id: selection.id,
4546 start: snapshot.anchor_after(emoji_shortcode_start),
4547 end: snapshot.anchor_before(selection.start),
4548 reversed: selection.reversed,
4549 goal: selection.goal,
4550 },
4551 0,
4552 ));
4553
4554 // Insert emoji
4555 let selection_start_anchor = snapshot.anchor_after(selection.start);
4556 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4557 edits.push((selection.start..selection.end, emoji.to_string().into()));
4558
4559 continue;
4560 }
4561
4562 // If not handling any auto-close operation, then just replace the selected
4563 // text with the given input and move the selection to the end of the
4564 // newly inserted text.
4565 let anchor = snapshot.anchor_after(selection.end);
4566 if !self.linked_edit_ranges.is_empty() {
4567 let start_anchor = snapshot.anchor_before(selection.start);
4568
4569 let is_word_char = text.chars().next().is_none_or(|char| {
4570 let classifier = snapshot
4571 .char_classifier_at(start_anchor.to_offset(&snapshot))
4572 .scope_context(Some(CharScopeContext::LinkedEdit));
4573 classifier.is_word(char)
4574 });
4575
4576 if is_word_char {
4577 if let Some(ranges) = self
4578 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4579 {
4580 for (buffer, edits) in ranges {
4581 linked_edits
4582 .entry(buffer.clone())
4583 .or_default()
4584 .extend(edits.into_iter().map(|range| (range, text.clone())));
4585 }
4586 }
4587 } else {
4588 clear_linked_edit_ranges = true;
4589 }
4590 }
4591
4592 new_selections.push((selection.map(|_| anchor), 0));
4593 edits.push((selection.start..selection.end, text.clone()));
4594 }
4595
4596 drop(snapshot);
4597
4598 self.transact(window, cx, |this, window, cx| {
4599 if clear_linked_edit_ranges {
4600 this.linked_edit_ranges.clear();
4601 }
4602 let initial_buffer_versions =
4603 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4604
4605 this.buffer.update(cx, |buffer, cx| {
4606 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4607 });
4608 for (buffer, edits) in linked_edits {
4609 buffer.update(cx, |buffer, cx| {
4610 let snapshot = buffer.snapshot();
4611 let edits = edits
4612 .into_iter()
4613 .map(|(range, text)| {
4614 use text::ToPoint as TP;
4615 let end_point = TP::to_point(&range.end, &snapshot);
4616 let start_point = TP::to_point(&range.start, &snapshot);
4617 (start_point..end_point, text)
4618 })
4619 .sorted_by_key(|(range, _)| range.start);
4620 buffer.edit(edits, None, cx);
4621 })
4622 }
4623 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4624 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4625 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4626 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4627 new_anchor_selections,
4628 &map,
4629 )
4630 .zip(new_selection_deltas)
4631 .map(|(selection, delta)| Selection {
4632 id: selection.id,
4633 start: selection.start + delta,
4634 end: selection.end + delta,
4635 reversed: selection.reversed,
4636 goal: SelectionGoal::None,
4637 })
4638 .collect::<Vec<_>>();
4639
4640 let mut i = 0;
4641 for (position, delta, selection_id, pair) in new_autoclose_regions {
4642 let position = position.to_offset(map.buffer_snapshot()) + delta;
4643 let start = map.buffer_snapshot().anchor_before(position);
4644 let end = map.buffer_snapshot().anchor_after(position);
4645 while let Some(existing_state) = this.autoclose_regions.get(i) {
4646 match existing_state
4647 .range
4648 .start
4649 .cmp(&start, map.buffer_snapshot())
4650 {
4651 Ordering::Less => i += 1,
4652 Ordering::Greater => break,
4653 Ordering::Equal => {
4654 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4655 Ordering::Less => i += 1,
4656 Ordering::Equal => break,
4657 Ordering::Greater => break,
4658 }
4659 }
4660 }
4661 }
4662 this.autoclose_regions.insert(
4663 i,
4664 AutocloseRegion {
4665 selection_id,
4666 range: start..end,
4667 pair,
4668 },
4669 );
4670 }
4671
4672 let had_active_edit_prediction = this.has_active_edit_prediction();
4673 this.change_selections(
4674 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4675 window,
4676 cx,
4677 |s| s.select(new_selections),
4678 );
4679
4680 if !bracket_inserted
4681 && let Some(on_type_format_task) =
4682 this.trigger_on_type_formatting(text.to_string(), window, cx)
4683 {
4684 on_type_format_task.detach_and_log_err(cx);
4685 }
4686
4687 let editor_settings = EditorSettings::get_global(cx);
4688 if bracket_inserted
4689 && (editor_settings.auto_signature_help
4690 || editor_settings.show_signature_help_after_edits)
4691 {
4692 this.show_signature_help(&ShowSignatureHelp, window, cx);
4693 }
4694
4695 let trigger_in_words =
4696 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4697 if this.hard_wrap.is_some() {
4698 let latest: Range<Point> = this.selections.newest(&map).range();
4699 if latest.is_empty()
4700 && this
4701 .buffer()
4702 .read(cx)
4703 .snapshot(cx)
4704 .line_len(MultiBufferRow(latest.start.row))
4705 == latest.start.column
4706 {
4707 this.rewrap_impl(
4708 RewrapOptions {
4709 override_language_settings: true,
4710 preserve_existing_whitespace: true,
4711 },
4712 cx,
4713 )
4714 }
4715 }
4716 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4717 refresh_linked_ranges(this, window, cx);
4718 this.refresh_edit_prediction(true, false, window, cx);
4719 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4720 });
4721 }
4722
4723 fn find_possible_emoji_shortcode_at_position(
4724 snapshot: &MultiBufferSnapshot,
4725 position: Point,
4726 ) -> Option<String> {
4727 let mut chars = Vec::new();
4728 let mut found_colon = false;
4729 for char in snapshot.reversed_chars_at(position).take(100) {
4730 // Found a possible emoji shortcode in the middle of the buffer
4731 if found_colon {
4732 if char.is_whitespace() {
4733 chars.reverse();
4734 return Some(chars.iter().collect());
4735 }
4736 // If the previous character is not a whitespace, we are in the middle of a word
4737 // and we only want to complete the shortcode if the word is made up of other emojis
4738 let mut containing_word = String::new();
4739 for ch in snapshot
4740 .reversed_chars_at(position)
4741 .skip(chars.len() + 1)
4742 .take(100)
4743 {
4744 if ch.is_whitespace() {
4745 break;
4746 }
4747 containing_word.push(ch);
4748 }
4749 let containing_word = containing_word.chars().rev().collect::<String>();
4750 if util::word_consists_of_emojis(containing_word.as_str()) {
4751 chars.reverse();
4752 return Some(chars.iter().collect());
4753 }
4754 }
4755
4756 if char.is_whitespace() || !char.is_ascii() {
4757 return None;
4758 }
4759 if char == ':' {
4760 found_colon = true;
4761 } else {
4762 chars.push(char);
4763 }
4764 }
4765 // Found a possible emoji shortcode at the beginning of the buffer
4766 chars.reverse();
4767 Some(chars.iter().collect())
4768 }
4769
4770 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4771 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4772 self.transact(window, cx, |this, window, cx| {
4773 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4774 let selections = this
4775 .selections
4776 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4777 let multi_buffer = this.buffer.read(cx);
4778 let buffer = multi_buffer.snapshot(cx);
4779 selections
4780 .iter()
4781 .map(|selection| {
4782 let start_point = selection.start.to_point(&buffer);
4783 let mut existing_indent =
4784 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4785 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4786 let start = selection.start;
4787 let end = selection.end;
4788 let selection_is_empty = start == end;
4789 let language_scope = buffer.language_scope_at(start);
4790 let (comment_delimiter, doc_delimiter, newline_formatting) =
4791 if let Some(language) = &language_scope {
4792 let mut newline_formatting =
4793 NewlineFormatting::new(&buffer, start..end, language);
4794
4795 // Comment extension on newline is allowed only for cursor selections
4796 let comment_delimiter = maybe!({
4797 if !selection_is_empty {
4798 return None;
4799 }
4800
4801 if !multi_buffer.language_settings(cx).extend_comment_on_newline
4802 {
4803 return None;
4804 }
4805
4806 return comment_delimiter_for_newline(
4807 &start_point,
4808 &buffer,
4809 language,
4810 );
4811 });
4812
4813 let doc_delimiter = maybe!({
4814 if !selection_is_empty {
4815 return None;
4816 }
4817
4818 if !multi_buffer.language_settings(cx).extend_comment_on_newline
4819 {
4820 return None;
4821 }
4822
4823 return documentation_delimiter_for_newline(
4824 &start_point,
4825 &buffer,
4826 language,
4827 &mut newline_formatting,
4828 );
4829 });
4830
4831 (comment_delimiter, doc_delimiter, newline_formatting)
4832 } else {
4833 (None, None, NewlineFormatting::default())
4834 };
4835
4836 let prevent_auto_indent = doc_delimiter.is_some();
4837 let delimiter = comment_delimiter.or(doc_delimiter);
4838
4839 let capacity_for_delimiter =
4840 delimiter.as_deref().map(str::len).unwrap_or_default();
4841 let mut new_text = String::with_capacity(
4842 1 + capacity_for_delimiter
4843 + existing_indent.len as usize
4844 + newline_formatting.indent_on_newline.len as usize
4845 + newline_formatting.indent_on_extra_newline.len as usize,
4846 );
4847 new_text.push('\n');
4848 new_text.extend(existing_indent.chars());
4849 new_text.extend(newline_formatting.indent_on_newline.chars());
4850
4851 if let Some(delimiter) = &delimiter {
4852 new_text.push_str(delimiter);
4853 }
4854
4855 if newline_formatting.insert_extra_newline {
4856 new_text.push('\n');
4857 new_text.extend(existing_indent.chars());
4858 new_text.extend(newline_formatting.indent_on_extra_newline.chars());
4859 }
4860
4861 let anchor = buffer.anchor_after(end);
4862 let new_selection = selection.map(|_| anchor);
4863 (
4864 ((start..end, new_text), prevent_auto_indent),
4865 (newline_formatting.insert_extra_newline, new_selection),
4866 )
4867 })
4868 .unzip()
4869 };
4870
4871 let mut auto_indent_edits = Vec::new();
4872 let mut edits = Vec::new();
4873 for (edit, prevent_auto_indent) in edits_with_flags {
4874 if prevent_auto_indent {
4875 edits.push(edit);
4876 } else {
4877 auto_indent_edits.push(edit);
4878 }
4879 }
4880 if !edits.is_empty() {
4881 this.edit(edits, cx);
4882 }
4883 if !auto_indent_edits.is_empty() {
4884 this.edit_with_autoindent(auto_indent_edits, cx);
4885 }
4886
4887 let buffer = this.buffer.read(cx).snapshot(cx);
4888 let new_selections = selection_info
4889 .into_iter()
4890 .map(|(extra_newline_inserted, new_selection)| {
4891 let mut cursor = new_selection.end.to_point(&buffer);
4892 if extra_newline_inserted {
4893 cursor.row -= 1;
4894 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4895 }
4896 new_selection.map(|_| cursor)
4897 })
4898 .collect();
4899
4900 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4901 this.refresh_edit_prediction(true, false, window, cx);
4902 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
4903 task.detach_and_log_err(cx);
4904 }
4905 });
4906 }
4907
4908 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4909 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4910
4911 let buffer = self.buffer.read(cx);
4912 let snapshot = buffer.snapshot(cx);
4913
4914 let mut edits = Vec::new();
4915 let mut rows = Vec::new();
4916
4917 for (rows_inserted, selection) in self
4918 .selections
4919 .all_adjusted(&self.display_snapshot(cx))
4920 .into_iter()
4921 .enumerate()
4922 {
4923 let cursor = selection.head();
4924 let row = cursor.row;
4925
4926 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4927
4928 let newline = "\n".to_string();
4929 edits.push((start_of_line..start_of_line, newline));
4930
4931 rows.push(row + rows_inserted as u32);
4932 }
4933
4934 self.transact(window, cx, |editor, window, cx| {
4935 editor.edit(edits, cx);
4936
4937 editor.change_selections(Default::default(), window, cx, |s| {
4938 let mut index = 0;
4939 s.move_cursors_with(|map, _, _| {
4940 let row = rows[index];
4941 index += 1;
4942
4943 let point = Point::new(row, 0);
4944 let boundary = map.next_line_boundary(point).1;
4945 let clipped = map.clip_point(boundary, Bias::Left);
4946
4947 (clipped, SelectionGoal::None)
4948 });
4949 });
4950
4951 let mut indent_edits = Vec::new();
4952 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4953 for row in rows {
4954 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4955 for (row, indent) in indents {
4956 if indent.len == 0 {
4957 continue;
4958 }
4959
4960 let text = match indent.kind {
4961 IndentKind::Space => " ".repeat(indent.len as usize),
4962 IndentKind::Tab => "\t".repeat(indent.len as usize),
4963 };
4964 let point = Point::new(row.0, 0);
4965 indent_edits.push((point..point, text));
4966 }
4967 }
4968 editor.edit(indent_edits, cx);
4969 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
4970 format.detach_and_log_err(cx);
4971 }
4972 });
4973 }
4974
4975 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4976 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4977
4978 let buffer = self.buffer.read(cx);
4979 let snapshot = buffer.snapshot(cx);
4980
4981 let mut edits = Vec::new();
4982 let mut rows = Vec::new();
4983 let mut rows_inserted = 0;
4984
4985 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4986 let cursor = selection.head();
4987 let row = cursor.row;
4988
4989 let point = Point::new(row + 1, 0);
4990 let start_of_line = snapshot.clip_point(point, Bias::Left);
4991
4992 let newline = "\n".to_string();
4993 edits.push((start_of_line..start_of_line, newline));
4994
4995 rows_inserted += 1;
4996 rows.push(row + rows_inserted);
4997 }
4998
4999 self.transact(window, cx, |editor, window, cx| {
5000 editor.edit(edits, cx);
5001
5002 editor.change_selections(Default::default(), window, cx, |s| {
5003 let mut index = 0;
5004 s.move_cursors_with(|map, _, _| {
5005 let row = rows[index];
5006 index += 1;
5007
5008 let point = Point::new(row, 0);
5009 let boundary = map.next_line_boundary(point).1;
5010 let clipped = map.clip_point(boundary, Bias::Left);
5011
5012 (clipped, SelectionGoal::None)
5013 });
5014 });
5015
5016 let mut indent_edits = Vec::new();
5017 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5018 for row in rows {
5019 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5020 for (row, indent) in indents {
5021 if indent.len == 0 {
5022 continue;
5023 }
5024
5025 let text = match indent.kind {
5026 IndentKind::Space => " ".repeat(indent.len as usize),
5027 IndentKind::Tab => "\t".repeat(indent.len as usize),
5028 };
5029 let point = Point::new(row.0, 0);
5030 indent_edits.push((point..point, text));
5031 }
5032 }
5033 editor.edit(indent_edits, cx);
5034 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5035 format.detach_and_log_err(cx);
5036 }
5037 });
5038 }
5039
5040 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5041 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5042 original_indent_columns: Vec::new(),
5043 });
5044 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5045 }
5046
5047 fn insert_with_autoindent_mode(
5048 &mut self,
5049 text: &str,
5050 autoindent_mode: Option<AutoindentMode>,
5051 window: &mut Window,
5052 cx: &mut Context<Self>,
5053 ) {
5054 if self.read_only(cx) {
5055 return;
5056 }
5057
5058 let text: Arc<str> = text.into();
5059 self.transact(window, cx, |this, window, cx| {
5060 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5061 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5062 let anchors = {
5063 let snapshot = buffer.read(cx);
5064 old_selections
5065 .iter()
5066 .map(|s| {
5067 let anchor = snapshot.anchor_after(s.head());
5068 s.map(|_| anchor)
5069 })
5070 .collect::<Vec<_>>()
5071 };
5072 buffer.edit(
5073 old_selections
5074 .iter()
5075 .map(|s| (s.start..s.end, text.clone())),
5076 autoindent_mode,
5077 cx,
5078 );
5079 anchors
5080 });
5081
5082 this.change_selections(Default::default(), window, cx, |s| {
5083 s.select_anchors(selection_anchors);
5084 });
5085
5086 cx.notify();
5087 });
5088 }
5089
5090 fn trigger_completion_on_input(
5091 &mut self,
5092 text: &str,
5093 trigger_in_words: bool,
5094 window: &mut Window,
5095 cx: &mut Context<Self>,
5096 ) {
5097 let completions_source = self
5098 .context_menu
5099 .borrow()
5100 .as_ref()
5101 .and_then(|menu| match menu {
5102 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5103 CodeContextMenu::CodeActions(_) => None,
5104 });
5105
5106 match completions_source {
5107 Some(CompletionsMenuSource::Words { .. }) => {
5108 self.open_or_update_completions_menu(
5109 Some(CompletionsMenuSource::Words {
5110 ignore_threshold: false,
5111 }),
5112 None,
5113 trigger_in_words,
5114 window,
5115 cx,
5116 );
5117 }
5118 _ => self.open_or_update_completions_menu(
5119 None,
5120 Some(text.to_owned()).filter(|x| !x.is_empty()),
5121 true,
5122 window,
5123 cx,
5124 ),
5125 }
5126 }
5127
5128 /// If any empty selections is touching the start of its innermost containing autoclose
5129 /// region, expand it to select the brackets.
5130 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5131 let selections = self
5132 .selections
5133 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5134 let buffer = self.buffer.read(cx).read(cx);
5135 let new_selections = self
5136 .selections_with_autoclose_regions(selections, &buffer)
5137 .map(|(mut selection, region)| {
5138 if !selection.is_empty() {
5139 return selection;
5140 }
5141
5142 if let Some(region) = region {
5143 let mut range = region.range.to_offset(&buffer);
5144 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5145 range.start -= region.pair.start.len();
5146 if buffer.contains_str_at(range.start, ®ion.pair.start)
5147 && buffer.contains_str_at(range.end, ®ion.pair.end)
5148 {
5149 range.end += region.pair.end.len();
5150 selection.start = range.start;
5151 selection.end = range.end;
5152
5153 return selection;
5154 }
5155 }
5156 }
5157
5158 let always_treat_brackets_as_autoclosed = buffer
5159 .language_settings_at(selection.start, cx)
5160 .always_treat_brackets_as_autoclosed;
5161
5162 if !always_treat_brackets_as_autoclosed {
5163 return selection;
5164 }
5165
5166 if let Some(scope) = buffer.language_scope_at(selection.start) {
5167 for (pair, enabled) in scope.brackets() {
5168 if !enabled || !pair.close {
5169 continue;
5170 }
5171
5172 if buffer.contains_str_at(selection.start, &pair.end) {
5173 let pair_start_len = pair.start.len();
5174 if buffer.contains_str_at(
5175 selection.start.saturating_sub_usize(pair_start_len),
5176 &pair.start,
5177 ) {
5178 selection.start -= pair_start_len;
5179 selection.end += pair.end.len();
5180
5181 return selection;
5182 }
5183 }
5184 }
5185 }
5186
5187 selection
5188 })
5189 .collect();
5190
5191 drop(buffer);
5192 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5193 selections.select(new_selections)
5194 });
5195 }
5196
5197 /// Iterate the given selections, and for each one, find the smallest surrounding
5198 /// autoclose region. This uses the ordering of the selections and the autoclose
5199 /// regions to avoid repeated comparisons.
5200 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5201 &'a self,
5202 selections: impl IntoIterator<Item = Selection<D>>,
5203 buffer: &'a MultiBufferSnapshot,
5204 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5205 let mut i = 0;
5206 let mut regions = self.autoclose_regions.as_slice();
5207 selections.into_iter().map(move |selection| {
5208 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5209
5210 let mut enclosing = None;
5211 while let Some(pair_state) = regions.get(i) {
5212 if pair_state.range.end.to_offset(buffer) < range.start {
5213 regions = ®ions[i + 1..];
5214 i = 0;
5215 } else if pair_state.range.start.to_offset(buffer) > range.end {
5216 break;
5217 } else {
5218 if pair_state.selection_id == selection.id {
5219 enclosing = Some(pair_state);
5220 }
5221 i += 1;
5222 }
5223 }
5224
5225 (selection, enclosing)
5226 })
5227 }
5228
5229 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5230 fn invalidate_autoclose_regions(
5231 &mut self,
5232 mut selections: &[Selection<Anchor>],
5233 buffer: &MultiBufferSnapshot,
5234 ) {
5235 self.autoclose_regions.retain(|state| {
5236 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5237 return false;
5238 }
5239
5240 let mut i = 0;
5241 while let Some(selection) = selections.get(i) {
5242 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5243 selections = &selections[1..];
5244 continue;
5245 }
5246 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5247 break;
5248 }
5249 if selection.id == state.selection_id {
5250 return true;
5251 } else {
5252 i += 1;
5253 }
5254 }
5255 false
5256 });
5257 }
5258
5259 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5260 let offset = position.to_offset(buffer);
5261 let (word_range, kind) =
5262 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5263 if offset > word_range.start && kind == Some(CharKind::Word) {
5264 Some(
5265 buffer
5266 .text_for_range(word_range.start..offset)
5267 .collect::<String>(),
5268 )
5269 } else {
5270 None
5271 }
5272 }
5273
5274 pub fn visible_excerpts(
5275 &self,
5276 lsp_related_only: bool,
5277 cx: &mut Context<Editor>,
5278 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5279 let project = self.project().cloned();
5280 let multi_buffer = self.buffer().read(cx);
5281 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5282 let multi_buffer_visible_start = self
5283 .scroll_manager
5284 .anchor()
5285 .anchor
5286 .to_point(&multi_buffer_snapshot);
5287 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5288 multi_buffer_visible_start
5289 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5290 Bias::Left,
5291 );
5292 multi_buffer_snapshot
5293 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5294 .into_iter()
5295 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5296 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5297 if !lsp_related_only {
5298 return Some((
5299 excerpt_id,
5300 (
5301 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5302 buffer.version().clone(),
5303 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5304 ),
5305 ));
5306 }
5307
5308 let project = project.as_ref()?.read(cx);
5309 let buffer_file = project::File::from_dyn(buffer.file())?;
5310 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5311 let worktree_entry = buffer_worktree
5312 .read(cx)
5313 .entry_for_id(buffer_file.project_entry_id()?)?;
5314 if worktree_entry.is_ignored {
5315 None
5316 } else {
5317 Some((
5318 excerpt_id,
5319 (
5320 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5321 buffer.version().clone(),
5322 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5323 ),
5324 ))
5325 }
5326 })
5327 .collect()
5328 }
5329
5330 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5331 TextLayoutDetails {
5332 text_system: window.text_system().clone(),
5333 editor_style: self.style.clone().unwrap(),
5334 rem_size: window.rem_size(),
5335 scroll_anchor: self.scroll_manager.anchor(),
5336 visible_rows: self.visible_line_count(),
5337 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5338 }
5339 }
5340
5341 fn trigger_on_type_formatting(
5342 &self,
5343 input: String,
5344 window: &mut Window,
5345 cx: &mut Context<Self>,
5346 ) -> Option<Task<Result<()>>> {
5347 if input.chars().count() != 1 {
5348 return None;
5349 }
5350
5351 let project = self.project()?;
5352 let position = self.selections.newest_anchor().head();
5353 let (buffer, buffer_position) = self
5354 .buffer
5355 .read(cx)
5356 .text_anchor_for_position(position, cx)?;
5357
5358 let settings = language_settings::language_settings(
5359 buffer
5360 .read(cx)
5361 .language_at(buffer_position)
5362 .map(|l| l.name()),
5363 buffer.read(cx).file(),
5364 cx,
5365 );
5366 if !settings.use_on_type_format {
5367 return None;
5368 }
5369
5370 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5371 // hence we do LSP request & edit on host side only — add formats to host's history.
5372 let push_to_lsp_host_history = true;
5373 // If this is not the host, append its history with new edits.
5374 let push_to_client_history = project.read(cx).is_via_collab();
5375
5376 let on_type_formatting = project.update(cx, |project, cx| {
5377 project.on_type_format(
5378 buffer.clone(),
5379 buffer_position,
5380 input,
5381 push_to_lsp_host_history,
5382 cx,
5383 )
5384 });
5385 Some(cx.spawn_in(window, async move |editor, cx| {
5386 if let Some(transaction) = on_type_formatting.await? {
5387 if push_to_client_history {
5388 buffer
5389 .update(cx, |buffer, _| {
5390 buffer.push_transaction(transaction, Instant::now());
5391 buffer.finalize_last_transaction();
5392 })
5393 .ok();
5394 }
5395 editor.update(cx, |editor, cx| {
5396 editor.refresh_document_highlights(cx);
5397 })?;
5398 }
5399 Ok(())
5400 }))
5401 }
5402
5403 pub fn show_word_completions(
5404 &mut self,
5405 _: &ShowWordCompletions,
5406 window: &mut Window,
5407 cx: &mut Context<Self>,
5408 ) {
5409 self.open_or_update_completions_menu(
5410 Some(CompletionsMenuSource::Words {
5411 ignore_threshold: true,
5412 }),
5413 None,
5414 false,
5415 window,
5416 cx,
5417 );
5418 }
5419
5420 pub fn show_completions(
5421 &mut self,
5422 _: &ShowCompletions,
5423 window: &mut Window,
5424 cx: &mut Context<Self>,
5425 ) {
5426 self.open_or_update_completions_menu(None, None, false, window, cx);
5427 }
5428
5429 fn open_or_update_completions_menu(
5430 &mut self,
5431 requested_source: Option<CompletionsMenuSource>,
5432 trigger: Option<String>,
5433 trigger_in_words: bool,
5434 window: &mut Window,
5435 cx: &mut Context<Self>,
5436 ) {
5437 if self.pending_rename.is_some() {
5438 return;
5439 }
5440
5441 let completions_source = self
5442 .context_menu
5443 .borrow()
5444 .as_ref()
5445 .and_then(|menu| match menu {
5446 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5447 CodeContextMenu::CodeActions(_) => None,
5448 });
5449
5450 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5451
5452 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5453 // inserted and selected. To handle that case, the start of the selection is used so that
5454 // the menu starts with all choices.
5455 let position = self
5456 .selections
5457 .newest_anchor()
5458 .start
5459 .bias_right(&multibuffer_snapshot);
5460 if position.diff_base_anchor.is_some() {
5461 return;
5462 }
5463 let buffer_position = multibuffer_snapshot.anchor_before(position);
5464 let Some(buffer) = buffer_position
5465 .text_anchor
5466 .buffer_id
5467 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5468 else {
5469 return;
5470 };
5471 let buffer_snapshot = buffer.read(cx).snapshot();
5472
5473 let menu_is_open = matches!(
5474 self.context_menu.borrow().as_ref(),
5475 Some(CodeContextMenu::Completions(_))
5476 );
5477
5478 let language = buffer_snapshot
5479 .language_at(buffer_position.text_anchor)
5480 .map(|language| language.name());
5481
5482 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5483 let completion_settings = language_settings.completions.clone();
5484
5485 let show_completions_on_input = self
5486 .show_completions_on_input_override
5487 .unwrap_or(language_settings.show_completions_on_input);
5488 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5489 return;
5490 }
5491
5492 let query: Option<Arc<String>> =
5493 Self::completion_query(&multibuffer_snapshot, buffer_position)
5494 .map(|query| query.into());
5495
5496 drop(multibuffer_snapshot);
5497
5498 // Hide the current completions menu when query is empty. Without this, cached
5499 // completions from before the trigger char may be reused (#32774).
5500 if query.is_none() && menu_is_open {
5501 self.hide_context_menu(window, cx);
5502 }
5503
5504 let mut ignore_word_threshold = false;
5505 let provider = match requested_source {
5506 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5507 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5508 ignore_word_threshold = ignore_threshold;
5509 None
5510 }
5511 Some(CompletionsMenuSource::SnippetChoices)
5512 | Some(CompletionsMenuSource::SnippetsOnly) => {
5513 log::error!("bug: SnippetChoices requested_source is not handled");
5514 None
5515 }
5516 };
5517
5518 let sort_completions = provider
5519 .as_ref()
5520 .is_some_and(|provider| provider.sort_completions());
5521
5522 let filter_completions = provider
5523 .as_ref()
5524 .is_none_or(|provider| provider.filter_completions());
5525
5526 let was_snippets_only = matches!(
5527 completions_source,
5528 Some(CompletionsMenuSource::SnippetsOnly)
5529 );
5530
5531 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5532 if filter_completions {
5533 menu.filter(
5534 query.clone().unwrap_or_default(),
5535 buffer_position.text_anchor,
5536 &buffer,
5537 provider.clone(),
5538 window,
5539 cx,
5540 );
5541 }
5542 // When `is_incomplete` is false, no need to re-query completions when the current query
5543 // is a suffix of the initial query.
5544 let was_complete = !menu.is_incomplete;
5545 if was_complete && !was_snippets_only {
5546 // If the new query is a suffix of the old query (typing more characters) and
5547 // the previous result was complete, the existing completions can be filtered.
5548 //
5549 // Note that snippet completions are always complete.
5550 let query_matches = match (&menu.initial_query, &query) {
5551 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5552 (None, _) => true,
5553 _ => false,
5554 };
5555 if query_matches {
5556 let position_matches = if menu.initial_position == position {
5557 true
5558 } else {
5559 let snapshot = self.buffer.read(cx).read(cx);
5560 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5561 };
5562 if position_matches {
5563 return;
5564 }
5565 }
5566 }
5567 };
5568
5569 let Anchor {
5570 excerpt_id: buffer_excerpt_id,
5571 text_anchor: buffer_position,
5572 ..
5573 } = buffer_position;
5574
5575 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5576 buffer_snapshot.surrounding_word(buffer_position, None)
5577 {
5578 let word_to_exclude = buffer_snapshot
5579 .text_for_range(word_range.clone())
5580 .collect::<String>();
5581 (
5582 buffer_snapshot.anchor_before(word_range.start)
5583 ..buffer_snapshot.anchor_after(buffer_position),
5584 Some(word_to_exclude),
5585 )
5586 } else {
5587 (buffer_position..buffer_position, None)
5588 };
5589
5590 let show_completion_documentation = buffer_snapshot
5591 .settings_at(buffer_position, cx)
5592 .show_completion_documentation;
5593
5594 // The document can be large, so stay in reasonable bounds when searching for words,
5595 // otherwise completion pop-up might be slow to appear.
5596 const WORD_LOOKUP_ROWS: u32 = 5_000;
5597 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5598 let min_word_search = buffer_snapshot.clip_point(
5599 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5600 Bias::Left,
5601 );
5602 let max_word_search = buffer_snapshot.clip_point(
5603 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5604 Bias::Right,
5605 );
5606 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5607 ..buffer_snapshot.point_to_offset(max_word_search);
5608
5609 let skip_digits = query
5610 .as_ref()
5611 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5612
5613 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5614 trigger.as_ref().is_none_or(|trigger| {
5615 provider.is_completion_trigger(
5616 &buffer,
5617 position.text_anchor,
5618 trigger,
5619 trigger_in_words,
5620 cx,
5621 )
5622 })
5623 });
5624
5625 let provider_responses = if let Some(provider) = &provider
5626 && load_provider_completions
5627 {
5628 let trigger_character =
5629 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5630 let completion_context = CompletionContext {
5631 trigger_kind: match &trigger_character {
5632 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5633 None => CompletionTriggerKind::INVOKED,
5634 },
5635 trigger_character,
5636 };
5637
5638 provider.completions(
5639 buffer_excerpt_id,
5640 &buffer,
5641 buffer_position,
5642 completion_context,
5643 window,
5644 cx,
5645 )
5646 } else {
5647 Task::ready(Ok(Vec::new()))
5648 };
5649
5650 let load_word_completions = if !self.word_completions_enabled {
5651 false
5652 } else if requested_source
5653 == Some(CompletionsMenuSource::Words {
5654 ignore_threshold: true,
5655 })
5656 {
5657 true
5658 } else {
5659 load_provider_completions
5660 && completion_settings.words != WordsCompletionMode::Disabled
5661 && (ignore_word_threshold || {
5662 let words_min_length = completion_settings.words_min_length;
5663 // check whether word has at least `words_min_length` characters
5664 let query_chars = query.iter().flat_map(|q| q.chars());
5665 query_chars.take(words_min_length).count() == words_min_length
5666 })
5667 };
5668
5669 let mut words = if load_word_completions {
5670 cx.background_spawn({
5671 let buffer_snapshot = buffer_snapshot.clone();
5672 async move {
5673 buffer_snapshot.words_in_range(WordsQuery {
5674 fuzzy_contents: None,
5675 range: word_search_range,
5676 skip_digits,
5677 })
5678 }
5679 })
5680 } else {
5681 Task::ready(BTreeMap::default())
5682 };
5683
5684 let snippets = if let Some(provider) = &provider
5685 && provider.show_snippets()
5686 && let Some(project) = self.project()
5687 {
5688 let char_classifier = buffer_snapshot
5689 .char_classifier_at(buffer_position)
5690 .scope_context(Some(CharScopeContext::Completion));
5691 project.update(cx, |project, cx| {
5692 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5693 })
5694 } else {
5695 Task::ready(Ok(CompletionResponse {
5696 completions: Vec::new(),
5697 display_options: Default::default(),
5698 is_incomplete: false,
5699 }))
5700 };
5701
5702 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5703
5704 let id = post_inc(&mut self.next_completion_id);
5705 let task = cx.spawn_in(window, async move |editor, cx| {
5706 let Ok(()) = editor.update(cx, |this, _| {
5707 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5708 }) else {
5709 return;
5710 };
5711
5712 // TODO: Ideally completions from different sources would be selectively re-queried, so
5713 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5714 let mut completions = Vec::new();
5715 let mut is_incomplete = false;
5716 let mut display_options: Option<CompletionDisplayOptions> = None;
5717 if let Some(provider_responses) = provider_responses.await.log_err()
5718 && !provider_responses.is_empty()
5719 {
5720 for response in provider_responses {
5721 completions.extend(response.completions);
5722 is_incomplete = is_incomplete || response.is_incomplete;
5723 match display_options.as_mut() {
5724 None => {
5725 display_options = Some(response.display_options);
5726 }
5727 Some(options) => options.merge(&response.display_options),
5728 }
5729 }
5730 if completion_settings.words == WordsCompletionMode::Fallback {
5731 words = Task::ready(BTreeMap::default());
5732 }
5733 }
5734 let display_options = display_options.unwrap_or_default();
5735
5736 let mut words = words.await;
5737 if let Some(word_to_exclude) = &word_to_exclude {
5738 words.remove(word_to_exclude);
5739 }
5740 for lsp_completion in &completions {
5741 words.remove(&lsp_completion.new_text);
5742 }
5743 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5744 replace_range: word_replace_range.clone(),
5745 new_text: word.clone(),
5746 label: CodeLabel::plain(word, None),
5747 match_start: None,
5748 snippet_deduplication_key: None,
5749 icon_path: None,
5750 documentation: None,
5751 source: CompletionSource::BufferWord {
5752 word_range,
5753 resolved: false,
5754 },
5755 insert_text_mode: Some(InsertTextMode::AS_IS),
5756 confirm: None,
5757 }));
5758
5759 completions.extend(
5760 snippets
5761 .await
5762 .into_iter()
5763 .flat_map(|response| response.completions),
5764 );
5765
5766 let menu = if completions.is_empty() {
5767 None
5768 } else {
5769 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5770 let languages = editor
5771 .workspace
5772 .as_ref()
5773 .and_then(|(workspace, _)| workspace.upgrade())
5774 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5775 let menu = CompletionsMenu::new(
5776 id,
5777 requested_source.unwrap_or(if load_provider_completions {
5778 CompletionsMenuSource::Normal
5779 } else {
5780 CompletionsMenuSource::SnippetsOnly
5781 }),
5782 sort_completions,
5783 show_completion_documentation,
5784 position,
5785 query.clone(),
5786 is_incomplete,
5787 buffer.clone(),
5788 completions.into(),
5789 editor
5790 .context_menu()
5791 .borrow_mut()
5792 .as_ref()
5793 .map(|menu| menu.primary_scroll_handle()),
5794 display_options,
5795 snippet_sort_order,
5796 languages,
5797 language,
5798 cx,
5799 );
5800
5801 let query = if filter_completions { query } else { None };
5802 let matches_task = menu.do_async_filtering(
5803 query.unwrap_or_default(),
5804 buffer_position,
5805 &buffer,
5806 cx,
5807 );
5808 (menu, matches_task)
5809 }) else {
5810 return;
5811 };
5812
5813 let matches = matches_task.await;
5814
5815 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5816 // Newer menu already set, so exit.
5817 if let Some(CodeContextMenu::Completions(prev_menu)) =
5818 editor.context_menu.borrow().as_ref()
5819 && prev_menu.id > id
5820 {
5821 return;
5822 };
5823
5824 // Only valid to take prev_menu because either the new menu is immediately set
5825 // below, or the menu is hidden.
5826 if let Some(CodeContextMenu::Completions(prev_menu)) =
5827 editor.context_menu.borrow_mut().take()
5828 {
5829 let position_matches =
5830 if prev_menu.initial_position == menu.initial_position {
5831 true
5832 } else {
5833 let snapshot = editor.buffer.read(cx).read(cx);
5834 prev_menu.initial_position.to_offset(&snapshot)
5835 == menu.initial_position.to_offset(&snapshot)
5836 };
5837 if position_matches {
5838 // Preserve markdown cache before `set_filter_results` because it will
5839 // try to populate the documentation cache.
5840 menu.preserve_markdown_cache(prev_menu);
5841 }
5842 };
5843
5844 menu.set_filter_results(matches, provider, window, cx);
5845 }) else {
5846 return;
5847 };
5848
5849 menu.visible().then_some(menu)
5850 };
5851
5852 editor
5853 .update_in(cx, |editor, window, cx| {
5854 if editor.focus_handle.is_focused(window)
5855 && let Some(menu) = menu
5856 {
5857 *editor.context_menu.borrow_mut() =
5858 Some(CodeContextMenu::Completions(menu));
5859
5860 crate::hover_popover::hide_hover(editor, cx);
5861 if editor.show_edit_predictions_in_menu() {
5862 editor.update_visible_edit_prediction(window, cx);
5863 } else {
5864 editor.discard_edit_prediction(false, cx);
5865 }
5866
5867 cx.notify();
5868 return;
5869 }
5870
5871 if editor.completion_tasks.len() <= 1 {
5872 // If there are no more completion tasks and the last menu was empty, we should hide it.
5873 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5874 // If it was already hidden and we don't show edit predictions in the menu,
5875 // we should also show the edit prediction when available.
5876 if was_hidden && editor.show_edit_predictions_in_menu() {
5877 editor.update_visible_edit_prediction(window, cx);
5878 }
5879 }
5880 })
5881 .ok();
5882 });
5883
5884 self.completion_tasks.push((id, task));
5885 }
5886
5887 #[cfg(feature = "test-support")]
5888 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5889 let menu = self.context_menu.borrow();
5890 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5891 let completions = menu.completions.borrow();
5892 Some(completions.to_vec())
5893 } else {
5894 None
5895 }
5896 }
5897
5898 pub fn with_completions_menu_matching_id<R>(
5899 &self,
5900 id: CompletionId,
5901 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5902 ) -> R {
5903 let mut context_menu = self.context_menu.borrow_mut();
5904 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5905 return f(None);
5906 };
5907 if completions_menu.id != id {
5908 return f(None);
5909 }
5910 f(Some(completions_menu))
5911 }
5912
5913 pub fn confirm_completion(
5914 &mut self,
5915 action: &ConfirmCompletion,
5916 window: &mut Window,
5917 cx: &mut Context<Self>,
5918 ) -> Option<Task<Result<()>>> {
5919 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5920 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5921 }
5922
5923 pub fn confirm_completion_insert(
5924 &mut self,
5925 _: &ConfirmCompletionInsert,
5926 window: &mut Window,
5927 cx: &mut Context<Self>,
5928 ) -> Option<Task<Result<()>>> {
5929 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5930 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5931 }
5932
5933 pub fn confirm_completion_replace(
5934 &mut self,
5935 _: &ConfirmCompletionReplace,
5936 window: &mut Window,
5937 cx: &mut Context<Self>,
5938 ) -> Option<Task<Result<()>>> {
5939 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5940 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5941 }
5942
5943 pub fn compose_completion(
5944 &mut self,
5945 action: &ComposeCompletion,
5946 window: &mut Window,
5947 cx: &mut Context<Self>,
5948 ) -> Option<Task<Result<()>>> {
5949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5950 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5951 }
5952
5953 fn do_completion(
5954 &mut self,
5955 item_ix: Option<usize>,
5956 intent: CompletionIntent,
5957 window: &mut Window,
5958 cx: &mut Context<Editor>,
5959 ) -> Option<Task<Result<()>>> {
5960 use language::ToOffset as _;
5961
5962 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5963 else {
5964 return None;
5965 };
5966
5967 let candidate_id = {
5968 let entries = completions_menu.entries.borrow();
5969 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5970 if self.show_edit_predictions_in_menu() {
5971 self.discard_edit_prediction(true, cx);
5972 }
5973 mat.candidate_id
5974 };
5975
5976 let completion = completions_menu
5977 .completions
5978 .borrow()
5979 .get(candidate_id)?
5980 .clone();
5981 cx.stop_propagation();
5982
5983 let buffer_handle = completions_menu.buffer.clone();
5984
5985 let CompletionEdit {
5986 new_text,
5987 snippet,
5988 replace_range,
5989 } = process_completion_for_edit(
5990 &completion,
5991 intent,
5992 &buffer_handle,
5993 &completions_menu.initial_position.text_anchor,
5994 cx,
5995 );
5996
5997 let buffer = buffer_handle.read(cx);
5998 let snapshot = self.buffer.read(cx).snapshot(cx);
5999 let newest_anchor = self.selections.newest_anchor();
6000 let replace_range_multibuffer = {
6001 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6002 excerpt.map_range_from_buffer(replace_range.clone())
6003 };
6004 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6005 return None;
6006 }
6007
6008 let old_text = buffer
6009 .text_for_range(replace_range.clone())
6010 .collect::<String>();
6011 let lookbehind = newest_anchor
6012 .start
6013 .text_anchor
6014 .to_offset(buffer)
6015 .saturating_sub(replace_range.start.0);
6016 let lookahead = replace_range
6017 .end
6018 .0
6019 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6020 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6021 let suffix = &old_text[lookbehind.min(old_text.len())..];
6022
6023 let selections = self
6024 .selections
6025 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6026 let mut ranges = Vec::new();
6027 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6028
6029 for selection in &selections {
6030 let range = if selection.id == newest_anchor.id {
6031 replace_range_multibuffer.clone()
6032 } else {
6033 let mut range = selection.range();
6034
6035 // if prefix is present, don't duplicate it
6036 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6037 range.start = range.start.saturating_sub_usize(lookbehind);
6038
6039 // if suffix is also present, mimic the newest cursor and replace it
6040 if selection.id != newest_anchor.id
6041 && snapshot.contains_str_at(range.end, suffix)
6042 {
6043 range.end += lookahead;
6044 }
6045 }
6046 range
6047 };
6048
6049 ranges.push(range.clone());
6050
6051 if !self.linked_edit_ranges.is_empty() {
6052 let start_anchor = snapshot.anchor_before(range.start);
6053 let end_anchor = snapshot.anchor_after(range.end);
6054 if let Some(ranges) = self
6055 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6056 {
6057 for (buffer, edits) in ranges {
6058 linked_edits
6059 .entry(buffer.clone())
6060 .or_default()
6061 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6062 }
6063 }
6064 }
6065 }
6066
6067 let common_prefix_len = old_text
6068 .chars()
6069 .zip(new_text.chars())
6070 .take_while(|(a, b)| a == b)
6071 .map(|(a, _)| a.len_utf8())
6072 .sum::<usize>();
6073
6074 cx.emit(EditorEvent::InputHandled {
6075 utf16_range_to_replace: None,
6076 text: new_text[common_prefix_len..].into(),
6077 });
6078
6079 self.transact(window, cx, |editor, window, cx| {
6080 if let Some(mut snippet) = snippet {
6081 snippet.text = new_text.to_string();
6082 editor
6083 .insert_snippet(&ranges, snippet, window, cx)
6084 .log_err();
6085 } else {
6086 editor.buffer.update(cx, |multi_buffer, cx| {
6087 let auto_indent = match completion.insert_text_mode {
6088 Some(InsertTextMode::AS_IS) => None,
6089 _ => editor.autoindent_mode.clone(),
6090 };
6091 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6092 multi_buffer.edit(edits, auto_indent, cx);
6093 });
6094 }
6095 for (buffer, edits) in linked_edits {
6096 buffer.update(cx, |buffer, cx| {
6097 let snapshot = buffer.snapshot();
6098 let edits = edits
6099 .into_iter()
6100 .map(|(range, text)| {
6101 use text::ToPoint as TP;
6102 let end_point = TP::to_point(&range.end, &snapshot);
6103 let start_point = TP::to_point(&range.start, &snapshot);
6104 (start_point..end_point, text)
6105 })
6106 .sorted_by_key(|(range, _)| range.start);
6107 buffer.edit(edits, None, cx);
6108 })
6109 }
6110
6111 editor.refresh_edit_prediction(true, false, window, cx);
6112 });
6113 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6114
6115 let show_new_completions_on_confirm = completion
6116 .confirm
6117 .as_ref()
6118 .is_some_and(|confirm| confirm(intent, window, cx));
6119 if show_new_completions_on_confirm {
6120 self.open_or_update_completions_menu(None, None, false, window, cx);
6121 }
6122
6123 let provider = self.completion_provider.as_ref()?;
6124
6125 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6126 let command = lsp_store.as_ref().and_then(|lsp_store| {
6127 let CompletionSource::Lsp {
6128 lsp_completion,
6129 server_id,
6130 ..
6131 } = &completion.source
6132 else {
6133 return None;
6134 };
6135 let lsp_command = lsp_completion.command.as_ref()?;
6136 let available_commands = lsp_store
6137 .read(cx)
6138 .lsp_server_capabilities
6139 .get(server_id)
6140 .and_then(|server_capabilities| {
6141 server_capabilities
6142 .execute_command_provider
6143 .as_ref()
6144 .map(|options| options.commands.as_slice())
6145 })?;
6146 if available_commands.contains(&lsp_command.command) {
6147 Some(CodeAction {
6148 server_id: *server_id,
6149 range: language::Anchor::MIN..language::Anchor::MIN,
6150 lsp_action: LspAction::Command(lsp_command.clone()),
6151 resolved: false,
6152 })
6153 } else {
6154 None
6155 }
6156 });
6157
6158 drop(completion);
6159 let apply_edits = provider.apply_additional_edits_for_completion(
6160 buffer_handle.clone(),
6161 completions_menu.completions.clone(),
6162 candidate_id,
6163 true,
6164 cx,
6165 );
6166
6167 let editor_settings = EditorSettings::get_global(cx);
6168 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6169 // After the code completion is finished, users often want to know what signatures are needed.
6170 // so we should automatically call signature_help
6171 self.show_signature_help(&ShowSignatureHelp, window, cx);
6172 }
6173
6174 Some(cx.spawn_in(window, async move |editor, cx| {
6175 apply_edits.await?;
6176
6177 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6178 let title = command.lsp_action.title().to_owned();
6179 let project_transaction = lsp_store
6180 .update(cx, |lsp_store, cx| {
6181 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6182 })?
6183 .await
6184 .context("applying post-completion command")?;
6185 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6186 Self::open_project_transaction(
6187 &editor,
6188 workspace.downgrade(),
6189 project_transaction,
6190 title,
6191 cx,
6192 )
6193 .await?;
6194 }
6195 }
6196
6197 Ok(())
6198 }))
6199 }
6200
6201 pub fn toggle_code_actions(
6202 &mut self,
6203 action: &ToggleCodeActions,
6204 window: &mut Window,
6205 cx: &mut Context<Self>,
6206 ) {
6207 let quick_launch = action.quick_launch;
6208 let mut context_menu = self.context_menu.borrow_mut();
6209 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6210 if code_actions.deployed_from == action.deployed_from {
6211 // Toggle if we're selecting the same one
6212 *context_menu = None;
6213 cx.notify();
6214 return;
6215 } else {
6216 // Otherwise, clear it and start a new one
6217 *context_menu = None;
6218 cx.notify();
6219 }
6220 }
6221 drop(context_menu);
6222 let snapshot = self.snapshot(window, cx);
6223 let deployed_from = action.deployed_from.clone();
6224 let action = action.clone();
6225 self.completion_tasks.clear();
6226 self.discard_edit_prediction(false, cx);
6227
6228 let multibuffer_point = match &action.deployed_from {
6229 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6230 DisplayPoint::new(*row, 0).to_point(&snapshot)
6231 }
6232 _ => self
6233 .selections
6234 .newest::<Point>(&snapshot.display_snapshot)
6235 .head(),
6236 };
6237 let Some((buffer, buffer_row)) = snapshot
6238 .buffer_snapshot()
6239 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6240 .and_then(|(buffer_snapshot, range)| {
6241 self.buffer()
6242 .read(cx)
6243 .buffer(buffer_snapshot.remote_id())
6244 .map(|buffer| (buffer, range.start.row))
6245 })
6246 else {
6247 return;
6248 };
6249 let buffer_id = buffer.read(cx).remote_id();
6250 let tasks = self
6251 .tasks
6252 .get(&(buffer_id, buffer_row))
6253 .map(|t| Arc::new(t.to_owned()));
6254
6255 if !self.focus_handle.is_focused(window) {
6256 return;
6257 }
6258 let project = self.project.clone();
6259
6260 let code_actions_task = match deployed_from {
6261 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6262 _ => self.code_actions(buffer_row, window, cx),
6263 };
6264
6265 let runnable_task = match deployed_from {
6266 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6267 _ => {
6268 let mut task_context_task = Task::ready(None);
6269 if let Some(tasks) = &tasks
6270 && let Some(project) = project
6271 {
6272 task_context_task =
6273 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6274 }
6275
6276 cx.spawn_in(window, {
6277 let buffer = buffer.clone();
6278 async move |editor, cx| {
6279 let task_context = task_context_task.await;
6280
6281 let resolved_tasks =
6282 tasks
6283 .zip(task_context.clone())
6284 .map(|(tasks, task_context)| ResolvedTasks {
6285 templates: tasks.resolve(&task_context).collect(),
6286 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6287 multibuffer_point.row,
6288 tasks.column,
6289 )),
6290 });
6291 let debug_scenarios = editor
6292 .update(cx, |editor, cx| {
6293 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6294 })?
6295 .await;
6296 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6297 }
6298 })
6299 }
6300 };
6301
6302 cx.spawn_in(window, async move |editor, cx| {
6303 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6304 let code_actions = code_actions_task.await;
6305 let spawn_straight_away = quick_launch
6306 && resolved_tasks
6307 .as_ref()
6308 .is_some_and(|tasks| tasks.templates.len() == 1)
6309 && code_actions
6310 .as_ref()
6311 .is_none_or(|actions| actions.is_empty())
6312 && debug_scenarios.is_empty();
6313
6314 editor.update_in(cx, |editor, window, cx| {
6315 crate::hover_popover::hide_hover(editor, cx);
6316 let actions = CodeActionContents::new(
6317 resolved_tasks,
6318 code_actions,
6319 debug_scenarios,
6320 task_context.unwrap_or_default(),
6321 );
6322
6323 // Don't show the menu if there are no actions available
6324 if actions.is_empty() {
6325 cx.notify();
6326 return Task::ready(Ok(()));
6327 }
6328
6329 *editor.context_menu.borrow_mut() =
6330 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6331 buffer,
6332 actions,
6333 selected_item: Default::default(),
6334 scroll_handle: UniformListScrollHandle::default(),
6335 deployed_from,
6336 }));
6337 cx.notify();
6338 if spawn_straight_away
6339 && let Some(task) = editor.confirm_code_action(
6340 &ConfirmCodeAction { item_ix: Some(0) },
6341 window,
6342 cx,
6343 )
6344 {
6345 return task;
6346 }
6347
6348 Task::ready(Ok(()))
6349 })
6350 })
6351 .detach_and_log_err(cx);
6352 }
6353
6354 fn debug_scenarios(
6355 &mut self,
6356 resolved_tasks: &Option<ResolvedTasks>,
6357 buffer: &Entity<Buffer>,
6358 cx: &mut App,
6359 ) -> Task<Vec<task::DebugScenario>> {
6360 maybe!({
6361 let project = self.project()?;
6362 let dap_store = project.read(cx).dap_store();
6363 let mut scenarios = vec![];
6364 let resolved_tasks = resolved_tasks.as_ref()?;
6365 let buffer = buffer.read(cx);
6366 let language = buffer.language()?;
6367 let file = buffer.file();
6368 let debug_adapter = language_settings(language.name().into(), file, cx)
6369 .debuggers
6370 .first()
6371 .map(SharedString::from)
6372 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6373
6374 dap_store.update(cx, |dap_store, cx| {
6375 for (_, task) in &resolved_tasks.templates {
6376 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6377 task.original_task().clone(),
6378 debug_adapter.clone().into(),
6379 task.display_label().to_owned().into(),
6380 cx,
6381 );
6382 scenarios.push(maybe_scenario);
6383 }
6384 });
6385 Some(cx.background_spawn(async move {
6386 futures::future::join_all(scenarios)
6387 .await
6388 .into_iter()
6389 .flatten()
6390 .collect::<Vec<_>>()
6391 }))
6392 })
6393 .unwrap_or_else(|| Task::ready(vec![]))
6394 }
6395
6396 fn code_actions(
6397 &mut self,
6398 buffer_row: u32,
6399 window: &mut Window,
6400 cx: &mut Context<Self>,
6401 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6402 let mut task = self.code_actions_task.take();
6403 cx.spawn_in(window, async move |editor, cx| {
6404 while let Some(prev_task) = task {
6405 prev_task.await.log_err();
6406 task = editor
6407 .update(cx, |this, _| this.code_actions_task.take())
6408 .ok()?;
6409 }
6410
6411 editor
6412 .update(cx, |editor, cx| {
6413 editor
6414 .available_code_actions
6415 .clone()
6416 .and_then(|(location, code_actions)| {
6417 let snapshot = location.buffer.read(cx).snapshot();
6418 let point_range = location.range.to_point(&snapshot);
6419 let point_range = point_range.start.row..=point_range.end.row;
6420 if point_range.contains(&buffer_row) {
6421 Some(code_actions)
6422 } else {
6423 None
6424 }
6425 })
6426 })
6427 .ok()
6428 .flatten()
6429 })
6430 }
6431
6432 pub fn confirm_code_action(
6433 &mut self,
6434 action: &ConfirmCodeAction,
6435 window: &mut Window,
6436 cx: &mut Context<Self>,
6437 ) -> Option<Task<Result<()>>> {
6438 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6439
6440 let actions_menu =
6441 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6442 menu
6443 } else {
6444 return None;
6445 };
6446
6447 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6448 let action = actions_menu.actions.get(action_ix)?;
6449 let title = action.label();
6450 let buffer = actions_menu.buffer;
6451 let workspace = self.workspace()?;
6452
6453 match action {
6454 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6455 workspace.update(cx, |workspace, cx| {
6456 workspace.schedule_resolved_task(
6457 task_source_kind,
6458 resolved_task,
6459 false,
6460 window,
6461 cx,
6462 );
6463
6464 Some(Task::ready(Ok(())))
6465 })
6466 }
6467 CodeActionsItem::CodeAction {
6468 excerpt_id,
6469 action,
6470 provider,
6471 } => {
6472 let apply_code_action =
6473 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6474 let workspace = workspace.downgrade();
6475 Some(cx.spawn_in(window, async move |editor, cx| {
6476 let project_transaction = apply_code_action.await?;
6477 Self::open_project_transaction(
6478 &editor,
6479 workspace,
6480 project_transaction,
6481 title,
6482 cx,
6483 )
6484 .await
6485 }))
6486 }
6487 CodeActionsItem::DebugScenario(scenario) => {
6488 let context = actions_menu.actions.context;
6489
6490 workspace.update(cx, |workspace, cx| {
6491 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6492 workspace.start_debug_session(
6493 scenario,
6494 context,
6495 Some(buffer),
6496 None,
6497 window,
6498 cx,
6499 );
6500 });
6501 Some(Task::ready(Ok(())))
6502 }
6503 }
6504 }
6505
6506 fn open_transaction_for_hidden_buffers(
6507 workspace: Entity<Workspace>,
6508 transaction: ProjectTransaction,
6509 title: String,
6510 window: &mut Window,
6511 cx: &mut Context<Self>,
6512 ) {
6513 if transaction.0.is_empty() {
6514 return;
6515 }
6516
6517 let edited_buffers_already_open = {
6518 let other_editors: Vec<Entity<Editor>> = workspace
6519 .read(cx)
6520 .panes()
6521 .iter()
6522 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6523 .filter(|editor| editor.entity_id() != cx.entity_id())
6524 .collect();
6525
6526 transaction.0.keys().all(|buffer| {
6527 other_editors.iter().any(|editor| {
6528 let multi_buffer = editor.read(cx).buffer();
6529 multi_buffer.read(cx).is_singleton()
6530 && multi_buffer
6531 .read(cx)
6532 .as_singleton()
6533 .map_or(false, |singleton| {
6534 singleton.entity_id() == buffer.entity_id()
6535 })
6536 })
6537 })
6538 };
6539 if !edited_buffers_already_open {
6540 let workspace = workspace.downgrade();
6541 cx.defer_in(window, move |_, window, cx| {
6542 cx.spawn_in(window, async move |editor, cx| {
6543 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6544 .await
6545 .ok()
6546 })
6547 .detach();
6548 });
6549 }
6550 }
6551
6552 pub async fn open_project_transaction(
6553 editor: &WeakEntity<Editor>,
6554 workspace: WeakEntity<Workspace>,
6555 transaction: ProjectTransaction,
6556 title: String,
6557 cx: &mut AsyncWindowContext,
6558 ) -> Result<()> {
6559 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6560 cx.update(|_, cx| {
6561 entries.sort_unstable_by_key(|(buffer, _)| {
6562 buffer.read(cx).file().map(|f| f.path().clone())
6563 });
6564 })?;
6565 if entries.is_empty() {
6566 return Ok(());
6567 }
6568
6569 // If the project transaction's edits are all contained within this editor, then
6570 // avoid opening a new editor to display them.
6571
6572 if let [(buffer, transaction)] = &*entries {
6573 let excerpt = editor.update(cx, |editor, cx| {
6574 editor
6575 .buffer()
6576 .read(cx)
6577 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6578 })?;
6579 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6580 && excerpted_buffer == *buffer
6581 {
6582 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6583 let excerpt_range = excerpt_range.to_offset(buffer);
6584 buffer
6585 .edited_ranges_for_transaction::<usize>(transaction)
6586 .all(|range| {
6587 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6588 })
6589 })?;
6590
6591 if all_edits_within_excerpt {
6592 return Ok(());
6593 }
6594 }
6595 }
6596
6597 let mut ranges_to_highlight = Vec::new();
6598 let excerpt_buffer = cx.new(|cx| {
6599 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6600 for (buffer_handle, transaction) in &entries {
6601 let edited_ranges = buffer_handle
6602 .read(cx)
6603 .edited_ranges_for_transaction::<Point>(transaction)
6604 .collect::<Vec<_>>();
6605 let (ranges, _) = multibuffer.set_excerpts_for_path(
6606 PathKey::for_buffer(buffer_handle, cx),
6607 buffer_handle.clone(),
6608 edited_ranges,
6609 multibuffer_context_lines(cx),
6610 cx,
6611 );
6612
6613 ranges_to_highlight.extend(ranges);
6614 }
6615 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6616 multibuffer
6617 })?;
6618
6619 workspace.update_in(cx, |workspace, window, cx| {
6620 let project = workspace.project().clone();
6621 let editor =
6622 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6623 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6624 editor.update(cx, |editor, cx| {
6625 editor.highlight_background::<Self>(
6626 &ranges_to_highlight,
6627 |_, theme| theme.colors().editor_highlighted_line_background,
6628 cx,
6629 );
6630 });
6631 })?;
6632
6633 Ok(())
6634 }
6635
6636 pub fn clear_code_action_providers(&mut self) {
6637 self.code_action_providers.clear();
6638 self.available_code_actions.take();
6639 }
6640
6641 pub fn add_code_action_provider(
6642 &mut self,
6643 provider: Rc<dyn CodeActionProvider>,
6644 window: &mut Window,
6645 cx: &mut Context<Self>,
6646 ) {
6647 if self
6648 .code_action_providers
6649 .iter()
6650 .any(|existing_provider| existing_provider.id() == provider.id())
6651 {
6652 return;
6653 }
6654
6655 self.code_action_providers.push(provider);
6656 self.refresh_code_actions(window, cx);
6657 }
6658
6659 pub fn remove_code_action_provider(
6660 &mut self,
6661 id: Arc<str>,
6662 window: &mut Window,
6663 cx: &mut Context<Self>,
6664 ) {
6665 self.code_action_providers
6666 .retain(|provider| provider.id() != id);
6667 self.refresh_code_actions(window, cx);
6668 }
6669
6670 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6671 !self.code_action_providers.is_empty()
6672 && EditorSettings::get_global(cx).toolbar.code_actions
6673 }
6674
6675 pub fn has_available_code_actions(&self) -> bool {
6676 self.available_code_actions
6677 .as_ref()
6678 .is_some_and(|(_, actions)| !actions.is_empty())
6679 }
6680
6681 fn render_inline_code_actions(
6682 &self,
6683 icon_size: ui::IconSize,
6684 display_row: DisplayRow,
6685 is_active: bool,
6686 cx: &mut Context<Self>,
6687 ) -> AnyElement {
6688 let show_tooltip = !self.context_menu_visible();
6689 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6690 .icon_size(icon_size)
6691 .shape(ui::IconButtonShape::Square)
6692 .icon_color(ui::Color::Hidden)
6693 .toggle_state(is_active)
6694 .when(show_tooltip, |this| {
6695 this.tooltip({
6696 let focus_handle = self.focus_handle.clone();
6697 move |_window, cx| {
6698 Tooltip::for_action_in(
6699 "Toggle Code Actions",
6700 &ToggleCodeActions {
6701 deployed_from: None,
6702 quick_launch: false,
6703 },
6704 &focus_handle,
6705 cx,
6706 )
6707 }
6708 })
6709 })
6710 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6711 window.focus(&editor.focus_handle(cx), cx);
6712 editor.toggle_code_actions(
6713 &crate::actions::ToggleCodeActions {
6714 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6715 display_row,
6716 )),
6717 quick_launch: false,
6718 },
6719 window,
6720 cx,
6721 );
6722 }))
6723 .into_any_element()
6724 }
6725
6726 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6727 &self.context_menu
6728 }
6729
6730 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6731 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6732 cx.background_executor()
6733 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6734 .await;
6735
6736 let (start_buffer, start, _, end, newest_selection) = this
6737 .update(cx, |this, cx| {
6738 let newest_selection = this.selections.newest_anchor().clone();
6739 if newest_selection.head().diff_base_anchor.is_some() {
6740 return None;
6741 }
6742 let display_snapshot = this.display_snapshot(cx);
6743 let newest_selection_adjusted =
6744 this.selections.newest_adjusted(&display_snapshot);
6745 let buffer = this.buffer.read(cx);
6746
6747 let (start_buffer, start) =
6748 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6749 let (end_buffer, end) =
6750 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6751
6752 Some((start_buffer, start, end_buffer, end, newest_selection))
6753 })?
6754 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6755 .context(
6756 "Expected selection to lie in a single buffer when refreshing code actions",
6757 )?;
6758 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6759 let providers = this.code_action_providers.clone();
6760 let tasks = this
6761 .code_action_providers
6762 .iter()
6763 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6764 .collect::<Vec<_>>();
6765 (providers, tasks)
6766 })?;
6767
6768 let mut actions = Vec::new();
6769 for (provider, provider_actions) in
6770 providers.into_iter().zip(future::join_all(tasks).await)
6771 {
6772 if let Some(provider_actions) = provider_actions.log_err() {
6773 actions.extend(provider_actions.into_iter().map(|action| {
6774 AvailableCodeAction {
6775 excerpt_id: newest_selection.start.excerpt_id,
6776 action,
6777 provider: provider.clone(),
6778 }
6779 }));
6780 }
6781 }
6782
6783 this.update(cx, |this, cx| {
6784 this.available_code_actions = if actions.is_empty() {
6785 None
6786 } else {
6787 Some((
6788 Location {
6789 buffer: start_buffer,
6790 range: start..end,
6791 },
6792 actions.into(),
6793 ))
6794 };
6795 cx.notify();
6796 })
6797 }));
6798 }
6799
6800 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6801 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6802 self.show_git_blame_inline = false;
6803
6804 self.show_git_blame_inline_delay_task =
6805 Some(cx.spawn_in(window, async move |this, cx| {
6806 cx.background_executor().timer(delay).await;
6807
6808 this.update(cx, |this, cx| {
6809 this.show_git_blame_inline = true;
6810 cx.notify();
6811 })
6812 .log_err();
6813 }));
6814 }
6815 }
6816
6817 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6818 let snapshot = self.snapshot(window, cx);
6819 let cursor = self
6820 .selections
6821 .newest::<Point>(&snapshot.display_snapshot)
6822 .head();
6823 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6824 else {
6825 return;
6826 };
6827
6828 if self.blame.is_none() {
6829 self.start_git_blame(true, window, cx);
6830 }
6831 let Some(blame) = self.blame.as_ref() else {
6832 return;
6833 };
6834
6835 let row_info = RowInfo {
6836 buffer_id: Some(buffer.remote_id()),
6837 buffer_row: Some(point.row),
6838 ..Default::default()
6839 };
6840 let Some((buffer, blame_entry)) = blame
6841 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6842 .flatten()
6843 else {
6844 return;
6845 };
6846
6847 let anchor = self.selections.newest_anchor().head();
6848 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6849 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6850 self.show_blame_popover(
6851 buffer,
6852 &blame_entry,
6853 position + last_bounds.origin,
6854 true,
6855 cx,
6856 );
6857 };
6858 }
6859
6860 fn show_blame_popover(
6861 &mut self,
6862 buffer: BufferId,
6863 blame_entry: &BlameEntry,
6864 position: gpui::Point<Pixels>,
6865 ignore_timeout: bool,
6866 cx: &mut Context<Self>,
6867 ) {
6868 if let Some(state) = &mut self.inline_blame_popover {
6869 state.hide_task.take();
6870 } else {
6871 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6872 let blame_entry = blame_entry.clone();
6873 let show_task = cx.spawn(async move |editor, cx| {
6874 if !ignore_timeout {
6875 cx.background_executor()
6876 .timer(std::time::Duration::from_millis(blame_popover_delay))
6877 .await;
6878 }
6879 editor
6880 .update(cx, |editor, cx| {
6881 editor.inline_blame_popover_show_task.take();
6882 let Some(blame) = editor.blame.as_ref() else {
6883 return;
6884 };
6885 let blame = blame.read(cx);
6886 let details = blame.details_for_entry(buffer, &blame_entry);
6887 let markdown = cx.new(|cx| {
6888 Markdown::new(
6889 details
6890 .as_ref()
6891 .map(|message| message.message.clone())
6892 .unwrap_or_default(),
6893 None,
6894 None,
6895 cx,
6896 )
6897 });
6898 editor.inline_blame_popover = Some(InlineBlamePopover {
6899 position,
6900 hide_task: None,
6901 popover_bounds: None,
6902 popover_state: InlineBlamePopoverState {
6903 scroll_handle: ScrollHandle::new(),
6904 commit_message: details,
6905 markdown,
6906 },
6907 keyboard_grace: ignore_timeout,
6908 });
6909 cx.notify();
6910 })
6911 .ok();
6912 });
6913 self.inline_blame_popover_show_task = Some(show_task);
6914 }
6915 }
6916
6917 pub fn has_mouse_context_menu(&self) -> bool {
6918 self.mouse_context_menu.is_some()
6919 }
6920
6921 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6922 self.inline_blame_popover_show_task.take();
6923 if let Some(state) = &mut self.inline_blame_popover {
6924 let hide_task = cx.spawn(async move |editor, cx| {
6925 if !ignore_timeout {
6926 cx.background_executor()
6927 .timer(std::time::Duration::from_millis(100))
6928 .await;
6929 }
6930 editor
6931 .update(cx, |editor, cx| {
6932 editor.inline_blame_popover.take();
6933 cx.notify();
6934 })
6935 .ok();
6936 });
6937 state.hide_task = Some(hide_task);
6938 true
6939 } else {
6940 false
6941 }
6942 }
6943
6944 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6945 if self.pending_rename.is_some() {
6946 return None;
6947 }
6948
6949 let provider = self.semantics_provider.clone()?;
6950 let buffer = self.buffer.read(cx);
6951 let newest_selection = self.selections.newest_anchor().clone();
6952 let cursor_position = newest_selection.head();
6953 let (cursor_buffer, cursor_buffer_position) =
6954 buffer.text_anchor_for_position(cursor_position, cx)?;
6955 let (tail_buffer, tail_buffer_position) =
6956 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6957 if cursor_buffer != tail_buffer {
6958 return None;
6959 }
6960
6961 let snapshot = cursor_buffer.read(cx).snapshot();
6962 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6963 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6964 if start_word_range != end_word_range {
6965 self.document_highlights_task.take();
6966 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6967 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6968 return None;
6969 }
6970
6971 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6972 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6973 cx.background_executor()
6974 .timer(Duration::from_millis(debounce))
6975 .await;
6976
6977 let highlights = if let Some(highlights) = cx
6978 .update(|cx| {
6979 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6980 })
6981 .ok()
6982 .flatten()
6983 {
6984 highlights.await.log_err()
6985 } else {
6986 None
6987 };
6988
6989 if let Some(highlights) = highlights {
6990 this.update(cx, |this, cx| {
6991 if this.pending_rename.is_some() {
6992 return;
6993 }
6994
6995 let buffer = this.buffer.read(cx);
6996 if buffer
6997 .text_anchor_for_position(cursor_position, cx)
6998 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6999 {
7000 return;
7001 }
7002
7003 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7004 let mut write_ranges = Vec::new();
7005 let mut read_ranges = Vec::new();
7006 for highlight in highlights {
7007 let buffer_id = cursor_buffer.read(cx).remote_id();
7008 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7009 {
7010 let start = highlight
7011 .range
7012 .start
7013 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7014 let end = highlight
7015 .range
7016 .end
7017 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7018 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7019 continue;
7020 }
7021
7022 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7023 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7024 write_ranges.push(range);
7025 } else {
7026 read_ranges.push(range);
7027 }
7028 }
7029 }
7030
7031 this.highlight_background::<DocumentHighlightRead>(
7032 &read_ranges,
7033 |_, theme| theme.colors().editor_document_highlight_read_background,
7034 cx,
7035 );
7036 this.highlight_background::<DocumentHighlightWrite>(
7037 &write_ranges,
7038 |_, theme| theme.colors().editor_document_highlight_write_background,
7039 cx,
7040 );
7041 cx.notify();
7042 })
7043 .log_err();
7044 }
7045 }));
7046 None
7047 }
7048
7049 fn prepare_highlight_query_from_selection(
7050 &mut self,
7051 window: &Window,
7052 cx: &mut Context<Editor>,
7053 ) -> Option<(String, Range<Anchor>)> {
7054 if matches!(self.mode, EditorMode::SingleLine) {
7055 return None;
7056 }
7057 if !EditorSettings::get_global(cx).selection_highlight {
7058 return None;
7059 }
7060 if self.selections.count() != 1 || self.selections.line_mode() {
7061 return None;
7062 }
7063 let snapshot = self.snapshot(window, cx);
7064 let selection = self.selections.newest::<Point>(&snapshot);
7065 // If the selection spans multiple rows OR it is empty
7066 if selection.start.row != selection.end.row
7067 || selection.start.column == selection.end.column
7068 {
7069 return None;
7070 }
7071 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7072 let query = snapshot
7073 .buffer_snapshot()
7074 .text_for_range(selection_anchor_range.clone())
7075 .collect::<String>();
7076 if query.trim().is_empty() {
7077 return None;
7078 }
7079 Some((query, selection_anchor_range))
7080 }
7081
7082 #[ztracing::instrument(skip_all)]
7083 fn update_selection_occurrence_highlights(
7084 &mut self,
7085 query_text: String,
7086 query_range: Range<Anchor>,
7087 multi_buffer_range_to_query: Range<Point>,
7088 use_debounce: bool,
7089 window: &mut Window,
7090 cx: &mut Context<Editor>,
7091 ) -> Task<()> {
7092 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7093 cx.spawn_in(window, async move |editor, cx| {
7094 if use_debounce {
7095 cx.background_executor()
7096 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7097 .await;
7098 }
7099 let match_task = cx.background_spawn(async move {
7100 let buffer_ranges = multi_buffer_snapshot
7101 .range_to_buffer_ranges(multi_buffer_range_to_query)
7102 .into_iter()
7103 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7104 let mut match_ranges = Vec::new();
7105 let Ok(regex) = project::search::SearchQuery::text(
7106 query_text.clone(),
7107 false,
7108 false,
7109 false,
7110 Default::default(),
7111 Default::default(),
7112 false,
7113 None,
7114 ) else {
7115 return Vec::default();
7116 };
7117 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7118 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7119 match_ranges.extend(
7120 regex
7121 .search(
7122 buffer_snapshot,
7123 Some(search_range.start.0..search_range.end.0),
7124 )
7125 .await
7126 .into_iter()
7127 .filter_map(|match_range| {
7128 let match_start = buffer_snapshot
7129 .anchor_after(search_range.start + match_range.start);
7130 let match_end = buffer_snapshot
7131 .anchor_before(search_range.start + match_range.end);
7132 let match_anchor_range =
7133 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7134 (match_anchor_range != query_range).then_some(match_anchor_range)
7135 }),
7136 );
7137 }
7138 match_ranges
7139 });
7140 let match_ranges = match_task.await;
7141 editor
7142 .update_in(cx, |editor, _, cx| {
7143 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7144 if !match_ranges.is_empty() {
7145 editor.highlight_background::<SelectedTextHighlight>(
7146 &match_ranges,
7147 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7148 cx,
7149 )
7150 }
7151 })
7152 .log_err();
7153 })
7154 }
7155
7156 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7157 struct NewlineFold;
7158 let type_id = std::any::TypeId::of::<NewlineFold>();
7159 if !self.mode.is_single_line() {
7160 return;
7161 }
7162 let snapshot = self.snapshot(window, cx);
7163 if snapshot.buffer_snapshot().max_point().row == 0 {
7164 return;
7165 }
7166 let task = cx.background_spawn(async move {
7167 let new_newlines = snapshot
7168 .buffer_chars_at(MultiBufferOffset(0))
7169 .filter_map(|(c, i)| {
7170 if c == '\n' {
7171 Some(
7172 snapshot.buffer_snapshot().anchor_after(i)
7173 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7174 )
7175 } else {
7176 None
7177 }
7178 })
7179 .collect::<Vec<_>>();
7180 let existing_newlines = snapshot
7181 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7182 .filter_map(|fold| {
7183 if fold.placeholder.type_tag == Some(type_id) {
7184 Some(fold.range.start..fold.range.end)
7185 } else {
7186 None
7187 }
7188 })
7189 .collect::<Vec<_>>();
7190
7191 (new_newlines, existing_newlines)
7192 });
7193 self.folding_newlines = cx.spawn(async move |this, cx| {
7194 let (new_newlines, existing_newlines) = task.await;
7195 if new_newlines == existing_newlines {
7196 return;
7197 }
7198 let placeholder = FoldPlaceholder {
7199 render: Arc::new(move |_, _, cx| {
7200 div()
7201 .bg(cx.theme().status().hint_background)
7202 .border_b_1()
7203 .size_full()
7204 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7205 .border_color(cx.theme().status().hint)
7206 .child("\\n")
7207 .into_any()
7208 }),
7209 constrain_width: false,
7210 merge_adjacent: false,
7211 type_tag: Some(type_id),
7212 };
7213 let creases = new_newlines
7214 .into_iter()
7215 .map(|range| Crease::simple(range, placeholder.clone()))
7216 .collect();
7217 this.update(cx, |this, cx| {
7218 this.display_map.update(cx, |display_map, cx| {
7219 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7220 display_map.fold(creases, cx);
7221 });
7222 })
7223 .ok();
7224 });
7225 }
7226
7227 #[ztracing::instrument(skip_all)]
7228 fn refresh_selected_text_highlights(
7229 &mut self,
7230 on_buffer_edit: bool,
7231 window: &mut Window,
7232 cx: &mut Context<Editor>,
7233 ) {
7234 let Some((query_text, query_range)) =
7235 self.prepare_highlight_query_from_selection(window, cx)
7236 else {
7237 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7238 self.quick_selection_highlight_task.take();
7239 self.debounced_selection_highlight_task.take();
7240 return;
7241 };
7242 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7243 if on_buffer_edit
7244 || self
7245 .quick_selection_highlight_task
7246 .as_ref()
7247 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7248 {
7249 let multi_buffer_visible_start = self
7250 .scroll_manager
7251 .anchor()
7252 .anchor
7253 .to_point(&multi_buffer_snapshot);
7254 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7255 multi_buffer_visible_start
7256 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7257 Bias::Left,
7258 );
7259 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7260 self.quick_selection_highlight_task = Some((
7261 query_range.clone(),
7262 self.update_selection_occurrence_highlights(
7263 query_text.clone(),
7264 query_range.clone(),
7265 multi_buffer_visible_range,
7266 false,
7267 window,
7268 cx,
7269 ),
7270 ));
7271 }
7272 if on_buffer_edit
7273 || self
7274 .debounced_selection_highlight_task
7275 .as_ref()
7276 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7277 {
7278 let multi_buffer_start = multi_buffer_snapshot
7279 .anchor_before(MultiBufferOffset(0))
7280 .to_point(&multi_buffer_snapshot);
7281 let multi_buffer_end = multi_buffer_snapshot
7282 .anchor_after(multi_buffer_snapshot.len())
7283 .to_point(&multi_buffer_snapshot);
7284 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7285 self.debounced_selection_highlight_task = Some((
7286 query_range.clone(),
7287 self.update_selection_occurrence_highlights(
7288 query_text,
7289 query_range,
7290 multi_buffer_full_range,
7291 true,
7292 window,
7293 cx,
7294 ),
7295 ));
7296 }
7297 }
7298
7299 pub fn refresh_edit_prediction(
7300 &mut self,
7301 debounce: bool,
7302 user_requested: bool,
7303 window: &mut Window,
7304 cx: &mut Context<Self>,
7305 ) -> Option<()> {
7306 if DisableAiSettings::get_global(cx).disable_ai {
7307 return None;
7308 }
7309
7310 let provider = self.edit_prediction_provider()?;
7311 let cursor = self.selections.newest_anchor().head();
7312 let (buffer, cursor_buffer_position) =
7313 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7314
7315 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7316 self.discard_edit_prediction(false, cx);
7317 return None;
7318 }
7319
7320 self.update_visible_edit_prediction(window, cx);
7321
7322 if !user_requested
7323 && (!self.should_show_edit_predictions()
7324 || !self.is_focused(window)
7325 || buffer.read(cx).is_empty())
7326 {
7327 self.discard_edit_prediction(false, cx);
7328 return None;
7329 }
7330
7331 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7332 Some(())
7333 }
7334
7335 fn show_edit_predictions_in_menu(&self) -> bool {
7336 match self.edit_prediction_settings {
7337 EditPredictionSettings::Disabled => false,
7338 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7339 }
7340 }
7341
7342 pub fn edit_predictions_enabled(&self) -> bool {
7343 match self.edit_prediction_settings {
7344 EditPredictionSettings::Disabled => false,
7345 EditPredictionSettings::Enabled { .. } => true,
7346 }
7347 }
7348
7349 fn edit_prediction_requires_modifier(&self) -> bool {
7350 match self.edit_prediction_settings {
7351 EditPredictionSettings::Disabled => false,
7352 EditPredictionSettings::Enabled {
7353 preview_requires_modifier,
7354 ..
7355 } => preview_requires_modifier,
7356 }
7357 }
7358
7359 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7360 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7361 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7362 self.discard_edit_prediction(false, cx);
7363 } else {
7364 let selection = self.selections.newest_anchor();
7365 let cursor = selection.head();
7366
7367 if let Some((buffer, cursor_buffer_position)) =
7368 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7369 {
7370 self.edit_prediction_settings =
7371 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7372 }
7373 }
7374 }
7375
7376 fn edit_prediction_settings_at_position(
7377 &self,
7378 buffer: &Entity<Buffer>,
7379 buffer_position: language::Anchor,
7380 cx: &App,
7381 ) -> EditPredictionSettings {
7382 if !self.mode.is_full()
7383 || !self.show_edit_predictions_override.unwrap_or(true)
7384 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7385 {
7386 return EditPredictionSettings::Disabled;
7387 }
7388
7389 let buffer = buffer.read(cx);
7390
7391 let file = buffer.file();
7392
7393 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7394 return EditPredictionSettings::Disabled;
7395 };
7396
7397 let by_provider = matches!(
7398 self.menu_edit_predictions_policy,
7399 MenuEditPredictionsPolicy::ByProvider
7400 );
7401
7402 let show_in_menu = by_provider
7403 && self
7404 .edit_prediction_provider
7405 .as_ref()
7406 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7407
7408 let preview_requires_modifier =
7409 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7410
7411 EditPredictionSettings::Enabled {
7412 show_in_menu,
7413 preview_requires_modifier,
7414 }
7415 }
7416
7417 fn should_show_edit_predictions(&self) -> bool {
7418 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7419 }
7420
7421 pub fn edit_prediction_preview_is_active(&self) -> bool {
7422 matches!(
7423 self.edit_prediction_preview,
7424 EditPredictionPreview::Active { .. }
7425 )
7426 }
7427
7428 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7429 let cursor = self.selections.newest_anchor().head();
7430 if let Some((buffer, cursor_position)) =
7431 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7432 {
7433 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7434 } else {
7435 false
7436 }
7437 }
7438
7439 pub fn supports_minimap(&self, cx: &App) -> bool {
7440 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7441 }
7442
7443 fn edit_predictions_enabled_in_buffer(
7444 &self,
7445 buffer: &Entity<Buffer>,
7446 buffer_position: language::Anchor,
7447 cx: &App,
7448 ) -> bool {
7449 maybe!({
7450 if self.read_only(cx) {
7451 return Some(false);
7452 }
7453 let provider = self.edit_prediction_provider()?;
7454 if !provider.is_enabled(buffer, buffer_position, cx) {
7455 return Some(false);
7456 }
7457 let buffer = buffer.read(cx);
7458 let Some(file) = buffer.file() else {
7459 return Some(true);
7460 };
7461 let settings = all_language_settings(Some(file), cx);
7462 Some(settings.edit_predictions_enabled_for_file(file, cx))
7463 })
7464 .unwrap_or(false)
7465 }
7466
7467 pub fn show_edit_prediction(
7468 &mut self,
7469 _: &ShowEditPrediction,
7470 window: &mut Window,
7471 cx: &mut Context<Self>,
7472 ) {
7473 if !self.has_active_edit_prediction() {
7474 self.refresh_edit_prediction(false, true, window, cx);
7475 return;
7476 }
7477
7478 self.update_visible_edit_prediction(window, cx);
7479 }
7480
7481 pub fn display_cursor_names(
7482 &mut self,
7483 _: &DisplayCursorNames,
7484 window: &mut Window,
7485 cx: &mut Context<Self>,
7486 ) {
7487 self.show_cursor_names(window, cx);
7488 }
7489
7490 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7491 self.show_cursor_names = true;
7492 cx.notify();
7493 cx.spawn_in(window, async move |this, cx| {
7494 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7495 this.update(cx, |this, cx| {
7496 this.show_cursor_names = false;
7497 cx.notify()
7498 })
7499 .ok()
7500 })
7501 .detach();
7502 }
7503
7504 pub fn accept_partial_edit_prediction(
7505 &mut self,
7506 granularity: EditPredictionGranularity,
7507 window: &mut Window,
7508 cx: &mut Context<Self>,
7509 ) {
7510 if self.show_edit_predictions_in_menu() {
7511 self.hide_context_menu(window, cx);
7512 }
7513
7514 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7515 return;
7516 };
7517
7518 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7519 return;
7520 }
7521
7522 match &active_edit_prediction.completion {
7523 EditPrediction::MoveWithin { target, .. } => {
7524 let target = *target;
7525
7526 if matches!(granularity, EditPredictionGranularity::Full) {
7527 if let Some(position_map) = &self.last_position_map {
7528 let target_row = target.to_display_point(&position_map.snapshot).row();
7529 let is_visible = position_map.visible_row_range.contains(&target_row);
7530
7531 if is_visible || !self.edit_prediction_requires_modifier() {
7532 self.unfold_ranges(&[target..target], true, false, cx);
7533 self.change_selections(
7534 SelectionEffects::scroll(Autoscroll::newest()),
7535 window,
7536 cx,
7537 |selections| {
7538 selections.select_anchor_ranges([target..target]);
7539 },
7540 );
7541 self.clear_row_highlights::<EditPredictionPreview>();
7542 self.edit_prediction_preview
7543 .set_previous_scroll_position(None);
7544 } else {
7545 // Highlight and request scroll
7546 self.edit_prediction_preview
7547 .set_previous_scroll_position(Some(
7548 position_map.snapshot.scroll_anchor,
7549 ));
7550 self.highlight_rows::<EditPredictionPreview>(
7551 target..target,
7552 cx.theme().colors().editor_highlighted_line_background,
7553 RowHighlightOptions {
7554 autoscroll: true,
7555 ..Default::default()
7556 },
7557 cx,
7558 );
7559 self.request_autoscroll(Autoscroll::fit(), cx);
7560 }
7561 }
7562 } else {
7563 self.change_selections(
7564 SelectionEffects::scroll(Autoscroll::newest()),
7565 window,
7566 cx,
7567 |selections| {
7568 selections.select_anchor_ranges([target..target]);
7569 },
7570 );
7571 }
7572 }
7573 EditPrediction::MoveOutside { snapshot, target } => {
7574 if let Some(workspace) = self.workspace() {
7575 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7576 .detach_and_log_err(cx);
7577 }
7578 }
7579 EditPrediction::Edit { edits, .. } => {
7580 self.report_edit_prediction_event(
7581 active_edit_prediction.completion_id.clone(),
7582 true,
7583 cx,
7584 );
7585
7586 match granularity {
7587 EditPredictionGranularity::Full => {
7588 if let Some(provider) = self.edit_prediction_provider() {
7589 provider.accept(cx);
7590 }
7591
7592 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7593 let snapshot = self.buffer.read(cx).snapshot(cx);
7594 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7595
7596 self.buffer.update(cx, |buffer, cx| {
7597 buffer.edit(edits.iter().cloned(), None, cx)
7598 });
7599
7600 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7601 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7602 });
7603
7604 let selections = self.selections.disjoint_anchors_arc();
7605 if let Some(transaction_id_now) =
7606 self.buffer.read(cx).last_transaction_id(cx)
7607 {
7608 if transaction_id_prev != Some(transaction_id_now) {
7609 self.selection_history
7610 .insert_transaction(transaction_id_now, selections);
7611 }
7612 }
7613
7614 self.update_visible_edit_prediction(window, cx);
7615 if self.active_edit_prediction.is_none() {
7616 self.refresh_edit_prediction(true, true, window, cx);
7617 }
7618 cx.notify();
7619 }
7620 _ => {
7621 let snapshot = self.buffer.read(cx).snapshot(cx);
7622 let cursor_offset = self
7623 .selections
7624 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7625 .head();
7626
7627 let insertion = edits.iter().find_map(|(range, text)| {
7628 let range = range.to_offset(&snapshot);
7629 if range.is_empty() && range.start == cursor_offset {
7630 Some(text)
7631 } else {
7632 None
7633 }
7634 });
7635
7636 if let Some(text) = insertion {
7637 let text_to_insert = match granularity {
7638 EditPredictionGranularity::Word => {
7639 let mut partial = text
7640 .chars()
7641 .by_ref()
7642 .take_while(|c| c.is_alphabetic())
7643 .collect::<String>();
7644 if partial.is_empty() {
7645 partial = text
7646 .chars()
7647 .by_ref()
7648 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7649 .collect::<String>();
7650 }
7651 partial
7652 }
7653 EditPredictionGranularity::Line => {
7654 if let Some(line) = text.split_inclusive('\n').next() {
7655 line.to_string()
7656 } else {
7657 text.to_string()
7658 }
7659 }
7660 EditPredictionGranularity::Full => unreachable!(),
7661 };
7662
7663 cx.emit(EditorEvent::InputHandled {
7664 utf16_range_to_replace: None,
7665 text: text_to_insert.clone().into(),
7666 });
7667
7668 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7669 self.refresh_edit_prediction(true, true, window, cx);
7670 cx.notify();
7671 } else {
7672 self.accept_partial_edit_prediction(
7673 EditPredictionGranularity::Full,
7674 window,
7675 cx,
7676 );
7677 }
7678 }
7679 }
7680 }
7681 }
7682
7683 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7684 }
7685
7686 pub fn accept_next_word_edit_prediction(
7687 &mut self,
7688 _: &AcceptNextWordEditPrediction,
7689 window: &mut Window,
7690 cx: &mut Context<Self>,
7691 ) {
7692 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7693 }
7694
7695 pub fn accept_next_line_edit_prediction(
7696 &mut self,
7697 _: &AcceptNextLineEditPrediction,
7698 window: &mut Window,
7699 cx: &mut Context<Self>,
7700 ) {
7701 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7702 }
7703
7704 pub fn accept_edit_prediction(
7705 &mut self,
7706 _: &AcceptEditPrediction,
7707 window: &mut Window,
7708 cx: &mut Context<Self>,
7709 ) {
7710 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7711 }
7712
7713 fn discard_edit_prediction(
7714 &mut self,
7715 should_report_edit_prediction_event: bool,
7716 cx: &mut Context<Self>,
7717 ) -> bool {
7718 if should_report_edit_prediction_event {
7719 let completion_id = self
7720 .active_edit_prediction
7721 .as_ref()
7722 .and_then(|active_completion| active_completion.completion_id.clone());
7723
7724 self.report_edit_prediction_event(completion_id, false, cx);
7725 }
7726
7727 if let Some(provider) = self.edit_prediction_provider() {
7728 provider.discard(cx);
7729 }
7730
7731 self.take_active_edit_prediction(cx)
7732 }
7733
7734 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7735 let Some(provider) = self.edit_prediction_provider() else {
7736 return;
7737 };
7738
7739 let Some((_, buffer, _)) = self
7740 .buffer
7741 .read(cx)
7742 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7743 else {
7744 return;
7745 };
7746
7747 let extension = buffer
7748 .read(cx)
7749 .file()
7750 .and_then(|file| Some(file.path().extension()?.to_string()));
7751
7752 let event_type = match accepted {
7753 true => "Edit Prediction Accepted",
7754 false => "Edit Prediction Discarded",
7755 };
7756 telemetry::event!(
7757 event_type,
7758 provider = provider.name(),
7759 prediction_id = id,
7760 suggestion_accepted = accepted,
7761 file_extension = extension,
7762 );
7763 }
7764
7765 fn open_editor_at_anchor(
7766 snapshot: &language::BufferSnapshot,
7767 target: language::Anchor,
7768 workspace: &Entity<Workspace>,
7769 window: &mut Window,
7770 cx: &mut App,
7771 ) -> Task<Result<()>> {
7772 workspace.update(cx, |workspace, cx| {
7773 let path = snapshot.file().map(|file| file.full_path(cx));
7774 let Some(path) =
7775 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7776 else {
7777 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7778 };
7779 let target = text::ToPoint::to_point(&target, snapshot);
7780 let item = workspace.open_path(path, None, true, window, cx);
7781 window.spawn(cx, async move |cx| {
7782 let Some(editor) = item.await?.downcast::<Editor>() else {
7783 return Ok(());
7784 };
7785 editor
7786 .update_in(cx, |editor, window, cx| {
7787 editor.go_to_singleton_buffer_point(target, window, cx);
7788 })
7789 .ok();
7790 anyhow::Ok(())
7791 })
7792 })
7793 }
7794
7795 pub fn has_active_edit_prediction(&self) -> bool {
7796 self.active_edit_prediction.is_some()
7797 }
7798
7799 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7800 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7801 return false;
7802 };
7803
7804 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7805 self.clear_highlights::<EditPredictionHighlight>(cx);
7806 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7807 true
7808 }
7809
7810 /// Returns true when we're displaying the edit prediction popover below the cursor
7811 /// like we are not previewing and the LSP autocomplete menu is visible
7812 /// or we are in `when_holding_modifier` mode.
7813 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7814 if self.edit_prediction_preview_is_active()
7815 || !self.show_edit_predictions_in_menu()
7816 || !self.edit_predictions_enabled()
7817 {
7818 return false;
7819 }
7820
7821 if self.has_visible_completions_menu() {
7822 return true;
7823 }
7824
7825 has_completion && self.edit_prediction_requires_modifier()
7826 }
7827
7828 fn handle_modifiers_changed(
7829 &mut self,
7830 modifiers: Modifiers,
7831 position_map: &PositionMap,
7832 window: &mut Window,
7833 cx: &mut Context<Self>,
7834 ) {
7835 // Ensure that the edit prediction preview is updated, even when not
7836 // enabled, if there's an active edit prediction preview.
7837 if self.show_edit_predictions_in_menu()
7838 || matches!(
7839 self.edit_prediction_preview,
7840 EditPredictionPreview::Active { .. }
7841 )
7842 {
7843 self.update_edit_prediction_preview(&modifiers, window, cx);
7844 }
7845
7846 self.update_selection_mode(&modifiers, position_map, window, cx);
7847
7848 let mouse_position = window.mouse_position();
7849 if !position_map.text_hitbox.is_hovered(window) {
7850 return;
7851 }
7852
7853 self.update_hovered_link(
7854 position_map.point_for_position(mouse_position),
7855 &position_map.snapshot,
7856 modifiers,
7857 window,
7858 cx,
7859 )
7860 }
7861
7862 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7863 match EditorSettings::get_global(cx).multi_cursor_modifier {
7864 MultiCursorModifier::Alt => modifiers.secondary(),
7865 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7866 }
7867 }
7868
7869 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7870 match EditorSettings::get_global(cx).multi_cursor_modifier {
7871 MultiCursorModifier::Alt => modifiers.alt,
7872 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7873 }
7874 }
7875
7876 fn columnar_selection_mode(
7877 modifiers: &Modifiers,
7878 cx: &mut Context<Self>,
7879 ) -> Option<ColumnarMode> {
7880 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7881 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7882 Some(ColumnarMode::FromMouse)
7883 } else if Self::is_alt_pressed(modifiers, cx) {
7884 Some(ColumnarMode::FromSelection)
7885 } else {
7886 None
7887 }
7888 } else {
7889 None
7890 }
7891 }
7892
7893 fn update_selection_mode(
7894 &mut self,
7895 modifiers: &Modifiers,
7896 position_map: &PositionMap,
7897 window: &mut Window,
7898 cx: &mut Context<Self>,
7899 ) {
7900 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7901 return;
7902 };
7903 if self.selections.pending_anchor().is_none() {
7904 return;
7905 }
7906
7907 let mouse_position = window.mouse_position();
7908 let point_for_position = position_map.point_for_position(mouse_position);
7909 let position = point_for_position.previous_valid;
7910
7911 self.select(
7912 SelectPhase::BeginColumnar {
7913 position,
7914 reset: false,
7915 mode,
7916 goal_column: point_for_position.exact_unclipped.column(),
7917 },
7918 window,
7919 cx,
7920 );
7921 }
7922
7923 fn update_edit_prediction_preview(
7924 &mut self,
7925 modifiers: &Modifiers,
7926 window: &mut Window,
7927 cx: &mut Context<Self>,
7928 ) {
7929 let mut modifiers_held = false;
7930
7931 // Check bindings for all granularities.
7932 // If the user holds the key for Word, Line, or Full, we want to show the preview.
7933 let granularities = [
7934 EditPredictionGranularity::Full,
7935 EditPredictionGranularity::Line,
7936 EditPredictionGranularity::Word,
7937 ];
7938
7939 for granularity in granularities {
7940 if let Some(keystroke) = self
7941 .accept_edit_prediction_keybind(granularity, window, cx)
7942 .keystroke()
7943 {
7944 modifiers_held = modifiers_held
7945 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
7946 }
7947 }
7948
7949 if modifiers_held {
7950 if matches!(
7951 self.edit_prediction_preview,
7952 EditPredictionPreview::Inactive { .. }
7953 ) {
7954 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7955 provider.provider.did_show(cx)
7956 }
7957
7958 self.edit_prediction_preview = EditPredictionPreview::Active {
7959 previous_scroll_position: None,
7960 since: Instant::now(),
7961 };
7962
7963 self.update_visible_edit_prediction(window, cx);
7964 cx.notify();
7965 }
7966 } else if let EditPredictionPreview::Active {
7967 previous_scroll_position,
7968 since,
7969 } = self.edit_prediction_preview
7970 {
7971 if let (Some(previous_scroll_position), Some(position_map)) =
7972 (previous_scroll_position, self.last_position_map.as_ref())
7973 {
7974 self.set_scroll_position(
7975 previous_scroll_position
7976 .scroll_position(&position_map.snapshot.display_snapshot),
7977 window,
7978 cx,
7979 );
7980 }
7981
7982 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7983 released_too_fast: since.elapsed() < Duration::from_millis(200),
7984 };
7985 self.clear_row_highlights::<EditPredictionPreview>();
7986 self.update_visible_edit_prediction(window, cx);
7987 cx.notify();
7988 }
7989 }
7990
7991 fn update_visible_edit_prediction(
7992 &mut self,
7993 _window: &mut Window,
7994 cx: &mut Context<Self>,
7995 ) -> Option<()> {
7996 if DisableAiSettings::get_global(cx).disable_ai {
7997 return None;
7998 }
7999
8000 if self.ime_transaction.is_some() {
8001 self.discard_edit_prediction(false, cx);
8002 return None;
8003 }
8004
8005 let selection = self.selections.newest_anchor();
8006 let cursor = selection.head();
8007 let multibuffer = self.buffer.read(cx).snapshot(cx);
8008 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8009 let excerpt_id = cursor.excerpt_id;
8010
8011 let show_in_menu = self.show_edit_predictions_in_menu();
8012 let completions_menu_has_precedence = !show_in_menu
8013 && (self.context_menu.borrow().is_some()
8014 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8015
8016 if completions_menu_has_precedence
8017 || !offset_selection.is_empty()
8018 || self
8019 .active_edit_prediction
8020 .as_ref()
8021 .is_some_and(|completion| {
8022 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8023 return false;
8024 };
8025 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8026 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8027 !invalidation_range.contains(&offset_selection.head())
8028 })
8029 {
8030 self.discard_edit_prediction(false, cx);
8031 return None;
8032 }
8033
8034 self.take_active_edit_prediction(cx);
8035 let Some(provider) = self.edit_prediction_provider() else {
8036 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8037 return None;
8038 };
8039
8040 let (buffer, cursor_buffer_position) =
8041 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8042
8043 self.edit_prediction_settings =
8044 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8045
8046 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8047
8048 if self.edit_prediction_indent_conflict {
8049 let cursor_point = cursor.to_point(&multibuffer);
8050 let mut suggested_indent = None;
8051 multibuffer.suggested_indents_callback(
8052 cursor_point.row..cursor_point.row + 1,
8053 |_, indent| {
8054 suggested_indent = Some(indent);
8055 ControlFlow::Break(())
8056 },
8057 cx,
8058 );
8059
8060 if let Some(indent) = suggested_indent
8061 && indent.len == cursor_point.column
8062 {
8063 self.edit_prediction_indent_conflict = false;
8064 }
8065 }
8066
8067 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8068
8069 let (completion_id, edits, edit_preview) = match edit_prediction {
8070 edit_prediction_types::EditPrediction::Local {
8071 id,
8072 edits,
8073 edit_preview,
8074 } => (id, edits, edit_preview),
8075 edit_prediction_types::EditPrediction::Jump {
8076 id,
8077 snapshot,
8078 target,
8079 } => {
8080 self.stale_edit_prediction_in_menu = None;
8081 self.active_edit_prediction = Some(EditPredictionState {
8082 inlay_ids: vec![],
8083 completion: EditPrediction::MoveOutside { snapshot, target },
8084 completion_id: id,
8085 invalidation_range: None,
8086 });
8087 cx.notify();
8088 return Some(());
8089 }
8090 };
8091
8092 let edits = edits
8093 .into_iter()
8094 .flat_map(|(range, new_text)| {
8095 Some((
8096 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8097 new_text,
8098 ))
8099 })
8100 .collect::<Vec<_>>();
8101 if edits.is_empty() {
8102 return None;
8103 }
8104
8105 let first_edit_start = edits.first().unwrap().0.start;
8106 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8107 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8108
8109 let last_edit_end = edits.last().unwrap().0.end;
8110 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8111 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8112
8113 let cursor_row = cursor.to_point(&multibuffer).row;
8114
8115 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8116
8117 let mut inlay_ids = Vec::new();
8118 let invalidation_row_range;
8119 let move_invalidation_row_range = if cursor_row < edit_start_row {
8120 Some(cursor_row..edit_end_row)
8121 } else if cursor_row > edit_end_row {
8122 Some(edit_start_row..cursor_row)
8123 } else {
8124 None
8125 };
8126 let supports_jump = self
8127 .edit_prediction_provider
8128 .as_ref()
8129 .map(|provider| provider.provider.supports_jump_to_edit())
8130 .unwrap_or(true);
8131
8132 let is_move = supports_jump
8133 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8134 let completion = if is_move {
8135 invalidation_row_range =
8136 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8137 let target = first_edit_start;
8138 EditPrediction::MoveWithin { target, snapshot }
8139 } else {
8140 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8141 && !self.edit_predictions_hidden_for_vim_mode;
8142
8143 if show_completions_in_buffer {
8144 if let Some(provider) = &self.edit_prediction_provider {
8145 provider.provider.did_show(cx);
8146 }
8147 if edits
8148 .iter()
8149 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8150 {
8151 let mut inlays = Vec::new();
8152 for (range, new_text) in &edits {
8153 let inlay = Inlay::edit_prediction(
8154 post_inc(&mut self.next_inlay_id),
8155 range.start,
8156 new_text.as_ref(),
8157 );
8158 inlay_ids.push(inlay.id);
8159 inlays.push(inlay);
8160 }
8161
8162 self.splice_inlays(&[], inlays, cx);
8163 } else {
8164 let background_color = cx.theme().status().deleted_background;
8165 self.highlight_text::<EditPredictionHighlight>(
8166 edits.iter().map(|(range, _)| range.clone()).collect(),
8167 HighlightStyle {
8168 background_color: Some(background_color),
8169 ..Default::default()
8170 },
8171 cx,
8172 );
8173 }
8174 }
8175
8176 invalidation_row_range = edit_start_row..edit_end_row;
8177
8178 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8179 if provider.show_tab_accept_marker() {
8180 EditDisplayMode::TabAccept
8181 } else {
8182 EditDisplayMode::Inline
8183 }
8184 } else {
8185 EditDisplayMode::DiffPopover
8186 };
8187
8188 EditPrediction::Edit {
8189 edits,
8190 edit_preview,
8191 display_mode,
8192 snapshot,
8193 }
8194 };
8195
8196 let invalidation_range = multibuffer
8197 .anchor_before(Point::new(invalidation_row_range.start, 0))
8198 ..multibuffer.anchor_after(Point::new(
8199 invalidation_row_range.end,
8200 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8201 ));
8202
8203 self.stale_edit_prediction_in_menu = None;
8204 self.active_edit_prediction = Some(EditPredictionState {
8205 inlay_ids,
8206 completion,
8207 completion_id,
8208 invalidation_range: Some(invalidation_range),
8209 });
8210
8211 cx.notify();
8212
8213 Some(())
8214 }
8215
8216 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8217 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8218 }
8219
8220 fn clear_tasks(&mut self) {
8221 self.tasks.clear()
8222 }
8223
8224 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8225 if self.tasks.insert(key, value).is_some() {
8226 // This case should hopefully be rare, but just in case...
8227 log::error!(
8228 "multiple different run targets found on a single line, only the last target will be rendered"
8229 )
8230 }
8231 }
8232
8233 /// Get all display points of breakpoints that will be rendered within editor
8234 ///
8235 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8236 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8237 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8238 fn active_breakpoints(
8239 &self,
8240 range: Range<DisplayRow>,
8241 window: &mut Window,
8242 cx: &mut Context<Self>,
8243 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8244 let mut breakpoint_display_points = HashMap::default();
8245
8246 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8247 return breakpoint_display_points;
8248 };
8249
8250 let snapshot = self.snapshot(window, cx);
8251
8252 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8253 let Some(project) = self.project() else {
8254 return breakpoint_display_points;
8255 };
8256
8257 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8258 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8259
8260 for (buffer_snapshot, range, excerpt_id) in
8261 multi_buffer_snapshot.range_to_buffer_ranges(range)
8262 {
8263 let Some(buffer) = project
8264 .read(cx)
8265 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8266 else {
8267 continue;
8268 };
8269 let breakpoints = breakpoint_store.read(cx).breakpoints(
8270 &buffer,
8271 Some(
8272 buffer_snapshot.anchor_before(range.start)
8273 ..buffer_snapshot.anchor_after(range.end),
8274 ),
8275 buffer_snapshot,
8276 cx,
8277 );
8278 for (breakpoint, state) in breakpoints {
8279 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8280 let position = multi_buffer_anchor
8281 .to_point(&multi_buffer_snapshot)
8282 .to_display_point(&snapshot);
8283
8284 breakpoint_display_points.insert(
8285 position.row(),
8286 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8287 );
8288 }
8289 }
8290
8291 breakpoint_display_points
8292 }
8293
8294 fn breakpoint_context_menu(
8295 &self,
8296 anchor: Anchor,
8297 window: &mut Window,
8298 cx: &mut Context<Self>,
8299 ) -> Entity<ui::ContextMenu> {
8300 let weak_editor = cx.weak_entity();
8301 let focus_handle = self.focus_handle(cx);
8302
8303 let row = self
8304 .buffer
8305 .read(cx)
8306 .snapshot(cx)
8307 .summary_for_anchor::<Point>(&anchor)
8308 .row;
8309
8310 let breakpoint = self
8311 .breakpoint_at_row(row, window, cx)
8312 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8313
8314 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8315 "Edit Log Breakpoint"
8316 } else {
8317 "Set Log Breakpoint"
8318 };
8319
8320 let condition_breakpoint_msg = if breakpoint
8321 .as_ref()
8322 .is_some_and(|bp| bp.1.condition.is_some())
8323 {
8324 "Edit Condition Breakpoint"
8325 } else {
8326 "Set Condition Breakpoint"
8327 };
8328
8329 let hit_condition_breakpoint_msg = if breakpoint
8330 .as_ref()
8331 .is_some_and(|bp| bp.1.hit_condition.is_some())
8332 {
8333 "Edit Hit Condition Breakpoint"
8334 } else {
8335 "Set Hit Condition Breakpoint"
8336 };
8337
8338 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8339 "Unset Breakpoint"
8340 } else {
8341 "Set Breakpoint"
8342 };
8343
8344 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8345
8346 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8347 BreakpointState::Enabled => Some("Disable"),
8348 BreakpointState::Disabled => Some("Enable"),
8349 });
8350
8351 let (anchor, breakpoint) =
8352 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8353
8354 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8355 menu.on_blur_subscription(Subscription::new(|| {}))
8356 .context(focus_handle)
8357 .when(run_to_cursor, |this| {
8358 let weak_editor = weak_editor.clone();
8359 this.entry("Run to cursor", None, move |window, cx| {
8360 weak_editor
8361 .update(cx, |editor, cx| {
8362 editor.change_selections(
8363 SelectionEffects::no_scroll(),
8364 window,
8365 cx,
8366 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8367 );
8368 })
8369 .ok();
8370
8371 window.dispatch_action(Box::new(RunToCursor), cx);
8372 })
8373 .separator()
8374 })
8375 .when_some(toggle_state_msg, |this, msg| {
8376 this.entry(msg, None, {
8377 let weak_editor = weak_editor.clone();
8378 let breakpoint = breakpoint.clone();
8379 move |_window, cx| {
8380 weak_editor
8381 .update(cx, |this, cx| {
8382 this.edit_breakpoint_at_anchor(
8383 anchor,
8384 breakpoint.as_ref().clone(),
8385 BreakpointEditAction::InvertState,
8386 cx,
8387 );
8388 })
8389 .log_err();
8390 }
8391 })
8392 })
8393 .entry(set_breakpoint_msg, None, {
8394 let weak_editor = weak_editor.clone();
8395 let breakpoint = breakpoint.clone();
8396 move |_window, cx| {
8397 weak_editor
8398 .update(cx, |this, cx| {
8399 this.edit_breakpoint_at_anchor(
8400 anchor,
8401 breakpoint.as_ref().clone(),
8402 BreakpointEditAction::Toggle,
8403 cx,
8404 );
8405 })
8406 .log_err();
8407 }
8408 })
8409 .entry(log_breakpoint_msg, None, {
8410 let breakpoint = breakpoint.clone();
8411 let weak_editor = weak_editor.clone();
8412 move |window, cx| {
8413 weak_editor
8414 .update(cx, |this, cx| {
8415 this.add_edit_breakpoint_block(
8416 anchor,
8417 breakpoint.as_ref(),
8418 BreakpointPromptEditAction::Log,
8419 window,
8420 cx,
8421 );
8422 })
8423 .log_err();
8424 }
8425 })
8426 .entry(condition_breakpoint_msg, None, {
8427 let breakpoint = breakpoint.clone();
8428 let weak_editor = weak_editor.clone();
8429 move |window, cx| {
8430 weak_editor
8431 .update(cx, |this, cx| {
8432 this.add_edit_breakpoint_block(
8433 anchor,
8434 breakpoint.as_ref(),
8435 BreakpointPromptEditAction::Condition,
8436 window,
8437 cx,
8438 );
8439 })
8440 .log_err();
8441 }
8442 })
8443 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8444 weak_editor
8445 .update(cx, |this, cx| {
8446 this.add_edit_breakpoint_block(
8447 anchor,
8448 breakpoint.as_ref(),
8449 BreakpointPromptEditAction::HitCondition,
8450 window,
8451 cx,
8452 );
8453 })
8454 .log_err();
8455 })
8456 })
8457 }
8458
8459 fn render_breakpoint(
8460 &self,
8461 position: Anchor,
8462 row: DisplayRow,
8463 breakpoint: &Breakpoint,
8464 state: Option<BreakpointSessionState>,
8465 cx: &mut Context<Self>,
8466 ) -> IconButton {
8467 let is_rejected = state.is_some_and(|s| !s.verified);
8468 // Is it a breakpoint that shows up when hovering over gutter?
8469 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8470 (false, false),
8471 |PhantomBreakpointIndicator {
8472 is_active,
8473 display_row,
8474 collides_with_existing_breakpoint,
8475 }| {
8476 (
8477 is_active && display_row == row,
8478 collides_with_existing_breakpoint,
8479 )
8480 },
8481 );
8482
8483 let (color, icon) = {
8484 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8485 (false, false) => ui::IconName::DebugBreakpoint,
8486 (true, false) => ui::IconName::DebugLogBreakpoint,
8487 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8488 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8489 };
8490
8491 let color = cx.theme().colors();
8492
8493 let color = if is_phantom {
8494 if collides_with_existing {
8495 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8496 } else {
8497 Color::Hint
8498 }
8499 } else if is_rejected {
8500 Color::Disabled
8501 } else {
8502 Color::Debugger
8503 };
8504
8505 (color, icon)
8506 };
8507
8508 let breakpoint = Arc::from(breakpoint.clone());
8509
8510 let alt_as_text = gpui::Keystroke {
8511 modifiers: Modifiers::secondary_key(),
8512 ..Default::default()
8513 };
8514 let primary_action_text = if breakpoint.is_disabled() {
8515 "Enable breakpoint"
8516 } else if is_phantom && !collides_with_existing {
8517 "Set breakpoint"
8518 } else {
8519 "Unset breakpoint"
8520 };
8521 let focus_handle = self.focus_handle.clone();
8522
8523 let meta = if is_rejected {
8524 SharedString::from("No executable code is associated with this line.")
8525 } else if collides_with_existing && !breakpoint.is_disabled() {
8526 SharedString::from(format!(
8527 "{alt_as_text}-click to disable,\nright-click for more options."
8528 ))
8529 } else {
8530 SharedString::from("Right-click for more options.")
8531 };
8532 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8533 .icon_size(IconSize::XSmall)
8534 .size(ui::ButtonSize::None)
8535 .when(is_rejected, |this| {
8536 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8537 })
8538 .icon_color(color)
8539 .style(ButtonStyle::Transparent)
8540 .on_click(cx.listener({
8541 move |editor, event: &ClickEvent, window, cx| {
8542 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8543 BreakpointEditAction::InvertState
8544 } else {
8545 BreakpointEditAction::Toggle
8546 };
8547
8548 window.focus(&editor.focus_handle(cx), cx);
8549 editor.edit_breakpoint_at_anchor(
8550 position,
8551 breakpoint.as_ref().clone(),
8552 edit_action,
8553 cx,
8554 );
8555 }
8556 }))
8557 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8558 editor.set_breakpoint_context_menu(
8559 row,
8560 Some(position),
8561 event.position(),
8562 window,
8563 cx,
8564 );
8565 }))
8566 .tooltip(move |_window, cx| {
8567 Tooltip::with_meta_in(
8568 primary_action_text,
8569 Some(&ToggleBreakpoint),
8570 meta.clone(),
8571 &focus_handle,
8572 cx,
8573 )
8574 })
8575 }
8576
8577 fn build_tasks_context(
8578 project: &Entity<Project>,
8579 buffer: &Entity<Buffer>,
8580 buffer_row: u32,
8581 tasks: &Arc<RunnableTasks>,
8582 cx: &mut Context<Self>,
8583 ) -> Task<Option<task::TaskContext>> {
8584 let position = Point::new(buffer_row, tasks.column);
8585 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8586 let location = Location {
8587 buffer: buffer.clone(),
8588 range: range_start..range_start,
8589 };
8590 // Fill in the environmental variables from the tree-sitter captures
8591 let mut captured_task_variables = TaskVariables::default();
8592 for (capture_name, value) in tasks.extra_variables.clone() {
8593 captured_task_variables.insert(
8594 task::VariableName::Custom(capture_name.into()),
8595 value.clone(),
8596 );
8597 }
8598 project.update(cx, |project, cx| {
8599 project.task_store().update(cx, |task_store, cx| {
8600 task_store.task_context_for_location(captured_task_variables, location, cx)
8601 })
8602 })
8603 }
8604
8605 pub fn spawn_nearest_task(
8606 &mut self,
8607 action: &SpawnNearestTask,
8608 window: &mut Window,
8609 cx: &mut Context<Self>,
8610 ) {
8611 let Some((workspace, _)) = self.workspace.clone() else {
8612 return;
8613 };
8614 let Some(project) = self.project.clone() else {
8615 return;
8616 };
8617
8618 // Try to find a closest, enclosing node using tree-sitter that has a task
8619 let Some((buffer, buffer_row, tasks)) = self
8620 .find_enclosing_node_task(cx)
8621 // Or find the task that's closest in row-distance.
8622 .or_else(|| self.find_closest_task(cx))
8623 else {
8624 return;
8625 };
8626
8627 let reveal_strategy = action.reveal;
8628 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8629 cx.spawn_in(window, async move |_, cx| {
8630 let context = task_context.await?;
8631 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8632
8633 let resolved = &mut resolved_task.resolved;
8634 resolved.reveal = reveal_strategy;
8635
8636 workspace
8637 .update_in(cx, |workspace, window, cx| {
8638 workspace.schedule_resolved_task(
8639 task_source_kind,
8640 resolved_task,
8641 false,
8642 window,
8643 cx,
8644 );
8645 })
8646 .ok()
8647 })
8648 .detach();
8649 }
8650
8651 fn find_closest_task(
8652 &mut self,
8653 cx: &mut Context<Self>,
8654 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8655 let cursor_row = self
8656 .selections
8657 .newest_adjusted(&self.display_snapshot(cx))
8658 .head()
8659 .row;
8660
8661 let ((buffer_id, row), tasks) = self
8662 .tasks
8663 .iter()
8664 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8665
8666 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8667 let tasks = Arc::new(tasks.to_owned());
8668 Some((buffer, *row, tasks))
8669 }
8670
8671 fn find_enclosing_node_task(
8672 &mut self,
8673 cx: &mut Context<Self>,
8674 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8675 let snapshot = self.buffer.read(cx).snapshot(cx);
8676 let offset = self
8677 .selections
8678 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8679 .head();
8680 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8681 let offset = excerpt.map_offset_to_buffer(offset);
8682 let buffer_id = excerpt.buffer().remote_id();
8683
8684 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8685 let mut cursor = layer.node().walk();
8686
8687 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8688 if cursor.node().end_byte() == offset.0 {
8689 cursor.goto_next_sibling();
8690 }
8691 }
8692
8693 // Ascend to the smallest ancestor that contains the range and has a task.
8694 loop {
8695 let node = cursor.node();
8696 let node_range = node.byte_range();
8697 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8698
8699 // Check if this node contains our offset
8700 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8701 // If it contains offset, check for task
8702 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8703 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8704 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8705 }
8706 }
8707
8708 if !cursor.goto_parent() {
8709 break;
8710 }
8711 }
8712 None
8713 }
8714
8715 fn render_run_indicator(
8716 &self,
8717 _style: &EditorStyle,
8718 is_active: bool,
8719 row: DisplayRow,
8720 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8721 cx: &mut Context<Self>,
8722 ) -> IconButton {
8723 let color = Color::Muted;
8724 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8725
8726 IconButton::new(
8727 ("run_indicator", row.0 as usize),
8728 ui::IconName::PlayOutlined,
8729 )
8730 .shape(ui::IconButtonShape::Square)
8731 .icon_size(IconSize::XSmall)
8732 .icon_color(color)
8733 .toggle_state(is_active)
8734 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8735 let quick_launch = match e {
8736 ClickEvent::Keyboard(_) => true,
8737 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8738 };
8739
8740 window.focus(&editor.focus_handle(cx), cx);
8741 editor.toggle_code_actions(
8742 &ToggleCodeActions {
8743 deployed_from: Some(CodeActionSource::RunMenu(row)),
8744 quick_launch,
8745 },
8746 window,
8747 cx,
8748 );
8749 }))
8750 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8751 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8752 }))
8753 }
8754
8755 pub fn context_menu_visible(&self) -> bool {
8756 !self.edit_prediction_preview_is_active()
8757 && self
8758 .context_menu
8759 .borrow()
8760 .as_ref()
8761 .is_some_and(|menu| menu.visible())
8762 }
8763
8764 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8765 self.context_menu
8766 .borrow()
8767 .as_ref()
8768 .map(|menu| menu.origin())
8769 }
8770
8771 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8772 self.context_menu_options = Some(options);
8773 }
8774
8775 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8776 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8777
8778 fn render_edit_prediction_popover(
8779 &mut self,
8780 text_bounds: &Bounds<Pixels>,
8781 content_origin: gpui::Point<Pixels>,
8782 right_margin: Pixels,
8783 editor_snapshot: &EditorSnapshot,
8784 visible_row_range: Range<DisplayRow>,
8785 scroll_top: ScrollOffset,
8786 scroll_bottom: ScrollOffset,
8787 line_layouts: &[LineWithInvisibles],
8788 line_height: Pixels,
8789 scroll_position: gpui::Point<ScrollOffset>,
8790 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8791 newest_selection_head: Option<DisplayPoint>,
8792 editor_width: Pixels,
8793 style: &EditorStyle,
8794 window: &mut Window,
8795 cx: &mut App,
8796 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8797 if self.mode().is_minimap() {
8798 return None;
8799 }
8800 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8801
8802 if self.edit_prediction_visible_in_cursor_popover(true) {
8803 return None;
8804 }
8805
8806 match &active_edit_prediction.completion {
8807 EditPrediction::MoveWithin { target, .. } => {
8808 let target_display_point = target.to_display_point(editor_snapshot);
8809
8810 if self.edit_prediction_requires_modifier() {
8811 if !self.edit_prediction_preview_is_active() {
8812 return None;
8813 }
8814
8815 self.render_edit_prediction_modifier_jump_popover(
8816 text_bounds,
8817 content_origin,
8818 visible_row_range,
8819 line_layouts,
8820 line_height,
8821 scroll_pixel_position,
8822 newest_selection_head,
8823 target_display_point,
8824 window,
8825 cx,
8826 )
8827 } else {
8828 self.render_edit_prediction_eager_jump_popover(
8829 text_bounds,
8830 content_origin,
8831 editor_snapshot,
8832 visible_row_range,
8833 scroll_top,
8834 scroll_bottom,
8835 line_height,
8836 scroll_pixel_position,
8837 target_display_point,
8838 editor_width,
8839 window,
8840 cx,
8841 )
8842 }
8843 }
8844 EditPrediction::Edit {
8845 display_mode: EditDisplayMode::Inline,
8846 ..
8847 } => None,
8848 EditPrediction::Edit {
8849 display_mode: EditDisplayMode::TabAccept,
8850 edits,
8851 ..
8852 } => {
8853 let range = &edits.first()?.0;
8854 let target_display_point = range.end.to_display_point(editor_snapshot);
8855
8856 self.render_edit_prediction_end_of_line_popover(
8857 "Accept",
8858 editor_snapshot,
8859 visible_row_range,
8860 target_display_point,
8861 line_height,
8862 scroll_pixel_position,
8863 content_origin,
8864 editor_width,
8865 window,
8866 cx,
8867 )
8868 }
8869 EditPrediction::Edit {
8870 edits,
8871 edit_preview,
8872 display_mode: EditDisplayMode::DiffPopover,
8873 snapshot,
8874 } => self.render_edit_prediction_diff_popover(
8875 text_bounds,
8876 content_origin,
8877 right_margin,
8878 editor_snapshot,
8879 visible_row_range,
8880 line_layouts,
8881 line_height,
8882 scroll_position,
8883 scroll_pixel_position,
8884 newest_selection_head,
8885 editor_width,
8886 style,
8887 edits,
8888 edit_preview,
8889 snapshot,
8890 window,
8891 cx,
8892 ),
8893 EditPrediction::MoveOutside { snapshot, .. } => {
8894 let mut element = self
8895 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8896 .into_any();
8897
8898 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8899 let origin_x = text_bounds.size.width - size.width - px(30.);
8900 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8901 element.prepaint_at(origin, window, cx);
8902
8903 Some((element, origin))
8904 }
8905 }
8906 }
8907
8908 fn render_edit_prediction_modifier_jump_popover(
8909 &mut self,
8910 text_bounds: &Bounds<Pixels>,
8911 content_origin: gpui::Point<Pixels>,
8912 visible_row_range: Range<DisplayRow>,
8913 line_layouts: &[LineWithInvisibles],
8914 line_height: Pixels,
8915 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8916 newest_selection_head: Option<DisplayPoint>,
8917 target_display_point: DisplayPoint,
8918 window: &mut Window,
8919 cx: &mut App,
8920 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8921 let scrolled_content_origin =
8922 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8923
8924 const SCROLL_PADDING_Y: Pixels = px(12.);
8925
8926 if target_display_point.row() < visible_row_range.start {
8927 return self.render_edit_prediction_scroll_popover(
8928 |_| SCROLL_PADDING_Y,
8929 IconName::ArrowUp,
8930 visible_row_range,
8931 line_layouts,
8932 newest_selection_head,
8933 scrolled_content_origin,
8934 window,
8935 cx,
8936 );
8937 } else if target_display_point.row() >= visible_row_range.end {
8938 return self.render_edit_prediction_scroll_popover(
8939 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8940 IconName::ArrowDown,
8941 visible_row_range,
8942 line_layouts,
8943 newest_selection_head,
8944 scrolled_content_origin,
8945 window,
8946 cx,
8947 );
8948 }
8949
8950 const POLE_WIDTH: Pixels = px(2.);
8951
8952 let line_layout =
8953 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8954 let target_column = target_display_point.column() as usize;
8955
8956 let target_x = line_layout.x_for_index(target_column);
8957 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8958 - scroll_pixel_position.y;
8959
8960 let flag_on_right = target_x < text_bounds.size.width / 2.;
8961
8962 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8963 border_color.l += 0.001;
8964
8965 let mut element = v_flex()
8966 .items_end()
8967 .when(flag_on_right, |el| el.items_start())
8968 .child(if flag_on_right {
8969 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8970 .rounded_bl(px(0.))
8971 .rounded_tl(px(0.))
8972 .border_l_2()
8973 .border_color(border_color)
8974 } else {
8975 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8976 .rounded_br(px(0.))
8977 .rounded_tr(px(0.))
8978 .border_r_2()
8979 .border_color(border_color)
8980 })
8981 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8982 .into_any();
8983
8984 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8985
8986 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8987 - point(
8988 if flag_on_right {
8989 POLE_WIDTH
8990 } else {
8991 size.width - POLE_WIDTH
8992 },
8993 size.height - line_height,
8994 );
8995
8996 origin.x = origin.x.max(content_origin.x);
8997
8998 element.prepaint_at(origin, window, cx);
8999
9000 Some((element, origin))
9001 }
9002
9003 fn render_edit_prediction_scroll_popover(
9004 &mut self,
9005 to_y: impl Fn(Size<Pixels>) -> Pixels,
9006 scroll_icon: IconName,
9007 visible_row_range: Range<DisplayRow>,
9008 line_layouts: &[LineWithInvisibles],
9009 newest_selection_head: Option<DisplayPoint>,
9010 scrolled_content_origin: gpui::Point<Pixels>,
9011 window: &mut Window,
9012 cx: &mut App,
9013 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9014 let mut element = self
9015 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9016 .into_any();
9017
9018 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9019
9020 let cursor = newest_selection_head?;
9021 let cursor_row_layout =
9022 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9023 let cursor_column = cursor.column() as usize;
9024
9025 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9026
9027 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9028
9029 element.prepaint_at(origin, window, cx);
9030 Some((element, origin))
9031 }
9032
9033 fn render_edit_prediction_eager_jump_popover(
9034 &mut self,
9035 text_bounds: &Bounds<Pixels>,
9036 content_origin: gpui::Point<Pixels>,
9037 editor_snapshot: &EditorSnapshot,
9038 visible_row_range: Range<DisplayRow>,
9039 scroll_top: ScrollOffset,
9040 scroll_bottom: ScrollOffset,
9041 line_height: Pixels,
9042 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9043 target_display_point: DisplayPoint,
9044 editor_width: Pixels,
9045 window: &mut Window,
9046 cx: &mut App,
9047 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9048 if target_display_point.row().as_f64() < scroll_top {
9049 let mut element = self
9050 .render_edit_prediction_line_popover(
9051 "Jump to Edit",
9052 Some(IconName::ArrowUp),
9053 window,
9054 cx,
9055 )
9056 .into_any();
9057
9058 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9059 let offset = point(
9060 (text_bounds.size.width - size.width) / 2.,
9061 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9062 );
9063
9064 let origin = text_bounds.origin + offset;
9065 element.prepaint_at(origin, window, cx);
9066 Some((element, origin))
9067 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9068 let mut element = self
9069 .render_edit_prediction_line_popover(
9070 "Jump to Edit",
9071 Some(IconName::ArrowDown),
9072 window,
9073 cx,
9074 )
9075 .into_any();
9076
9077 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9078 let offset = point(
9079 (text_bounds.size.width - size.width) / 2.,
9080 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9081 );
9082
9083 let origin = text_bounds.origin + offset;
9084 element.prepaint_at(origin, window, cx);
9085 Some((element, origin))
9086 } else {
9087 self.render_edit_prediction_end_of_line_popover(
9088 "Jump to Edit",
9089 editor_snapshot,
9090 visible_row_range,
9091 target_display_point,
9092 line_height,
9093 scroll_pixel_position,
9094 content_origin,
9095 editor_width,
9096 window,
9097 cx,
9098 )
9099 }
9100 }
9101
9102 fn render_edit_prediction_end_of_line_popover(
9103 self: &mut Editor,
9104 label: &'static str,
9105 editor_snapshot: &EditorSnapshot,
9106 visible_row_range: Range<DisplayRow>,
9107 target_display_point: DisplayPoint,
9108 line_height: Pixels,
9109 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9110 content_origin: gpui::Point<Pixels>,
9111 editor_width: Pixels,
9112 window: &mut Window,
9113 cx: &mut App,
9114 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9115 let target_line_end = DisplayPoint::new(
9116 target_display_point.row(),
9117 editor_snapshot.line_len(target_display_point.row()),
9118 );
9119
9120 let mut element = self
9121 .render_edit_prediction_line_popover(label, None, window, cx)
9122 .into_any();
9123
9124 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9125
9126 let line_origin =
9127 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9128
9129 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9130 let mut origin = start_point
9131 + line_origin
9132 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9133 origin.x = origin.x.max(content_origin.x);
9134
9135 let max_x = content_origin.x + editor_width - size.width;
9136
9137 if origin.x > max_x {
9138 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9139
9140 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9141 origin.y += offset;
9142 IconName::ArrowUp
9143 } else {
9144 origin.y -= offset;
9145 IconName::ArrowDown
9146 };
9147
9148 element = self
9149 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9150 .into_any();
9151
9152 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9153
9154 origin.x = content_origin.x + editor_width - size.width - px(2.);
9155 }
9156
9157 element.prepaint_at(origin, window, cx);
9158 Some((element, origin))
9159 }
9160
9161 fn render_edit_prediction_diff_popover(
9162 self: &Editor,
9163 text_bounds: &Bounds<Pixels>,
9164 content_origin: gpui::Point<Pixels>,
9165 right_margin: Pixels,
9166 editor_snapshot: &EditorSnapshot,
9167 visible_row_range: Range<DisplayRow>,
9168 line_layouts: &[LineWithInvisibles],
9169 line_height: Pixels,
9170 scroll_position: gpui::Point<ScrollOffset>,
9171 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9172 newest_selection_head: Option<DisplayPoint>,
9173 editor_width: Pixels,
9174 style: &EditorStyle,
9175 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9176 edit_preview: &Option<language::EditPreview>,
9177 snapshot: &language::BufferSnapshot,
9178 window: &mut Window,
9179 cx: &mut App,
9180 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9181 let edit_start = edits
9182 .first()
9183 .unwrap()
9184 .0
9185 .start
9186 .to_display_point(editor_snapshot);
9187 let edit_end = edits
9188 .last()
9189 .unwrap()
9190 .0
9191 .end
9192 .to_display_point(editor_snapshot);
9193
9194 let is_visible = visible_row_range.contains(&edit_start.row())
9195 || visible_row_range.contains(&edit_end.row());
9196 if !is_visible {
9197 return None;
9198 }
9199
9200 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9201 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9202 } else {
9203 // Fallback for providers without edit_preview
9204 crate::edit_prediction_fallback_text(edits, cx)
9205 };
9206
9207 let styled_text = highlighted_edits.to_styled_text(&style.text);
9208 let line_count = highlighted_edits.text.lines().count();
9209
9210 const BORDER_WIDTH: Pixels = px(1.);
9211
9212 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9213 let has_keybind = keybind.is_some();
9214
9215 let mut element = h_flex()
9216 .items_start()
9217 .child(
9218 h_flex()
9219 .bg(cx.theme().colors().editor_background)
9220 .border(BORDER_WIDTH)
9221 .shadow_xs()
9222 .border_color(cx.theme().colors().border)
9223 .rounded_l_lg()
9224 .when(line_count > 1, |el| el.rounded_br_lg())
9225 .pr_1()
9226 .child(styled_text),
9227 )
9228 .child(
9229 h_flex()
9230 .h(line_height + BORDER_WIDTH * 2.)
9231 .px_1p5()
9232 .gap_1()
9233 // Workaround: For some reason, there's a gap if we don't do this
9234 .ml(-BORDER_WIDTH)
9235 .shadow(vec![gpui::BoxShadow {
9236 color: gpui::black().opacity(0.05),
9237 offset: point(px(1.), px(1.)),
9238 blur_radius: px(2.),
9239 spread_radius: px(0.),
9240 }])
9241 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9242 .border(BORDER_WIDTH)
9243 .border_color(cx.theme().colors().border)
9244 .rounded_r_lg()
9245 .id("edit_prediction_diff_popover_keybind")
9246 .when(!has_keybind, |el| {
9247 let status_colors = cx.theme().status();
9248
9249 el.bg(status_colors.error_background)
9250 .border_color(status_colors.error.opacity(0.6))
9251 .child(Icon::new(IconName::Info).color(Color::Error))
9252 .cursor_default()
9253 .hoverable_tooltip(move |_window, cx| {
9254 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9255 })
9256 })
9257 .children(keybind),
9258 )
9259 .into_any();
9260
9261 let longest_row =
9262 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9263 let longest_line_width = if visible_row_range.contains(&longest_row) {
9264 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9265 } else {
9266 layout_line(
9267 longest_row,
9268 editor_snapshot,
9269 style,
9270 editor_width,
9271 |_| false,
9272 window,
9273 cx,
9274 )
9275 .width
9276 };
9277
9278 let viewport_bounds =
9279 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9280 right: -right_margin,
9281 ..Default::default()
9282 });
9283
9284 let x_after_longest = Pixels::from(
9285 ScrollPixelOffset::from(
9286 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9287 ) - scroll_pixel_position.x,
9288 );
9289
9290 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9291
9292 // Fully visible if it can be displayed within the window (allow overlapping other
9293 // panes). However, this is only allowed if the popover starts within text_bounds.
9294 let can_position_to_the_right = x_after_longest < text_bounds.right()
9295 && x_after_longest + element_bounds.width < viewport_bounds.right();
9296
9297 let mut origin = if can_position_to_the_right {
9298 point(
9299 x_after_longest,
9300 text_bounds.origin.y
9301 + Pixels::from(
9302 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9303 - scroll_pixel_position.y,
9304 ),
9305 )
9306 } else {
9307 let cursor_row = newest_selection_head.map(|head| head.row());
9308 let above_edit = edit_start
9309 .row()
9310 .0
9311 .checked_sub(line_count as u32)
9312 .map(DisplayRow);
9313 let below_edit = Some(edit_end.row() + 1);
9314 let above_cursor =
9315 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9316 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9317
9318 // Place the edit popover adjacent to the edit if there is a location
9319 // available that is onscreen and does not obscure the cursor. Otherwise,
9320 // place it adjacent to the cursor.
9321 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9322 .into_iter()
9323 .flatten()
9324 .find(|&start_row| {
9325 let end_row = start_row + line_count as u32;
9326 visible_row_range.contains(&start_row)
9327 && visible_row_range.contains(&end_row)
9328 && cursor_row
9329 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9330 })?;
9331
9332 content_origin
9333 + point(
9334 Pixels::from(-scroll_pixel_position.x),
9335 Pixels::from(
9336 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9337 ),
9338 )
9339 };
9340
9341 origin.x -= BORDER_WIDTH;
9342
9343 window.defer_draw(element, origin, 1);
9344
9345 // Do not return an element, since it will already be drawn due to defer_draw.
9346 None
9347 }
9348
9349 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9350 px(30.)
9351 }
9352
9353 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9354 if self.read_only(cx) {
9355 cx.theme().players().read_only()
9356 } else {
9357 self.style.as_ref().unwrap().local_player
9358 }
9359 }
9360
9361 fn render_edit_prediction_accept_keybind(
9362 &self,
9363 window: &mut Window,
9364 cx: &mut App,
9365 ) -> Option<AnyElement> {
9366 let accept_binding =
9367 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9368 let accept_keystroke = accept_binding.keystroke()?;
9369
9370 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9371
9372 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9373 Color::Accent
9374 } else {
9375 Color::Muted
9376 };
9377
9378 h_flex()
9379 .px_0p5()
9380 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9381 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9382 .text_size(TextSize::XSmall.rems(cx))
9383 .child(h_flex().children(ui::render_modifiers(
9384 accept_keystroke.modifiers(),
9385 PlatformStyle::platform(),
9386 Some(modifiers_color),
9387 Some(IconSize::XSmall.rems().into()),
9388 true,
9389 )))
9390 .when(is_platform_style_mac, |parent| {
9391 parent.child(accept_keystroke.key().to_string())
9392 })
9393 .when(!is_platform_style_mac, |parent| {
9394 parent.child(
9395 Key::new(
9396 util::capitalize(accept_keystroke.key()),
9397 Some(Color::Default),
9398 )
9399 .size(Some(IconSize::XSmall.rems().into())),
9400 )
9401 })
9402 .into_any()
9403 .into()
9404 }
9405
9406 fn render_edit_prediction_line_popover(
9407 &self,
9408 label: impl Into<SharedString>,
9409 icon: Option<IconName>,
9410 window: &mut Window,
9411 cx: &mut App,
9412 ) -> Stateful<Div> {
9413 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9414
9415 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9416 let has_keybind = keybind.is_some();
9417
9418 h_flex()
9419 .id("ep-line-popover")
9420 .py_0p5()
9421 .pl_1()
9422 .pr(padding_right)
9423 .gap_1()
9424 .rounded_md()
9425 .border_1()
9426 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9427 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9428 .shadow_xs()
9429 .when(!has_keybind, |el| {
9430 let status_colors = cx.theme().status();
9431
9432 el.bg(status_colors.error_background)
9433 .border_color(status_colors.error.opacity(0.6))
9434 .pl_2()
9435 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9436 .cursor_default()
9437 .hoverable_tooltip(move |_window, cx| {
9438 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9439 })
9440 })
9441 .children(keybind)
9442 .child(
9443 Label::new(label)
9444 .size(LabelSize::Small)
9445 .when(!has_keybind, |el| {
9446 el.color(cx.theme().status().error.into()).strikethrough()
9447 }),
9448 )
9449 .when(!has_keybind, |el| {
9450 el.child(
9451 h_flex().ml_1().child(
9452 Icon::new(IconName::Info)
9453 .size(IconSize::Small)
9454 .color(cx.theme().status().error.into()),
9455 ),
9456 )
9457 })
9458 .when_some(icon, |element, icon| {
9459 element.child(
9460 div()
9461 .mt(px(1.5))
9462 .child(Icon::new(icon).size(IconSize::Small)),
9463 )
9464 })
9465 }
9466
9467 fn render_edit_prediction_jump_outside_popover(
9468 &self,
9469 snapshot: &BufferSnapshot,
9470 window: &mut Window,
9471 cx: &mut App,
9472 ) -> Stateful<Div> {
9473 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9474 let has_keybind = keybind.is_some();
9475
9476 let file_name = snapshot
9477 .file()
9478 .map(|file| SharedString::new(file.file_name(cx)))
9479 .unwrap_or(SharedString::new_static("untitled"));
9480
9481 h_flex()
9482 .id("ep-jump-outside-popover")
9483 .py_1()
9484 .px_2()
9485 .gap_1()
9486 .rounded_md()
9487 .border_1()
9488 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9489 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9490 .shadow_xs()
9491 .when(!has_keybind, |el| {
9492 let status_colors = cx.theme().status();
9493
9494 el.bg(status_colors.error_background)
9495 .border_color(status_colors.error.opacity(0.6))
9496 .pl_2()
9497 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9498 .cursor_default()
9499 .hoverable_tooltip(move |_window, cx| {
9500 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9501 })
9502 })
9503 .children(keybind)
9504 .child(
9505 Label::new(file_name)
9506 .size(LabelSize::Small)
9507 .buffer_font(cx)
9508 .when(!has_keybind, |el| {
9509 el.color(cx.theme().status().error.into()).strikethrough()
9510 }),
9511 )
9512 .when(!has_keybind, |el| {
9513 el.child(
9514 h_flex().ml_1().child(
9515 Icon::new(IconName::Info)
9516 .size(IconSize::Small)
9517 .color(cx.theme().status().error.into()),
9518 ),
9519 )
9520 })
9521 .child(
9522 div()
9523 .mt(px(1.5))
9524 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9525 )
9526 }
9527
9528 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9529 let accent_color = cx.theme().colors().text_accent;
9530 let editor_bg_color = cx.theme().colors().editor_background;
9531 editor_bg_color.blend(accent_color.opacity(0.1))
9532 }
9533
9534 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9535 let accent_color = cx.theme().colors().text_accent;
9536 let editor_bg_color = cx.theme().colors().editor_background;
9537 editor_bg_color.blend(accent_color.opacity(0.6))
9538 }
9539 fn get_prediction_provider_icon_name(
9540 provider: &Option<RegisteredEditPredictionDelegate>,
9541 ) -> IconName {
9542 match provider {
9543 Some(provider) => match provider.provider.name() {
9544 "copilot" => IconName::Copilot,
9545 "supermaven" => IconName::Supermaven,
9546 _ => IconName::ZedPredict,
9547 },
9548 None => IconName::ZedPredict,
9549 }
9550 }
9551
9552 fn render_edit_prediction_cursor_popover(
9553 &self,
9554 min_width: Pixels,
9555 max_width: Pixels,
9556 cursor_point: Point,
9557 style: &EditorStyle,
9558 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9559 _window: &Window,
9560 cx: &mut Context<Editor>,
9561 ) -> Option<AnyElement> {
9562 let provider = self.edit_prediction_provider.as_ref()?;
9563 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9564
9565 let is_refreshing = provider.provider.is_refreshing(cx);
9566
9567 fn pending_completion_container(icon: IconName) -> Div {
9568 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9569 }
9570
9571 let completion = match &self.active_edit_prediction {
9572 Some(prediction) => {
9573 if !self.has_visible_completions_menu() {
9574 const RADIUS: Pixels = px(6.);
9575 const BORDER_WIDTH: Pixels = px(1.);
9576
9577 return Some(
9578 h_flex()
9579 .elevation_2(cx)
9580 .border(BORDER_WIDTH)
9581 .border_color(cx.theme().colors().border)
9582 .when(accept_keystroke.is_none(), |el| {
9583 el.border_color(cx.theme().status().error)
9584 })
9585 .rounded(RADIUS)
9586 .rounded_tl(px(0.))
9587 .overflow_hidden()
9588 .child(div().px_1p5().child(match &prediction.completion {
9589 EditPrediction::MoveWithin { target, snapshot } => {
9590 use text::ToPoint as _;
9591 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9592 {
9593 Icon::new(IconName::ZedPredictDown)
9594 } else {
9595 Icon::new(IconName::ZedPredictUp)
9596 }
9597 }
9598 EditPrediction::MoveOutside { .. } => {
9599 // TODO [zeta2] custom icon for external jump?
9600 Icon::new(provider_icon)
9601 }
9602 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9603 }))
9604 .child(
9605 h_flex()
9606 .gap_1()
9607 .py_1()
9608 .px_2()
9609 .rounded_r(RADIUS - BORDER_WIDTH)
9610 .border_l_1()
9611 .border_color(cx.theme().colors().border)
9612 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9613 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9614 el.child(
9615 Label::new("Hold")
9616 .size(LabelSize::Small)
9617 .when(accept_keystroke.is_none(), |el| {
9618 el.strikethrough()
9619 })
9620 .line_height_style(LineHeightStyle::UiLabel),
9621 )
9622 })
9623 .id("edit_prediction_cursor_popover_keybind")
9624 .when(accept_keystroke.is_none(), |el| {
9625 let status_colors = cx.theme().status();
9626
9627 el.bg(status_colors.error_background)
9628 .border_color(status_colors.error.opacity(0.6))
9629 .child(Icon::new(IconName::Info).color(Color::Error))
9630 .cursor_default()
9631 .hoverable_tooltip(move |_window, cx| {
9632 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9633 .into()
9634 })
9635 })
9636 .when_some(
9637 accept_keystroke.as_ref(),
9638 |el, accept_keystroke| {
9639 el.child(h_flex().children(ui::render_modifiers(
9640 accept_keystroke.modifiers(),
9641 PlatformStyle::platform(),
9642 Some(Color::Default),
9643 Some(IconSize::XSmall.rems().into()),
9644 false,
9645 )))
9646 },
9647 ),
9648 )
9649 .into_any(),
9650 );
9651 }
9652
9653 self.render_edit_prediction_cursor_popover_preview(
9654 prediction,
9655 cursor_point,
9656 style,
9657 cx,
9658 )?
9659 }
9660
9661 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9662 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9663 stale_completion,
9664 cursor_point,
9665 style,
9666 cx,
9667 )?,
9668
9669 None => pending_completion_container(provider_icon)
9670 .child(Label::new("...").size(LabelSize::Small)),
9671 },
9672
9673 None => pending_completion_container(provider_icon)
9674 .child(Label::new("...").size(LabelSize::Small)),
9675 };
9676
9677 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9678 completion
9679 .with_animation(
9680 "loading-completion",
9681 Animation::new(Duration::from_secs(2))
9682 .repeat()
9683 .with_easing(pulsating_between(0.4, 0.8)),
9684 |label, delta| label.opacity(delta),
9685 )
9686 .into_any_element()
9687 } else {
9688 completion.into_any_element()
9689 };
9690
9691 let has_completion = self.active_edit_prediction.is_some();
9692
9693 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9694 Some(
9695 h_flex()
9696 .min_w(min_width)
9697 .max_w(max_width)
9698 .flex_1()
9699 .elevation_2(cx)
9700 .border_color(cx.theme().colors().border)
9701 .child(
9702 div()
9703 .flex_1()
9704 .py_1()
9705 .px_2()
9706 .overflow_hidden()
9707 .child(completion),
9708 )
9709 .when_some(accept_keystroke, |el, accept_keystroke| {
9710 if !accept_keystroke.modifiers().modified() {
9711 return el;
9712 }
9713
9714 el.child(
9715 h_flex()
9716 .h_full()
9717 .border_l_1()
9718 .rounded_r_lg()
9719 .border_color(cx.theme().colors().border)
9720 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9721 .gap_1()
9722 .py_1()
9723 .px_2()
9724 .child(
9725 h_flex()
9726 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9727 .when(is_platform_style_mac, |parent| parent.gap_1())
9728 .child(h_flex().children(ui::render_modifiers(
9729 accept_keystroke.modifiers(),
9730 PlatformStyle::platform(),
9731 Some(if !has_completion {
9732 Color::Muted
9733 } else {
9734 Color::Default
9735 }),
9736 None,
9737 false,
9738 ))),
9739 )
9740 .child(Label::new("Preview").into_any_element())
9741 .opacity(if has_completion { 1.0 } else { 0.4 }),
9742 )
9743 })
9744 .into_any(),
9745 )
9746 }
9747
9748 fn render_edit_prediction_cursor_popover_preview(
9749 &self,
9750 completion: &EditPredictionState,
9751 cursor_point: Point,
9752 style: &EditorStyle,
9753 cx: &mut Context<Editor>,
9754 ) -> Option<Div> {
9755 use text::ToPoint as _;
9756
9757 fn render_relative_row_jump(
9758 prefix: impl Into<String>,
9759 current_row: u32,
9760 target_row: u32,
9761 ) -> Div {
9762 let (row_diff, arrow) = if target_row < current_row {
9763 (current_row - target_row, IconName::ArrowUp)
9764 } else {
9765 (target_row - current_row, IconName::ArrowDown)
9766 };
9767
9768 h_flex()
9769 .child(
9770 Label::new(format!("{}{}", prefix.into(), row_diff))
9771 .color(Color::Muted)
9772 .size(LabelSize::Small),
9773 )
9774 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9775 }
9776
9777 let supports_jump = self
9778 .edit_prediction_provider
9779 .as_ref()
9780 .map(|provider| provider.provider.supports_jump_to_edit())
9781 .unwrap_or(true);
9782
9783 match &completion.completion {
9784 EditPrediction::MoveWithin {
9785 target, snapshot, ..
9786 } => {
9787 if !supports_jump {
9788 return None;
9789 }
9790
9791 Some(
9792 h_flex()
9793 .px_2()
9794 .gap_2()
9795 .flex_1()
9796 .child(
9797 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9798 Icon::new(IconName::ZedPredictDown)
9799 } else {
9800 Icon::new(IconName::ZedPredictUp)
9801 },
9802 )
9803 .child(Label::new("Jump to Edit")),
9804 )
9805 }
9806 EditPrediction::MoveOutside { snapshot, .. } => {
9807 let file_name = snapshot
9808 .file()
9809 .map(|file| file.file_name(cx))
9810 .unwrap_or("untitled");
9811 Some(
9812 h_flex()
9813 .px_2()
9814 .gap_2()
9815 .flex_1()
9816 .child(Icon::new(IconName::ZedPredict))
9817 .child(Label::new(format!("Jump to {file_name}"))),
9818 )
9819 }
9820 EditPrediction::Edit {
9821 edits,
9822 edit_preview,
9823 snapshot,
9824 display_mode: _,
9825 } => {
9826 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9827
9828 let (highlighted_edits, has_more_lines) =
9829 if let Some(edit_preview) = edit_preview.as_ref() {
9830 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9831 .first_line_preview()
9832 } else {
9833 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9834 };
9835
9836 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9837 .with_default_highlights(&style.text, highlighted_edits.highlights);
9838
9839 let preview = h_flex()
9840 .gap_1()
9841 .min_w_16()
9842 .child(styled_text)
9843 .when(has_more_lines, |parent| parent.child("…"));
9844
9845 let left = if supports_jump && first_edit_row != cursor_point.row {
9846 render_relative_row_jump("", cursor_point.row, first_edit_row)
9847 .into_any_element()
9848 } else {
9849 let icon_name =
9850 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9851 Icon::new(icon_name).into_any_element()
9852 };
9853
9854 Some(
9855 h_flex()
9856 .h_full()
9857 .flex_1()
9858 .gap_2()
9859 .pr_1()
9860 .overflow_x_hidden()
9861 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9862 .child(left)
9863 .child(preview),
9864 )
9865 }
9866 }
9867 }
9868
9869 pub fn render_context_menu(
9870 &mut self,
9871 max_height_in_lines: u32,
9872 window: &mut Window,
9873 cx: &mut Context<Editor>,
9874 ) -> Option<AnyElement> {
9875 let menu = self.context_menu.borrow();
9876 let menu = menu.as_ref()?;
9877 if !menu.visible() {
9878 return None;
9879 };
9880 self.style
9881 .as_ref()
9882 .map(|style| menu.render(style, max_height_in_lines, window, cx))
9883 }
9884
9885 fn render_context_menu_aside(
9886 &mut self,
9887 max_size: Size<Pixels>,
9888 window: &mut Window,
9889 cx: &mut Context<Editor>,
9890 ) -> Option<AnyElement> {
9891 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9892 if menu.visible() {
9893 menu.render_aside(max_size, window, cx)
9894 } else {
9895 None
9896 }
9897 })
9898 }
9899
9900 fn hide_context_menu(
9901 &mut self,
9902 window: &mut Window,
9903 cx: &mut Context<Self>,
9904 ) -> Option<CodeContextMenu> {
9905 cx.notify();
9906 self.completion_tasks.clear();
9907 let context_menu = self.context_menu.borrow_mut().take();
9908 self.stale_edit_prediction_in_menu.take();
9909 self.update_visible_edit_prediction(window, cx);
9910 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9911 && let Some(completion_provider) = &self.completion_provider
9912 {
9913 completion_provider.selection_changed(None, window, cx);
9914 }
9915 context_menu
9916 }
9917
9918 fn show_snippet_choices(
9919 &mut self,
9920 choices: &Vec<String>,
9921 selection: Range<Anchor>,
9922 cx: &mut Context<Self>,
9923 ) {
9924 let Some((_, buffer, _)) = self
9925 .buffer()
9926 .read(cx)
9927 .excerpt_containing(selection.start, cx)
9928 else {
9929 return;
9930 };
9931 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9932 else {
9933 return;
9934 };
9935 if buffer != end_buffer {
9936 log::error!("expected anchor range to have matching buffer IDs");
9937 return;
9938 }
9939
9940 let id = post_inc(&mut self.next_completion_id);
9941 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9942 let mut context_menu = self.context_menu.borrow_mut();
9943 let old_menu = context_menu.take();
9944 *context_menu = Some(CodeContextMenu::Completions(
9945 CompletionsMenu::new_snippet_choices(
9946 id,
9947 true,
9948 choices,
9949 selection,
9950 buffer,
9951 old_menu.map(|menu| menu.primary_scroll_handle()),
9952 snippet_sort_order,
9953 ),
9954 ));
9955 }
9956
9957 pub fn insert_snippet(
9958 &mut self,
9959 insertion_ranges: &[Range<MultiBufferOffset>],
9960 snippet: Snippet,
9961 window: &mut Window,
9962 cx: &mut Context<Self>,
9963 ) -> Result<()> {
9964 struct Tabstop<T> {
9965 is_end_tabstop: bool,
9966 ranges: Vec<Range<T>>,
9967 choices: Option<Vec<String>>,
9968 }
9969
9970 let tabstops = self.buffer.update(cx, |buffer, cx| {
9971 let snippet_text: Arc<str> = snippet.text.clone().into();
9972 let edits = insertion_ranges
9973 .iter()
9974 .cloned()
9975 .map(|range| (range, snippet_text.clone()));
9976 let autoindent_mode = AutoindentMode::Block {
9977 original_indent_columns: Vec::new(),
9978 };
9979 buffer.edit(edits, Some(autoindent_mode), cx);
9980
9981 let snapshot = &*buffer.read(cx);
9982 let snippet = &snippet;
9983 snippet
9984 .tabstops
9985 .iter()
9986 .map(|tabstop| {
9987 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9988 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9989 });
9990 let mut tabstop_ranges = tabstop
9991 .ranges
9992 .iter()
9993 .flat_map(|tabstop_range| {
9994 let mut delta = 0_isize;
9995 insertion_ranges.iter().map(move |insertion_range| {
9996 let insertion_start = insertion_range.start + delta;
9997 delta += snippet.text.len() as isize
9998 - (insertion_range.end - insertion_range.start) as isize;
9999
10000 let start =
10001 (insertion_start + tabstop_range.start).min(snapshot.len());
10002 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10003 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10004 })
10005 })
10006 .collect::<Vec<_>>();
10007 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10008
10009 Tabstop {
10010 is_end_tabstop,
10011 ranges: tabstop_ranges,
10012 choices: tabstop.choices.clone(),
10013 }
10014 })
10015 .collect::<Vec<_>>()
10016 });
10017 if let Some(tabstop) = tabstops.first() {
10018 self.change_selections(Default::default(), window, cx, |s| {
10019 // Reverse order so that the first range is the newest created selection.
10020 // Completions will use it and autoscroll will prioritize it.
10021 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10022 });
10023
10024 if let Some(choices) = &tabstop.choices
10025 && let Some(selection) = tabstop.ranges.first()
10026 {
10027 self.show_snippet_choices(choices, selection.clone(), cx)
10028 }
10029
10030 // If we're already at the last tabstop and it's at the end of the snippet,
10031 // we're done, we don't need to keep the state around.
10032 if !tabstop.is_end_tabstop {
10033 let choices = tabstops
10034 .iter()
10035 .map(|tabstop| tabstop.choices.clone())
10036 .collect();
10037
10038 let ranges = tabstops
10039 .into_iter()
10040 .map(|tabstop| tabstop.ranges)
10041 .collect::<Vec<_>>();
10042
10043 self.snippet_stack.push(SnippetState {
10044 active_index: 0,
10045 ranges,
10046 choices,
10047 });
10048 }
10049
10050 // Check whether the just-entered snippet ends with an auto-closable bracket.
10051 if self.autoclose_regions.is_empty() {
10052 let snapshot = self.buffer.read(cx).snapshot(cx);
10053 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10054 let selection_head = selection.head();
10055 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10056 continue;
10057 };
10058
10059 let mut bracket_pair = None;
10060 let max_lookup_length = scope
10061 .brackets()
10062 .map(|(pair, _)| {
10063 pair.start
10064 .as_str()
10065 .chars()
10066 .count()
10067 .max(pair.end.as_str().chars().count())
10068 })
10069 .max();
10070 if let Some(max_lookup_length) = max_lookup_length {
10071 let next_text = snapshot
10072 .chars_at(selection_head)
10073 .take(max_lookup_length)
10074 .collect::<String>();
10075 let prev_text = snapshot
10076 .reversed_chars_at(selection_head)
10077 .take(max_lookup_length)
10078 .collect::<String>();
10079
10080 for (pair, enabled) in scope.brackets() {
10081 if enabled
10082 && pair.close
10083 && prev_text.starts_with(pair.start.as_str())
10084 && next_text.starts_with(pair.end.as_str())
10085 {
10086 bracket_pair = Some(pair.clone());
10087 break;
10088 }
10089 }
10090 }
10091
10092 if let Some(pair) = bracket_pair {
10093 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10094 let autoclose_enabled =
10095 self.use_autoclose && snapshot_settings.use_autoclose;
10096 if autoclose_enabled {
10097 let start = snapshot.anchor_after(selection_head);
10098 let end = snapshot.anchor_after(selection_head);
10099 self.autoclose_regions.push(AutocloseRegion {
10100 selection_id: selection.id,
10101 range: start..end,
10102 pair,
10103 });
10104 }
10105 }
10106 }
10107 }
10108 }
10109 Ok(())
10110 }
10111
10112 pub fn move_to_next_snippet_tabstop(
10113 &mut self,
10114 window: &mut Window,
10115 cx: &mut Context<Self>,
10116 ) -> bool {
10117 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10118 }
10119
10120 pub fn move_to_prev_snippet_tabstop(
10121 &mut self,
10122 window: &mut Window,
10123 cx: &mut Context<Self>,
10124 ) -> bool {
10125 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10126 }
10127
10128 pub fn move_to_snippet_tabstop(
10129 &mut self,
10130 bias: Bias,
10131 window: &mut Window,
10132 cx: &mut Context<Self>,
10133 ) -> bool {
10134 if let Some(mut snippet) = self.snippet_stack.pop() {
10135 match bias {
10136 Bias::Left => {
10137 if snippet.active_index > 0 {
10138 snippet.active_index -= 1;
10139 } else {
10140 self.snippet_stack.push(snippet);
10141 return false;
10142 }
10143 }
10144 Bias::Right => {
10145 if snippet.active_index + 1 < snippet.ranges.len() {
10146 snippet.active_index += 1;
10147 } else {
10148 self.snippet_stack.push(snippet);
10149 return false;
10150 }
10151 }
10152 }
10153 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10154 self.change_selections(Default::default(), window, cx, |s| {
10155 // Reverse order so that the first range is the newest created selection.
10156 // Completions will use it and autoscroll will prioritize it.
10157 s.select_ranges(current_ranges.iter().rev().cloned())
10158 });
10159
10160 if let Some(choices) = &snippet.choices[snippet.active_index]
10161 && let Some(selection) = current_ranges.first()
10162 {
10163 self.show_snippet_choices(choices, selection.clone(), cx);
10164 }
10165
10166 // If snippet state is not at the last tabstop, push it back on the stack
10167 if snippet.active_index + 1 < snippet.ranges.len() {
10168 self.snippet_stack.push(snippet);
10169 }
10170 return true;
10171 }
10172 }
10173
10174 false
10175 }
10176
10177 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10178 self.transact(window, cx, |this, window, cx| {
10179 this.select_all(&SelectAll, window, cx);
10180 this.insert("", window, cx);
10181 });
10182 }
10183
10184 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10185 if self.read_only(cx) {
10186 return;
10187 }
10188 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10189 self.transact(window, cx, |this, window, cx| {
10190 this.select_autoclose_pair(window, cx);
10191
10192 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10193
10194 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10195 if !this.linked_edit_ranges.is_empty() {
10196 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10197 let snapshot = this.buffer.read(cx).snapshot(cx);
10198
10199 for selection in selections.iter() {
10200 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10201 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10202 if selection_start.buffer_id != selection_end.buffer_id {
10203 continue;
10204 }
10205 if let Some(ranges) =
10206 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10207 {
10208 for (buffer, entries) in ranges {
10209 linked_ranges.entry(buffer).or_default().extend(entries);
10210 }
10211 }
10212 }
10213 }
10214
10215 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10216 for selection in &mut selections {
10217 if selection.is_empty() {
10218 let old_head = selection.head();
10219 let mut new_head =
10220 movement::left(&display_map, old_head.to_display_point(&display_map))
10221 .to_point(&display_map);
10222 if let Some((buffer, line_buffer_range)) = display_map
10223 .buffer_snapshot()
10224 .buffer_line_for_row(MultiBufferRow(old_head.row))
10225 {
10226 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10227 let indent_len = match indent_size.kind {
10228 IndentKind::Space => {
10229 buffer.settings_at(line_buffer_range.start, cx).tab_size
10230 }
10231 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10232 };
10233 if old_head.column <= indent_size.len && old_head.column > 0 {
10234 let indent_len = indent_len.get();
10235 new_head = cmp::min(
10236 new_head,
10237 MultiBufferPoint::new(
10238 old_head.row,
10239 ((old_head.column - 1) / indent_len) * indent_len,
10240 ),
10241 );
10242 }
10243 }
10244
10245 selection.set_head(new_head, SelectionGoal::None);
10246 }
10247 }
10248
10249 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10250 this.insert("", window, cx);
10251 let empty_str: Arc<str> = Arc::from("");
10252 for (buffer, edits) in linked_ranges {
10253 let snapshot = buffer.read(cx).snapshot();
10254 use text::ToPoint as TP;
10255
10256 let edits = edits
10257 .into_iter()
10258 .map(|range| {
10259 let end_point = TP::to_point(&range.end, &snapshot);
10260 let mut start_point = TP::to_point(&range.start, &snapshot);
10261
10262 if end_point == start_point {
10263 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10264 .saturating_sub(1);
10265 start_point =
10266 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10267 };
10268
10269 (start_point..end_point, empty_str.clone())
10270 })
10271 .sorted_by_key(|(range, _)| range.start)
10272 .collect::<Vec<_>>();
10273 buffer.update(cx, |this, cx| {
10274 this.edit(edits, None, cx);
10275 })
10276 }
10277 this.refresh_edit_prediction(true, false, window, cx);
10278 refresh_linked_ranges(this, window, cx);
10279 });
10280 }
10281
10282 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10283 if self.read_only(cx) {
10284 return;
10285 }
10286 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10287 self.transact(window, cx, |this, window, cx| {
10288 this.change_selections(Default::default(), window, cx, |s| {
10289 s.move_with(|map, selection| {
10290 if selection.is_empty() {
10291 let cursor = movement::right(map, selection.head());
10292 selection.end = cursor;
10293 selection.reversed = true;
10294 selection.goal = SelectionGoal::None;
10295 }
10296 })
10297 });
10298 this.insert("", window, cx);
10299 this.refresh_edit_prediction(true, false, window, cx);
10300 });
10301 }
10302
10303 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10304 if self.mode.is_single_line() {
10305 cx.propagate();
10306 return;
10307 }
10308
10309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10310 if self.move_to_prev_snippet_tabstop(window, cx) {
10311 return;
10312 }
10313 self.outdent(&Outdent, window, cx);
10314 }
10315
10316 pub fn next_snippet_tabstop(
10317 &mut self,
10318 _: &NextSnippetTabstop,
10319 window: &mut Window,
10320 cx: &mut Context<Self>,
10321 ) {
10322 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10323 cx.propagate();
10324 return;
10325 }
10326
10327 if self.move_to_next_snippet_tabstop(window, cx) {
10328 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10329 return;
10330 }
10331 cx.propagate();
10332 }
10333
10334 pub fn previous_snippet_tabstop(
10335 &mut self,
10336 _: &PreviousSnippetTabstop,
10337 window: &mut Window,
10338 cx: &mut Context<Self>,
10339 ) {
10340 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10341 cx.propagate();
10342 return;
10343 }
10344
10345 if self.move_to_prev_snippet_tabstop(window, cx) {
10346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10347 return;
10348 }
10349 cx.propagate();
10350 }
10351
10352 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10353 if self.mode.is_single_line() {
10354 cx.propagate();
10355 return;
10356 }
10357
10358 if self.move_to_next_snippet_tabstop(window, cx) {
10359 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10360 return;
10361 }
10362 if self.read_only(cx) {
10363 return;
10364 }
10365 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10366 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10367 let buffer = self.buffer.read(cx);
10368 let snapshot = buffer.snapshot(cx);
10369 let rows_iter = selections.iter().map(|s| s.head().row);
10370 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10371
10372 let has_some_cursor_in_whitespace = selections
10373 .iter()
10374 .filter(|selection| selection.is_empty())
10375 .any(|selection| {
10376 let cursor = selection.head();
10377 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10378 cursor.column < current_indent.len
10379 });
10380
10381 let mut edits = Vec::new();
10382 let mut prev_edited_row = 0;
10383 let mut row_delta = 0;
10384 for selection in &mut selections {
10385 if selection.start.row != prev_edited_row {
10386 row_delta = 0;
10387 }
10388 prev_edited_row = selection.end.row;
10389
10390 // If the selection is non-empty, then increase the indentation of the selected lines.
10391 if !selection.is_empty() {
10392 row_delta =
10393 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10394 continue;
10395 }
10396
10397 let cursor = selection.head();
10398 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10399 if let Some(suggested_indent) =
10400 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10401 {
10402 // Don't do anything if already at suggested indent
10403 // and there is any other cursor which is not
10404 if has_some_cursor_in_whitespace
10405 && cursor.column == current_indent.len
10406 && current_indent.len == suggested_indent.len
10407 {
10408 continue;
10409 }
10410
10411 // Adjust line and move cursor to suggested indent
10412 // if cursor is not at suggested indent
10413 if cursor.column < suggested_indent.len
10414 && cursor.column <= current_indent.len
10415 && current_indent.len <= suggested_indent.len
10416 {
10417 selection.start = Point::new(cursor.row, suggested_indent.len);
10418 selection.end = selection.start;
10419 if row_delta == 0 {
10420 edits.extend(Buffer::edit_for_indent_size_adjustment(
10421 cursor.row,
10422 current_indent,
10423 suggested_indent,
10424 ));
10425 row_delta = suggested_indent.len - current_indent.len;
10426 }
10427 continue;
10428 }
10429
10430 // If current indent is more than suggested indent
10431 // only move cursor to current indent and skip indent
10432 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10433 selection.start = Point::new(cursor.row, current_indent.len);
10434 selection.end = selection.start;
10435 continue;
10436 }
10437 }
10438
10439 // Otherwise, insert a hard or soft tab.
10440 let settings = buffer.language_settings_at(cursor, cx);
10441 let tab_size = if settings.hard_tabs {
10442 IndentSize::tab()
10443 } else {
10444 let tab_size = settings.tab_size.get();
10445 let indent_remainder = snapshot
10446 .text_for_range(Point::new(cursor.row, 0)..cursor)
10447 .flat_map(str::chars)
10448 .fold(row_delta % tab_size, |counter: u32, c| {
10449 if c == '\t' {
10450 0
10451 } else {
10452 (counter + 1) % tab_size
10453 }
10454 });
10455
10456 let chars_to_next_tab_stop = tab_size - indent_remainder;
10457 IndentSize::spaces(chars_to_next_tab_stop)
10458 };
10459 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10460 selection.end = selection.start;
10461 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10462 row_delta += tab_size.len;
10463 }
10464
10465 self.transact(window, cx, |this, window, cx| {
10466 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10467 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10468 this.refresh_edit_prediction(true, false, window, cx);
10469 });
10470 }
10471
10472 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10473 if self.read_only(cx) {
10474 return;
10475 }
10476 if self.mode.is_single_line() {
10477 cx.propagate();
10478 return;
10479 }
10480
10481 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10482 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10483 let mut prev_edited_row = 0;
10484 let mut row_delta = 0;
10485 let mut edits = Vec::new();
10486 let buffer = self.buffer.read(cx);
10487 let snapshot = buffer.snapshot(cx);
10488 for selection in &mut selections {
10489 if selection.start.row != prev_edited_row {
10490 row_delta = 0;
10491 }
10492 prev_edited_row = selection.end.row;
10493
10494 row_delta =
10495 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10496 }
10497
10498 self.transact(window, cx, |this, window, cx| {
10499 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10500 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10501 });
10502 }
10503
10504 fn indent_selection(
10505 buffer: &MultiBuffer,
10506 snapshot: &MultiBufferSnapshot,
10507 selection: &mut Selection<Point>,
10508 edits: &mut Vec<(Range<Point>, String)>,
10509 delta_for_start_row: u32,
10510 cx: &App,
10511 ) -> u32 {
10512 let settings = buffer.language_settings_at(selection.start, cx);
10513 let tab_size = settings.tab_size.get();
10514 let indent_kind = if settings.hard_tabs {
10515 IndentKind::Tab
10516 } else {
10517 IndentKind::Space
10518 };
10519 let mut start_row = selection.start.row;
10520 let mut end_row = selection.end.row + 1;
10521
10522 // If a selection ends at the beginning of a line, don't indent
10523 // that last line.
10524 if selection.end.column == 0 && selection.end.row > selection.start.row {
10525 end_row -= 1;
10526 }
10527
10528 // Avoid re-indenting a row that has already been indented by a
10529 // previous selection, but still update this selection's column
10530 // to reflect that indentation.
10531 if delta_for_start_row > 0 {
10532 start_row += 1;
10533 selection.start.column += delta_for_start_row;
10534 if selection.end.row == selection.start.row {
10535 selection.end.column += delta_for_start_row;
10536 }
10537 }
10538
10539 let mut delta_for_end_row = 0;
10540 let has_multiple_rows = start_row + 1 != end_row;
10541 for row in start_row..end_row {
10542 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10543 let indent_delta = match (current_indent.kind, indent_kind) {
10544 (IndentKind::Space, IndentKind::Space) => {
10545 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10546 IndentSize::spaces(columns_to_next_tab_stop)
10547 }
10548 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10549 (_, IndentKind::Tab) => IndentSize::tab(),
10550 };
10551
10552 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10553 0
10554 } else {
10555 selection.start.column
10556 };
10557 let row_start = Point::new(row, start);
10558 edits.push((
10559 row_start..row_start,
10560 indent_delta.chars().collect::<String>(),
10561 ));
10562
10563 // Update this selection's endpoints to reflect the indentation.
10564 if row == selection.start.row {
10565 selection.start.column += indent_delta.len;
10566 }
10567 if row == selection.end.row {
10568 selection.end.column += indent_delta.len;
10569 delta_for_end_row = indent_delta.len;
10570 }
10571 }
10572
10573 if selection.start.row == selection.end.row {
10574 delta_for_start_row + delta_for_end_row
10575 } else {
10576 delta_for_end_row
10577 }
10578 }
10579
10580 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10581 if self.read_only(cx) {
10582 return;
10583 }
10584 if self.mode.is_single_line() {
10585 cx.propagate();
10586 return;
10587 }
10588
10589 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10590 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10591 let selections = self.selections.all::<Point>(&display_map);
10592 let mut deletion_ranges = Vec::new();
10593 let mut last_outdent = None;
10594 {
10595 let buffer = self.buffer.read(cx);
10596 let snapshot = buffer.snapshot(cx);
10597 for selection in &selections {
10598 let settings = buffer.language_settings_at(selection.start, cx);
10599 let tab_size = settings.tab_size.get();
10600 let mut rows = selection.spanned_rows(false, &display_map);
10601
10602 // Avoid re-outdenting a row that has already been outdented by a
10603 // previous selection.
10604 if let Some(last_row) = last_outdent
10605 && last_row == rows.start
10606 {
10607 rows.start = rows.start.next_row();
10608 }
10609 let has_multiple_rows = rows.len() > 1;
10610 for row in rows.iter_rows() {
10611 let indent_size = snapshot.indent_size_for_line(row);
10612 if indent_size.len > 0 {
10613 let deletion_len = match indent_size.kind {
10614 IndentKind::Space => {
10615 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10616 if columns_to_prev_tab_stop == 0 {
10617 tab_size
10618 } else {
10619 columns_to_prev_tab_stop
10620 }
10621 }
10622 IndentKind::Tab => 1,
10623 };
10624 let start = if has_multiple_rows
10625 || deletion_len > selection.start.column
10626 || indent_size.len < selection.start.column
10627 {
10628 0
10629 } else {
10630 selection.start.column - deletion_len
10631 };
10632 deletion_ranges.push(
10633 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10634 );
10635 last_outdent = Some(row);
10636 }
10637 }
10638 }
10639 }
10640
10641 self.transact(window, cx, |this, window, cx| {
10642 this.buffer.update(cx, |buffer, cx| {
10643 let empty_str: Arc<str> = Arc::default();
10644 buffer.edit(
10645 deletion_ranges
10646 .into_iter()
10647 .map(|range| (range, empty_str.clone())),
10648 None,
10649 cx,
10650 );
10651 });
10652 let selections = this
10653 .selections
10654 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10655 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10656 });
10657 }
10658
10659 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10660 if self.read_only(cx) {
10661 return;
10662 }
10663 if self.mode.is_single_line() {
10664 cx.propagate();
10665 return;
10666 }
10667
10668 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10669 let selections = self
10670 .selections
10671 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10672 .into_iter()
10673 .map(|s| s.range());
10674
10675 self.transact(window, cx, |this, window, cx| {
10676 this.buffer.update(cx, |buffer, cx| {
10677 buffer.autoindent_ranges(selections, cx);
10678 });
10679 let selections = this
10680 .selections
10681 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10682 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10683 });
10684 }
10685
10686 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10687 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10688 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10689 let selections = self.selections.all::<Point>(&display_map);
10690
10691 let mut new_cursors = Vec::new();
10692 let mut edit_ranges = Vec::new();
10693 let mut selections = selections.iter().peekable();
10694 while let Some(selection) = selections.next() {
10695 let mut rows = selection.spanned_rows(false, &display_map);
10696
10697 // Accumulate contiguous regions of rows that we want to delete.
10698 while let Some(next_selection) = selections.peek() {
10699 let next_rows = next_selection.spanned_rows(false, &display_map);
10700 if next_rows.start <= rows.end {
10701 rows.end = next_rows.end;
10702 selections.next().unwrap();
10703 } else {
10704 break;
10705 }
10706 }
10707
10708 let buffer = display_map.buffer_snapshot();
10709 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10710 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10711 // If there's a line after the range, delete the \n from the end of the row range
10712 (
10713 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10714 rows.end,
10715 )
10716 } else {
10717 // If there isn't a line after the range, delete the \n from the line before the
10718 // start of the row range
10719 edit_start = edit_start.saturating_sub_usize(1);
10720 (buffer.len(), rows.start.previous_row())
10721 };
10722
10723 let text_layout_details = self.text_layout_details(window);
10724 let x = display_map.x_for_display_point(
10725 selection.head().to_display_point(&display_map),
10726 &text_layout_details,
10727 );
10728 let row = Point::new(target_row.0, 0)
10729 .to_display_point(&display_map)
10730 .row();
10731 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10732
10733 new_cursors.push((
10734 selection.id,
10735 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10736 SelectionGoal::None,
10737 ));
10738 edit_ranges.push(edit_start..edit_end);
10739 }
10740
10741 self.transact(window, cx, |this, window, cx| {
10742 let buffer = this.buffer.update(cx, |buffer, cx| {
10743 let empty_str: Arc<str> = Arc::default();
10744 buffer.edit(
10745 edit_ranges
10746 .into_iter()
10747 .map(|range| (range, empty_str.clone())),
10748 None,
10749 cx,
10750 );
10751 buffer.snapshot(cx)
10752 });
10753 let new_selections = new_cursors
10754 .into_iter()
10755 .map(|(id, cursor, goal)| {
10756 let cursor = cursor.to_point(&buffer);
10757 Selection {
10758 id,
10759 start: cursor,
10760 end: cursor,
10761 reversed: false,
10762 goal,
10763 }
10764 })
10765 .collect();
10766
10767 this.change_selections(Default::default(), window, cx, |s| {
10768 s.select(new_selections);
10769 });
10770 });
10771 }
10772
10773 pub fn join_lines_impl(
10774 &mut self,
10775 insert_whitespace: bool,
10776 window: &mut Window,
10777 cx: &mut Context<Self>,
10778 ) {
10779 if self.read_only(cx) {
10780 return;
10781 }
10782 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10783 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10784 let start = MultiBufferRow(selection.start.row);
10785 // Treat single line selections as if they include the next line. Otherwise this action
10786 // would do nothing for single line selections individual cursors.
10787 let end = if selection.start.row == selection.end.row {
10788 MultiBufferRow(selection.start.row + 1)
10789 } else {
10790 MultiBufferRow(selection.end.row)
10791 };
10792
10793 if let Some(last_row_range) = row_ranges.last_mut()
10794 && start <= last_row_range.end
10795 {
10796 last_row_range.end = end;
10797 continue;
10798 }
10799 row_ranges.push(start..end);
10800 }
10801
10802 let snapshot = self.buffer.read(cx).snapshot(cx);
10803 let mut cursor_positions = Vec::new();
10804 for row_range in &row_ranges {
10805 let anchor = snapshot.anchor_before(Point::new(
10806 row_range.end.previous_row().0,
10807 snapshot.line_len(row_range.end.previous_row()),
10808 ));
10809 cursor_positions.push(anchor..anchor);
10810 }
10811
10812 self.transact(window, cx, |this, window, cx| {
10813 for row_range in row_ranges.into_iter().rev() {
10814 for row in row_range.iter_rows().rev() {
10815 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10816 let next_line_row = row.next_row();
10817 let indent = snapshot.indent_size_for_line(next_line_row);
10818 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10819
10820 let replace =
10821 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10822 " "
10823 } else {
10824 ""
10825 };
10826
10827 this.buffer.update(cx, |buffer, cx| {
10828 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10829 });
10830 }
10831 }
10832
10833 this.change_selections(Default::default(), window, cx, |s| {
10834 s.select_anchor_ranges(cursor_positions)
10835 });
10836 });
10837 }
10838
10839 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10840 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10841 self.join_lines_impl(true, window, cx);
10842 }
10843
10844 pub fn sort_lines_case_sensitive(
10845 &mut self,
10846 _: &SortLinesCaseSensitive,
10847 window: &mut Window,
10848 cx: &mut Context<Self>,
10849 ) {
10850 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10851 }
10852
10853 pub fn sort_lines_by_length(
10854 &mut self,
10855 _: &SortLinesByLength,
10856 window: &mut Window,
10857 cx: &mut Context<Self>,
10858 ) {
10859 self.manipulate_immutable_lines(window, cx, |lines| {
10860 lines.sort_by_key(|&line| line.chars().count())
10861 })
10862 }
10863
10864 pub fn sort_lines_case_insensitive(
10865 &mut self,
10866 _: &SortLinesCaseInsensitive,
10867 window: &mut Window,
10868 cx: &mut Context<Self>,
10869 ) {
10870 self.manipulate_immutable_lines(window, cx, |lines| {
10871 lines.sort_by_key(|line| line.to_lowercase())
10872 })
10873 }
10874
10875 pub fn unique_lines_case_insensitive(
10876 &mut self,
10877 _: &UniqueLinesCaseInsensitive,
10878 window: &mut Window,
10879 cx: &mut Context<Self>,
10880 ) {
10881 self.manipulate_immutable_lines(window, cx, |lines| {
10882 let mut seen = HashSet::default();
10883 lines.retain(|line| seen.insert(line.to_lowercase()));
10884 })
10885 }
10886
10887 pub fn unique_lines_case_sensitive(
10888 &mut self,
10889 _: &UniqueLinesCaseSensitive,
10890 window: &mut Window,
10891 cx: &mut Context<Self>,
10892 ) {
10893 self.manipulate_immutable_lines(window, cx, |lines| {
10894 let mut seen = HashSet::default();
10895 lines.retain(|line| seen.insert(*line));
10896 })
10897 }
10898
10899 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10900 let snapshot = self.buffer.read(cx).snapshot(cx);
10901 for selection in self.selections.disjoint_anchors_arc().iter() {
10902 if snapshot
10903 .language_at(selection.start)
10904 .and_then(|lang| lang.config().wrap_characters.as_ref())
10905 .is_some()
10906 {
10907 return true;
10908 }
10909 }
10910 false
10911 }
10912
10913 fn wrap_selections_in_tag(
10914 &mut self,
10915 _: &WrapSelectionsInTag,
10916 window: &mut Window,
10917 cx: &mut Context<Self>,
10918 ) {
10919 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10920
10921 let snapshot = self.buffer.read(cx).snapshot(cx);
10922
10923 let mut edits = Vec::new();
10924 let mut boundaries = Vec::new();
10925
10926 for selection in self
10927 .selections
10928 .all_adjusted(&self.display_snapshot(cx))
10929 .iter()
10930 {
10931 let Some(wrap_config) = snapshot
10932 .language_at(selection.start)
10933 .and_then(|lang| lang.config().wrap_characters.clone())
10934 else {
10935 continue;
10936 };
10937
10938 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10939 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10940
10941 let start_before = snapshot.anchor_before(selection.start);
10942 let end_after = snapshot.anchor_after(selection.end);
10943
10944 edits.push((start_before..start_before, open_tag));
10945 edits.push((end_after..end_after, close_tag));
10946
10947 boundaries.push((
10948 start_before,
10949 end_after,
10950 wrap_config.start_prefix.len(),
10951 wrap_config.end_suffix.len(),
10952 ));
10953 }
10954
10955 if edits.is_empty() {
10956 return;
10957 }
10958
10959 self.transact(window, cx, |this, window, cx| {
10960 let buffer = this.buffer.update(cx, |buffer, cx| {
10961 buffer.edit(edits, None, cx);
10962 buffer.snapshot(cx)
10963 });
10964
10965 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10966 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10967 boundaries.into_iter()
10968 {
10969 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10970 let close_offset = end_after
10971 .to_offset(&buffer)
10972 .saturating_sub_usize(end_suffix_len);
10973 new_selections.push(open_offset..open_offset);
10974 new_selections.push(close_offset..close_offset);
10975 }
10976
10977 this.change_selections(Default::default(), window, cx, |s| {
10978 s.select_ranges(new_selections);
10979 });
10980
10981 this.request_autoscroll(Autoscroll::fit(), cx);
10982 });
10983 }
10984
10985 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10986 let Some(project) = self.project.clone() else {
10987 return;
10988 };
10989 self.reload(project, window, cx)
10990 .detach_and_notify_err(window, cx);
10991 }
10992
10993 pub fn restore_file(
10994 &mut self,
10995 _: &::git::RestoreFile,
10996 window: &mut Window,
10997 cx: &mut Context<Self>,
10998 ) {
10999 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11000 let mut buffer_ids = HashSet::default();
11001 let snapshot = self.buffer().read(cx).snapshot(cx);
11002 for selection in self
11003 .selections
11004 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11005 {
11006 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11007 }
11008
11009 let buffer = self.buffer().read(cx);
11010 let ranges = buffer_ids
11011 .into_iter()
11012 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11013 .collect::<Vec<_>>();
11014
11015 self.restore_hunks_in_ranges(ranges, window, cx);
11016 }
11017
11018 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11019 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11020 let selections = self
11021 .selections
11022 .all(&self.display_snapshot(cx))
11023 .into_iter()
11024 .map(|s| s.range())
11025 .collect();
11026 self.restore_hunks_in_ranges(selections, window, cx);
11027 }
11028
11029 pub fn restore_hunks_in_ranges(
11030 &mut self,
11031 ranges: Vec<Range<Point>>,
11032 window: &mut Window,
11033 cx: &mut Context<Editor>,
11034 ) {
11035 let mut revert_changes = HashMap::default();
11036 let chunk_by = self
11037 .snapshot(window, cx)
11038 .hunks_for_ranges(ranges)
11039 .into_iter()
11040 .chunk_by(|hunk| hunk.buffer_id);
11041 for (buffer_id, hunks) in &chunk_by {
11042 let hunks = hunks.collect::<Vec<_>>();
11043 for hunk in &hunks {
11044 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11045 }
11046 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11047 }
11048 drop(chunk_by);
11049 if !revert_changes.is_empty() {
11050 self.transact(window, cx, |editor, window, cx| {
11051 editor.restore(revert_changes, window, cx);
11052 });
11053 }
11054 }
11055
11056 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11057 if let Some(status) = self
11058 .addons
11059 .iter()
11060 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11061 {
11062 return Some(status);
11063 }
11064 self.project
11065 .as_ref()?
11066 .read(cx)
11067 .status_for_buffer_id(buffer_id, cx)
11068 }
11069
11070 pub fn open_active_item_in_terminal(
11071 &mut self,
11072 _: &OpenInTerminal,
11073 window: &mut Window,
11074 cx: &mut Context<Self>,
11075 ) {
11076 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11077 let project_path = buffer.read(cx).project_path(cx)?;
11078 let project = self.project()?.read(cx);
11079 let entry = project.entry_for_path(&project_path, cx)?;
11080 let parent = match &entry.canonical_path {
11081 Some(canonical_path) => canonical_path.to_path_buf(),
11082 None => project.absolute_path(&project_path, cx)?,
11083 }
11084 .parent()?
11085 .to_path_buf();
11086 Some(parent)
11087 }) {
11088 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11089 }
11090 }
11091
11092 fn set_breakpoint_context_menu(
11093 &mut self,
11094 display_row: DisplayRow,
11095 position: Option<Anchor>,
11096 clicked_point: gpui::Point<Pixels>,
11097 window: &mut Window,
11098 cx: &mut Context<Self>,
11099 ) {
11100 let source = self
11101 .buffer
11102 .read(cx)
11103 .snapshot(cx)
11104 .anchor_before(Point::new(display_row.0, 0u32));
11105
11106 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11107
11108 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11109 self,
11110 source,
11111 clicked_point,
11112 context_menu,
11113 window,
11114 cx,
11115 );
11116 }
11117
11118 fn add_edit_breakpoint_block(
11119 &mut self,
11120 anchor: Anchor,
11121 breakpoint: &Breakpoint,
11122 edit_action: BreakpointPromptEditAction,
11123 window: &mut Window,
11124 cx: &mut Context<Self>,
11125 ) {
11126 let weak_editor = cx.weak_entity();
11127 let bp_prompt = cx.new(|cx| {
11128 BreakpointPromptEditor::new(
11129 weak_editor,
11130 anchor,
11131 breakpoint.clone(),
11132 edit_action,
11133 window,
11134 cx,
11135 )
11136 });
11137
11138 let height = bp_prompt.update(cx, |this, cx| {
11139 this.prompt
11140 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11141 });
11142 let cloned_prompt = bp_prompt.clone();
11143 let blocks = vec![BlockProperties {
11144 style: BlockStyle::Sticky,
11145 placement: BlockPlacement::Above(anchor),
11146 height: Some(height),
11147 render: Arc::new(move |cx| {
11148 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11149 cloned_prompt.clone().into_any_element()
11150 }),
11151 priority: 0,
11152 }];
11153
11154 let focus_handle = bp_prompt.focus_handle(cx);
11155 window.focus(&focus_handle, cx);
11156
11157 let block_ids = self.insert_blocks(blocks, None, cx);
11158 bp_prompt.update(cx, |prompt, _| {
11159 prompt.add_block_ids(block_ids);
11160 });
11161 }
11162
11163 pub(crate) fn breakpoint_at_row(
11164 &self,
11165 row: u32,
11166 window: &mut Window,
11167 cx: &mut Context<Self>,
11168 ) -> Option<(Anchor, Breakpoint)> {
11169 let snapshot = self.snapshot(window, cx);
11170 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11171
11172 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11173 }
11174
11175 pub(crate) fn breakpoint_at_anchor(
11176 &self,
11177 breakpoint_position: Anchor,
11178 snapshot: &EditorSnapshot,
11179 cx: &mut Context<Self>,
11180 ) -> Option<(Anchor, Breakpoint)> {
11181 let buffer = self
11182 .buffer
11183 .read(cx)
11184 .buffer_for_anchor(breakpoint_position, cx)?;
11185
11186 let enclosing_excerpt = breakpoint_position.excerpt_id;
11187 let buffer_snapshot = buffer.read(cx).snapshot();
11188
11189 let row = buffer_snapshot
11190 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11191 .row;
11192
11193 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11194 let anchor_end = snapshot
11195 .buffer_snapshot()
11196 .anchor_after(Point::new(row, line_len));
11197
11198 self.breakpoint_store
11199 .as_ref()?
11200 .read_with(cx, |breakpoint_store, cx| {
11201 breakpoint_store
11202 .breakpoints(
11203 &buffer,
11204 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11205 &buffer_snapshot,
11206 cx,
11207 )
11208 .next()
11209 .and_then(|(bp, _)| {
11210 let breakpoint_row = buffer_snapshot
11211 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11212 .row;
11213
11214 if breakpoint_row == row {
11215 snapshot
11216 .buffer_snapshot()
11217 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11218 .map(|position| (position, bp.bp.clone()))
11219 } else {
11220 None
11221 }
11222 })
11223 })
11224 }
11225
11226 pub fn edit_log_breakpoint(
11227 &mut self,
11228 _: &EditLogBreakpoint,
11229 window: &mut Window,
11230 cx: &mut Context<Self>,
11231 ) {
11232 if self.breakpoint_store.is_none() {
11233 return;
11234 }
11235
11236 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11237 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11238 message: None,
11239 state: BreakpointState::Enabled,
11240 condition: None,
11241 hit_condition: None,
11242 });
11243
11244 self.add_edit_breakpoint_block(
11245 anchor,
11246 &breakpoint,
11247 BreakpointPromptEditAction::Log,
11248 window,
11249 cx,
11250 );
11251 }
11252 }
11253
11254 fn breakpoints_at_cursors(
11255 &self,
11256 window: &mut Window,
11257 cx: &mut Context<Self>,
11258 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11259 let snapshot = self.snapshot(window, cx);
11260 let cursors = self
11261 .selections
11262 .disjoint_anchors_arc()
11263 .iter()
11264 .map(|selection| {
11265 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11266
11267 let breakpoint_position = self
11268 .breakpoint_at_row(cursor_position.row, window, cx)
11269 .map(|bp| bp.0)
11270 .unwrap_or_else(|| {
11271 snapshot
11272 .display_snapshot
11273 .buffer_snapshot()
11274 .anchor_after(Point::new(cursor_position.row, 0))
11275 });
11276
11277 let breakpoint = self
11278 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11279 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11280
11281 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11282 })
11283 // 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.
11284 .collect::<HashMap<Anchor, _>>();
11285
11286 cursors.into_iter().collect()
11287 }
11288
11289 pub fn enable_breakpoint(
11290 &mut self,
11291 _: &crate::actions::EnableBreakpoint,
11292 window: &mut Window,
11293 cx: &mut Context<Self>,
11294 ) {
11295 if self.breakpoint_store.is_none() {
11296 return;
11297 }
11298
11299 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11300 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11301 continue;
11302 };
11303 self.edit_breakpoint_at_anchor(
11304 anchor,
11305 breakpoint,
11306 BreakpointEditAction::InvertState,
11307 cx,
11308 );
11309 }
11310 }
11311
11312 pub fn disable_breakpoint(
11313 &mut self,
11314 _: &crate::actions::DisableBreakpoint,
11315 window: &mut Window,
11316 cx: &mut Context<Self>,
11317 ) {
11318 if self.breakpoint_store.is_none() {
11319 return;
11320 }
11321
11322 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11323 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11324 continue;
11325 };
11326 self.edit_breakpoint_at_anchor(
11327 anchor,
11328 breakpoint,
11329 BreakpointEditAction::InvertState,
11330 cx,
11331 );
11332 }
11333 }
11334
11335 pub fn toggle_breakpoint(
11336 &mut self,
11337 _: &crate::actions::ToggleBreakpoint,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 ) {
11341 if self.breakpoint_store.is_none() {
11342 return;
11343 }
11344
11345 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11346 if let Some(breakpoint) = breakpoint {
11347 self.edit_breakpoint_at_anchor(
11348 anchor,
11349 breakpoint,
11350 BreakpointEditAction::Toggle,
11351 cx,
11352 );
11353 } else {
11354 self.edit_breakpoint_at_anchor(
11355 anchor,
11356 Breakpoint::new_standard(),
11357 BreakpointEditAction::Toggle,
11358 cx,
11359 );
11360 }
11361 }
11362 }
11363
11364 pub fn edit_breakpoint_at_anchor(
11365 &mut self,
11366 breakpoint_position: Anchor,
11367 breakpoint: Breakpoint,
11368 edit_action: BreakpointEditAction,
11369 cx: &mut Context<Self>,
11370 ) {
11371 let Some(breakpoint_store) = &self.breakpoint_store else {
11372 return;
11373 };
11374
11375 let Some(buffer) = self
11376 .buffer
11377 .read(cx)
11378 .buffer_for_anchor(breakpoint_position, cx)
11379 else {
11380 return;
11381 };
11382
11383 breakpoint_store.update(cx, |breakpoint_store, cx| {
11384 breakpoint_store.toggle_breakpoint(
11385 buffer,
11386 BreakpointWithPosition {
11387 position: breakpoint_position.text_anchor,
11388 bp: breakpoint,
11389 },
11390 edit_action,
11391 cx,
11392 );
11393 });
11394
11395 cx.notify();
11396 }
11397
11398 #[cfg(any(test, feature = "test-support"))]
11399 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11400 self.breakpoint_store.clone()
11401 }
11402
11403 pub fn prepare_restore_change(
11404 &self,
11405 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11406 hunk: &MultiBufferDiffHunk,
11407 cx: &mut App,
11408 ) -> Option<()> {
11409 if hunk.is_created_file() {
11410 return None;
11411 }
11412 let buffer = self.buffer.read(cx);
11413 let diff = buffer.diff_for(hunk.buffer_id)?;
11414 let buffer = buffer.buffer(hunk.buffer_id)?;
11415 let buffer = buffer.read(cx);
11416 let original_text = diff
11417 .read(cx)
11418 .base_text()
11419 .as_rope()
11420 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11421 let buffer_snapshot = buffer.snapshot();
11422 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11423 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11424 probe
11425 .0
11426 .start
11427 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11428 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11429 }) {
11430 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11431 Some(())
11432 } else {
11433 None
11434 }
11435 }
11436
11437 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11438 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11439 }
11440
11441 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11442 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11443 }
11444
11445 pub fn rotate_selections_forward(
11446 &mut self,
11447 _: &RotateSelectionsForward,
11448 window: &mut Window,
11449 cx: &mut Context<Self>,
11450 ) {
11451 self.rotate_selections(window, cx, false)
11452 }
11453
11454 pub fn rotate_selections_backward(
11455 &mut self,
11456 _: &RotateSelectionsBackward,
11457 window: &mut Window,
11458 cx: &mut Context<Self>,
11459 ) {
11460 self.rotate_selections(window, cx, true)
11461 }
11462
11463 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11465 let display_snapshot = self.display_snapshot(cx);
11466 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11467
11468 if selections.len() < 2 {
11469 return;
11470 }
11471
11472 let (edits, new_selections) = {
11473 let buffer = self.buffer.read(cx).read(cx);
11474 let has_selections = selections.iter().any(|s| !s.is_empty());
11475 if has_selections {
11476 let mut selected_texts: Vec<String> = selections
11477 .iter()
11478 .map(|selection| {
11479 buffer
11480 .text_for_range(selection.start..selection.end)
11481 .collect()
11482 })
11483 .collect();
11484
11485 if reverse {
11486 selected_texts.rotate_left(1);
11487 } else {
11488 selected_texts.rotate_right(1);
11489 }
11490
11491 let mut offset_delta: i64 = 0;
11492 let mut new_selections = Vec::new();
11493 let edits: Vec<_> = selections
11494 .iter()
11495 .zip(selected_texts.iter())
11496 .map(|(selection, new_text)| {
11497 let old_len = (selection.end.0 - selection.start.0) as i64;
11498 let new_len = new_text.len() as i64;
11499 let adjusted_start =
11500 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11501 let adjusted_end =
11502 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11503
11504 new_selections.push(Selection {
11505 id: selection.id,
11506 start: adjusted_start,
11507 end: adjusted_end,
11508 reversed: selection.reversed,
11509 goal: selection.goal,
11510 });
11511
11512 offset_delta += new_len - old_len;
11513 (selection.start..selection.end, new_text.clone())
11514 })
11515 .collect();
11516 (edits, new_selections)
11517 } else {
11518 let mut all_rows: Vec<u32> = selections
11519 .iter()
11520 .map(|selection| buffer.offset_to_point(selection.start).row)
11521 .collect();
11522 all_rows.sort_unstable();
11523 all_rows.dedup();
11524
11525 if all_rows.len() < 2 {
11526 return;
11527 }
11528
11529 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11530 .iter()
11531 .map(|&row| {
11532 let start = Point::new(row, 0);
11533 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11534 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11535 })
11536 .collect();
11537
11538 let mut line_texts: Vec<String> = line_ranges
11539 .iter()
11540 .map(|range| buffer.text_for_range(range.clone()).collect())
11541 .collect();
11542
11543 if reverse {
11544 line_texts.rotate_left(1);
11545 } else {
11546 line_texts.rotate_right(1);
11547 }
11548
11549 let edits = line_ranges
11550 .iter()
11551 .zip(line_texts.iter())
11552 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11553 .collect();
11554
11555 let num_rows = all_rows.len();
11556 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11557 .iter()
11558 .enumerate()
11559 .map(|(i, &row)| (row, i))
11560 .collect();
11561
11562 // Compute new line start offsets after rotation (handles CRLF)
11563 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11564 let first_line_start = line_ranges[0].start.0;
11565 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11566 for text in line_texts.iter().take(num_rows - 1) {
11567 let prev_start = *new_line_starts.last().unwrap();
11568 new_line_starts.push(prev_start + text.len() + newline_len);
11569 }
11570
11571 let new_selections = selections
11572 .iter()
11573 .map(|selection| {
11574 let point = buffer.offset_to_point(selection.start);
11575 let old_index = row_to_index[&point.row];
11576 let new_index = if reverse {
11577 (old_index + num_rows - 1) % num_rows
11578 } else {
11579 (old_index + 1) % num_rows
11580 };
11581 let new_offset =
11582 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11583 Selection {
11584 id: selection.id,
11585 start: new_offset,
11586 end: new_offset,
11587 reversed: selection.reversed,
11588 goal: selection.goal,
11589 }
11590 })
11591 .collect();
11592
11593 (edits, new_selections)
11594 }
11595 };
11596
11597 self.transact(window, cx, |this, window, cx| {
11598 this.buffer.update(cx, |buffer, cx| {
11599 buffer.edit(edits, None, cx);
11600 });
11601 this.change_selections(Default::default(), window, cx, |s| {
11602 s.select(new_selections);
11603 });
11604 });
11605 }
11606
11607 fn manipulate_lines<M>(
11608 &mut self,
11609 window: &mut Window,
11610 cx: &mut Context<Self>,
11611 mut manipulate: M,
11612 ) where
11613 M: FnMut(&str) -> LineManipulationResult,
11614 {
11615 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11616
11617 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11618 let buffer = self.buffer.read(cx).snapshot(cx);
11619
11620 let mut edits = Vec::new();
11621
11622 let selections = self.selections.all::<Point>(&display_map);
11623 let mut selections = selections.iter().peekable();
11624 let mut contiguous_row_selections = Vec::new();
11625 let mut new_selections = Vec::new();
11626 let mut added_lines = 0;
11627 let mut removed_lines = 0;
11628
11629 while let Some(selection) = selections.next() {
11630 let (start_row, end_row) = consume_contiguous_rows(
11631 &mut contiguous_row_selections,
11632 selection,
11633 &display_map,
11634 &mut selections,
11635 );
11636
11637 let start_point = Point::new(start_row.0, 0);
11638 let end_point = Point::new(
11639 end_row.previous_row().0,
11640 buffer.line_len(end_row.previous_row()),
11641 );
11642 let text = buffer
11643 .text_for_range(start_point..end_point)
11644 .collect::<String>();
11645
11646 let LineManipulationResult {
11647 new_text,
11648 line_count_before,
11649 line_count_after,
11650 } = manipulate(&text);
11651
11652 edits.push((start_point..end_point, new_text));
11653
11654 // Selections must change based on added and removed line count
11655 let start_row =
11656 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11657 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11658 new_selections.push(Selection {
11659 id: selection.id,
11660 start: start_row,
11661 end: end_row,
11662 goal: SelectionGoal::None,
11663 reversed: selection.reversed,
11664 });
11665
11666 if line_count_after > line_count_before {
11667 added_lines += line_count_after - line_count_before;
11668 } else if line_count_before > line_count_after {
11669 removed_lines += line_count_before - line_count_after;
11670 }
11671 }
11672
11673 self.transact(window, cx, |this, window, cx| {
11674 let buffer = this.buffer.update(cx, |buffer, cx| {
11675 buffer.edit(edits, None, cx);
11676 buffer.snapshot(cx)
11677 });
11678
11679 // Recalculate offsets on newly edited buffer
11680 let new_selections = new_selections
11681 .iter()
11682 .map(|s| {
11683 let start_point = Point::new(s.start.0, 0);
11684 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11685 Selection {
11686 id: s.id,
11687 start: buffer.point_to_offset(start_point),
11688 end: buffer.point_to_offset(end_point),
11689 goal: s.goal,
11690 reversed: s.reversed,
11691 }
11692 })
11693 .collect();
11694
11695 this.change_selections(Default::default(), window, cx, |s| {
11696 s.select(new_selections);
11697 });
11698
11699 this.request_autoscroll(Autoscroll::fit(), cx);
11700 });
11701 }
11702
11703 fn manipulate_immutable_lines<Fn>(
11704 &mut self,
11705 window: &mut Window,
11706 cx: &mut Context<Self>,
11707 mut callback: Fn,
11708 ) where
11709 Fn: FnMut(&mut Vec<&str>),
11710 {
11711 self.manipulate_lines(window, cx, |text| {
11712 let mut lines: Vec<&str> = text.split('\n').collect();
11713 let line_count_before = lines.len();
11714
11715 callback(&mut lines);
11716
11717 LineManipulationResult {
11718 new_text: lines.join("\n"),
11719 line_count_before,
11720 line_count_after: lines.len(),
11721 }
11722 });
11723 }
11724
11725 fn manipulate_mutable_lines<Fn>(
11726 &mut self,
11727 window: &mut Window,
11728 cx: &mut Context<Self>,
11729 mut callback: Fn,
11730 ) where
11731 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11732 {
11733 self.manipulate_lines(window, cx, |text| {
11734 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11735 let line_count_before = lines.len();
11736
11737 callback(&mut lines);
11738
11739 LineManipulationResult {
11740 new_text: lines.join("\n"),
11741 line_count_before,
11742 line_count_after: lines.len(),
11743 }
11744 });
11745 }
11746
11747 pub fn convert_indentation_to_spaces(
11748 &mut self,
11749 _: &ConvertIndentationToSpaces,
11750 window: &mut Window,
11751 cx: &mut Context<Self>,
11752 ) {
11753 let settings = self.buffer.read(cx).language_settings(cx);
11754 let tab_size = settings.tab_size.get() as usize;
11755
11756 self.manipulate_mutable_lines(window, cx, |lines| {
11757 // Allocates a reasonably sized scratch buffer once for the whole loop
11758 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11759 // Avoids recomputing spaces that could be inserted many times
11760 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11761 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11762 .collect();
11763
11764 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11765 let mut chars = line.as_ref().chars();
11766 let mut col = 0;
11767 let mut changed = false;
11768
11769 for ch in chars.by_ref() {
11770 match ch {
11771 ' ' => {
11772 reindented_line.push(' ');
11773 col += 1;
11774 }
11775 '\t' => {
11776 // \t are converted to spaces depending on the current column
11777 let spaces_len = tab_size - (col % tab_size);
11778 reindented_line.extend(&space_cache[spaces_len - 1]);
11779 col += spaces_len;
11780 changed = true;
11781 }
11782 _ => {
11783 // If we dont append before break, the character is consumed
11784 reindented_line.push(ch);
11785 break;
11786 }
11787 }
11788 }
11789
11790 if !changed {
11791 reindented_line.clear();
11792 continue;
11793 }
11794 // Append the rest of the line and replace old reference with new one
11795 reindented_line.extend(chars);
11796 *line = Cow::Owned(reindented_line.clone());
11797 reindented_line.clear();
11798 }
11799 });
11800 }
11801
11802 pub fn convert_indentation_to_tabs(
11803 &mut self,
11804 _: &ConvertIndentationToTabs,
11805 window: &mut Window,
11806 cx: &mut Context<Self>,
11807 ) {
11808 let settings = self.buffer.read(cx).language_settings(cx);
11809 let tab_size = settings.tab_size.get() as usize;
11810
11811 self.manipulate_mutable_lines(window, cx, |lines| {
11812 // Allocates a reasonably sized buffer once for the whole loop
11813 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11814 // Avoids recomputing spaces that could be inserted many times
11815 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11816 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11817 .collect();
11818
11819 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11820 let mut chars = line.chars();
11821 let mut spaces_count = 0;
11822 let mut first_non_indent_char = None;
11823 let mut changed = false;
11824
11825 for ch in chars.by_ref() {
11826 match ch {
11827 ' ' => {
11828 // Keep track of spaces. Append \t when we reach tab_size
11829 spaces_count += 1;
11830 changed = true;
11831 if spaces_count == tab_size {
11832 reindented_line.push('\t');
11833 spaces_count = 0;
11834 }
11835 }
11836 '\t' => {
11837 reindented_line.push('\t');
11838 spaces_count = 0;
11839 }
11840 _ => {
11841 // Dont append it yet, we might have remaining spaces
11842 first_non_indent_char = Some(ch);
11843 break;
11844 }
11845 }
11846 }
11847
11848 if !changed {
11849 reindented_line.clear();
11850 continue;
11851 }
11852 // Remaining spaces that didn't make a full tab stop
11853 if spaces_count > 0 {
11854 reindented_line.extend(&space_cache[spaces_count - 1]);
11855 }
11856 // If we consume an extra character that was not indentation, add it back
11857 if let Some(extra_char) = first_non_indent_char {
11858 reindented_line.push(extra_char);
11859 }
11860 // Append the rest of the line and replace old reference with new one
11861 reindented_line.extend(chars);
11862 *line = Cow::Owned(reindented_line.clone());
11863 reindented_line.clear();
11864 }
11865 });
11866 }
11867
11868 pub fn convert_to_upper_case(
11869 &mut self,
11870 _: &ConvertToUpperCase,
11871 window: &mut Window,
11872 cx: &mut Context<Self>,
11873 ) {
11874 self.manipulate_text(window, cx, |text| text.to_uppercase())
11875 }
11876
11877 pub fn convert_to_lower_case(
11878 &mut self,
11879 _: &ConvertToLowerCase,
11880 window: &mut Window,
11881 cx: &mut Context<Self>,
11882 ) {
11883 self.manipulate_text(window, cx, |text| text.to_lowercase())
11884 }
11885
11886 pub fn convert_to_title_case(
11887 &mut self,
11888 _: &ConvertToTitleCase,
11889 window: &mut Window,
11890 cx: &mut Context<Self>,
11891 ) {
11892 self.manipulate_text(window, cx, |text| {
11893 text.split('\n')
11894 .map(|line| line.to_case(Case::Title))
11895 .join("\n")
11896 })
11897 }
11898
11899 pub fn convert_to_snake_case(
11900 &mut self,
11901 _: &ConvertToSnakeCase,
11902 window: &mut Window,
11903 cx: &mut Context<Self>,
11904 ) {
11905 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11906 }
11907
11908 pub fn convert_to_kebab_case(
11909 &mut self,
11910 _: &ConvertToKebabCase,
11911 window: &mut Window,
11912 cx: &mut Context<Self>,
11913 ) {
11914 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11915 }
11916
11917 pub fn convert_to_upper_camel_case(
11918 &mut self,
11919 _: &ConvertToUpperCamelCase,
11920 window: &mut Window,
11921 cx: &mut Context<Self>,
11922 ) {
11923 self.manipulate_text(window, cx, |text| {
11924 text.split('\n')
11925 .map(|line| line.to_case(Case::UpperCamel))
11926 .join("\n")
11927 })
11928 }
11929
11930 pub fn convert_to_lower_camel_case(
11931 &mut self,
11932 _: &ConvertToLowerCamelCase,
11933 window: &mut Window,
11934 cx: &mut Context<Self>,
11935 ) {
11936 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11937 }
11938
11939 pub fn convert_to_opposite_case(
11940 &mut self,
11941 _: &ConvertToOppositeCase,
11942 window: &mut Window,
11943 cx: &mut Context<Self>,
11944 ) {
11945 self.manipulate_text(window, cx, |text| {
11946 text.chars()
11947 .fold(String::with_capacity(text.len()), |mut t, c| {
11948 if c.is_uppercase() {
11949 t.extend(c.to_lowercase());
11950 } else {
11951 t.extend(c.to_uppercase());
11952 }
11953 t
11954 })
11955 })
11956 }
11957
11958 pub fn convert_to_sentence_case(
11959 &mut self,
11960 _: &ConvertToSentenceCase,
11961 window: &mut Window,
11962 cx: &mut Context<Self>,
11963 ) {
11964 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11965 }
11966
11967 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11968 self.manipulate_text(window, cx, |text| {
11969 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11970 if has_upper_case_characters {
11971 text.to_lowercase()
11972 } else {
11973 text.to_uppercase()
11974 }
11975 })
11976 }
11977
11978 pub fn convert_to_rot13(
11979 &mut self,
11980 _: &ConvertToRot13,
11981 window: &mut Window,
11982 cx: &mut Context<Self>,
11983 ) {
11984 self.manipulate_text(window, cx, |text| {
11985 text.chars()
11986 .map(|c| match c {
11987 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11988 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11989 _ => c,
11990 })
11991 .collect()
11992 })
11993 }
11994
11995 pub fn convert_to_rot47(
11996 &mut self,
11997 _: &ConvertToRot47,
11998 window: &mut Window,
11999 cx: &mut Context<Self>,
12000 ) {
12001 self.manipulate_text(window, cx, |text| {
12002 text.chars()
12003 .map(|c| {
12004 let code_point = c as u32;
12005 if code_point >= 33 && code_point <= 126 {
12006 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12007 }
12008 c
12009 })
12010 .collect()
12011 })
12012 }
12013
12014 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12015 where
12016 Fn: FnMut(&str) -> String,
12017 {
12018 let buffer = self.buffer.read(cx).snapshot(cx);
12019
12020 let mut new_selections = Vec::new();
12021 let mut edits = Vec::new();
12022 let mut selection_adjustment = 0isize;
12023
12024 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12025 let selection_is_empty = selection.is_empty();
12026
12027 let (start, end) = if selection_is_empty {
12028 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12029 (word_range.start, word_range.end)
12030 } else {
12031 (
12032 buffer.point_to_offset(selection.start),
12033 buffer.point_to_offset(selection.end),
12034 )
12035 };
12036
12037 let text = buffer.text_for_range(start..end).collect::<String>();
12038 let old_length = text.len() as isize;
12039 let text = callback(&text);
12040
12041 new_selections.push(Selection {
12042 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12043 end: MultiBufferOffset(
12044 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12045 ),
12046 goal: SelectionGoal::None,
12047 id: selection.id,
12048 reversed: selection.reversed,
12049 });
12050
12051 selection_adjustment += old_length - text.len() as isize;
12052
12053 edits.push((start..end, text));
12054 }
12055
12056 self.transact(window, cx, |this, window, cx| {
12057 this.buffer.update(cx, |buffer, cx| {
12058 buffer.edit(edits, None, cx);
12059 });
12060
12061 this.change_selections(Default::default(), window, cx, |s| {
12062 s.select(new_selections);
12063 });
12064
12065 this.request_autoscroll(Autoscroll::fit(), cx);
12066 });
12067 }
12068
12069 pub fn move_selection_on_drop(
12070 &mut self,
12071 selection: &Selection<Anchor>,
12072 target: DisplayPoint,
12073 is_cut: bool,
12074 window: &mut Window,
12075 cx: &mut Context<Self>,
12076 ) {
12077 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12078 let buffer = display_map.buffer_snapshot();
12079 let mut edits = Vec::new();
12080 let insert_point = display_map
12081 .clip_point(target, Bias::Left)
12082 .to_point(&display_map);
12083 let text = buffer
12084 .text_for_range(selection.start..selection.end)
12085 .collect::<String>();
12086 if is_cut {
12087 edits.push(((selection.start..selection.end), String::new()));
12088 }
12089 let insert_anchor = buffer.anchor_before(insert_point);
12090 edits.push(((insert_anchor..insert_anchor), text));
12091 let last_edit_start = insert_anchor.bias_left(buffer);
12092 let last_edit_end = insert_anchor.bias_right(buffer);
12093 self.transact(window, cx, |this, window, cx| {
12094 this.buffer.update(cx, |buffer, cx| {
12095 buffer.edit(edits, None, cx);
12096 });
12097 this.change_selections(Default::default(), window, cx, |s| {
12098 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12099 });
12100 });
12101 }
12102
12103 pub fn clear_selection_drag_state(&mut self) {
12104 self.selection_drag_state = SelectionDragState::None;
12105 }
12106
12107 pub fn duplicate(
12108 &mut self,
12109 upwards: bool,
12110 whole_lines: bool,
12111 window: &mut Window,
12112 cx: &mut Context<Self>,
12113 ) {
12114 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12115
12116 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12117 let buffer = display_map.buffer_snapshot();
12118 let selections = self.selections.all::<Point>(&display_map);
12119
12120 let mut edits = Vec::new();
12121 let mut selections_iter = selections.iter().peekable();
12122 while let Some(selection) = selections_iter.next() {
12123 let mut rows = selection.spanned_rows(false, &display_map);
12124 // duplicate line-wise
12125 if whole_lines || selection.start == selection.end {
12126 // Avoid duplicating the same lines twice.
12127 while let Some(next_selection) = selections_iter.peek() {
12128 let next_rows = next_selection.spanned_rows(false, &display_map);
12129 if next_rows.start < rows.end {
12130 rows.end = next_rows.end;
12131 selections_iter.next().unwrap();
12132 } else {
12133 break;
12134 }
12135 }
12136
12137 // Copy the text from the selected row region and splice it either at the start
12138 // or end of the region.
12139 let start = Point::new(rows.start.0, 0);
12140 let end = Point::new(
12141 rows.end.previous_row().0,
12142 buffer.line_len(rows.end.previous_row()),
12143 );
12144
12145 let mut text = buffer.text_for_range(start..end).collect::<String>();
12146
12147 let insert_location = if upwards {
12148 // When duplicating upward, we need to insert before the current line.
12149 // If we're on the last line and it doesn't end with a newline,
12150 // we need to add a newline before the duplicated content.
12151 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12152 && buffer.max_point().column > 0
12153 && !text.ends_with('\n');
12154
12155 if needs_leading_newline {
12156 text.insert(0, '\n');
12157 end
12158 } else {
12159 text.push('\n');
12160 Point::new(rows.start.0, 0)
12161 }
12162 } else {
12163 text.push('\n');
12164 start
12165 };
12166 edits.push((insert_location..insert_location, text));
12167 } else {
12168 // duplicate character-wise
12169 let start = selection.start;
12170 let end = selection.end;
12171 let text = buffer.text_for_range(start..end).collect::<String>();
12172 edits.push((selection.end..selection.end, text));
12173 }
12174 }
12175
12176 self.transact(window, cx, |this, window, cx| {
12177 this.buffer.update(cx, |buffer, cx| {
12178 buffer.edit(edits, None, cx);
12179 });
12180
12181 // When duplicating upward with whole lines, move the cursor to the duplicated line
12182 if upwards && whole_lines {
12183 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12184
12185 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12186 let mut new_ranges = Vec::new();
12187 let selections = s.all::<Point>(&display_map);
12188 let mut selections_iter = selections.iter().peekable();
12189
12190 while let Some(first_selection) = selections_iter.next() {
12191 // Group contiguous selections together to find the total row span
12192 let mut group_selections = vec![first_selection];
12193 let mut rows = first_selection.spanned_rows(false, &display_map);
12194
12195 while let Some(next_selection) = selections_iter.peek() {
12196 let next_rows = next_selection.spanned_rows(false, &display_map);
12197 if next_rows.start < rows.end {
12198 rows.end = next_rows.end;
12199 group_selections.push(selections_iter.next().unwrap());
12200 } else {
12201 break;
12202 }
12203 }
12204
12205 let row_count = rows.end.0 - rows.start.0;
12206
12207 // Move all selections in this group up by the total number of duplicated rows
12208 for selection in group_selections {
12209 let new_start = Point::new(
12210 selection.start.row.saturating_sub(row_count),
12211 selection.start.column,
12212 );
12213
12214 let new_end = Point::new(
12215 selection.end.row.saturating_sub(row_count),
12216 selection.end.column,
12217 );
12218
12219 new_ranges.push(new_start..new_end);
12220 }
12221 }
12222
12223 s.select_ranges(new_ranges);
12224 });
12225 }
12226
12227 this.request_autoscroll(Autoscroll::fit(), cx);
12228 });
12229 }
12230
12231 pub fn duplicate_line_up(
12232 &mut self,
12233 _: &DuplicateLineUp,
12234 window: &mut Window,
12235 cx: &mut Context<Self>,
12236 ) {
12237 self.duplicate(true, true, window, cx);
12238 }
12239
12240 pub fn duplicate_line_down(
12241 &mut self,
12242 _: &DuplicateLineDown,
12243 window: &mut Window,
12244 cx: &mut Context<Self>,
12245 ) {
12246 self.duplicate(false, true, window, cx);
12247 }
12248
12249 pub fn duplicate_selection(
12250 &mut self,
12251 _: &DuplicateSelection,
12252 window: &mut Window,
12253 cx: &mut Context<Self>,
12254 ) {
12255 self.duplicate(false, false, window, cx);
12256 }
12257
12258 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12259 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12260 if self.mode.is_single_line() {
12261 cx.propagate();
12262 return;
12263 }
12264
12265 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12266 let buffer = self.buffer.read(cx).snapshot(cx);
12267
12268 let mut edits = Vec::new();
12269 let mut unfold_ranges = Vec::new();
12270 let mut refold_creases = Vec::new();
12271
12272 let selections = self.selections.all::<Point>(&display_map);
12273 let mut selections = selections.iter().peekable();
12274 let mut contiguous_row_selections = Vec::new();
12275 let mut new_selections = Vec::new();
12276
12277 while let Some(selection) = selections.next() {
12278 // Find all the selections that span a contiguous row range
12279 let (start_row, end_row) = consume_contiguous_rows(
12280 &mut contiguous_row_selections,
12281 selection,
12282 &display_map,
12283 &mut selections,
12284 );
12285
12286 // Move the text spanned by the row range to be before the line preceding the row range
12287 if start_row.0 > 0 {
12288 let range_to_move = Point::new(
12289 start_row.previous_row().0,
12290 buffer.line_len(start_row.previous_row()),
12291 )
12292 ..Point::new(
12293 end_row.previous_row().0,
12294 buffer.line_len(end_row.previous_row()),
12295 );
12296 let insertion_point = display_map
12297 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12298 .0;
12299
12300 // Don't move lines across excerpts
12301 if buffer
12302 .excerpt_containing(insertion_point..range_to_move.end)
12303 .is_some()
12304 {
12305 let text = buffer
12306 .text_for_range(range_to_move.clone())
12307 .flat_map(|s| s.chars())
12308 .skip(1)
12309 .chain(['\n'])
12310 .collect::<String>();
12311
12312 edits.push((
12313 buffer.anchor_after(range_to_move.start)
12314 ..buffer.anchor_before(range_to_move.end),
12315 String::new(),
12316 ));
12317 let insertion_anchor = buffer.anchor_after(insertion_point);
12318 edits.push((insertion_anchor..insertion_anchor, text));
12319
12320 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12321
12322 // Move selections up
12323 new_selections.extend(contiguous_row_selections.drain(..).map(
12324 |mut selection| {
12325 selection.start.row -= row_delta;
12326 selection.end.row -= row_delta;
12327 selection
12328 },
12329 ));
12330
12331 // Move folds up
12332 unfold_ranges.push(range_to_move.clone());
12333 for fold in display_map.folds_in_range(
12334 buffer.anchor_before(range_to_move.start)
12335 ..buffer.anchor_after(range_to_move.end),
12336 ) {
12337 let mut start = fold.range.start.to_point(&buffer);
12338 let mut end = fold.range.end.to_point(&buffer);
12339 start.row -= row_delta;
12340 end.row -= row_delta;
12341 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12342 }
12343 }
12344 }
12345
12346 // If we didn't move line(s), preserve the existing selections
12347 new_selections.append(&mut contiguous_row_selections);
12348 }
12349
12350 self.transact(window, cx, |this, window, cx| {
12351 this.unfold_ranges(&unfold_ranges, true, true, cx);
12352 this.buffer.update(cx, |buffer, cx| {
12353 for (range, text) in edits {
12354 buffer.edit([(range, text)], None, cx);
12355 }
12356 });
12357 this.fold_creases(refold_creases, true, window, cx);
12358 this.change_selections(Default::default(), window, cx, |s| {
12359 s.select(new_selections);
12360 })
12361 });
12362 }
12363
12364 pub fn move_line_down(
12365 &mut self,
12366 _: &MoveLineDown,
12367 window: &mut Window,
12368 cx: &mut Context<Self>,
12369 ) {
12370 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12371 if self.mode.is_single_line() {
12372 cx.propagate();
12373 return;
12374 }
12375
12376 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12377 let buffer = self.buffer.read(cx).snapshot(cx);
12378
12379 let mut edits = Vec::new();
12380 let mut unfold_ranges = Vec::new();
12381 let mut refold_creases = Vec::new();
12382
12383 let selections = self.selections.all::<Point>(&display_map);
12384 let mut selections = selections.iter().peekable();
12385 let mut contiguous_row_selections = Vec::new();
12386 let mut new_selections = Vec::new();
12387
12388 while let Some(selection) = selections.next() {
12389 // Find all the selections that span a contiguous row range
12390 let (start_row, end_row) = consume_contiguous_rows(
12391 &mut contiguous_row_selections,
12392 selection,
12393 &display_map,
12394 &mut selections,
12395 );
12396
12397 // Move the text spanned by the row range to be after the last line of the row range
12398 if end_row.0 <= buffer.max_point().row {
12399 let range_to_move =
12400 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12401 let insertion_point = display_map
12402 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12403 .0;
12404
12405 // Don't move lines across excerpt boundaries
12406 if buffer
12407 .excerpt_containing(range_to_move.start..insertion_point)
12408 .is_some()
12409 {
12410 let mut text = String::from("\n");
12411 text.extend(buffer.text_for_range(range_to_move.clone()));
12412 text.pop(); // Drop trailing newline
12413 edits.push((
12414 buffer.anchor_after(range_to_move.start)
12415 ..buffer.anchor_before(range_to_move.end),
12416 String::new(),
12417 ));
12418 let insertion_anchor = buffer.anchor_after(insertion_point);
12419 edits.push((insertion_anchor..insertion_anchor, text));
12420
12421 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12422
12423 // Move selections down
12424 new_selections.extend(contiguous_row_selections.drain(..).map(
12425 |mut selection| {
12426 selection.start.row += row_delta;
12427 selection.end.row += row_delta;
12428 selection
12429 },
12430 ));
12431
12432 // Move folds down
12433 unfold_ranges.push(range_to_move.clone());
12434 for fold in display_map.folds_in_range(
12435 buffer.anchor_before(range_to_move.start)
12436 ..buffer.anchor_after(range_to_move.end),
12437 ) {
12438 let mut start = fold.range.start.to_point(&buffer);
12439 let mut end = fold.range.end.to_point(&buffer);
12440 start.row += row_delta;
12441 end.row += row_delta;
12442 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12443 }
12444 }
12445 }
12446
12447 // If we didn't move line(s), preserve the existing selections
12448 new_selections.append(&mut contiguous_row_selections);
12449 }
12450
12451 self.transact(window, cx, |this, window, cx| {
12452 this.unfold_ranges(&unfold_ranges, true, true, cx);
12453 this.buffer.update(cx, |buffer, cx| {
12454 for (range, text) in edits {
12455 buffer.edit([(range, text)], None, cx);
12456 }
12457 });
12458 this.fold_creases(refold_creases, true, window, cx);
12459 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12460 });
12461 }
12462
12463 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12465 let text_layout_details = &self.text_layout_details(window);
12466 self.transact(window, cx, |this, window, cx| {
12467 let edits = this.change_selections(Default::default(), window, cx, |s| {
12468 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12469 s.move_with(|display_map, selection| {
12470 if !selection.is_empty() {
12471 return;
12472 }
12473
12474 let mut head = selection.head();
12475 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12476 if head.column() == display_map.line_len(head.row()) {
12477 transpose_offset = display_map
12478 .buffer_snapshot()
12479 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12480 }
12481
12482 if transpose_offset == MultiBufferOffset(0) {
12483 return;
12484 }
12485
12486 *head.column_mut() += 1;
12487 head = display_map.clip_point(head, Bias::Right);
12488 let goal = SelectionGoal::HorizontalPosition(
12489 display_map
12490 .x_for_display_point(head, text_layout_details)
12491 .into(),
12492 );
12493 selection.collapse_to(head, goal);
12494
12495 let transpose_start = display_map
12496 .buffer_snapshot()
12497 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12498 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12499 let transpose_end = display_map
12500 .buffer_snapshot()
12501 .clip_offset(transpose_offset + 1usize, Bias::Right);
12502 if let Some(ch) = display_map
12503 .buffer_snapshot()
12504 .chars_at(transpose_start)
12505 .next()
12506 {
12507 edits.push((transpose_start..transpose_offset, String::new()));
12508 edits.push((transpose_end..transpose_end, ch.to_string()));
12509 }
12510 }
12511 });
12512 edits
12513 });
12514 this.buffer
12515 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12516 let selections = this
12517 .selections
12518 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12519 this.change_selections(Default::default(), window, cx, |s| {
12520 s.select(selections);
12521 });
12522 });
12523 }
12524
12525 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12527 if self.mode.is_single_line() {
12528 cx.propagate();
12529 return;
12530 }
12531
12532 self.rewrap_impl(RewrapOptions::default(), cx)
12533 }
12534
12535 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12536 let buffer = self.buffer.read(cx).snapshot(cx);
12537 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12538
12539 #[derive(Clone, Debug, PartialEq)]
12540 enum CommentFormat {
12541 /// single line comment, with prefix for line
12542 Line(String),
12543 /// single line within a block comment, with prefix for line
12544 BlockLine(String),
12545 /// a single line of a block comment that includes the initial delimiter
12546 BlockCommentWithStart(BlockCommentConfig),
12547 /// a single line of a block comment that includes the ending delimiter
12548 BlockCommentWithEnd(BlockCommentConfig),
12549 }
12550
12551 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12552 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12553 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12554 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12555 .peekable();
12556
12557 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12558 row
12559 } else {
12560 return Vec::new();
12561 };
12562
12563 let language_settings = buffer.language_settings_at(selection.head(), cx);
12564 let language_scope = buffer.language_scope_at(selection.head());
12565
12566 let indent_and_prefix_for_row =
12567 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12568 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12569 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12570 &language_scope
12571 {
12572 let indent_end = Point::new(row, indent.len);
12573 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12574 let line_text_after_indent = buffer
12575 .text_for_range(indent_end..line_end)
12576 .collect::<String>();
12577
12578 let is_within_comment_override = buffer
12579 .language_scope_at(indent_end)
12580 .is_some_and(|scope| scope.override_name() == Some("comment"));
12581 let comment_delimiters = if is_within_comment_override {
12582 // we are within a comment syntax node, but we don't
12583 // yet know what kind of comment: block, doc or line
12584 match (
12585 language_scope.documentation_comment(),
12586 language_scope.block_comment(),
12587 ) {
12588 (Some(config), _) | (_, Some(config))
12589 if buffer.contains_str_at(indent_end, &config.start) =>
12590 {
12591 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12592 }
12593 (Some(config), _) | (_, Some(config))
12594 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12595 {
12596 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12597 }
12598 (Some(config), _) | (_, Some(config))
12599 if buffer.contains_str_at(indent_end, &config.prefix) =>
12600 {
12601 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12602 }
12603 (_, _) => language_scope
12604 .line_comment_prefixes()
12605 .iter()
12606 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12607 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12608 }
12609 } else {
12610 // we not in an overridden comment node, but we may
12611 // be within a non-overridden line comment node
12612 language_scope
12613 .line_comment_prefixes()
12614 .iter()
12615 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12616 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12617 };
12618
12619 let rewrap_prefix = language_scope
12620 .rewrap_prefixes()
12621 .iter()
12622 .find_map(|prefix_regex| {
12623 prefix_regex.find(&line_text_after_indent).map(|mat| {
12624 if mat.start() == 0 {
12625 Some(mat.as_str().to_string())
12626 } else {
12627 None
12628 }
12629 })
12630 })
12631 .flatten();
12632 (comment_delimiters, rewrap_prefix)
12633 } else {
12634 (None, None)
12635 };
12636 (indent, comment_prefix, rewrap_prefix)
12637 };
12638
12639 let mut ranges = Vec::new();
12640 let from_empty_selection = selection.is_empty();
12641
12642 let mut current_range_start = first_row;
12643 let mut prev_row = first_row;
12644 let (
12645 mut current_range_indent,
12646 mut current_range_comment_delimiters,
12647 mut current_range_rewrap_prefix,
12648 ) = indent_and_prefix_for_row(first_row);
12649
12650 for row in non_blank_rows_iter.skip(1) {
12651 let has_paragraph_break = row > prev_row + 1;
12652
12653 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12654 indent_and_prefix_for_row(row);
12655
12656 let has_indent_change = row_indent != current_range_indent;
12657 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12658
12659 let has_boundary_change = has_comment_change
12660 || row_rewrap_prefix.is_some()
12661 || (has_indent_change && current_range_comment_delimiters.is_some());
12662
12663 if has_paragraph_break || has_boundary_change {
12664 ranges.push((
12665 language_settings.clone(),
12666 Point::new(current_range_start, 0)
12667 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12668 current_range_indent,
12669 current_range_comment_delimiters.clone(),
12670 current_range_rewrap_prefix.clone(),
12671 from_empty_selection,
12672 ));
12673 current_range_start = row;
12674 current_range_indent = row_indent;
12675 current_range_comment_delimiters = row_comment_delimiters;
12676 current_range_rewrap_prefix = row_rewrap_prefix;
12677 }
12678 prev_row = row;
12679 }
12680
12681 ranges.push((
12682 language_settings.clone(),
12683 Point::new(current_range_start, 0)
12684 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12685 current_range_indent,
12686 current_range_comment_delimiters,
12687 current_range_rewrap_prefix,
12688 from_empty_selection,
12689 ));
12690
12691 ranges
12692 });
12693
12694 let mut edits = Vec::new();
12695 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12696
12697 for (
12698 language_settings,
12699 wrap_range,
12700 mut indent_size,
12701 comment_prefix,
12702 rewrap_prefix,
12703 from_empty_selection,
12704 ) in wrap_ranges
12705 {
12706 let mut start_row = wrap_range.start.row;
12707 let mut end_row = wrap_range.end.row;
12708
12709 // Skip selections that overlap with a range that has already been rewrapped.
12710 let selection_range = start_row..end_row;
12711 if rewrapped_row_ranges
12712 .iter()
12713 .any(|range| range.overlaps(&selection_range))
12714 {
12715 continue;
12716 }
12717
12718 let tab_size = language_settings.tab_size;
12719
12720 let (line_prefix, inside_comment) = match &comment_prefix {
12721 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12722 (Some(prefix.as_str()), true)
12723 }
12724 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12725 (Some(prefix.as_ref()), true)
12726 }
12727 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12728 start: _,
12729 end: _,
12730 prefix,
12731 tab_size,
12732 })) => {
12733 indent_size.len += tab_size;
12734 (Some(prefix.as_ref()), true)
12735 }
12736 None => (None, false),
12737 };
12738 let indent_prefix = indent_size.chars().collect::<String>();
12739 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12740
12741 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12742 RewrapBehavior::InComments => inside_comment,
12743 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12744 RewrapBehavior::Anywhere => true,
12745 };
12746
12747 let should_rewrap = options.override_language_settings
12748 || allow_rewrap_based_on_language
12749 || self.hard_wrap.is_some();
12750 if !should_rewrap {
12751 continue;
12752 }
12753
12754 if from_empty_selection {
12755 'expand_upwards: while start_row > 0 {
12756 let prev_row = start_row - 1;
12757 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12758 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12759 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12760 {
12761 start_row = prev_row;
12762 } else {
12763 break 'expand_upwards;
12764 }
12765 }
12766
12767 'expand_downwards: while end_row < buffer.max_point().row {
12768 let next_row = end_row + 1;
12769 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12770 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12771 && !buffer.is_line_blank(MultiBufferRow(next_row))
12772 {
12773 end_row = next_row;
12774 } else {
12775 break 'expand_downwards;
12776 }
12777 }
12778 }
12779
12780 let start = Point::new(start_row, 0);
12781 let start_offset = ToOffset::to_offset(&start, &buffer);
12782 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12783 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12784 let mut first_line_delimiter = None;
12785 let mut last_line_delimiter = None;
12786 let Some(lines_without_prefixes) = selection_text
12787 .lines()
12788 .enumerate()
12789 .map(|(ix, line)| {
12790 let line_trimmed = line.trim_start();
12791 if rewrap_prefix.is_some() && ix > 0 {
12792 Ok(line_trimmed)
12793 } else if let Some(
12794 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12795 start,
12796 prefix,
12797 end,
12798 tab_size,
12799 })
12800 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12801 start,
12802 prefix,
12803 end,
12804 tab_size,
12805 }),
12806 ) = &comment_prefix
12807 {
12808 let line_trimmed = line_trimmed
12809 .strip_prefix(start.as_ref())
12810 .map(|s| {
12811 let mut indent_size = indent_size;
12812 indent_size.len -= tab_size;
12813 let indent_prefix: String = indent_size.chars().collect();
12814 first_line_delimiter = Some((indent_prefix, start));
12815 s.trim_start()
12816 })
12817 .unwrap_or(line_trimmed);
12818 let line_trimmed = line_trimmed
12819 .strip_suffix(end.as_ref())
12820 .map(|s| {
12821 last_line_delimiter = Some(end);
12822 s.trim_end()
12823 })
12824 .unwrap_or(line_trimmed);
12825 let line_trimmed = line_trimmed
12826 .strip_prefix(prefix.as_ref())
12827 .unwrap_or(line_trimmed);
12828 Ok(line_trimmed)
12829 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12830 line_trimmed.strip_prefix(prefix).with_context(|| {
12831 format!("line did not start with prefix {prefix:?}: {line:?}")
12832 })
12833 } else {
12834 line_trimmed
12835 .strip_prefix(&line_prefix.trim_start())
12836 .with_context(|| {
12837 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12838 })
12839 }
12840 })
12841 .collect::<Result<Vec<_>, _>>()
12842 .log_err()
12843 else {
12844 continue;
12845 };
12846
12847 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12848 buffer
12849 .language_settings_at(Point::new(start_row, 0), cx)
12850 .preferred_line_length as usize
12851 });
12852
12853 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12854 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12855 } else {
12856 line_prefix.clone()
12857 };
12858
12859 let wrapped_text = {
12860 let mut wrapped_text = wrap_with_prefix(
12861 line_prefix,
12862 subsequent_lines_prefix,
12863 lines_without_prefixes.join("\n"),
12864 wrap_column,
12865 tab_size,
12866 options.preserve_existing_whitespace,
12867 );
12868
12869 if let Some((indent, delimiter)) = first_line_delimiter {
12870 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12871 }
12872 if let Some(last_line) = last_line_delimiter {
12873 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12874 }
12875
12876 wrapped_text
12877 };
12878
12879 // TODO: should always use char-based diff while still supporting cursor behavior that
12880 // matches vim.
12881 let mut diff_options = DiffOptions::default();
12882 if options.override_language_settings {
12883 diff_options.max_word_diff_len = 0;
12884 diff_options.max_word_diff_line_count = 0;
12885 } else {
12886 diff_options.max_word_diff_len = usize::MAX;
12887 diff_options.max_word_diff_line_count = usize::MAX;
12888 }
12889
12890 for (old_range, new_text) in
12891 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12892 {
12893 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12894 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12895 edits.push((edit_start..edit_end, new_text));
12896 }
12897
12898 rewrapped_row_ranges.push(start_row..=end_row);
12899 }
12900
12901 self.buffer
12902 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12903 }
12904
12905 pub fn cut_common(
12906 &mut self,
12907 cut_no_selection_line: bool,
12908 window: &mut Window,
12909 cx: &mut Context<Self>,
12910 ) -> ClipboardItem {
12911 let mut text = String::new();
12912 let buffer = self.buffer.read(cx).snapshot(cx);
12913 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12914 let mut clipboard_selections = Vec::with_capacity(selections.len());
12915 {
12916 let max_point = buffer.max_point();
12917 let mut is_first = true;
12918 let mut prev_selection_was_entire_line = false;
12919 for selection in &mut selections {
12920 let is_entire_line =
12921 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12922 if is_entire_line {
12923 selection.start = Point::new(selection.start.row, 0);
12924 if !selection.is_empty() && selection.end.column == 0 {
12925 selection.end = cmp::min(max_point, selection.end);
12926 } else {
12927 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12928 }
12929 selection.goal = SelectionGoal::None;
12930 }
12931 if is_first {
12932 is_first = false;
12933 } else if !prev_selection_was_entire_line {
12934 text += "\n";
12935 }
12936 prev_selection_was_entire_line = is_entire_line;
12937 let mut len = 0;
12938 for chunk in buffer.text_for_range(selection.start..selection.end) {
12939 text.push_str(chunk);
12940 len += chunk.len();
12941 }
12942
12943 clipboard_selections.push(ClipboardSelection::for_buffer(
12944 len,
12945 is_entire_line,
12946 selection.range(),
12947 &buffer,
12948 self.project.as_ref(),
12949 cx,
12950 ));
12951 }
12952 }
12953
12954 self.transact(window, cx, |this, window, cx| {
12955 this.change_selections(Default::default(), window, cx, |s| {
12956 s.select(selections);
12957 });
12958 this.insert("", window, cx);
12959 });
12960 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12961 }
12962
12963 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12965 let item = self.cut_common(true, window, cx);
12966 cx.write_to_clipboard(item);
12967 }
12968
12969 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12970 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12971 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12972 s.move_with(|snapshot, sel| {
12973 if sel.is_empty() {
12974 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12975 }
12976 if sel.is_empty() {
12977 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12978 }
12979 });
12980 });
12981 let item = self.cut_common(false, window, cx);
12982 cx.set_global(KillRing(item))
12983 }
12984
12985 pub fn kill_ring_yank(
12986 &mut self,
12987 _: &KillRingYank,
12988 window: &mut Window,
12989 cx: &mut Context<Self>,
12990 ) {
12991 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12992 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12993 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12994 (kill_ring.text().to_string(), kill_ring.metadata_json())
12995 } else {
12996 return;
12997 }
12998 } else {
12999 return;
13000 };
13001 self.do_paste(&text, metadata, false, window, cx);
13002 }
13003
13004 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13005 self.do_copy(true, cx);
13006 }
13007
13008 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13009 self.do_copy(false, cx);
13010 }
13011
13012 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13013 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13014 let buffer = self.buffer.read(cx).read(cx);
13015 let mut text = String::new();
13016
13017 let mut clipboard_selections = Vec::with_capacity(selections.len());
13018 {
13019 let max_point = buffer.max_point();
13020 let mut is_first = true;
13021 let mut prev_selection_was_entire_line = false;
13022 for selection in &selections {
13023 let mut start = selection.start;
13024 let mut end = selection.end;
13025 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13026 let mut add_trailing_newline = false;
13027 if is_entire_line {
13028 start = Point::new(start.row, 0);
13029 let next_line_start = Point::new(end.row + 1, 0);
13030 if next_line_start <= max_point {
13031 end = next_line_start;
13032 } else {
13033 // We're on the last line without a trailing newline.
13034 // Copy to the end of the line and add a newline afterwards.
13035 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13036 add_trailing_newline = true;
13037 }
13038 }
13039
13040 let mut trimmed_selections = Vec::new();
13041 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13042 let row = MultiBufferRow(start.row);
13043 let first_indent = buffer.indent_size_for_line(row);
13044 if first_indent.len == 0 || start.column > first_indent.len {
13045 trimmed_selections.push(start..end);
13046 } else {
13047 trimmed_selections.push(
13048 Point::new(row.0, first_indent.len)
13049 ..Point::new(row.0, buffer.line_len(row)),
13050 );
13051 for row in start.row + 1..=end.row {
13052 let mut line_len = buffer.line_len(MultiBufferRow(row));
13053 if row == end.row {
13054 line_len = end.column;
13055 }
13056 if line_len == 0 {
13057 trimmed_selections
13058 .push(Point::new(row, 0)..Point::new(row, line_len));
13059 continue;
13060 }
13061 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13062 if row_indent_size.len >= first_indent.len {
13063 trimmed_selections.push(
13064 Point::new(row, first_indent.len)..Point::new(row, line_len),
13065 );
13066 } else {
13067 trimmed_selections.clear();
13068 trimmed_selections.push(start..end);
13069 break;
13070 }
13071 }
13072 }
13073 } else {
13074 trimmed_selections.push(start..end);
13075 }
13076
13077 for trimmed_range in trimmed_selections {
13078 if is_first {
13079 is_first = false;
13080 } else if !prev_selection_was_entire_line {
13081 text += "\n";
13082 }
13083 prev_selection_was_entire_line = is_entire_line;
13084 let mut len = 0;
13085 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13086 text.push_str(chunk);
13087 len += chunk.len();
13088 }
13089 if add_trailing_newline {
13090 text.push('\n');
13091 len += 1;
13092 }
13093 clipboard_selections.push(ClipboardSelection::for_buffer(
13094 len,
13095 is_entire_line,
13096 trimmed_range,
13097 &buffer,
13098 self.project.as_ref(),
13099 cx,
13100 ));
13101 }
13102 }
13103 }
13104
13105 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13106 text,
13107 clipboard_selections,
13108 ));
13109 }
13110
13111 pub fn do_paste(
13112 &mut self,
13113 text: &String,
13114 clipboard_selections: Option<Vec<ClipboardSelection>>,
13115 handle_entire_lines: bool,
13116 window: &mut Window,
13117 cx: &mut Context<Self>,
13118 ) {
13119 if self.read_only(cx) {
13120 return;
13121 }
13122
13123 let clipboard_text = Cow::Borrowed(text.as_str());
13124
13125 self.transact(window, cx, |this, window, cx| {
13126 let had_active_edit_prediction = this.has_active_edit_prediction();
13127 let display_map = this.display_snapshot(cx);
13128 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13129 let cursor_offset = this
13130 .selections
13131 .last::<MultiBufferOffset>(&display_map)
13132 .head();
13133
13134 if let Some(mut clipboard_selections) = clipboard_selections {
13135 let all_selections_were_entire_line =
13136 clipboard_selections.iter().all(|s| s.is_entire_line);
13137 let first_selection_indent_column =
13138 clipboard_selections.first().map(|s| s.first_line_indent);
13139 if clipboard_selections.len() != old_selections.len() {
13140 clipboard_selections.drain(..);
13141 }
13142 let mut auto_indent_on_paste = true;
13143
13144 this.buffer.update(cx, |buffer, cx| {
13145 let snapshot = buffer.read(cx);
13146 auto_indent_on_paste = snapshot
13147 .language_settings_at(cursor_offset, cx)
13148 .auto_indent_on_paste;
13149
13150 let mut start_offset = 0;
13151 let mut edits = Vec::new();
13152 let mut original_indent_columns = Vec::new();
13153 for (ix, selection) in old_selections.iter().enumerate() {
13154 let to_insert;
13155 let entire_line;
13156 let original_indent_column;
13157 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13158 let end_offset = start_offset + clipboard_selection.len;
13159 to_insert = &clipboard_text[start_offset..end_offset];
13160 entire_line = clipboard_selection.is_entire_line;
13161 start_offset = if entire_line {
13162 end_offset
13163 } else {
13164 end_offset + 1
13165 };
13166 original_indent_column = Some(clipboard_selection.first_line_indent);
13167 } else {
13168 to_insert = &*clipboard_text;
13169 entire_line = all_selections_were_entire_line;
13170 original_indent_column = first_selection_indent_column
13171 }
13172
13173 let (range, to_insert) =
13174 if selection.is_empty() && handle_entire_lines && entire_line {
13175 // If the corresponding selection was empty when this slice of the
13176 // clipboard text was written, then the entire line containing the
13177 // selection was copied. If this selection is also currently empty,
13178 // then paste the line before the current line of the buffer.
13179 let column = selection.start.to_point(&snapshot).column as usize;
13180 let line_start = selection.start - column;
13181 (line_start..line_start, Cow::Borrowed(to_insert))
13182 } else {
13183 let language = snapshot.language_at(selection.head());
13184 let range = selection.range();
13185 if let Some(language) = language
13186 && language.name() == "Markdown".into()
13187 {
13188 edit_for_markdown_paste(
13189 &snapshot,
13190 range,
13191 to_insert,
13192 url::Url::parse(to_insert).ok(),
13193 )
13194 } else {
13195 (range, Cow::Borrowed(to_insert))
13196 }
13197 };
13198
13199 edits.push((range, to_insert));
13200 original_indent_columns.push(original_indent_column);
13201 }
13202 drop(snapshot);
13203
13204 buffer.edit(
13205 edits,
13206 if auto_indent_on_paste {
13207 Some(AutoindentMode::Block {
13208 original_indent_columns,
13209 })
13210 } else {
13211 None
13212 },
13213 cx,
13214 );
13215 });
13216
13217 let selections = this
13218 .selections
13219 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13220 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13221 } else {
13222 let url = url::Url::parse(&clipboard_text).ok();
13223
13224 let auto_indent_mode = if !clipboard_text.is_empty() {
13225 Some(AutoindentMode::Block {
13226 original_indent_columns: Vec::new(),
13227 })
13228 } else {
13229 None
13230 };
13231
13232 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13233 let snapshot = buffer.snapshot(cx);
13234
13235 let anchors = old_selections
13236 .iter()
13237 .map(|s| {
13238 let anchor = snapshot.anchor_after(s.head());
13239 s.map(|_| anchor)
13240 })
13241 .collect::<Vec<_>>();
13242
13243 let mut edits = Vec::new();
13244
13245 for selection in old_selections.iter() {
13246 let language = snapshot.language_at(selection.head());
13247 let range = selection.range();
13248
13249 let (edit_range, edit_text) = if let Some(language) = language
13250 && language.name() == "Markdown".into()
13251 {
13252 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13253 } else {
13254 (range, clipboard_text.clone())
13255 };
13256
13257 edits.push((edit_range, edit_text));
13258 }
13259
13260 drop(snapshot);
13261 buffer.edit(edits, auto_indent_mode, cx);
13262
13263 anchors
13264 });
13265
13266 this.change_selections(Default::default(), window, cx, |s| {
13267 s.select_anchors(selection_anchors);
13268 });
13269 }
13270
13271 // 🤔 | .. | show_in_menu |
13272 // | .. | true true
13273 // | had_edit_prediction | false true
13274
13275 let trigger_in_words =
13276 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13277
13278 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13279 });
13280 }
13281
13282 pub fn diff_clipboard_with_selection(
13283 &mut self,
13284 _: &DiffClipboardWithSelection,
13285 window: &mut Window,
13286 cx: &mut Context<Self>,
13287 ) {
13288 let selections = self
13289 .selections
13290 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13291
13292 if selections.is_empty() {
13293 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13294 return;
13295 };
13296
13297 let clipboard_text = match cx.read_from_clipboard() {
13298 Some(item) => match item.entries().first() {
13299 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13300 _ => None,
13301 },
13302 None => None,
13303 };
13304
13305 let Some(clipboard_text) = clipboard_text else {
13306 log::warn!("Clipboard doesn't contain text.");
13307 return;
13308 };
13309
13310 window.dispatch_action(
13311 Box::new(DiffClipboardWithSelectionData {
13312 clipboard_text,
13313 editor: cx.entity(),
13314 }),
13315 cx,
13316 );
13317 }
13318
13319 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13320 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13321 if let Some(item) = cx.read_from_clipboard() {
13322 let entries = item.entries();
13323
13324 match entries.first() {
13325 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13326 // of all the pasted entries.
13327 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13328 .do_paste(
13329 clipboard_string.text(),
13330 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13331 true,
13332 window,
13333 cx,
13334 ),
13335 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13336 }
13337 }
13338 }
13339
13340 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13341 if self.read_only(cx) {
13342 return;
13343 }
13344
13345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13346
13347 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13348 if let Some((selections, _)) =
13349 self.selection_history.transaction(transaction_id).cloned()
13350 {
13351 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13352 s.select_anchors(selections.to_vec());
13353 });
13354 } else {
13355 log::error!(
13356 "No entry in selection_history found for undo. \
13357 This may correspond to a bug where undo does not update the selection. \
13358 If this is occurring, please add details to \
13359 https://github.com/zed-industries/zed/issues/22692"
13360 );
13361 }
13362 self.request_autoscroll(Autoscroll::fit(), cx);
13363 self.unmark_text(window, cx);
13364 self.refresh_edit_prediction(true, false, window, cx);
13365 cx.emit(EditorEvent::Edited { transaction_id });
13366 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13367 }
13368 }
13369
13370 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13371 if self.read_only(cx) {
13372 return;
13373 }
13374
13375 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13376
13377 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13378 if let Some((_, Some(selections))) =
13379 self.selection_history.transaction(transaction_id).cloned()
13380 {
13381 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13382 s.select_anchors(selections.to_vec());
13383 });
13384 } else {
13385 log::error!(
13386 "No entry in selection_history found for redo. \
13387 This may correspond to a bug where undo does not update the selection. \
13388 If this is occurring, please add details to \
13389 https://github.com/zed-industries/zed/issues/22692"
13390 );
13391 }
13392 self.request_autoscroll(Autoscroll::fit(), cx);
13393 self.unmark_text(window, cx);
13394 self.refresh_edit_prediction(true, false, window, cx);
13395 cx.emit(EditorEvent::Edited { transaction_id });
13396 }
13397 }
13398
13399 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13400 self.buffer
13401 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13402 }
13403
13404 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13405 self.buffer
13406 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13407 }
13408
13409 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13410 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13411 self.change_selections(Default::default(), window, cx, |s| {
13412 s.move_with(|map, selection| {
13413 let cursor = if selection.is_empty() {
13414 movement::left(map, selection.start)
13415 } else {
13416 selection.start
13417 };
13418 selection.collapse_to(cursor, SelectionGoal::None);
13419 });
13420 })
13421 }
13422
13423 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13424 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13425 self.change_selections(Default::default(), window, cx, |s| {
13426 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13427 })
13428 }
13429
13430 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13431 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13432 self.change_selections(Default::default(), window, cx, |s| {
13433 s.move_with(|map, selection| {
13434 let cursor = if selection.is_empty() {
13435 movement::right(map, selection.end)
13436 } else {
13437 selection.end
13438 };
13439 selection.collapse_to(cursor, SelectionGoal::None)
13440 });
13441 })
13442 }
13443
13444 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13445 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13446 self.change_selections(Default::default(), window, cx, |s| {
13447 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13448 });
13449 }
13450
13451 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13452 if self.take_rename(true, window, cx).is_some() {
13453 return;
13454 }
13455
13456 if self.mode.is_single_line() {
13457 cx.propagate();
13458 return;
13459 }
13460
13461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13462
13463 let text_layout_details = &self.text_layout_details(window);
13464 let selection_count = self.selections.count();
13465 let first_selection = self.selections.first_anchor();
13466
13467 self.change_selections(Default::default(), window, cx, |s| {
13468 s.move_with(|map, selection| {
13469 if !selection.is_empty() {
13470 selection.goal = SelectionGoal::None;
13471 }
13472 let (cursor, goal) = movement::up(
13473 map,
13474 selection.start,
13475 selection.goal,
13476 false,
13477 text_layout_details,
13478 );
13479 selection.collapse_to(cursor, goal);
13480 });
13481 });
13482
13483 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13484 {
13485 cx.propagate();
13486 }
13487 }
13488
13489 pub fn move_up_by_lines(
13490 &mut self,
13491 action: &MoveUpByLines,
13492 window: &mut Window,
13493 cx: &mut Context<Self>,
13494 ) {
13495 if self.take_rename(true, window, cx).is_some() {
13496 return;
13497 }
13498
13499 if self.mode.is_single_line() {
13500 cx.propagate();
13501 return;
13502 }
13503
13504 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13505
13506 let text_layout_details = &self.text_layout_details(window);
13507
13508 self.change_selections(Default::default(), window, cx, |s| {
13509 s.move_with(|map, selection| {
13510 if !selection.is_empty() {
13511 selection.goal = SelectionGoal::None;
13512 }
13513 let (cursor, goal) = movement::up_by_rows(
13514 map,
13515 selection.start,
13516 action.lines,
13517 selection.goal,
13518 false,
13519 text_layout_details,
13520 );
13521 selection.collapse_to(cursor, goal);
13522 });
13523 })
13524 }
13525
13526 pub fn move_down_by_lines(
13527 &mut self,
13528 action: &MoveDownByLines,
13529 window: &mut Window,
13530 cx: &mut Context<Self>,
13531 ) {
13532 if self.take_rename(true, window, cx).is_some() {
13533 return;
13534 }
13535
13536 if self.mode.is_single_line() {
13537 cx.propagate();
13538 return;
13539 }
13540
13541 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13542
13543 let text_layout_details = &self.text_layout_details(window);
13544
13545 self.change_selections(Default::default(), window, cx, |s| {
13546 s.move_with(|map, selection| {
13547 if !selection.is_empty() {
13548 selection.goal = SelectionGoal::None;
13549 }
13550 let (cursor, goal) = movement::down_by_rows(
13551 map,
13552 selection.start,
13553 action.lines,
13554 selection.goal,
13555 false,
13556 text_layout_details,
13557 );
13558 selection.collapse_to(cursor, goal);
13559 });
13560 })
13561 }
13562
13563 pub fn select_down_by_lines(
13564 &mut self,
13565 action: &SelectDownByLines,
13566 window: &mut Window,
13567 cx: &mut Context<Self>,
13568 ) {
13569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13570 let text_layout_details = &self.text_layout_details(window);
13571 self.change_selections(Default::default(), window, cx, |s| {
13572 s.move_heads_with(|map, head, goal| {
13573 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13574 })
13575 })
13576 }
13577
13578 pub fn select_up_by_lines(
13579 &mut self,
13580 action: &SelectUpByLines,
13581 window: &mut Window,
13582 cx: &mut Context<Self>,
13583 ) {
13584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13585 let text_layout_details = &self.text_layout_details(window);
13586 self.change_selections(Default::default(), window, cx, |s| {
13587 s.move_heads_with(|map, head, goal| {
13588 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13589 })
13590 })
13591 }
13592
13593 pub fn select_page_up(
13594 &mut self,
13595 _: &SelectPageUp,
13596 window: &mut Window,
13597 cx: &mut Context<Self>,
13598 ) {
13599 let Some(row_count) = self.visible_row_count() else {
13600 return;
13601 };
13602
13603 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13604
13605 let text_layout_details = &self.text_layout_details(window);
13606
13607 self.change_selections(Default::default(), window, cx, |s| {
13608 s.move_heads_with(|map, head, goal| {
13609 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13610 })
13611 })
13612 }
13613
13614 pub fn move_page_up(
13615 &mut self,
13616 action: &MovePageUp,
13617 window: &mut Window,
13618 cx: &mut Context<Self>,
13619 ) {
13620 if self.take_rename(true, window, cx).is_some() {
13621 return;
13622 }
13623
13624 if self
13625 .context_menu
13626 .borrow_mut()
13627 .as_mut()
13628 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13629 .unwrap_or(false)
13630 {
13631 return;
13632 }
13633
13634 if matches!(self.mode, EditorMode::SingleLine) {
13635 cx.propagate();
13636 return;
13637 }
13638
13639 let Some(row_count) = self.visible_row_count() else {
13640 return;
13641 };
13642
13643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13644
13645 let effects = if action.center_cursor {
13646 SelectionEffects::scroll(Autoscroll::center())
13647 } else {
13648 SelectionEffects::default()
13649 };
13650
13651 let text_layout_details = &self.text_layout_details(window);
13652
13653 self.change_selections(effects, window, cx, |s| {
13654 s.move_with(|map, selection| {
13655 if !selection.is_empty() {
13656 selection.goal = SelectionGoal::None;
13657 }
13658 let (cursor, goal) = movement::up_by_rows(
13659 map,
13660 selection.end,
13661 row_count,
13662 selection.goal,
13663 false,
13664 text_layout_details,
13665 );
13666 selection.collapse_to(cursor, goal);
13667 });
13668 });
13669 }
13670
13671 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13672 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13673 let text_layout_details = &self.text_layout_details(window);
13674 self.change_selections(Default::default(), window, cx, |s| {
13675 s.move_heads_with(|map, head, goal| {
13676 movement::up(map, head, goal, false, text_layout_details)
13677 })
13678 })
13679 }
13680
13681 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13682 self.take_rename(true, window, cx);
13683
13684 if self.mode.is_single_line() {
13685 cx.propagate();
13686 return;
13687 }
13688
13689 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13690
13691 let text_layout_details = &self.text_layout_details(window);
13692 let selection_count = self.selections.count();
13693 let first_selection = self.selections.first_anchor();
13694
13695 self.change_selections(Default::default(), window, cx, |s| {
13696 s.move_with(|map, selection| {
13697 if !selection.is_empty() {
13698 selection.goal = SelectionGoal::None;
13699 }
13700 let (cursor, goal) = movement::down(
13701 map,
13702 selection.end,
13703 selection.goal,
13704 false,
13705 text_layout_details,
13706 );
13707 selection.collapse_to(cursor, goal);
13708 });
13709 });
13710
13711 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13712 {
13713 cx.propagate();
13714 }
13715 }
13716
13717 pub fn select_page_down(
13718 &mut self,
13719 _: &SelectPageDown,
13720 window: &mut Window,
13721 cx: &mut Context<Self>,
13722 ) {
13723 let Some(row_count) = self.visible_row_count() else {
13724 return;
13725 };
13726
13727 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13728
13729 let text_layout_details = &self.text_layout_details(window);
13730
13731 self.change_selections(Default::default(), window, cx, |s| {
13732 s.move_heads_with(|map, head, goal| {
13733 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13734 })
13735 })
13736 }
13737
13738 pub fn move_page_down(
13739 &mut self,
13740 action: &MovePageDown,
13741 window: &mut Window,
13742 cx: &mut Context<Self>,
13743 ) {
13744 if self.take_rename(true, window, cx).is_some() {
13745 return;
13746 }
13747
13748 if self
13749 .context_menu
13750 .borrow_mut()
13751 .as_mut()
13752 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13753 .unwrap_or(false)
13754 {
13755 return;
13756 }
13757
13758 if matches!(self.mode, EditorMode::SingleLine) {
13759 cx.propagate();
13760 return;
13761 }
13762
13763 let Some(row_count) = self.visible_row_count() else {
13764 return;
13765 };
13766
13767 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13768
13769 let effects = if action.center_cursor {
13770 SelectionEffects::scroll(Autoscroll::center())
13771 } else {
13772 SelectionEffects::default()
13773 };
13774
13775 let text_layout_details = &self.text_layout_details(window);
13776 self.change_selections(effects, window, cx, |s| {
13777 s.move_with(|map, selection| {
13778 if !selection.is_empty() {
13779 selection.goal = SelectionGoal::None;
13780 }
13781 let (cursor, goal) = movement::down_by_rows(
13782 map,
13783 selection.end,
13784 row_count,
13785 selection.goal,
13786 false,
13787 text_layout_details,
13788 );
13789 selection.collapse_to(cursor, goal);
13790 });
13791 });
13792 }
13793
13794 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13795 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13796 let text_layout_details = &self.text_layout_details(window);
13797 self.change_selections(Default::default(), window, cx, |s| {
13798 s.move_heads_with(|map, head, goal| {
13799 movement::down(map, head, goal, false, text_layout_details)
13800 })
13801 });
13802 }
13803
13804 pub fn context_menu_first(
13805 &mut self,
13806 _: &ContextMenuFirst,
13807 window: &mut Window,
13808 cx: &mut Context<Self>,
13809 ) {
13810 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13811 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13812 }
13813 }
13814
13815 pub fn context_menu_prev(
13816 &mut self,
13817 _: &ContextMenuPrevious,
13818 window: &mut Window,
13819 cx: &mut Context<Self>,
13820 ) {
13821 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13822 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13823 }
13824 }
13825
13826 pub fn context_menu_next(
13827 &mut self,
13828 _: &ContextMenuNext,
13829 window: &mut Window,
13830 cx: &mut Context<Self>,
13831 ) {
13832 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13833 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13834 }
13835 }
13836
13837 pub fn context_menu_last(
13838 &mut self,
13839 _: &ContextMenuLast,
13840 window: &mut Window,
13841 cx: &mut Context<Self>,
13842 ) {
13843 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13844 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13845 }
13846 }
13847
13848 pub fn signature_help_prev(
13849 &mut self,
13850 _: &SignatureHelpPrevious,
13851 _: &mut Window,
13852 cx: &mut Context<Self>,
13853 ) {
13854 if let Some(popover) = self.signature_help_state.popover_mut() {
13855 if popover.current_signature == 0 {
13856 popover.current_signature = popover.signatures.len() - 1;
13857 } else {
13858 popover.current_signature -= 1;
13859 }
13860 cx.notify();
13861 }
13862 }
13863
13864 pub fn signature_help_next(
13865 &mut self,
13866 _: &SignatureHelpNext,
13867 _: &mut Window,
13868 cx: &mut Context<Self>,
13869 ) {
13870 if let Some(popover) = self.signature_help_state.popover_mut() {
13871 if popover.current_signature + 1 == popover.signatures.len() {
13872 popover.current_signature = 0;
13873 } else {
13874 popover.current_signature += 1;
13875 }
13876 cx.notify();
13877 }
13878 }
13879
13880 pub fn move_to_previous_word_start(
13881 &mut self,
13882 _: &MoveToPreviousWordStart,
13883 window: &mut Window,
13884 cx: &mut Context<Self>,
13885 ) {
13886 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13887 self.change_selections(Default::default(), window, cx, |s| {
13888 s.move_cursors_with(|map, head, _| {
13889 (
13890 movement::previous_word_start(map, head),
13891 SelectionGoal::None,
13892 )
13893 });
13894 })
13895 }
13896
13897 pub fn move_to_previous_subword_start(
13898 &mut self,
13899 _: &MoveToPreviousSubwordStart,
13900 window: &mut Window,
13901 cx: &mut Context<Self>,
13902 ) {
13903 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13904 self.change_selections(Default::default(), window, cx, |s| {
13905 s.move_cursors_with(|map, head, _| {
13906 (
13907 movement::previous_subword_start(map, head),
13908 SelectionGoal::None,
13909 )
13910 });
13911 })
13912 }
13913
13914 pub fn select_to_previous_word_start(
13915 &mut self,
13916 _: &SelectToPreviousWordStart,
13917 window: &mut Window,
13918 cx: &mut Context<Self>,
13919 ) {
13920 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13921 self.change_selections(Default::default(), window, cx, |s| {
13922 s.move_heads_with(|map, head, _| {
13923 (
13924 movement::previous_word_start(map, head),
13925 SelectionGoal::None,
13926 )
13927 });
13928 })
13929 }
13930
13931 pub fn select_to_previous_subword_start(
13932 &mut self,
13933 _: &SelectToPreviousSubwordStart,
13934 window: &mut Window,
13935 cx: &mut Context<Self>,
13936 ) {
13937 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13938 self.change_selections(Default::default(), window, cx, |s| {
13939 s.move_heads_with(|map, head, _| {
13940 (
13941 movement::previous_subword_start(map, head),
13942 SelectionGoal::None,
13943 )
13944 });
13945 })
13946 }
13947
13948 pub fn delete_to_previous_word_start(
13949 &mut self,
13950 action: &DeleteToPreviousWordStart,
13951 window: &mut Window,
13952 cx: &mut Context<Self>,
13953 ) {
13954 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13955 self.transact(window, cx, |this, window, cx| {
13956 this.select_autoclose_pair(window, cx);
13957 this.change_selections(Default::default(), window, cx, |s| {
13958 s.move_with(|map, selection| {
13959 if selection.is_empty() {
13960 let mut cursor = if action.ignore_newlines {
13961 movement::previous_word_start(map, selection.head())
13962 } else {
13963 movement::previous_word_start_or_newline(map, selection.head())
13964 };
13965 cursor = movement::adjust_greedy_deletion(
13966 map,
13967 selection.head(),
13968 cursor,
13969 action.ignore_brackets,
13970 );
13971 selection.set_head(cursor, SelectionGoal::None);
13972 }
13973 });
13974 });
13975 this.insert("", window, cx);
13976 });
13977 }
13978
13979 pub fn delete_to_previous_subword_start(
13980 &mut self,
13981 _: &DeleteToPreviousSubwordStart,
13982 window: &mut Window,
13983 cx: &mut Context<Self>,
13984 ) {
13985 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13986 self.transact(window, cx, |this, window, cx| {
13987 this.select_autoclose_pair(window, cx);
13988 this.change_selections(Default::default(), window, cx, |s| {
13989 s.move_with(|map, selection| {
13990 if selection.is_empty() {
13991 let mut cursor = movement::previous_subword_start(map, selection.head());
13992 cursor =
13993 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13994 selection.set_head(cursor, SelectionGoal::None);
13995 }
13996 });
13997 });
13998 this.insert("", window, cx);
13999 });
14000 }
14001
14002 pub fn move_to_next_word_end(
14003 &mut self,
14004 _: &MoveToNextWordEnd,
14005 window: &mut Window,
14006 cx: &mut Context<Self>,
14007 ) {
14008 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14009 self.change_selections(Default::default(), window, cx, |s| {
14010 s.move_cursors_with(|map, head, _| {
14011 (movement::next_word_end(map, head), SelectionGoal::None)
14012 });
14013 })
14014 }
14015
14016 pub fn move_to_next_subword_end(
14017 &mut self,
14018 _: &MoveToNextSubwordEnd,
14019 window: &mut Window,
14020 cx: &mut Context<Self>,
14021 ) {
14022 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14023 self.change_selections(Default::default(), window, cx, |s| {
14024 s.move_cursors_with(|map, head, _| {
14025 (movement::next_subword_end(map, head), SelectionGoal::None)
14026 });
14027 })
14028 }
14029
14030 pub fn select_to_next_word_end(
14031 &mut self,
14032 _: &SelectToNextWordEnd,
14033 window: &mut Window,
14034 cx: &mut Context<Self>,
14035 ) {
14036 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14037 self.change_selections(Default::default(), window, cx, |s| {
14038 s.move_heads_with(|map, head, _| {
14039 (movement::next_word_end(map, head), SelectionGoal::None)
14040 });
14041 })
14042 }
14043
14044 pub fn select_to_next_subword_end(
14045 &mut self,
14046 _: &SelectToNextSubwordEnd,
14047 window: &mut Window,
14048 cx: &mut Context<Self>,
14049 ) {
14050 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14051 self.change_selections(Default::default(), window, cx, |s| {
14052 s.move_heads_with(|map, head, _| {
14053 (movement::next_subword_end(map, head), SelectionGoal::None)
14054 });
14055 })
14056 }
14057
14058 pub fn delete_to_next_word_end(
14059 &mut self,
14060 action: &DeleteToNextWordEnd,
14061 window: &mut Window,
14062 cx: &mut Context<Self>,
14063 ) {
14064 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14065 self.transact(window, cx, |this, window, cx| {
14066 this.change_selections(Default::default(), window, cx, |s| {
14067 s.move_with(|map, selection| {
14068 if selection.is_empty() {
14069 let mut cursor = if action.ignore_newlines {
14070 movement::next_word_end(map, selection.head())
14071 } else {
14072 movement::next_word_end_or_newline(map, selection.head())
14073 };
14074 cursor = movement::adjust_greedy_deletion(
14075 map,
14076 selection.head(),
14077 cursor,
14078 action.ignore_brackets,
14079 );
14080 selection.set_head(cursor, SelectionGoal::None);
14081 }
14082 });
14083 });
14084 this.insert("", window, cx);
14085 });
14086 }
14087
14088 pub fn delete_to_next_subword_end(
14089 &mut self,
14090 _: &DeleteToNextSubwordEnd,
14091 window: &mut Window,
14092 cx: &mut Context<Self>,
14093 ) {
14094 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14095 self.transact(window, cx, |this, window, cx| {
14096 this.change_selections(Default::default(), window, cx, |s| {
14097 s.move_with(|map, selection| {
14098 if selection.is_empty() {
14099 let mut cursor = movement::next_subword_end(map, selection.head());
14100 cursor =
14101 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14102 selection.set_head(cursor, SelectionGoal::None);
14103 }
14104 });
14105 });
14106 this.insert("", window, cx);
14107 });
14108 }
14109
14110 pub fn move_to_beginning_of_line(
14111 &mut self,
14112 action: &MoveToBeginningOfLine,
14113 window: &mut Window,
14114 cx: &mut Context<Self>,
14115 ) {
14116 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14117 self.change_selections(Default::default(), window, cx, |s| {
14118 s.move_cursors_with(|map, head, _| {
14119 (
14120 movement::indented_line_beginning(
14121 map,
14122 head,
14123 action.stop_at_soft_wraps,
14124 action.stop_at_indent,
14125 ),
14126 SelectionGoal::None,
14127 )
14128 });
14129 })
14130 }
14131
14132 pub fn select_to_beginning_of_line(
14133 &mut self,
14134 action: &SelectToBeginningOfLine,
14135 window: &mut Window,
14136 cx: &mut Context<Self>,
14137 ) {
14138 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14139 self.change_selections(Default::default(), window, cx, |s| {
14140 s.move_heads_with(|map, head, _| {
14141 (
14142 movement::indented_line_beginning(
14143 map,
14144 head,
14145 action.stop_at_soft_wraps,
14146 action.stop_at_indent,
14147 ),
14148 SelectionGoal::None,
14149 )
14150 });
14151 });
14152 }
14153
14154 pub fn delete_to_beginning_of_line(
14155 &mut self,
14156 action: &DeleteToBeginningOfLine,
14157 window: &mut Window,
14158 cx: &mut Context<Self>,
14159 ) {
14160 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14161 self.transact(window, cx, |this, window, cx| {
14162 this.change_selections(Default::default(), window, cx, |s| {
14163 s.move_with(|_, selection| {
14164 selection.reversed = true;
14165 });
14166 });
14167
14168 this.select_to_beginning_of_line(
14169 &SelectToBeginningOfLine {
14170 stop_at_soft_wraps: false,
14171 stop_at_indent: action.stop_at_indent,
14172 },
14173 window,
14174 cx,
14175 );
14176 this.backspace(&Backspace, window, cx);
14177 });
14178 }
14179
14180 pub fn move_to_end_of_line(
14181 &mut self,
14182 action: &MoveToEndOfLine,
14183 window: &mut Window,
14184 cx: &mut Context<Self>,
14185 ) {
14186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14187 self.change_selections(Default::default(), window, cx, |s| {
14188 s.move_cursors_with(|map, head, _| {
14189 (
14190 movement::line_end(map, head, action.stop_at_soft_wraps),
14191 SelectionGoal::None,
14192 )
14193 });
14194 })
14195 }
14196
14197 pub fn select_to_end_of_line(
14198 &mut self,
14199 action: &SelectToEndOfLine,
14200 window: &mut Window,
14201 cx: &mut Context<Self>,
14202 ) {
14203 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14204 self.change_selections(Default::default(), window, cx, |s| {
14205 s.move_heads_with(|map, head, _| {
14206 (
14207 movement::line_end(map, head, action.stop_at_soft_wraps),
14208 SelectionGoal::None,
14209 )
14210 });
14211 })
14212 }
14213
14214 pub fn delete_to_end_of_line(
14215 &mut self,
14216 _: &DeleteToEndOfLine,
14217 window: &mut Window,
14218 cx: &mut Context<Self>,
14219 ) {
14220 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14221 self.transact(window, cx, |this, window, cx| {
14222 this.select_to_end_of_line(
14223 &SelectToEndOfLine {
14224 stop_at_soft_wraps: false,
14225 },
14226 window,
14227 cx,
14228 );
14229 this.delete(&Delete, window, cx);
14230 });
14231 }
14232
14233 pub fn cut_to_end_of_line(
14234 &mut self,
14235 action: &CutToEndOfLine,
14236 window: &mut Window,
14237 cx: &mut Context<Self>,
14238 ) {
14239 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14240 self.transact(window, cx, |this, window, cx| {
14241 this.select_to_end_of_line(
14242 &SelectToEndOfLine {
14243 stop_at_soft_wraps: false,
14244 },
14245 window,
14246 cx,
14247 );
14248 if !action.stop_at_newlines {
14249 this.change_selections(Default::default(), window, cx, |s| {
14250 s.move_with(|_, sel| {
14251 if sel.is_empty() {
14252 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14253 }
14254 });
14255 });
14256 }
14257 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14258 let item = this.cut_common(false, window, cx);
14259 cx.write_to_clipboard(item);
14260 });
14261 }
14262
14263 pub fn move_to_start_of_paragraph(
14264 &mut self,
14265 _: &MoveToStartOfParagraph,
14266 window: &mut Window,
14267 cx: &mut Context<Self>,
14268 ) {
14269 if matches!(self.mode, EditorMode::SingleLine) {
14270 cx.propagate();
14271 return;
14272 }
14273 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14274 self.change_selections(Default::default(), window, cx, |s| {
14275 s.move_with(|map, selection| {
14276 selection.collapse_to(
14277 movement::start_of_paragraph(map, selection.head(), 1),
14278 SelectionGoal::None,
14279 )
14280 });
14281 })
14282 }
14283
14284 pub fn move_to_end_of_paragraph(
14285 &mut self,
14286 _: &MoveToEndOfParagraph,
14287 window: &mut Window,
14288 cx: &mut Context<Self>,
14289 ) {
14290 if matches!(self.mode, EditorMode::SingleLine) {
14291 cx.propagate();
14292 return;
14293 }
14294 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14295 self.change_selections(Default::default(), window, cx, |s| {
14296 s.move_with(|map, selection| {
14297 selection.collapse_to(
14298 movement::end_of_paragraph(map, selection.head(), 1),
14299 SelectionGoal::None,
14300 )
14301 });
14302 })
14303 }
14304
14305 pub fn select_to_start_of_paragraph(
14306 &mut self,
14307 _: &SelectToStartOfParagraph,
14308 window: &mut Window,
14309 cx: &mut Context<Self>,
14310 ) {
14311 if matches!(self.mode, EditorMode::SingleLine) {
14312 cx.propagate();
14313 return;
14314 }
14315 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14316 self.change_selections(Default::default(), window, cx, |s| {
14317 s.move_heads_with(|map, head, _| {
14318 (
14319 movement::start_of_paragraph(map, head, 1),
14320 SelectionGoal::None,
14321 )
14322 });
14323 })
14324 }
14325
14326 pub fn select_to_end_of_paragraph(
14327 &mut self,
14328 _: &SelectToEndOfParagraph,
14329 window: &mut Window,
14330 cx: &mut Context<Self>,
14331 ) {
14332 if matches!(self.mode, EditorMode::SingleLine) {
14333 cx.propagate();
14334 return;
14335 }
14336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14337 self.change_selections(Default::default(), window, cx, |s| {
14338 s.move_heads_with(|map, head, _| {
14339 (
14340 movement::end_of_paragraph(map, head, 1),
14341 SelectionGoal::None,
14342 )
14343 });
14344 })
14345 }
14346
14347 pub fn move_to_start_of_excerpt(
14348 &mut self,
14349 _: &MoveToStartOfExcerpt,
14350 window: &mut Window,
14351 cx: &mut Context<Self>,
14352 ) {
14353 if matches!(self.mode, EditorMode::SingleLine) {
14354 cx.propagate();
14355 return;
14356 }
14357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14358 self.change_selections(Default::default(), window, cx, |s| {
14359 s.move_with(|map, selection| {
14360 selection.collapse_to(
14361 movement::start_of_excerpt(
14362 map,
14363 selection.head(),
14364 workspace::searchable::Direction::Prev,
14365 ),
14366 SelectionGoal::None,
14367 )
14368 });
14369 })
14370 }
14371
14372 pub fn move_to_start_of_next_excerpt(
14373 &mut self,
14374 _: &MoveToStartOfNextExcerpt,
14375 window: &mut Window,
14376 cx: &mut Context<Self>,
14377 ) {
14378 if matches!(self.mode, EditorMode::SingleLine) {
14379 cx.propagate();
14380 return;
14381 }
14382
14383 self.change_selections(Default::default(), window, cx, |s| {
14384 s.move_with(|map, selection| {
14385 selection.collapse_to(
14386 movement::start_of_excerpt(
14387 map,
14388 selection.head(),
14389 workspace::searchable::Direction::Next,
14390 ),
14391 SelectionGoal::None,
14392 )
14393 });
14394 })
14395 }
14396
14397 pub fn move_to_end_of_excerpt(
14398 &mut self,
14399 _: &MoveToEndOfExcerpt,
14400 window: &mut Window,
14401 cx: &mut Context<Self>,
14402 ) {
14403 if matches!(self.mode, EditorMode::SingleLine) {
14404 cx.propagate();
14405 return;
14406 }
14407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14408 self.change_selections(Default::default(), window, cx, |s| {
14409 s.move_with(|map, selection| {
14410 selection.collapse_to(
14411 movement::end_of_excerpt(
14412 map,
14413 selection.head(),
14414 workspace::searchable::Direction::Next,
14415 ),
14416 SelectionGoal::None,
14417 )
14418 });
14419 })
14420 }
14421
14422 pub fn move_to_end_of_previous_excerpt(
14423 &mut self,
14424 _: &MoveToEndOfPreviousExcerpt,
14425 window: &mut Window,
14426 cx: &mut Context<Self>,
14427 ) {
14428 if matches!(self.mode, EditorMode::SingleLine) {
14429 cx.propagate();
14430 return;
14431 }
14432 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14433 self.change_selections(Default::default(), window, cx, |s| {
14434 s.move_with(|map, selection| {
14435 selection.collapse_to(
14436 movement::end_of_excerpt(
14437 map,
14438 selection.head(),
14439 workspace::searchable::Direction::Prev,
14440 ),
14441 SelectionGoal::None,
14442 )
14443 });
14444 })
14445 }
14446
14447 pub fn select_to_start_of_excerpt(
14448 &mut self,
14449 _: &SelectToStartOfExcerpt,
14450 window: &mut Window,
14451 cx: &mut Context<Self>,
14452 ) {
14453 if matches!(self.mode, EditorMode::SingleLine) {
14454 cx.propagate();
14455 return;
14456 }
14457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14458 self.change_selections(Default::default(), window, cx, |s| {
14459 s.move_heads_with(|map, head, _| {
14460 (
14461 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14462 SelectionGoal::None,
14463 )
14464 });
14465 })
14466 }
14467
14468 pub fn select_to_start_of_next_excerpt(
14469 &mut self,
14470 _: &SelectToStartOfNextExcerpt,
14471 window: &mut Window,
14472 cx: &mut Context<Self>,
14473 ) {
14474 if matches!(self.mode, EditorMode::SingleLine) {
14475 cx.propagate();
14476 return;
14477 }
14478 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14479 self.change_selections(Default::default(), window, cx, |s| {
14480 s.move_heads_with(|map, head, _| {
14481 (
14482 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14483 SelectionGoal::None,
14484 )
14485 });
14486 })
14487 }
14488
14489 pub fn select_to_end_of_excerpt(
14490 &mut self,
14491 _: &SelectToEndOfExcerpt,
14492 window: &mut Window,
14493 cx: &mut Context<Self>,
14494 ) {
14495 if matches!(self.mode, EditorMode::SingleLine) {
14496 cx.propagate();
14497 return;
14498 }
14499 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14500 self.change_selections(Default::default(), window, cx, |s| {
14501 s.move_heads_with(|map, head, _| {
14502 (
14503 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14504 SelectionGoal::None,
14505 )
14506 });
14507 })
14508 }
14509
14510 pub fn select_to_end_of_previous_excerpt(
14511 &mut self,
14512 _: &SelectToEndOfPreviousExcerpt,
14513 window: &mut Window,
14514 cx: &mut Context<Self>,
14515 ) {
14516 if matches!(self.mode, EditorMode::SingleLine) {
14517 cx.propagate();
14518 return;
14519 }
14520 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14521 self.change_selections(Default::default(), window, cx, |s| {
14522 s.move_heads_with(|map, head, _| {
14523 (
14524 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14525 SelectionGoal::None,
14526 )
14527 });
14528 })
14529 }
14530
14531 pub fn move_to_beginning(
14532 &mut self,
14533 _: &MoveToBeginning,
14534 window: &mut Window,
14535 cx: &mut Context<Self>,
14536 ) {
14537 if matches!(self.mode, EditorMode::SingleLine) {
14538 cx.propagate();
14539 return;
14540 }
14541 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14542 self.change_selections(Default::default(), window, cx, |s| {
14543 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14544 });
14545 }
14546
14547 pub fn select_to_beginning(
14548 &mut self,
14549 _: &SelectToBeginning,
14550 window: &mut Window,
14551 cx: &mut Context<Self>,
14552 ) {
14553 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14554 selection.set_head(Point::zero(), SelectionGoal::None);
14555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14556 self.change_selections(Default::default(), window, cx, |s| {
14557 s.select(vec![selection]);
14558 });
14559 }
14560
14561 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14562 if matches!(self.mode, EditorMode::SingleLine) {
14563 cx.propagate();
14564 return;
14565 }
14566 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14567 let cursor = self.buffer.read(cx).read(cx).len();
14568 self.change_selections(Default::default(), window, cx, |s| {
14569 s.select_ranges(vec![cursor..cursor])
14570 });
14571 }
14572
14573 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14574 self.nav_history = nav_history;
14575 }
14576
14577 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14578 self.nav_history.as_ref()
14579 }
14580
14581 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14582 self.push_to_nav_history(
14583 self.selections.newest_anchor().head(),
14584 None,
14585 false,
14586 true,
14587 cx,
14588 );
14589 }
14590
14591 fn push_to_nav_history(
14592 &mut self,
14593 cursor_anchor: Anchor,
14594 new_position: Option<Point>,
14595 is_deactivate: bool,
14596 always: bool,
14597 cx: &mut Context<Self>,
14598 ) {
14599 if let Some(nav_history) = self.nav_history.as_mut() {
14600 let buffer = self.buffer.read(cx).read(cx);
14601 let cursor_position = cursor_anchor.to_point(&buffer);
14602 let scroll_state = self.scroll_manager.anchor();
14603 let scroll_top_row = scroll_state.top_row(&buffer);
14604 drop(buffer);
14605
14606 if let Some(new_position) = new_position {
14607 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14608 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14609 return;
14610 }
14611 }
14612
14613 nav_history.push(
14614 Some(NavigationData {
14615 cursor_anchor,
14616 cursor_position,
14617 scroll_anchor: scroll_state,
14618 scroll_top_row,
14619 }),
14620 cx,
14621 );
14622 cx.emit(EditorEvent::PushedToNavHistory {
14623 anchor: cursor_anchor,
14624 is_deactivate,
14625 })
14626 }
14627 }
14628
14629 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14630 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14631 let buffer = self.buffer.read(cx).snapshot(cx);
14632 let mut selection = self
14633 .selections
14634 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14635 selection.set_head(buffer.len(), SelectionGoal::None);
14636 self.change_selections(Default::default(), window, cx, |s| {
14637 s.select(vec![selection]);
14638 });
14639 }
14640
14641 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14643 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14644 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14645 });
14646 }
14647
14648 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14649 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14650 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14651 let mut selections = self.selections.all::<Point>(&display_map);
14652 let max_point = display_map.buffer_snapshot().max_point();
14653 for selection in &mut selections {
14654 let rows = selection.spanned_rows(true, &display_map);
14655 selection.start = Point::new(rows.start.0, 0);
14656 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14657 selection.reversed = false;
14658 }
14659 self.change_selections(Default::default(), window, cx, |s| {
14660 s.select(selections);
14661 });
14662 }
14663
14664 pub fn split_selection_into_lines(
14665 &mut self,
14666 action: &SplitSelectionIntoLines,
14667 window: &mut Window,
14668 cx: &mut Context<Self>,
14669 ) {
14670 let selections = self
14671 .selections
14672 .all::<Point>(&self.display_snapshot(cx))
14673 .into_iter()
14674 .map(|selection| selection.start..selection.end)
14675 .collect::<Vec<_>>();
14676 self.unfold_ranges(&selections, true, true, cx);
14677
14678 let mut new_selection_ranges = Vec::new();
14679 {
14680 let buffer = self.buffer.read(cx).read(cx);
14681 for selection in selections {
14682 for row in selection.start.row..selection.end.row {
14683 let line_start = Point::new(row, 0);
14684 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14685
14686 if action.keep_selections {
14687 // Keep the selection range for each line
14688 let selection_start = if row == selection.start.row {
14689 selection.start
14690 } else {
14691 line_start
14692 };
14693 new_selection_ranges.push(selection_start..line_end);
14694 } else {
14695 // Collapse to cursor at end of line
14696 new_selection_ranges.push(line_end..line_end);
14697 }
14698 }
14699
14700 let is_multiline_selection = selection.start.row != selection.end.row;
14701 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14702 // so this action feels more ergonomic when paired with other selection operations
14703 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14704 if !should_skip_last {
14705 if action.keep_selections {
14706 if is_multiline_selection {
14707 let line_start = Point::new(selection.end.row, 0);
14708 new_selection_ranges.push(line_start..selection.end);
14709 } else {
14710 new_selection_ranges.push(selection.start..selection.end);
14711 }
14712 } else {
14713 new_selection_ranges.push(selection.end..selection.end);
14714 }
14715 }
14716 }
14717 }
14718 self.change_selections(Default::default(), window, cx, |s| {
14719 s.select_ranges(new_selection_ranges);
14720 });
14721 }
14722
14723 pub fn add_selection_above(
14724 &mut self,
14725 action: &AddSelectionAbove,
14726 window: &mut Window,
14727 cx: &mut Context<Self>,
14728 ) {
14729 self.add_selection(true, action.skip_soft_wrap, window, cx);
14730 }
14731
14732 pub fn add_selection_below(
14733 &mut self,
14734 action: &AddSelectionBelow,
14735 window: &mut Window,
14736 cx: &mut Context<Self>,
14737 ) {
14738 self.add_selection(false, action.skip_soft_wrap, window, cx);
14739 }
14740
14741 fn add_selection(
14742 &mut self,
14743 above: bool,
14744 skip_soft_wrap: bool,
14745 window: &mut Window,
14746 cx: &mut Context<Self>,
14747 ) {
14748 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14749
14750 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14751 let all_selections = self.selections.all::<Point>(&display_map);
14752 let text_layout_details = self.text_layout_details(window);
14753
14754 let (mut columnar_selections, new_selections_to_columnarize) = {
14755 if let Some(state) = self.add_selections_state.as_ref() {
14756 let columnar_selection_ids: HashSet<_> = state
14757 .groups
14758 .iter()
14759 .flat_map(|group| group.stack.iter())
14760 .copied()
14761 .collect();
14762
14763 all_selections
14764 .into_iter()
14765 .partition(|s| columnar_selection_ids.contains(&s.id))
14766 } else {
14767 (Vec::new(), all_selections)
14768 }
14769 };
14770
14771 let mut state = self
14772 .add_selections_state
14773 .take()
14774 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14775
14776 for selection in new_selections_to_columnarize {
14777 let range = selection.display_range(&display_map).sorted();
14778 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14779 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14780 let positions = start_x.min(end_x)..start_x.max(end_x);
14781 let mut stack = Vec::new();
14782 for row in range.start.row().0..=range.end.row().0 {
14783 if let Some(selection) = self.selections.build_columnar_selection(
14784 &display_map,
14785 DisplayRow(row),
14786 &positions,
14787 selection.reversed,
14788 &text_layout_details,
14789 ) {
14790 stack.push(selection.id);
14791 columnar_selections.push(selection);
14792 }
14793 }
14794 if !stack.is_empty() {
14795 if above {
14796 stack.reverse();
14797 }
14798 state.groups.push(AddSelectionsGroup { above, stack });
14799 }
14800 }
14801
14802 let mut final_selections = Vec::new();
14803 let end_row = if above {
14804 DisplayRow(0)
14805 } else {
14806 display_map.max_point().row()
14807 };
14808
14809 let mut last_added_item_per_group = HashMap::default();
14810 for group in state.groups.iter_mut() {
14811 if let Some(last_id) = group.stack.last() {
14812 last_added_item_per_group.insert(*last_id, group);
14813 }
14814 }
14815
14816 for selection in columnar_selections {
14817 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14818 if above == group.above {
14819 let range = selection.display_range(&display_map).sorted();
14820 debug_assert_eq!(range.start.row(), range.end.row());
14821 let mut row = range.start.row();
14822 let positions =
14823 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14824 Pixels::from(start)..Pixels::from(end)
14825 } else {
14826 let start_x =
14827 display_map.x_for_display_point(range.start, &text_layout_details);
14828 let end_x =
14829 display_map.x_for_display_point(range.end, &text_layout_details);
14830 start_x.min(end_x)..start_x.max(end_x)
14831 };
14832
14833 let mut maybe_new_selection = None;
14834 let direction = if above { -1 } else { 1 };
14835
14836 while row != end_row {
14837 if skip_soft_wrap {
14838 row = display_map
14839 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14840 .row();
14841 } else if above {
14842 row.0 -= 1;
14843 } else {
14844 row.0 += 1;
14845 }
14846
14847 if let Some(new_selection) = self.selections.build_columnar_selection(
14848 &display_map,
14849 row,
14850 &positions,
14851 selection.reversed,
14852 &text_layout_details,
14853 ) {
14854 maybe_new_selection = Some(new_selection);
14855 break;
14856 }
14857 }
14858
14859 if let Some(new_selection) = maybe_new_selection {
14860 group.stack.push(new_selection.id);
14861 if above {
14862 final_selections.push(new_selection);
14863 final_selections.push(selection);
14864 } else {
14865 final_selections.push(selection);
14866 final_selections.push(new_selection);
14867 }
14868 } else {
14869 final_selections.push(selection);
14870 }
14871 } else {
14872 group.stack.pop();
14873 }
14874 } else {
14875 final_selections.push(selection);
14876 }
14877 }
14878
14879 self.change_selections(Default::default(), window, cx, |s| {
14880 s.select(final_selections);
14881 });
14882
14883 let final_selection_ids: HashSet<_> = self
14884 .selections
14885 .all::<Point>(&display_map)
14886 .iter()
14887 .map(|s| s.id)
14888 .collect();
14889 state.groups.retain_mut(|group| {
14890 // selections might get merged above so we remove invalid items from stacks
14891 group.stack.retain(|id| final_selection_ids.contains(id));
14892
14893 // single selection in stack can be treated as initial state
14894 group.stack.len() > 1
14895 });
14896
14897 if !state.groups.is_empty() {
14898 self.add_selections_state = Some(state);
14899 }
14900 }
14901
14902 pub fn insert_snippet_at_selections(
14903 &mut self,
14904 action: &InsertSnippet,
14905 window: &mut Window,
14906 cx: &mut Context<Self>,
14907 ) {
14908 self.try_insert_snippet_at_selections(action, window, cx)
14909 .log_err();
14910 }
14911
14912 fn try_insert_snippet_at_selections(
14913 &mut self,
14914 action: &InsertSnippet,
14915 window: &mut Window,
14916 cx: &mut Context<Self>,
14917 ) -> Result<()> {
14918 let insertion_ranges = self
14919 .selections
14920 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
14921 .into_iter()
14922 .map(|selection| selection.range())
14923 .collect_vec();
14924
14925 let snippet = if let Some(snippet_body) = &action.snippet {
14926 if action.language.is_none() && action.name.is_none() {
14927 Snippet::parse(snippet_body)?
14928 } else {
14929 bail!("`snippet` is mutually exclusive with `language` and `name`")
14930 }
14931 } else if let Some(name) = &action.name {
14932 let project = self.project().context("no project")?;
14933 let snippet_store = project.read(cx).snippets().read(cx);
14934 let snippet = snippet_store
14935 .snippets_for(action.language.clone(), cx)
14936 .into_iter()
14937 .find(|snippet| snippet.name == *name)
14938 .context("snippet not found")?;
14939 Snippet::parse(&snippet.body)?
14940 } else {
14941 // todo(andrew): open modal to select snippet
14942 bail!("`name` or `snippet` is required")
14943 };
14944
14945 self.insert_snippet(&insertion_ranges, snippet, window, cx)
14946 }
14947
14948 fn select_match_ranges(
14949 &mut self,
14950 range: Range<MultiBufferOffset>,
14951 reversed: bool,
14952 replace_newest: bool,
14953 auto_scroll: Option<Autoscroll>,
14954 window: &mut Window,
14955 cx: &mut Context<Editor>,
14956 ) {
14957 self.unfold_ranges(
14958 std::slice::from_ref(&range),
14959 false,
14960 auto_scroll.is_some(),
14961 cx,
14962 );
14963 let effects = if let Some(scroll) = auto_scroll {
14964 SelectionEffects::scroll(scroll)
14965 } else {
14966 SelectionEffects::no_scroll()
14967 };
14968 self.change_selections(effects, window, cx, |s| {
14969 if replace_newest {
14970 s.delete(s.newest_anchor().id);
14971 }
14972 if reversed {
14973 s.insert_range(range.end..range.start);
14974 } else {
14975 s.insert_range(range);
14976 }
14977 });
14978 }
14979
14980 pub fn select_next_match_internal(
14981 &mut self,
14982 display_map: &DisplaySnapshot,
14983 replace_newest: bool,
14984 autoscroll: Option<Autoscroll>,
14985 window: &mut Window,
14986 cx: &mut Context<Self>,
14987 ) -> Result<()> {
14988 let buffer = display_map.buffer_snapshot();
14989 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14990 if let Some(mut select_next_state) = self.select_next_state.take() {
14991 let query = &select_next_state.query;
14992 if !select_next_state.done {
14993 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14994 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14995 let mut next_selected_range = None;
14996
14997 let bytes_after_last_selection =
14998 buffer.bytes_in_range(last_selection.end..buffer.len());
14999 let bytes_before_first_selection =
15000 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15001 let query_matches = query
15002 .stream_find_iter(bytes_after_last_selection)
15003 .map(|result| (last_selection.end, result))
15004 .chain(
15005 query
15006 .stream_find_iter(bytes_before_first_selection)
15007 .map(|result| (MultiBufferOffset(0), result)),
15008 );
15009
15010 for (start_offset, query_match) in query_matches {
15011 let query_match = query_match.unwrap(); // can only fail due to I/O
15012 let offset_range =
15013 start_offset + query_match.start()..start_offset + query_match.end();
15014
15015 if !select_next_state.wordwise
15016 || (!buffer.is_inside_word(offset_range.start, None)
15017 && !buffer.is_inside_word(offset_range.end, None))
15018 {
15019 let idx = selections
15020 .partition_point(|selection| selection.end <= offset_range.start);
15021 let overlaps = selections
15022 .get(idx)
15023 .map_or(false, |selection| selection.start < offset_range.end);
15024
15025 if !overlaps {
15026 next_selected_range = Some(offset_range);
15027 break;
15028 }
15029 }
15030 }
15031
15032 if let Some(next_selected_range) = next_selected_range {
15033 self.select_match_ranges(
15034 next_selected_range,
15035 last_selection.reversed,
15036 replace_newest,
15037 autoscroll,
15038 window,
15039 cx,
15040 );
15041 } else {
15042 select_next_state.done = true;
15043 }
15044 }
15045
15046 self.select_next_state = Some(select_next_state);
15047 } else {
15048 let mut only_carets = true;
15049 let mut same_text_selected = true;
15050 let mut selected_text = None;
15051
15052 let mut selections_iter = selections.iter().peekable();
15053 while let Some(selection) = selections_iter.next() {
15054 if selection.start != selection.end {
15055 only_carets = false;
15056 }
15057
15058 if same_text_selected {
15059 if selected_text.is_none() {
15060 selected_text =
15061 Some(buffer.text_for_range(selection.range()).collect::<String>());
15062 }
15063
15064 if let Some(next_selection) = selections_iter.peek() {
15065 if next_selection.len() == selection.len() {
15066 let next_selected_text = buffer
15067 .text_for_range(next_selection.range())
15068 .collect::<String>();
15069 if Some(next_selected_text) != selected_text {
15070 same_text_selected = false;
15071 selected_text = None;
15072 }
15073 } else {
15074 same_text_selected = false;
15075 selected_text = None;
15076 }
15077 }
15078 }
15079 }
15080
15081 if only_carets {
15082 for selection in &mut selections {
15083 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15084 selection.start = word_range.start;
15085 selection.end = word_range.end;
15086 selection.goal = SelectionGoal::None;
15087 selection.reversed = false;
15088 self.select_match_ranges(
15089 selection.start..selection.end,
15090 selection.reversed,
15091 replace_newest,
15092 autoscroll,
15093 window,
15094 cx,
15095 );
15096 }
15097
15098 if selections.len() == 1 {
15099 let selection = selections
15100 .last()
15101 .expect("ensured that there's only one selection");
15102 let query = buffer
15103 .text_for_range(selection.start..selection.end)
15104 .collect::<String>();
15105 let is_empty = query.is_empty();
15106 let select_state = SelectNextState {
15107 query: self.build_query(&[query], cx)?,
15108 wordwise: true,
15109 done: is_empty,
15110 };
15111 self.select_next_state = Some(select_state);
15112 } else {
15113 self.select_next_state = None;
15114 }
15115 } else if let Some(selected_text) = selected_text {
15116 self.select_next_state = Some(SelectNextState {
15117 query: self.build_query(&[selected_text], cx)?,
15118 wordwise: false,
15119 done: false,
15120 });
15121 self.select_next_match_internal(
15122 display_map,
15123 replace_newest,
15124 autoscroll,
15125 window,
15126 cx,
15127 )?;
15128 }
15129 }
15130 Ok(())
15131 }
15132
15133 pub fn select_all_matches(
15134 &mut self,
15135 _action: &SelectAllMatches,
15136 window: &mut Window,
15137 cx: &mut Context<Self>,
15138 ) -> Result<()> {
15139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15140
15141 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15142
15143 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15144 let Some(select_next_state) = self.select_next_state.as_mut() else {
15145 return Ok(());
15146 };
15147 if select_next_state.done {
15148 return Ok(());
15149 }
15150
15151 let mut new_selections = Vec::new();
15152
15153 let reversed = self
15154 .selections
15155 .oldest::<MultiBufferOffset>(&display_map)
15156 .reversed;
15157 let buffer = display_map.buffer_snapshot();
15158 let query_matches = select_next_state
15159 .query
15160 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15161
15162 for query_match in query_matches.into_iter() {
15163 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15164 let offset_range = if reversed {
15165 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15166 } else {
15167 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15168 };
15169
15170 if !select_next_state.wordwise
15171 || (!buffer.is_inside_word(offset_range.start, None)
15172 && !buffer.is_inside_word(offset_range.end, None))
15173 {
15174 new_selections.push(offset_range.start..offset_range.end);
15175 }
15176 }
15177
15178 select_next_state.done = true;
15179
15180 if new_selections.is_empty() {
15181 log::error!("bug: new_selections is empty in select_all_matches");
15182 return Ok(());
15183 }
15184
15185 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15186 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15187 selections.select_ranges(new_selections)
15188 });
15189
15190 Ok(())
15191 }
15192
15193 pub fn select_next(
15194 &mut self,
15195 action: &SelectNext,
15196 window: &mut Window,
15197 cx: &mut Context<Self>,
15198 ) -> Result<()> {
15199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15200 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15201 self.select_next_match_internal(
15202 &display_map,
15203 action.replace_newest,
15204 Some(Autoscroll::newest()),
15205 window,
15206 cx,
15207 )?;
15208 Ok(())
15209 }
15210
15211 pub fn select_previous(
15212 &mut self,
15213 action: &SelectPrevious,
15214 window: &mut Window,
15215 cx: &mut Context<Self>,
15216 ) -> Result<()> {
15217 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15218 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15219 let buffer = display_map.buffer_snapshot();
15220 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15221 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15222 let query = &select_prev_state.query;
15223 if !select_prev_state.done {
15224 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15225 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15226 let mut next_selected_range = None;
15227 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15228 let bytes_before_last_selection =
15229 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15230 let bytes_after_first_selection =
15231 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15232 let query_matches = query
15233 .stream_find_iter(bytes_before_last_selection)
15234 .map(|result| (last_selection.start, result))
15235 .chain(
15236 query
15237 .stream_find_iter(bytes_after_first_selection)
15238 .map(|result| (buffer.len(), result)),
15239 );
15240 for (end_offset, query_match) in query_matches {
15241 let query_match = query_match.unwrap(); // can only fail due to I/O
15242 let offset_range =
15243 end_offset - query_match.end()..end_offset - query_match.start();
15244
15245 if !select_prev_state.wordwise
15246 || (!buffer.is_inside_word(offset_range.start, None)
15247 && !buffer.is_inside_word(offset_range.end, None))
15248 {
15249 next_selected_range = Some(offset_range);
15250 break;
15251 }
15252 }
15253
15254 if let Some(next_selected_range) = next_selected_range {
15255 self.select_match_ranges(
15256 next_selected_range,
15257 last_selection.reversed,
15258 action.replace_newest,
15259 Some(Autoscroll::newest()),
15260 window,
15261 cx,
15262 );
15263 } else {
15264 select_prev_state.done = true;
15265 }
15266 }
15267
15268 self.select_prev_state = Some(select_prev_state);
15269 } else {
15270 let mut only_carets = true;
15271 let mut same_text_selected = true;
15272 let mut selected_text = None;
15273
15274 let mut selections_iter = selections.iter().peekable();
15275 while let Some(selection) = selections_iter.next() {
15276 if selection.start != selection.end {
15277 only_carets = false;
15278 }
15279
15280 if same_text_selected {
15281 if selected_text.is_none() {
15282 selected_text =
15283 Some(buffer.text_for_range(selection.range()).collect::<String>());
15284 }
15285
15286 if let Some(next_selection) = selections_iter.peek() {
15287 if next_selection.len() == selection.len() {
15288 let next_selected_text = buffer
15289 .text_for_range(next_selection.range())
15290 .collect::<String>();
15291 if Some(next_selected_text) != selected_text {
15292 same_text_selected = false;
15293 selected_text = None;
15294 }
15295 } else {
15296 same_text_selected = false;
15297 selected_text = None;
15298 }
15299 }
15300 }
15301 }
15302
15303 if only_carets {
15304 for selection in &mut selections {
15305 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15306 selection.start = word_range.start;
15307 selection.end = word_range.end;
15308 selection.goal = SelectionGoal::None;
15309 selection.reversed = false;
15310 self.select_match_ranges(
15311 selection.start..selection.end,
15312 selection.reversed,
15313 action.replace_newest,
15314 Some(Autoscroll::newest()),
15315 window,
15316 cx,
15317 );
15318 }
15319 if selections.len() == 1 {
15320 let selection = selections
15321 .last()
15322 .expect("ensured that there's only one selection");
15323 let query = buffer
15324 .text_for_range(selection.start..selection.end)
15325 .collect::<String>();
15326 let is_empty = query.is_empty();
15327 let select_state = SelectNextState {
15328 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15329 wordwise: true,
15330 done: is_empty,
15331 };
15332 self.select_prev_state = Some(select_state);
15333 } else {
15334 self.select_prev_state = None;
15335 }
15336 } else if let Some(selected_text) = selected_text {
15337 self.select_prev_state = Some(SelectNextState {
15338 query: self
15339 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15340 wordwise: false,
15341 done: false,
15342 });
15343 self.select_previous(action, window, cx)?;
15344 }
15345 }
15346 Ok(())
15347 }
15348
15349 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15350 /// setting the case sensitivity based on the global
15351 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15352 /// editor's settings.
15353 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15354 where
15355 I: IntoIterator<Item = P>,
15356 P: AsRef<[u8]>,
15357 {
15358 let case_sensitive = self
15359 .select_next_is_case_sensitive
15360 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15361
15362 let mut builder = AhoCorasickBuilder::new();
15363 builder.ascii_case_insensitive(!case_sensitive);
15364 builder.build(patterns)
15365 }
15366
15367 pub fn find_next_match(
15368 &mut self,
15369 _: &FindNextMatch,
15370 window: &mut Window,
15371 cx: &mut Context<Self>,
15372 ) -> Result<()> {
15373 let selections = self.selections.disjoint_anchors_arc();
15374 match selections.first() {
15375 Some(first) if selections.len() >= 2 => {
15376 self.change_selections(Default::default(), window, cx, |s| {
15377 s.select_ranges([first.range()]);
15378 });
15379 }
15380 _ => self.select_next(
15381 &SelectNext {
15382 replace_newest: true,
15383 },
15384 window,
15385 cx,
15386 )?,
15387 }
15388 Ok(())
15389 }
15390
15391 pub fn find_previous_match(
15392 &mut self,
15393 _: &FindPreviousMatch,
15394 window: &mut Window,
15395 cx: &mut Context<Self>,
15396 ) -> Result<()> {
15397 let selections = self.selections.disjoint_anchors_arc();
15398 match selections.last() {
15399 Some(last) if selections.len() >= 2 => {
15400 self.change_selections(Default::default(), window, cx, |s| {
15401 s.select_ranges([last.range()]);
15402 });
15403 }
15404 _ => self.select_previous(
15405 &SelectPrevious {
15406 replace_newest: true,
15407 },
15408 window,
15409 cx,
15410 )?,
15411 }
15412 Ok(())
15413 }
15414
15415 pub fn toggle_comments(
15416 &mut self,
15417 action: &ToggleComments,
15418 window: &mut Window,
15419 cx: &mut Context<Self>,
15420 ) {
15421 if self.read_only(cx) {
15422 return;
15423 }
15424 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15425 let text_layout_details = &self.text_layout_details(window);
15426 self.transact(window, cx, |this, window, cx| {
15427 let mut selections = this
15428 .selections
15429 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15430 let mut edits = Vec::new();
15431 let mut selection_edit_ranges = Vec::new();
15432 let mut last_toggled_row = None;
15433 let snapshot = this.buffer.read(cx).read(cx);
15434 let empty_str: Arc<str> = Arc::default();
15435 let mut suffixes_inserted = Vec::new();
15436 let ignore_indent = action.ignore_indent;
15437
15438 fn comment_prefix_range(
15439 snapshot: &MultiBufferSnapshot,
15440 row: MultiBufferRow,
15441 comment_prefix: &str,
15442 comment_prefix_whitespace: &str,
15443 ignore_indent: bool,
15444 ) -> Range<Point> {
15445 let indent_size = if ignore_indent {
15446 0
15447 } else {
15448 snapshot.indent_size_for_line(row).len
15449 };
15450
15451 let start = Point::new(row.0, indent_size);
15452
15453 let mut line_bytes = snapshot
15454 .bytes_in_range(start..snapshot.max_point())
15455 .flatten()
15456 .copied();
15457
15458 // If this line currently begins with the line comment prefix, then record
15459 // the range containing the prefix.
15460 if line_bytes
15461 .by_ref()
15462 .take(comment_prefix.len())
15463 .eq(comment_prefix.bytes())
15464 {
15465 // Include any whitespace that matches the comment prefix.
15466 let matching_whitespace_len = line_bytes
15467 .zip(comment_prefix_whitespace.bytes())
15468 .take_while(|(a, b)| a == b)
15469 .count() as u32;
15470 let end = Point::new(
15471 start.row,
15472 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15473 );
15474 start..end
15475 } else {
15476 start..start
15477 }
15478 }
15479
15480 fn comment_suffix_range(
15481 snapshot: &MultiBufferSnapshot,
15482 row: MultiBufferRow,
15483 comment_suffix: &str,
15484 comment_suffix_has_leading_space: bool,
15485 ) -> Range<Point> {
15486 let end = Point::new(row.0, snapshot.line_len(row));
15487 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15488
15489 let mut line_end_bytes = snapshot
15490 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15491 .flatten()
15492 .copied();
15493
15494 let leading_space_len = if suffix_start_column > 0
15495 && line_end_bytes.next() == Some(b' ')
15496 && comment_suffix_has_leading_space
15497 {
15498 1
15499 } else {
15500 0
15501 };
15502
15503 // If this line currently begins with the line comment prefix, then record
15504 // the range containing the prefix.
15505 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15506 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15507 start..end
15508 } else {
15509 end..end
15510 }
15511 }
15512
15513 // TODO: Handle selections that cross excerpts
15514 for selection in &mut selections {
15515 let start_column = snapshot
15516 .indent_size_for_line(MultiBufferRow(selection.start.row))
15517 .len;
15518 let language = if let Some(language) =
15519 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15520 {
15521 language
15522 } else {
15523 continue;
15524 };
15525
15526 selection_edit_ranges.clear();
15527
15528 // If multiple selections contain a given row, avoid processing that
15529 // row more than once.
15530 let mut start_row = MultiBufferRow(selection.start.row);
15531 if last_toggled_row == Some(start_row) {
15532 start_row = start_row.next_row();
15533 }
15534 let end_row =
15535 if selection.end.row > selection.start.row && selection.end.column == 0 {
15536 MultiBufferRow(selection.end.row - 1)
15537 } else {
15538 MultiBufferRow(selection.end.row)
15539 };
15540 last_toggled_row = Some(end_row);
15541
15542 if start_row > end_row {
15543 continue;
15544 }
15545
15546 // If the language has line comments, toggle those.
15547 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15548
15549 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15550 if ignore_indent {
15551 full_comment_prefixes = full_comment_prefixes
15552 .into_iter()
15553 .map(|s| Arc::from(s.trim_end()))
15554 .collect();
15555 }
15556
15557 if !full_comment_prefixes.is_empty() {
15558 let first_prefix = full_comment_prefixes
15559 .first()
15560 .expect("prefixes is non-empty");
15561 let prefix_trimmed_lengths = full_comment_prefixes
15562 .iter()
15563 .map(|p| p.trim_end_matches(' ').len())
15564 .collect::<SmallVec<[usize; 4]>>();
15565
15566 let mut all_selection_lines_are_comments = true;
15567
15568 for row in start_row.0..=end_row.0 {
15569 let row = MultiBufferRow(row);
15570 if start_row < end_row && snapshot.is_line_blank(row) {
15571 continue;
15572 }
15573
15574 let prefix_range = full_comment_prefixes
15575 .iter()
15576 .zip(prefix_trimmed_lengths.iter().copied())
15577 .map(|(prefix, trimmed_prefix_len)| {
15578 comment_prefix_range(
15579 snapshot.deref(),
15580 row,
15581 &prefix[..trimmed_prefix_len],
15582 &prefix[trimmed_prefix_len..],
15583 ignore_indent,
15584 )
15585 })
15586 .max_by_key(|range| range.end.column - range.start.column)
15587 .expect("prefixes is non-empty");
15588
15589 if prefix_range.is_empty() {
15590 all_selection_lines_are_comments = false;
15591 }
15592
15593 selection_edit_ranges.push(prefix_range);
15594 }
15595
15596 if all_selection_lines_are_comments {
15597 edits.extend(
15598 selection_edit_ranges
15599 .iter()
15600 .cloned()
15601 .map(|range| (range, empty_str.clone())),
15602 );
15603 } else {
15604 let min_column = selection_edit_ranges
15605 .iter()
15606 .map(|range| range.start.column)
15607 .min()
15608 .unwrap_or(0);
15609 edits.extend(selection_edit_ranges.iter().map(|range| {
15610 let position = Point::new(range.start.row, min_column);
15611 (position..position, first_prefix.clone())
15612 }));
15613 }
15614 } else if let Some(BlockCommentConfig {
15615 start: full_comment_prefix,
15616 end: comment_suffix,
15617 ..
15618 }) = language.block_comment()
15619 {
15620 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15621 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15622 let prefix_range = comment_prefix_range(
15623 snapshot.deref(),
15624 start_row,
15625 comment_prefix,
15626 comment_prefix_whitespace,
15627 ignore_indent,
15628 );
15629 let suffix_range = comment_suffix_range(
15630 snapshot.deref(),
15631 end_row,
15632 comment_suffix.trim_start_matches(' '),
15633 comment_suffix.starts_with(' '),
15634 );
15635
15636 if prefix_range.is_empty() || suffix_range.is_empty() {
15637 edits.push((
15638 prefix_range.start..prefix_range.start,
15639 full_comment_prefix.clone(),
15640 ));
15641 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15642 suffixes_inserted.push((end_row, comment_suffix.len()));
15643 } else {
15644 edits.push((prefix_range, empty_str.clone()));
15645 edits.push((suffix_range, empty_str.clone()));
15646 }
15647 } else {
15648 continue;
15649 }
15650 }
15651
15652 drop(snapshot);
15653 this.buffer.update(cx, |buffer, cx| {
15654 buffer.edit(edits, None, cx);
15655 });
15656
15657 // Adjust selections so that they end before any comment suffixes that
15658 // were inserted.
15659 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15660 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15661 let snapshot = this.buffer.read(cx).read(cx);
15662 for selection in &mut selections {
15663 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15664 match row.cmp(&MultiBufferRow(selection.end.row)) {
15665 Ordering::Less => {
15666 suffixes_inserted.next();
15667 continue;
15668 }
15669 Ordering::Greater => break,
15670 Ordering::Equal => {
15671 if selection.end.column == snapshot.line_len(row) {
15672 if selection.is_empty() {
15673 selection.start.column -= suffix_len as u32;
15674 }
15675 selection.end.column -= suffix_len as u32;
15676 }
15677 break;
15678 }
15679 }
15680 }
15681 }
15682
15683 drop(snapshot);
15684 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15685
15686 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15687 let selections_on_single_row = selections.windows(2).all(|selections| {
15688 selections[0].start.row == selections[1].start.row
15689 && selections[0].end.row == selections[1].end.row
15690 && selections[0].start.row == selections[0].end.row
15691 });
15692 let selections_selecting = selections
15693 .iter()
15694 .any(|selection| selection.start != selection.end);
15695 let advance_downwards = action.advance_downwards
15696 && selections_on_single_row
15697 && !selections_selecting
15698 && !matches!(this.mode, EditorMode::SingleLine);
15699
15700 if advance_downwards {
15701 let snapshot = this.buffer.read(cx).snapshot(cx);
15702
15703 this.change_selections(Default::default(), window, cx, |s| {
15704 s.move_cursors_with(|display_snapshot, display_point, _| {
15705 let mut point = display_point.to_point(display_snapshot);
15706 point.row += 1;
15707 point = snapshot.clip_point(point, Bias::Left);
15708 let display_point = point.to_display_point(display_snapshot);
15709 let goal = SelectionGoal::HorizontalPosition(
15710 display_snapshot
15711 .x_for_display_point(display_point, text_layout_details)
15712 .into(),
15713 );
15714 (display_point, goal)
15715 })
15716 });
15717 }
15718 });
15719 }
15720
15721 pub fn select_enclosing_symbol(
15722 &mut self,
15723 _: &SelectEnclosingSymbol,
15724 window: &mut Window,
15725 cx: &mut Context<Self>,
15726 ) {
15727 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15728
15729 let buffer = self.buffer.read(cx).snapshot(cx);
15730 let old_selections = self
15731 .selections
15732 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15733 .into_boxed_slice();
15734
15735 fn update_selection(
15736 selection: &Selection<MultiBufferOffset>,
15737 buffer_snap: &MultiBufferSnapshot,
15738 ) -> Option<Selection<MultiBufferOffset>> {
15739 let cursor = selection.head();
15740 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15741 for symbol in symbols.iter().rev() {
15742 let start = symbol.range.start.to_offset(buffer_snap);
15743 let end = symbol.range.end.to_offset(buffer_snap);
15744 let new_range = start..end;
15745 if start < selection.start || end > selection.end {
15746 return Some(Selection {
15747 id: selection.id,
15748 start: new_range.start,
15749 end: new_range.end,
15750 goal: SelectionGoal::None,
15751 reversed: selection.reversed,
15752 });
15753 }
15754 }
15755 None
15756 }
15757
15758 let mut selected_larger_symbol = false;
15759 let new_selections = old_selections
15760 .iter()
15761 .map(|selection| match update_selection(selection, &buffer) {
15762 Some(new_selection) => {
15763 if new_selection.range() != selection.range() {
15764 selected_larger_symbol = true;
15765 }
15766 new_selection
15767 }
15768 None => selection.clone(),
15769 })
15770 .collect::<Vec<_>>();
15771
15772 if selected_larger_symbol {
15773 self.change_selections(Default::default(), window, cx, |s| {
15774 s.select(new_selections);
15775 });
15776 }
15777 }
15778
15779 pub fn select_larger_syntax_node(
15780 &mut self,
15781 _: &SelectLargerSyntaxNode,
15782 window: &mut Window,
15783 cx: &mut Context<Self>,
15784 ) {
15785 let Some(visible_row_count) = self.visible_row_count() else {
15786 return;
15787 };
15788 let old_selections: Box<[_]> = self
15789 .selections
15790 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15791 .into();
15792 if old_selections.is_empty() {
15793 return;
15794 }
15795
15796 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15797
15798 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15799 let buffer = self.buffer.read(cx).snapshot(cx);
15800
15801 let mut selected_larger_node = false;
15802 let mut new_selections = old_selections
15803 .iter()
15804 .map(|selection| {
15805 let old_range = selection.start..selection.end;
15806
15807 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15808 // manually select word at selection
15809 if ["string_content", "inline"].contains(&node.kind()) {
15810 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15811 // ignore if word is already selected
15812 if !word_range.is_empty() && old_range != word_range {
15813 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15814 // only select word if start and end point belongs to same word
15815 if word_range == last_word_range {
15816 selected_larger_node = true;
15817 return Selection {
15818 id: selection.id,
15819 start: word_range.start,
15820 end: word_range.end,
15821 goal: SelectionGoal::None,
15822 reversed: selection.reversed,
15823 };
15824 }
15825 }
15826 }
15827 }
15828
15829 let mut new_range = old_range.clone();
15830 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15831 new_range = range;
15832 if !node.is_named() {
15833 continue;
15834 }
15835 if !display_map.intersects_fold(new_range.start)
15836 && !display_map.intersects_fold(new_range.end)
15837 {
15838 break;
15839 }
15840 }
15841
15842 selected_larger_node |= new_range != old_range;
15843 Selection {
15844 id: selection.id,
15845 start: new_range.start,
15846 end: new_range.end,
15847 goal: SelectionGoal::None,
15848 reversed: selection.reversed,
15849 }
15850 })
15851 .collect::<Vec<_>>();
15852
15853 if !selected_larger_node {
15854 return; // don't put this call in the history
15855 }
15856
15857 // scroll based on transformation done to the last selection created by the user
15858 let (last_old, last_new) = old_selections
15859 .last()
15860 .zip(new_selections.last().cloned())
15861 .expect("old_selections isn't empty");
15862
15863 // revert selection
15864 let is_selection_reversed = {
15865 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15866 new_selections.last_mut().expect("checked above").reversed =
15867 should_newest_selection_be_reversed;
15868 should_newest_selection_be_reversed
15869 };
15870
15871 if selected_larger_node {
15872 self.select_syntax_node_history.disable_clearing = true;
15873 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15874 s.select(new_selections.clone());
15875 });
15876 self.select_syntax_node_history.disable_clearing = false;
15877 }
15878
15879 let start_row = last_new.start.to_display_point(&display_map).row().0;
15880 let end_row = last_new.end.to_display_point(&display_map).row().0;
15881 let selection_height = end_row - start_row + 1;
15882 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15883
15884 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15885 let scroll_behavior = if fits_on_the_screen {
15886 self.request_autoscroll(Autoscroll::fit(), cx);
15887 SelectSyntaxNodeScrollBehavior::FitSelection
15888 } else if is_selection_reversed {
15889 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15890 SelectSyntaxNodeScrollBehavior::CursorTop
15891 } else {
15892 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15893 SelectSyntaxNodeScrollBehavior::CursorBottom
15894 };
15895
15896 self.select_syntax_node_history.push((
15897 old_selections,
15898 scroll_behavior,
15899 is_selection_reversed,
15900 ));
15901 }
15902
15903 pub fn select_smaller_syntax_node(
15904 &mut self,
15905 _: &SelectSmallerSyntaxNode,
15906 window: &mut Window,
15907 cx: &mut Context<Self>,
15908 ) {
15909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15910
15911 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15912 self.select_syntax_node_history.pop()
15913 {
15914 if let Some(selection) = selections.last_mut() {
15915 selection.reversed = is_selection_reversed;
15916 }
15917
15918 self.select_syntax_node_history.disable_clearing = true;
15919 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15920 s.select(selections.to_vec());
15921 });
15922 self.select_syntax_node_history.disable_clearing = false;
15923
15924 match scroll_behavior {
15925 SelectSyntaxNodeScrollBehavior::CursorTop => {
15926 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15927 }
15928 SelectSyntaxNodeScrollBehavior::FitSelection => {
15929 self.request_autoscroll(Autoscroll::fit(), cx);
15930 }
15931 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15932 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15933 }
15934 }
15935 }
15936 }
15937
15938 pub fn unwrap_syntax_node(
15939 &mut self,
15940 _: &UnwrapSyntaxNode,
15941 window: &mut Window,
15942 cx: &mut Context<Self>,
15943 ) {
15944 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15945
15946 let buffer = self.buffer.read(cx).snapshot(cx);
15947 let selections = self
15948 .selections
15949 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15950 .into_iter()
15951 // subtracting the offset requires sorting
15952 .sorted_by_key(|i| i.start);
15953
15954 let full_edits = selections
15955 .into_iter()
15956 .filter_map(|selection| {
15957 let child = if selection.is_empty()
15958 && let Some((_, ancestor_range)) =
15959 buffer.syntax_ancestor(selection.start..selection.end)
15960 {
15961 ancestor_range
15962 } else {
15963 selection.range()
15964 };
15965
15966 let mut parent = child.clone();
15967 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15968 parent = ancestor_range;
15969 if parent.start < child.start || parent.end > child.end {
15970 break;
15971 }
15972 }
15973
15974 if parent == child {
15975 return None;
15976 }
15977 let text = buffer.text_for_range(child).collect::<String>();
15978 Some((selection.id, parent, text))
15979 })
15980 .collect::<Vec<_>>();
15981 if full_edits.is_empty() {
15982 return;
15983 }
15984
15985 self.transact(window, cx, |this, window, cx| {
15986 this.buffer.update(cx, |buffer, cx| {
15987 buffer.edit(
15988 full_edits
15989 .iter()
15990 .map(|(_, p, t)| (p.clone(), t.clone()))
15991 .collect::<Vec<_>>(),
15992 None,
15993 cx,
15994 );
15995 });
15996 this.change_selections(Default::default(), window, cx, |s| {
15997 let mut offset = 0;
15998 let mut selections = vec![];
15999 for (id, parent, text) in full_edits {
16000 let start = parent.start - offset;
16001 offset += (parent.end - parent.start) - text.len();
16002 selections.push(Selection {
16003 id,
16004 start,
16005 end: start + text.len(),
16006 reversed: false,
16007 goal: Default::default(),
16008 });
16009 }
16010 s.select(selections);
16011 });
16012 });
16013 }
16014
16015 pub fn select_next_syntax_node(
16016 &mut self,
16017 _: &SelectNextSyntaxNode,
16018 window: &mut Window,
16019 cx: &mut Context<Self>,
16020 ) {
16021 let old_selections: Box<[_]> = self
16022 .selections
16023 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16024 .into();
16025 if old_selections.is_empty() {
16026 return;
16027 }
16028
16029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16030
16031 let buffer = self.buffer.read(cx).snapshot(cx);
16032 let mut selected_sibling = false;
16033
16034 let new_selections = old_selections
16035 .iter()
16036 .map(|selection| {
16037 let old_range = selection.start..selection.end;
16038
16039 let old_range =
16040 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16041 let excerpt = buffer.excerpt_containing(old_range.clone());
16042
16043 if let Some(mut excerpt) = excerpt
16044 && let Some(node) = excerpt
16045 .buffer()
16046 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16047 {
16048 let new_range = excerpt.map_range_from_buffer(
16049 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16050 );
16051 selected_sibling = true;
16052 Selection {
16053 id: selection.id,
16054 start: new_range.start,
16055 end: new_range.end,
16056 goal: SelectionGoal::None,
16057 reversed: selection.reversed,
16058 }
16059 } else {
16060 selection.clone()
16061 }
16062 })
16063 .collect::<Vec<_>>();
16064
16065 if selected_sibling {
16066 self.change_selections(
16067 SelectionEffects::scroll(Autoscroll::fit()),
16068 window,
16069 cx,
16070 |s| {
16071 s.select(new_selections);
16072 },
16073 );
16074 }
16075 }
16076
16077 pub fn select_prev_syntax_node(
16078 &mut self,
16079 _: &SelectPreviousSyntaxNode,
16080 window: &mut Window,
16081 cx: &mut Context<Self>,
16082 ) {
16083 let old_selections: Box<[_]> = self
16084 .selections
16085 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16086 .into();
16087 if old_selections.is_empty() {
16088 return;
16089 }
16090
16091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16092
16093 let buffer = self.buffer.read(cx).snapshot(cx);
16094 let mut selected_sibling = false;
16095
16096 let new_selections = old_selections
16097 .iter()
16098 .map(|selection| {
16099 let old_range = selection.start..selection.end;
16100 let old_range =
16101 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16102 let excerpt = buffer.excerpt_containing(old_range.clone());
16103
16104 if let Some(mut excerpt) = excerpt
16105 && let Some(node) = excerpt
16106 .buffer()
16107 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16108 {
16109 let new_range = excerpt.map_range_from_buffer(
16110 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16111 );
16112 selected_sibling = true;
16113 Selection {
16114 id: selection.id,
16115 start: new_range.start,
16116 end: new_range.end,
16117 goal: SelectionGoal::None,
16118 reversed: selection.reversed,
16119 }
16120 } else {
16121 selection.clone()
16122 }
16123 })
16124 .collect::<Vec<_>>();
16125
16126 if selected_sibling {
16127 self.change_selections(
16128 SelectionEffects::scroll(Autoscroll::fit()),
16129 window,
16130 cx,
16131 |s| {
16132 s.select(new_selections);
16133 },
16134 );
16135 }
16136 }
16137
16138 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16139 if !EditorSettings::get_global(cx).gutter.runnables {
16140 self.clear_tasks();
16141 return Task::ready(());
16142 }
16143 let project = self.project().map(Entity::downgrade);
16144 let task_sources = self.lsp_task_sources(cx);
16145 let multi_buffer = self.buffer.downgrade();
16146 cx.spawn_in(window, async move |editor, cx| {
16147 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16148 let Some(project) = project.and_then(|p| p.upgrade()) else {
16149 return;
16150 };
16151 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16152 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16153 }) else {
16154 return;
16155 };
16156
16157 let hide_runnables = project
16158 .update(cx, |project, _| project.is_via_collab())
16159 .unwrap_or(true);
16160 if hide_runnables {
16161 return;
16162 }
16163 let new_rows =
16164 cx.background_spawn({
16165 let snapshot = display_snapshot.clone();
16166 async move {
16167 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16168 }
16169 })
16170 .await;
16171 let Ok(lsp_tasks) =
16172 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16173 else {
16174 return;
16175 };
16176 let lsp_tasks = lsp_tasks.await;
16177
16178 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16179 lsp_tasks
16180 .into_iter()
16181 .flat_map(|(kind, tasks)| {
16182 tasks.into_iter().filter_map(move |(location, task)| {
16183 Some((kind.clone(), location?, task))
16184 })
16185 })
16186 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16187 let buffer = location.target.buffer;
16188 let buffer_snapshot = buffer.read(cx).snapshot();
16189 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16190 |(excerpt_id, snapshot, _)| {
16191 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16192 display_snapshot
16193 .buffer_snapshot()
16194 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16195 } else {
16196 None
16197 }
16198 },
16199 );
16200 if let Some(offset) = offset {
16201 let task_buffer_range =
16202 location.target.range.to_point(&buffer_snapshot);
16203 let context_buffer_range =
16204 task_buffer_range.to_offset(&buffer_snapshot);
16205 let context_range = BufferOffset(context_buffer_range.start)
16206 ..BufferOffset(context_buffer_range.end);
16207
16208 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16209 .or_insert_with(|| RunnableTasks {
16210 templates: Vec::new(),
16211 offset,
16212 column: task_buffer_range.start.column,
16213 extra_variables: HashMap::default(),
16214 context_range,
16215 })
16216 .templates
16217 .push((kind, task.original_task().clone()));
16218 }
16219
16220 acc
16221 })
16222 }) else {
16223 return;
16224 };
16225
16226 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16227 buffer.language_settings(cx).tasks.prefer_lsp
16228 }) else {
16229 return;
16230 };
16231
16232 let rows = Self::runnable_rows(
16233 project,
16234 display_snapshot,
16235 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16236 new_rows,
16237 cx.clone(),
16238 )
16239 .await;
16240 editor
16241 .update(cx, |editor, _| {
16242 editor.clear_tasks();
16243 for (key, mut value) in rows {
16244 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16245 value.templates.extend(lsp_tasks.templates);
16246 }
16247
16248 editor.insert_tasks(key, value);
16249 }
16250 for (key, value) in lsp_tasks_by_rows {
16251 editor.insert_tasks(key, value);
16252 }
16253 })
16254 .ok();
16255 })
16256 }
16257 fn fetch_runnable_ranges(
16258 snapshot: &DisplaySnapshot,
16259 range: Range<Anchor>,
16260 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16261 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16262 }
16263
16264 fn runnable_rows(
16265 project: Entity<Project>,
16266 snapshot: DisplaySnapshot,
16267 prefer_lsp: bool,
16268 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16269 cx: AsyncWindowContext,
16270 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16271 cx.spawn(async move |cx| {
16272 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16273 for (run_range, mut runnable) in runnable_ranges {
16274 let Some(tasks) = cx
16275 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16276 .ok()
16277 else {
16278 continue;
16279 };
16280 let mut tasks = tasks.await;
16281
16282 if prefer_lsp {
16283 tasks.retain(|(task_kind, _)| {
16284 !matches!(task_kind, TaskSourceKind::Language { .. })
16285 });
16286 }
16287 if tasks.is_empty() {
16288 continue;
16289 }
16290
16291 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16292 let Some(row) = snapshot
16293 .buffer_snapshot()
16294 .buffer_line_for_row(MultiBufferRow(point.row))
16295 .map(|(_, range)| range.start.row)
16296 else {
16297 continue;
16298 };
16299
16300 let context_range =
16301 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16302 runnable_rows.push((
16303 (runnable.buffer_id, row),
16304 RunnableTasks {
16305 templates: tasks,
16306 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16307 context_range,
16308 column: point.column,
16309 extra_variables: runnable.extra_captures,
16310 },
16311 ));
16312 }
16313 runnable_rows
16314 })
16315 }
16316
16317 fn templates_with_tags(
16318 project: &Entity<Project>,
16319 runnable: &mut Runnable,
16320 cx: &mut App,
16321 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16322 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16323 let (worktree_id, file) = project
16324 .buffer_for_id(runnable.buffer, cx)
16325 .and_then(|buffer| buffer.read(cx).file())
16326 .map(|file| (file.worktree_id(cx), file.clone()))
16327 .unzip();
16328
16329 (
16330 project.task_store().read(cx).task_inventory().cloned(),
16331 worktree_id,
16332 file,
16333 )
16334 });
16335
16336 let tags = mem::take(&mut runnable.tags);
16337 let language = runnable.language.clone();
16338 cx.spawn(async move |cx| {
16339 let mut templates_with_tags = Vec::new();
16340 if let Some(inventory) = inventory {
16341 for RunnableTag(tag) in tags {
16342 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16343 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16344 }) else {
16345 return templates_with_tags;
16346 };
16347 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16348 move |(_, template)| {
16349 template.tags.iter().any(|source_tag| source_tag == &tag)
16350 },
16351 ));
16352 }
16353 }
16354 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16355
16356 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16357 // Strongest source wins; if we have worktree tag binding, prefer that to
16358 // global and language bindings;
16359 // if we have a global binding, prefer that to language binding.
16360 let first_mismatch = templates_with_tags
16361 .iter()
16362 .position(|(tag_source, _)| tag_source != leading_tag_source);
16363 if let Some(index) = first_mismatch {
16364 templates_with_tags.truncate(index);
16365 }
16366 }
16367
16368 templates_with_tags
16369 })
16370 }
16371
16372 pub fn move_to_enclosing_bracket(
16373 &mut self,
16374 _: &MoveToEnclosingBracket,
16375 window: &mut Window,
16376 cx: &mut Context<Self>,
16377 ) {
16378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16379 self.change_selections(Default::default(), window, cx, |s| {
16380 s.move_offsets_with(|snapshot, selection| {
16381 let Some(enclosing_bracket_ranges) =
16382 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16383 else {
16384 return;
16385 };
16386
16387 let mut best_length = usize::MAX;
16388 let mut best_inside = false;
16389 let mut best_in_bracket_range = false;
16390 let mut best_destination = None;
16391 for (open, close) in enclosing_bracket_ranges {
16392 let close = close.to_inclusive();
16393 let length = *close.end() - open.start;
16394 let inside = selection.start >= open.end && selection.end <= *close.start();
16395 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16396 || close.contains(&selection.head());
16397
16398 // If best is next to a bracket and current isn't, skip
16399 if !in_bracket_range && best_in_bracket_range {
16400 continue;
16401 }
16402
16403 // Prefer smaller lengths unless best is inside and current isn't
16404 if length > best_length && (best_inside || !inside) {
16405 continue;
16406 }
16407
16408 best_length = length;
16409 best_inside = inside;
16410 best_in_bracket_range = in_bracket_range;
16411 best_destination = Some(
16412 if close.contains(&selection.start) && close.contains(&selection.end) {
16413 if inside { open.end } else { open.start }
16414 } else if inside {
16415 *close.start()
16416 } else {
16417 *close.end()
16418 },
16419 );
16420 }
16421
16422 if let Some(destination) = best_destination {
16423 selection.collapse_to(destination, SelectionGoal::None);
16424 }
16425 })
16426 });
16427 }
16428
16429 pub fn undo_selection(
16430 &mut self,
16431 _: &UndoSelection,
16432 window: &mut Window,
16433 cx: &mut Context<Self>,
16434 ) {
16435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16436 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16437 self.selection_history.mode = SelectionHistoryMode::Undoing;
16438 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16439 this.end_selection(window, cx);
16440 this.change_selections(
16441 SelectionEffects::scroll(Autoscroll::newest()),
16442 window,
16443 cx,
16444 |s| s.select_anchors(entry.selections.to_vec()),
16445 );
16446 });
16447 self.selection_history.mode = SelectionHistoryMode::Normal;
16448
16449 self.select_next_state = entry.select_next_state;
16450 self.select_prev_state = entry.select_prev_state;
16451 self.add_selections_state = entry.add_selections_state;
16452 }
16453 }
16454
16455 pub fn redo_selection(
16456 &mut self,
16457 _: &RedoSelection,
16458 window: &mut Window,
16459 cx: &mut Context<Self>,
16460 ) {
16461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16462 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16463 self.selection_history.mode = SelectionHistoryMode::Redoing;
16464 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16465 this.end_selection(window, cx);
16466 this.change_selections(
16467 SelectionEffects::scroll(Autoscroll::newest()),
16468 window,
16469 cx,
16470 |s| s.select_anchors(entry.selections.to_vec()),
16471 );
16472 });
16473 self.selection_history.mode = SelectionHistoryMode::Normal;
16474
16475 self.select_next_state = entry.select_next_state;
16476 self.select_prev_state = entry.select_prev_state;
16477 self.add_selections_state = entry.add_selections_state;
16478 }
16479 }
16480
16481 pub fn expand_excerpts(
16482 &mut self,
16483 action: &ExpandExcerpts,
16484 _: &mut Window,
16485 cx: &mut Context<Self>,
16486 ) {
16487 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16488 }
16489
16490 pub fn expand_excerpts_down(
16491 &mut self,
16492 action: &ExpandExcerptsDown,
16493 _: &mut Window,
16494 cx: &mut Context<Self>,
16495 ) {
16496 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16497 }
16498
16499 pub fn expand_excerpts_up(
16500 &mut self,
16501 action: &ExpandExcerptsUp,
16502 _: &mut Window,
16503 cx: &mut Context<Self>,
16504 ) {
16505 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16506 }
16507
16508 pub fn expand_excerpts_for_direction(
16509 &mut self,
16510 lines: u32,
16511 direction: ExpandExcerptDirection,
16512
16513 cx: &mut Context<Self>,
16514 ) {
16515 let selections = self.selections.disjoint_anchors_arc();
16516
16517 let lines = if lines == 0 {
16518 EditorSettings::get_global(cx).expand_excerpt_lines
16519 } else {
16520 lines
16521 };
16522
16523 self.buffer.update(cx, |buffer, cx| {
16524 let snapshot = buffer.snapshot(cx);
16525 let mut excerpt_ids = selections
16526 .iter()
16527 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16528 .collect::<Vec<_>>();
16529 excerpt_ids.sort();
16530 excerpt_ids.dedup();
16531 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16532 })
16533 }
16534
16535 pub fn expand_excerpt(
16536 &mut self,
16537 excerpt: ExcerptId,
16538 direction: ExpandExcerptDirection,
16539 window: &mut Window,
16540 cx: &mut Context<Self>,
16541 ) {
16542 let current_scroll_position = self.scroll_position(cx);
16543 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16544 let mut scroll = None;
16545
16546 if direction == ExpandExcerptDirection::Down {
16547 let multi_buffer = self.buffer.read(cx);
16548 let snapshot = multi_buffer.snapshot(cx);
16549 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16550 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16551 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16552 {
16553 let buffer_snapshot = buffer.read(cx).snapshot();
16554 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16555 let last_row = buffer_snapshot.max_point().row;
16556 let lines_below = last_row.saturating_sub(excerpt_end_row);
16557 if lines_below >= lines_to_expand {
16558 scroll = Some(
16559 current_scroll_position
16560 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16561 );
16562 }
16563 }
16564 }
16565 if direction == ExpandExcerptDirection::Up
16566 && self
16567 .buffer
16568 .read(cx)
16569 .snapshot(cx)
16570 .excerpt_before(excerpt)
16571 .is_none()
16572 {
16573 scroll = Some(current_scroll_position);
16574 }
16575
16576 self.buffer.update(cx, |buffer, cx| {
16577 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16578 });
16579
16580 if let Some(new_scroll_position) = scroll {
16581 self.set_scroll_position(new_scroll_position, window, cx);
16582 }
16583 }
16584
16585 pub fn go_to_singleton_buffer_point(
16586 &mut self,
16587 point: Point,
16588 window: &mut Window,
16589 cx: &mut Context<Self>,
16590 ) {
16591 self.go_to_singleton_buffer_range(point..point, window, cx);
16592 }
16593
16594 pub fn go_to_singleton_buffer_range(
16595 &mut self,
16596 range: Range<Point>,
16597 window: &mut Window,
16598 cx: &mut Context<Self>,
16599 ) {
16600 let multibuffer = self.buffer().read(cx);
16601 let Some(buffer) = multibuffer.as_singleton() else {
16602 return;
16603 };
16604 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16605 return;
16606 };
16607 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16608 return;
16609 };
16610 self.change_selections(
16611 SelectionEffects::default().nav_history(true),
16612 window,
16613 cx,
16614 |s| s.select_anchor_ranges([start..end]),
16615 );
16616 }
16617
16618 pub fn go_to_diagnostic(
16619 &mut self,
16620 action: &GoToDiagnostic,
16621 window: &mut Window,
16622 cx: &mut Context<Self>,
16623 ) {
16624 if !self.diagnostics_enabled() {
16625 return;
16626 }
16627 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16628 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16629 }
16630
16631 pub fn go_to_prev_diagnostic(
16632 &mut self,
16633 action: &GoToPreviousDiagnostic,
16634 window: &mut Window,
16635 cx: &mut Context<Self>,
16636 ) {
16637 if !self.diagnostics_enabled() {
16638 return;
16639 }
16640 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16641 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16642 }
16643
16644 pub fn go_to_diagnostic_impl(
16645 &mut self,
16646 direction: Direction,
16647 severity: GoToDiagnosticSeverityFilter,
16648 window: &mut Window,
16649 cx: &mut Context<Self>,
16650 ) {
16651 let buffer = self.buffer.read(cx).snapshot(cx);
16652 let selection = self
16653 .selections
16654 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16655
16656 let mut active_group_id = None;
16657 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16658 && active_group.active_range.start.to_offset(&buffer) == selection.start
16659 {
16660 active_group_id = Some(active_group.group_id);
16661 }
16662
16663 fn filtered<'a>(
16664 severity: GoToDiagnosticSeverityFilter,
16665 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16666 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16667 diagnostics
16668 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16669 .filter(|entry| entry.range.start != entry.range.end)
16670 .filter(|entry| !entry.diagnostic.is_unnecessary)
16671 }
16672
16673 let before = filtered(
16674 severity,
16675 buffer
16676 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16677 .filter(|entry| entry.range.start <= selection.start),
16678 );
16679 let after = filtered(
16680 severity,
16681 buffer
16682 .diagnostics_in_range(selection.start..buffer.len())
16683 .filter(|entry| entry.range.start >= selection.start),
16684 );
16685
16686 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16687 if direction == Direction::Prev {
16688 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16689 {
16690 for diagnostic in prev_diagnostics.into_iter().rev() {
16691 if diagnostic.range.start != selection.start
16692 || active_group_id
16693 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16694 {
16695 found = Some(diagnostic);
16696 break 'outer;
16697 }
16698 }
16699 }
16700 } else {
16701 for diagnostic in after.chain(before) {
16702 if diagnostic.range.start != selection.start
16703 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16704 {
16705 found = Some(diagnostic);
16706 break;
16707 }
16708 }
16709 }
16710 let Some(next_diagnostic) = found else {
16711 return;
16712 };
16713
16714 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16715 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16716 return;
16717 };
16718 let snapshot = self.snapshot(window, cx);
16719 if snapshot.intersects_fold(next_diagnostic.range.start) {
16720 self.unfold_ranges(
16721 std::slice::from_ref(&next_diagnostic.range),
16722 true,
16723 false,
16724 cx,
16725 );
16726 }
16727 self.change_selections(Default::default(), window, cx, |s| {
16728 s.select_ranges(vec![
16729 next_diagnostic.range.start..next_diagnostic.range.start,
16730 ])
16731 });
16732 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16733 self.refresh_edit_prediction(false, true, window, cx);
16734 }
16735
16736 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16738 let snapshot = self.snapshot(window, cx);
16739 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16740 self.go_to_hunk_before_or_after_position(
16741 &snapshot,
16742 selection.head(),
16743 Direction::Next,
16744 window,
16745 cx,
16746 );
16747 }
16748
16749 pub fn go_to_hunk_before_or_after_position(
16750 &mut self,
16751 snapshot: &EditorSnapshot,
16752 position: Point,
16753 direction: Direction,
16754 window: &mut Window,
16755 cx: &mut Context<Editor>,
16756 ) {
16757 let row = if direction == Direction::Next {
16758 self.hunk_after_position(snapshot, position)
16759 .map(|hunk| hunk.row_range.start)
16760 } else {
16761 self.hunk_before_position(snapshot, position)
16762 };
16763
16764 if let Some(row) = row {
16765 let destination = Point::new(row.0, 0);
16766 let autoscroll = Autoscroll::center();
16767
16768 self.unfold_ranges(&[destination..destination], false, false, cx);
16769 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16770 s.select_ranges([destination..destination]);
16771 });
16772 }
16773 }
16774
16775 fn hunk_after_position(
16776 &mut self,
16777 snapshot: &EditorSnapshot,
16778 position: Point,
16779 ) -> Option<MultiBufferDiffHunk> {
16780 snapshot
16781 .buffer_snapshot()
16782 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16783 .find(|hunk| hunk.row_range.start.0 > position.row)
16784 .or_else(|| {
16785 snapshot
16786 .buffer_snapshot()
16787 .diff_hunks_in_range(Point::zero()..position)
16788 .find(|hunk| hunk.row_range.end.0 < position.row)
16789 })
16790 }
16791
16792 fn go_to_prev_hunk(
16793 &mut self,
16794 _: &GoToPreviousHunk,
16795 window: &mut Window,
16796 cx: &mut Context<Self>,
16797 ) {
16798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16799 let snapshot = self.snapshot(window, cx);
16800 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16801 self.go_to_hunk_before_or_after_position(
16802 &snapshot,
16803 selection.head(),
16804 Direction::Prev,
16805 window,
16806 cx,
16807 );
16808 }
16809
16810 fn hunk_before_position(
16811 &mut self,
16812 snapshot: &EditorSnapshot,
16813 position: Point,
16814 ) -> Option<MultiBufferRow> {
16815 snapshot
16816 .buffer_snapshot()
16817 .diff_hunk_before(position)
16818 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16819 }
16820
16821 fn go_to_next_change(
16822 &mut self,
16823 _: &GoToNextChange,
16824 window: &mut Window,
16825 cx: &mut Context<Self>,
16826 ) {
16827 if let Some(selections) = self
16828 .change_list
16829 .next_change(1, Direction::Next)
16830 .map(|s| s.to_vec())
16831 {
16832 self.change_selections(Default::default(), window, cx, |s| {
16833 let map = s.display_snapshot();
16834 s.select_display_ranges(selections.iter().map(|a| {
16835 let point = a.to_display_point(&map);
16836 point..point
16837 }))
16838 })
16839 }
16840 }
16841
16842 fn go_to_previous_change(
16843 &mut self,
16844 _: &GoToPreviousChange,
16845 window: &mut Window,
16846 cx: &mut Context<Self>,
16847 ) {
16848 if let Some(selections) = self
16849 .change_list
16850 .next_change(1, Direction::Prev)
16851 .map(|s| s.to_vec())
16852 {
16853 self.change_selections(Default::default(), window, cx, |s| {
16854 let map = s.display_snapshot();
16855 s.select_display_ranges(selections.iter().map(|a| {
16856 let point = a.to_display_point(&map);
16857 point..point
16858 }))
16859 })
16860 }
16861 }
16862
16863 pub fn go_to_next_document_highlight(
16864 &mut self,
16865 _: &GoToNextDocumentHighlight,
16866 window: &mut Window,
16867 cx: &mut Context<Self>,
16868 ) {
16869 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16870 }
16871
16872 pub fn go_to_prev_document_highlight(
16873 &mut self,
16874 _: &GoToPreviousDocumentHighlight,
16875 window: &mut Window,
16876 cx: &mut Context<Self>,
16877 ) {
16878 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16879 }
16880
16881 pub fn go_to_document_highlight_before_or_after_position(
16882 &mut self,
16883 direction: Direction,
16884 window: &mut Window,
16885 cx: &mut Context<Editor>,
16886 ) {
16887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16888 let snapshot = self.snapshot(window, cx);
16889 let buffer = &snapshot.buffer_snapshot();
16890 let position = self
16891 .selections
16892 .newest::<Point>(&snapshot.display_snapshot)
16893 .head();
16894 let anchor_position = buffer.anchor_after(position);
16895
16896 // Get all document highlights (both read and write)
16897 let mut all_highlights = Vec::new();
16898
16899 if let Some((_, read_highlights)) = self
16900 .background_highlights
16901 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16902 {
16903 all_highlights.extend(read_highlights.iter());
16904 }
16905
16906 if let Some((_, write_highlights)) = self
16907 .background_highlights
16908 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16909 {
16910 all_highlights.extend(write_highlights.iter());
16911 }
16912
16913 if all_highlights.is_empty() {
16914 return;
16915 }
16916
16917 // Sort highlights by position
16918 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16919
16920 let target_highlight = match direction {
16921 Direction::Next => {
16922 // Find the first highlight after the current position
16923 all_highlights
16924 .iter()
16925 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16926 }
16927 Direction::Prev => {
16928 // Find the last highlight before the current position
16929 all_highlights
16930 .iter()
16931 .rev()
16932 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16933 }
16934 };
16935
16936 if let Some(highlight) = target_highlight {
16937 let destination = highlight.start.to_point(buffer);
16938 let autoscroll = Autoscroll::center();
16939
16940 self.unfold_ranges(&[destination..destination], false, false, cx);
16941 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16942 s.select_ranges([destination..destination]);
16943 });
16944 }
16945 }
16946
16947 fn go_to_line<T: 'static>(
16948 &mut self,
16949 position: Anchor,
16950 highlight_color: Option<Hsla>,
16951 window: &mut Window,
16952 cx: &mut Context<Self>,
16953 ) {
16954 let snapshot = self.snapshot(window, cx).display_snapshot;
16955 let position = position.to_point(&snapshot.buffer_snapshot());
16956 let start = snapshot
16957 .buffer_snapshot()
16958 .clip_point(Point::new(position.row, 0), Bias::Left);
16959 let end = start + Point::new(1, 0);
16960 let start = snapshot.buffer_snapshot().anchor_before(start);
16961 let end = snapshot.buffer_snapshot().anchor_before(end);
16962
16963 self.highlight_rows::<T>(
16964 start..end,
16965 highlight_color
16966 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16967 Default::default(),
16968 cx,
16969 );
16970
16971 if self.buffer.read(cx).is_singleton() {
16972 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16973 }
16974 }
16975
16976 pub fn go_to_definition(
16977 &mut self,
16978 _: &GoToDefinition,
16979 window: &mut Window,
16980 cx: &mut Context<Self>,
16981 ) -> Task<Result<Navigated>> {
16982 let definition =
16983 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16984 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16985 cx.spawn_in(window, async move |editor, cx| {
16986 if definition.await? == Navigated::Yes {
16987 return Ok(Navigated::Yes);
16988 }
16989 match fallback_strategy {
16990 GoToDefinitionFallback::None => Ok(Navigated::No),
16991 GoToDefinitionFallback::FindAllReferences => {
16992 match editor.update_in(cx, |editor, window, cx| {
16993 editor.find_all_references(&FindAllReferences::default(), window, cx)
16994 })? {
16995 Some(references) => references.await,
16996 None => Ok(Navigated::No),
16997 }
16998 }
16999 }
17000 })
17001 }
17002
17003 pub fn go_to_declaration(
17004 &mut self,
17005 _: &GoToDeclaration,
17006 window: &mut Window,
17007 cx: &mut Context<Self>,
17008 ) -> Task<Result<Navigated>> {
17009 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17010 }
17011
17012 pub fn go_to_declaration_split(
17013 &mut self,
17014 _: &GoToDeclaration,
17015 window: &mut Window,
17016 cx: &mut Context<Self>,
17017 ) -> Task<Result<Navigated>> {
17018 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17019 }
17020
17021 pub fn go_to_implementation(
17022 &mut self,
17023 _: &GoToImplementation,
17024 window: &mut Window,
17025 cx: &mut Context<Self>,
17026 ) -> Task<Result<Navigated>> {
17027 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17028 }
17029
17030 pub fn go_to_implementation_split(
17031 &mut self,
17032 _: &GoToImplementationSplit,
17033 window: &mut Window,
17034 cx: &mut Context<Self>,
17035 ) -> Task<Result<Navigated>> {
17036 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17037 }
17038
17039 pub fn go_to_type_definition(
17040 &mut self,
17041 _: &GoToTypeDefinition,
17042 window: &mut Window,
17043 cx: &mut Context<Self>,
17044 ) -> Task<Result<Navigated>> {
17045 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17046 }
17047
17048 pub fn go_to_definition_split(
17049 &mut self,
17050 _: &GoToDefinitionSplit,
17051 window: &mut Window,
17052 cx: &mut Context<Self>,
17053 ) -> Task<Result<Navigated>> {
17054 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17055 }
17056
17057 pub fn go_to_type_definition_split(
17058 &mut self,
17059 _: &GoToTypeDefinitionSplit,
17060 window: &mut Window,
17061 cx: &mut Context<Self>,
17062 ) -> Task<Result<Navigated>> {
17063 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17064 }
17065
17066 fn go_to_definition_of_kind(
17067 &mut self,
17068 kind: GotoDefinitionKind,
17069 split: bool,
17070 window: &mut Window,
17071 cx: &mut Context<Self>,
17072 ) -> Task<Result<Navigated>> {
17073 let Some(provider) = self.semantics_provider.clone() else {
17074 return Task::ready(Ok(Navigated::No));
17075 };
17076 let head = self
17077 .selections
17078 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17079 .head();
17080 let buffer = self.buffer.read(cx);
17081 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17082 return Task::ready(Ok(Navigated::No));
17083 };
17084 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17085 return Task::ready(Ok(Navigated::No));
17086 };
17087
17088 cx.spawn_in(window, async move |editor, cx| {
17089 let Some(definitions) = definitions.await? else {
17090 return Ok(Navigated::No);
17091 };
17092 let navigated = editor
17093 .update_in(cx, |editor, window, cx| {
17094 editor.navigate_to_hover_links(
17095 Some(kind),
17096 definitions
17097 .into_iter()
17098 .filter(|location| {
17099 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17100 })
17101 .map(HoverLink::Text)
17102 .collect::<Vec<_>>(),
17103 split,
17104 window,
17105 cx,
17106 )
17107 })?
17108 .await?;
17109 anyhow::Ok(navigated)
17110 })
17111 }
17112
17113 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17114 let selection = self.selections.newest_anchor();
17115 let head = selection.head();
17116 let tail = selection.tail();
17117
17118 let Some((buffer, start_position)) =
17119 self.buffer.read(cx).text_anchor_for_position(head, cx)
17120 else {
17121 return;
17122 };
17123
17124 let end_position = if head != tail {
17125 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17126 return;
17127 };
17128 Some(pos)
17129 } else {
17130 None
17131 };
17132
17133 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17134 let url = if let Some(end_pos) = end_position {
17135 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17136 } else {
17137 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17138 };
17139
17140 if let Some(url) = url {
17141 cx.update(|window, cx| {
17142 if parse_zed_link(&url, cx).is_some() {
17143 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17144 } else {
17145 cx.open_url(&url);
17146 }
17147 })?;
17148 }
17149
17150 anyhow::Ok(())
17151 });
17152
17153 url_finder.detach();
17154 }
17155
17156 pub fn open_selected_filename(
17157 &mut self,
17158 _: &OpenSelectedFilename,
17159 window: &mut Window,
17160 cx: &mut Context<Self>,
17161 ) {
17162 let Some(workspace) = self.workspace() else {
17163 return;
17164 };
17165
17166 let position = self.selections.newest_anchor().head();
17167
17168 let Some((buffer, buffer_position)) =
17169 self.buffer.read(cx).text_anchor_for_position(position, cx)
17170 else {
17171 return;
17172 };
17173
17174 let project = self.project.clone();
17175
17176 cx.spawn_in(window, async move |_, cx| {
17177 let result = find_file(&buffer, project, buffer_position, cx).await;
17178
17179 if let Some((_, path)) = result {
17180 workspace
17181 .update_in(cx, |workspace, window, cx| {
17182 workspace.open_resolved_path(path, window, cx)
17183 })?
17184 .await?;
17185 }
17186 anyhow::Ok(())
17187 })
17188 .detach();
17189 }
17190
17191 pub(crate) fn navigate_to_hover_links(
17192 &mut self,
17193 kind: Option<GotoDefinitionKind>,
17194 definitions: Vec<HoverLink>,
17195 split: bool,
17196 window: &mut Window,
17197 cx: &mut Context<Editor>,
17198 ) -> Task<Result<Navigated>> {
17199 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17200 let mut first_url_or_file = None;
17201 let definitions: Vec<_> = definitions
17202 .into_iter()
17203 .filter_map(|def| match def {
17204 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17205 HoverLink::InlayHint(lsp_location, server_id) => {
17206 let computation =
17207 self.compute_target_location(lsp_location, server_id, window, cx);
17208 Some(cx.background_spawn(computation))
17209 }
17210 HoverLink::Url(url) => {
17211 first_url_or_file = Some(Either::Left(url));
17212 None
17213 }
17214 HoverLink::File(path) => {
17215 first_url_or_file = Some(Either::Right(path));
17216 None
17217 }
17218 })
17219 .collect();
17220
17221 let workspace = self.workspace();
17222
17223 cx.spawn_in(window, async move |editor, cx| {
17224 let locations: Vec<Location> = future::join_all(definitions)
17225 .await
17226 .into_iter()
17227 .filter_map(|location| location.transpose())
17228 .collect::<Result<_>>()
17229 .context("location tasks")?;
17230 let mut locations = cx.update(|_, cx| {
17231 locations
17232 .into_iter()
17233 .map(|location| {
17234 let buffer = location.buffer.read(cx);
17235 (location.buffer, location.range.to_point(buffer))
17236 })
17237 .into_group_map()
17238 })?;
17239 let mut num_locations = 0;
17240 for ranges in locations.values_mut() {
17241 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17242 ranges.dedup();
17243 num_locations += ranges.len();
17244 }
17245
17246 if num_locations > 1 {
17247 let tab_kind = match kind {
17248 Some(GotoDefinitionKind::Implementation) => "Implementations",
17249 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17250 Some(GotoDefinitionKind::Declaration) => "Declarations",
17251 Some(GotoDefinitionKind::Type) => "Types",
17252 };
17253 let title = editor
17254 .update_in(cx, |_, _, cx| {
17255 let target = locations
17256 .iter()
17257 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17258 .map(|(buffer, location)| {
17259 buffer
17260 .read(cx)
17261 .text_for_range(location.clone())
17262 .collect::<String>()
17263 })
17264 .filter(|text| !text.contains('\n'))
17265 .unique()
17266 .take(3)
17267 .join(", ");
17268 if target.is_empty() {
17269 tab_kind.to_owned()
17270 } else {
17271 format!("{tab_kind} for {target}")
17272 }
17273 })
17274 .context("buffer title")?;
17275
17276 let Some(workspace) = workspace else {
17277 return Ok(Navigated::No);
17278 };
17279
17280 let opened = workspace
17281 .update_in(cx, |workspace, window, cx| {
17282 let allow_preview = PreviewTabsSettings::get_global(cx)
17283 .enable_preview_multibuffer_from_code_navigation;
17284 Self::open_locations_in_multibuffer(
17285 workspace,
17286 locations,
17287 title,
17288 split,
17289 allow_preview,
17290 MultibufferSelectionMode::First,
17291 window,
17292 cx,
17293 )
17294 })
17295 .is_ok();
17296
17297 anyhow::Ok(Navigated::from_bool(opened))
17298 } else if num_locations == 0 {
17299 // If there is one url or file, open it directly
17300 match first_url_or_file {
17301 Some(Either::Left(url)) => {
17302 cx.update(|window, cx| {
17303 if parse_zed_link(&url, cx).is_some() {
17304 window
17305 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17306 } else {
17307 cx.open_url(&url);
17308 }
17309 })?;
17310 Ok(Navigated::Yes)
17311 }
17312 Some(Either::Right(path)) => {
17313 // TODO(andrew): respect preview tab settings
17314 // `enable_keep_preview_on_code_navigation` and
17315 // `enable_preview_file_from_code_navigation`
17316 let Some(workspace) = workspace else {
17317 return Ok(Navigated::No);
17318 };
17319 workspace
17320 .update_in(cx, |workspace, window, cx| {
17321 workspace.open_resolved_path(path, window, cx)
17322 })?
17323 .await?;
17324 Ok(Navigated::Yes)
17325 }
17326 None => Ok(Navigated::No),
17327 }
17328 } else {
17329 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17330 let target_range = target_ranges.first().unwrap().clone();
17331
17332 editor.update_in(cx, |editor, window, cx| {
17333 let range = target_range.to_point(target_buffer.read(cx));
17334 let range = editor.range_for_match(&range);
17335 let range = collapse_multiline_range(range);
17336
17337 if !split
17338 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17339 {
17340 editor.go_to_singleton_buffer_range(range, window, cx);
17341 } else {
17342 let Some(workspace) = workspace else {
17343 return Navigated::No;
17344 };
17345 let pane = workspace.read(cx).active_pane().clone();
17346 window.defer(cx, move |window, cx| {
17347 let target_editor: Entity<Self> =
17348 workspace.update(cx, |workspace, cx| {
17349 let pane = if split {
17350 workspace.adjacent_pane(window, cx)
17351 } else {
17352 workspace.active_pane().clone()
17353 };
17354
17355 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17356 let keep_old_preview = preview_tabs_settings
17357 .enable_keep_preview_on_code_navigation;
17358 let allow_new_preview = preview_tabs_settings
17359 .enable_preview_file_from_code_navigation;
17360
17361 workspace.open_project_item(
17362 pane,
17363 target_buffer.clone(),
17364 true,
17365 true,
17366 keep_old_preview,
17367 allow_new_preview,
17368 window,
17369 cx,
17370 )
17371 });
17372 target_editor.update(cx, |target_editor, cx| {
17373 // When selecting a definition in a different buffer, disable the nav history
17374 // to avoid creating a history entry at the previous cursor location.
17375 pane.update(cx, |pane, _| pane.disable_history());
17376 target_editor.go_to_singleton_buffer_range(range, window, cx);
17377 pane.update(cx, |pane, _| pane.enable_history());
17378 });
17379 });
17380 }
17381 Navigated::Yes
17382 })
17383 }
17384 })
17385 }
17386
17387 fn compute_target_location(
17388 &self,
17389 lsp_location: lsp::Location,
17390 server_id: LanguageServerId,
17391 window: &mut Window,
17392 cx: &mut Context<Self>,
17393 ) -> Task<anyhow::Result<Option<Location>>> {
17394 let Some(project) = self.project.clone() else {
17395 return Task::ready(Ok(None));
17396 };
17397
17398 cx.spawn_in(window, async move |editor, cx| {
17399 let location_task = editor.update(cx, |_, cx| {
17400 project.update(cx, |project, cx| {
17401 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17402 })
17403 })?;
17404 let location = Some({
17405 let target_buffer_handle = location_task.await.context("open local buffer")?;
17406 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17407 let target_start = target_buffer
17408 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17409 let target_end = target_buffer
17410 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17411 target_buffer.anchor_after(target_start)
17412 ..target_buffer.anchor_before(target_end)
17413 })?;
17414 Location {
17415 buffer: target_buffer_handle,
17416 range,
17417 }
17418 });
17419 Ok(location)
17420 })
17421 }
17422
17423 fn go_to_next_reference(
17424 &mut self,
17425 _: &GoToNextReference,
17426 window: &mut Window,
17427 cx: &mut Context<Self>,
17428 ) {
17429 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17430 if let Some(task) = task {
17431 task.detach();
17432 };
17433 }
17434
17435 fn go_to_prev_reference(
17436 &mut self,
17437 _: &GoToPreviousReference,
17438 window: &mut Window,
17439 cx: &mut Context<Self>,
17440 ) {
17441 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17442 if let Some(task) = task {
17443 task.detach();
17444 };
17445 }
17446
17447 pub fn go_to_reference_before_or_after_position(
17448 &mut self,
17449 direction: Direction,
17450 count: usize,
17451 window: &mut Window,
17452 cx: &mut Context<Self>,
17453 ) -> Option<Task<Result<()>>> {
17454 let selection = self.selections.newest_anchor();
17455 let head = selection.head();
17456
17457 let multi_buffer = self.buffer.read(cx);
17458
17459 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17460 let workspace = self.workspace()?;
17461 let project = workspace.read(cx).project().clone();
17462 let references =
17463 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17464 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17465 let Some(locations) = references.await? else {
17466 return Ok(());
17467 };
17468
17469 if locations.is_empty() {
17470 // totally normal - the cursor may be on something which is not
17471 // a symbol (e.g. a keyword)
17472 log::info!("no references found under cursor");
17473 return Ok(());
17474 }
17475
17476 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17477
17478 let (locations, current_location_index) =
17479 multi_buffer.update(cx, |multi_buffer, cx| {
17480 let mut locations = locations
17481 .into_iter()
17482 .filter_map(|loc| {
17483 let start = multi_buffer.buffer_anchor_to_anchor(
17484 &loc.buffer,
17485 loc.range.start,
17486 cx,
17487 )?;
17488 let end = multi_buffer.buffer_anchor_to_anchor(
17489 &loc.buffer,
17490 loc.range.end,
17491 cx,
17492 )?;
17493 Some(start..end)
17494 })
17495 .collect::<Vec<_>>();
17496
17497 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17498 // There is an O(n) implementation, but given this list will be
17499 // small (usually <100 items), the extra O(log(n)) factor isn't
17500 // worth the (surprisingly large amount of) extra complexity.
17501 locations
17502 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17503
17504 let head_offset = head.to_offset(&multi_buffer_snapshot);
17505
17506 let current_location_index = locations.iter().position(|loc| {
17507 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17508 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17509 });
17510
17511 (locations, current_location_index)
17512 })?;
17513
17514 let Some(current_location_index) = current_location_index else {
17515 // This indicates something has gone wrong, because we already
17516 // handle the "no references" case above
17517 log::error!(
17518 "failed to find current reference under cursor. Total references: {}",
17519 locations.len()
17520 );
17521 return Ok(());
17522 };
17523
17524 let destination_location_index = match direction {
17525 Direction::Next => (current_location_index + count) % locations.len(),
17526 Direction::Prev => {
17527 (current_location_index + locations.len() - count % locations.len())
17528 % locations.len()
17529 }
17530 };
17531
17532 // TODO(cameron): is this needed?
17533 // the thinking is to avoid "jumping to the current location" (avoid
17534 // polluting "jumplist" in vim terms)
17535 if current_location_index == destination_location_index {
17536 return Ok(());
17537 }
17538
17539 let Range { start, end } = locations[destination_location_index];
17540
17541 editor.update_in(cx, |editor, window, cx| {
17542 let effects = SelectionEffects::default();
17543
17544 editor.unfold_ranges(&[start..end], false, false, cx);
17545 editor.change_selections(effects, window, cx, |s| {
17546 s.select_ranges([start..start]);
17547 });
17548 })?;
17549
17550 Ok(())
17551 }))
17552 }
17553
17554 pub fn find_all_references(
17555 &mut self,
17556 action: &FindAllReferences,
17557 window: &mut Window,
17558 cx: &mut Context<Self>,
17559 ) -> Option<Task<Result<Navigated>>> {
17560 let always_open_multibuffer = action.always_open_multibuffer;
17561 let selection = self.selections.newest_anchor();
17562 let multi_buffer = self.buffer.read(cx);
17563 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17564 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17565 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17566 let head = selection_offset.head();
17567
17568 let head_anchor = multi_buffer_snapshot.anchor_at(
17569 head,
17570 if head < selection_offset.tail() {
17571 Bias::Right
17572 } else {
17573 Bias::Left
17574 },
17575 );
17576
17577 match self
17578 .find_all_references_task_sources
17579 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17580 {
17581 Ok(_) => {
17582 log::info!(
17583 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17584 );
17585 return None;
17586 }
17587 Err(i) => {
17588 self.find_all_references_task_sources.insert(i, head_anchor);
17589 }
17590 }
17591
17592 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17593 let workspace = self.workspace()?;
17594 let project = workspace.read(cx).project().clone();
17595 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17596 Some(cx.spawn_in(window, async move |editor, cx| {
17597 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17598 if let Ok(i) = editor
17599 .find_all_references_task_sources
17600 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17601 {
17602 editor.find_all_references_task_sources.remove(i);
17603 }
17604 });
17605
17606 let Some(locations) = references.await? else {
17607 return anyhow::Ok(Navigated::No);
17608 };
17609 let mut locations = cx.update(|_, cx| {
17610 locations
17611 .into_iter()
17612 .map(|location| {
17613 let buffer = location.buffer.read(cx);
17614 (location.buffer, location.range.to_point(buffer))
17615 })
17616 // if special-casing the single-match case, remove ranges
17617 // that intersect current selection
17618 .filter(|(location_buffer, location)| {
17619 if always_open_multibuffer || &buffer != location_buffer {
17620 return true;
17621 }
17622
17623 !location.contains_inclusive(&selection_point.range())
17624 })
17625 .into_group_map()
17626 })?;
17627 if locations.is_empty() {
17628 return anyhow::Ok(Navigated::No);
17629 }
17630 for ranges in locations.values_mut() {
17631 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17632 ranges.dedup();
17633 }
17634 let mut num_locations = 0;
17635 for ranges in locations.values_mut() {
17636 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17637 ranges.dedup();
17638 num_locations += ranges.len();
17639 }
17640
17641 if num_locations == 1 && !always_open_multibuffer {
17642 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17643 let target_range = target_ranges.first().unwrap().clone();
17644
17645 return editor.update_in(cx, |editor, window, cx| {
17646 let range = target_range.to_point(target_buffer.read(cx));
17647 let range = editor.range_for_match(&range);
17648 let range = range.start..range.start;
17649
17650 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17651 editor.go_to_singleton_buffer_range(range, window, cx);
17652 } else {
17653 let pane = workspace.read(cx).active_pane().clone();
17654 window.defer(cx, move |window, cx| {
17655 let target_editor: Entity<Self> =
17656 workspace.update(cx, |workspace, cx| {
17657 let pane = workspace.active_pane().clone();
17658
17659 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17660 let keep_old_preview = preview_tabs_settings
17661 .enable_keep_preview_on_code_navigation;
17662 let allow_new_preview = preview_tabs_settings
17663 .enable_preview_file_from_code_navigation;
17664
17665 workspace.open_project_item(
17666 pane,
17667 target_buffer.clone(),
17668 true,
17669 true,
17670 keep_old_preview,
17671 allow_new_preview,
17672 window,
17673 cx,
17674 )
17675 });
17676 target_editor.update(cx, |target_editor, cx| {
17677 // When selecting a definition in a different buffer, disable the nav history
17678 // to avoid creating a history entry at the previous cursor location.
17679 pane.update(cx, |pane, _| pane.disable_history());
17680 target_editor.go_to_singleton_buffer_range(range, window, cx);
17681 pane.update(cx, |pane, _| pane.enable_history());
17682 });
17683 });
17684 }
17685 Navigated::No
17686 });
17687 }
17688
17689 workspace.update_in(cx, |workspace, window, cx| {
17690 let target = locations
17691 .iter()
17692 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17693 .map(|(buffer, location)| {
17694 buffer
17695 .read(cx)
17696 .text_for_range(location.clone())
17697 .collect::<String>()
17698 })
17699 .filter(|text| !text.contains('\n'))
17700 .unique()
17701 .take(3)
17702 .join(", ");
17703 let title = if target.is_empty() {
17704 "References".to_owned()
17705 } else {
17706 format!("References to {target}")
17707 };
17708 let allow_preview = PreviewTabsSettings::get_global(cx)
17709 .enable_preview_multibuffer_from_code_navigation;
17710 Self::open_locations_in_multibuffer(
17711 workspace,
17712 locations,
17713 title,
17714 false,
17715 allow_preview,
17716 MultibufferSelectionMode::First,
17717 window,
17718 cx,
17719 );
17720 Navigated::Yes
17721 })
17722 }))
17723 }
17724
17725 /// Opens a multibuffer with the given project locations in it.
17726 pub fn open_locations_in_multibuffer(
17727 workspace: &mut Workspace,
17728 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17729 title: String,
17730 split: bool,
17731 allow_preview: bool,
17732 multibuffer_selection_mode: MultibufferSelectionMode,
17733 window: &mut Window,
17734 cx: &mut Context<Workspace>,
17735 ) {
17736 if locations.is_empty() {
17737 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17738 return;
17739 }
17740
17741 let capability = workspace.project().read(cx).capability();
17742 let mut ranges = <Vec<Range<Anchor>>>::new();
17743
17744 // a key to find existing multibuffer editors with the same set of locations
17745 // to prevent us from opening more and more multibuffer tabs for searches and the like
17746 let mut key = (title.clone(), vec![]);
17747 let excerpt_buffer = cx.new(|cx| {
17748 let key = &mut key.1;
17749 let mut multibuffer = MultiBuffer::new(capability);
17750 for (buffer, mut ranges_for_buffer) in locations {
17751 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17752 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17753 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17754 PathKey::for_buffer(&buffer, cx),
17755 buffer.clone(),
17756 ranges_for_buffer,
17757 multibuffer_context_lines(cx),
17758 cx,
17759 );
17760 ranges.extend(new_ranges)
17761 }
17762
17763 multibuffer.with_title(title)
17764 });
17765 let existing = workspace.active_pane().update(cx, |pane, cx| {
17766 pane.items()
17767 .filter_map(|item| item.downcast::<Editor>())
17768 .find(|editor| {
17769 editor
17770 .read(cx)
17771 .lookup_key
17772 .as_ref()
17773 .and_then(|it| {
17774 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17775 })
17776 .is_some_and(|it| *it == key)
17777 })
17778 });
17779 let was_existing = existing.is_some();
17780 let editor = existing.unwrap_or_else(|| {
17781 cx.new(|cx| {
17782 let mut editor = Editor::for_multibuffer(
17783 excerpt_buffer,
17784 Some(workspace.project().clone()),
17785 window,
17786 cx,
17787 );
17788 editor.lookup_key = Some(Box::new(key));
17789 editor
17790 })
17791 });
17792 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17793 MultibufferSelectionMode::First => {
17794 if let Some(first_range) = ranges.first() {
17795 editor.change_selections(
17796 SelectionEffects::no_scroll(),
17797 window,
17798 cx,
17799 |selections| {
17800 selections.clear_disjoint();
17801 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17802 },
17803 );
17804 }
17805 editor.highlight_background::<Self>(
17806 &ranges,
17807 |_, theme| theme.colors().editor_highlighted_line_background,
17808 cx,
17809 );
17810 }
17811 MultibufferSelectionMode::All => {
17812 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17813 selections.clear_disjoint();
17814 selections.select_anchor_ranges(ranges);
17815 });
17816 }
17817 });
17818
17819 let item = Box::new(editor);
17820
17821 let pane = if split {
17822 workspace.adjacent_pane(window, cx)
17823 } else {
17824 workspace.active_pane().clone()
17825 };
17826 let activate_pane = split;
17827
17828 let mut destination_index = None;
17829 pane.update(cx, |pane, cx| {
17830 if allow_preview && !was_existing {
17831 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
17832 }
17833 if was_existing && !allow_preview {
17834 pane.unpreview_item_if_preview(item.item_id());
17835 }
17836 pane.add_item(item, activate_pane, true, destination_index, window, cx);
17837 });
17838 }
17839
17840 pub fn rename(
17841 &mut self,
17842 _: &Rename,
17843 window: &mut Window,
17844 cx: &mut Context<Self>,
17845 ) -> Option<Task<Result<()>>> {
17846 use language::ToOffset as _;
17847
17848 let provider = self.semantics_provider.clone()?;
17849 let selection = self.selections.newest_anchor().clone();
17850 let (cursor_buffer, cursor_buffer_position) = self
17851 .buffer
17852 .read(cx)
17853 .text_anchor_for_position(selection.head(), cx)?;
17854 let (tail_buffer, cursor_buffer_position_end) = self
17855 .buffer
17856 .read(cx)
17857 .text_anchor_for_position(selection.tail(), cx)?;
17858 if tail_buffer != cursor_buffer {
17859 return None;
17860 }
17861
17862 let snapshot = cursor_buffer.read(cx).snapshot();
17863 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17864 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17865 let prepare_rename = provider
17866 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17867 .unwrap_or_else(|| Task::ready(Ok(None)));
17868 drop(snapshot);
17869
17870 Some(cx.spawn_in(window, async move |this, cx| {
17871 let rename_range = if let Some(range) = prepare_rename.await? {
17872 Some(range)
17873 } else {
17874 this.update(cx, |this, cx| {
17875 let buffer = this.buffer.read(cx).snapshot(cx);
17876 let mut buffer_highlights = this
17877 .document_highlights_for_position(selection.head(), &buffer)
17878 .filter(|highlight| {
17879 highlight.start.excerpt_id == selection.head().excerpt_id
17880 && highlight.end.excerpt_id == selection.head().excerpt_id
17881 });
17882 buffer_highlights
17883 .next()
17884 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17885 })?
17886 };
17887 if let Some(rename_range) = rename_range {
17888 this.update_in(cx, |this, window, cx| {
17889 let snapshot = cursor_buffer.read(cx).snapshot();
17890 let rename_buffer_range = rename_range.to_offset(&snapshot);
17891 let cursor_offset_in_rename_range =
17892 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17893 let cursor_offset_in_rename_range_end =
17894 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17895
17896 this.take_rename(false, window, cx);
17897 let buffer = this.buffer.read(cx).read(cx);
17898 let cursor_offset = selection.head().to_offset(&buffer);
17899 let rename_start =
17900 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17901 let rename_end = rename_start + rename_buffer_range.len();
17902 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17903 let mut old_highlight_id = None;
17904 let old_name: Arc<str> = buffer
17905 .chunks(rename_start..rename_end, true)
17906 .map(|chunk| {
17907 if old_highlight_id.is_none() {
17908 old_highlight_id = chunk.syntax_highlight_id;
17909 }
17910 chunk.text
17911 })
17912 .collect::<String>()
17913 .into();
17914
17915 drop(buffer);
17916
17917 // Position the selection in the rename editor so that it matches the current selection.
17918 this.show_local_selections = false;
17919 let rename_editor = cx.new(|cx| {
17920 let mut editor = Editor::single_line(window, cx);
17921 editor.buffer.update(cx, |buffer, cx| {
17922 buffer.edit(
17923 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17924 None,
17925 cx,
17926 )
17927 });
17928 let cursor_offset_in_rename_range =
17929 MultiBufferOffset(cursor_offset_in_rename_range);
17930 let cursor_offset_in_rename_range_end =
17931 MultiBufferOffset(cursor_offset_in_rename_range_end);
17932 let rename_selection_range = match cursor_offset_in_rename_range
17933 .cmp(&cursor_offset_in_rename_range_end)
17934 {
17935 Ordering::Equal => {
17936 editor.select_all(&SelectAll, window, cx);
17937 return editor;
17938 }
17939 Ordering::Less => {
17940 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17941 }
17942 Ordering::Greater => {
17943 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17944 }
17945 };
17946 if rename_selection_range.end.0 > old_name.len() {
17947 editor.select_all(&SelectAll, window, cx);
17948 } else {
17949 editor.change_selections(Default::default(), window, cx, |s| {
17950 s.select_ranges([rename_selection_range]);
17951 });
17952 }
17953 editor
17954 });
17955 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17956 if e == &EditorEvent::Focused {
17957 cx.emit(EditorEvent::FocusedIn)
17958 }
17959 })
17960 .detach();
17961
17962 let write_highlights =
17963 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17964 let read_highlights =
17965 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17966 let ranges = write_highlights
17967 .iter()
17968 .flat_map(|(_, ranges)| ranges.iter())
17969 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17970 .cloned()
17971 .collect();
17972
17973 this.highlight_text::<Rename>(
17974 ranges,
17975 HighlightStyle {
17976 fade_out: Some(0.6),
17977 ..Default::default()
17978 },
17979 cx,
17980 );
17981 let rename_focus_handle = rename_editor.focus_handle(cx);
17982 window.focus(&rename_focus_handle, cx);
17983 let block_id = this.insert_blocks(
17984 [BlockProperties {
17985 style: BlockStyle::Flex,
17986 placement: BlockPlacement::Below(range.start),
17987 height: Some(1),
17988 render: Arc::new({
17989 let rename_editor = rename_editor.clone();
17990 move |cx: &mut BlockContext| {
17991 let mut text_style = cx.editor_style.text.clone();
17992 if let Some(highlight_style) = old_highlight_id
17993 .and_then(|h| h.style(&cx.editor_style.syntax))
17994 {
17995 text_style = text_style.highlight(highlight_style);
17996 }
17997 div()
17998 .block_mouse_except_scroll()
17999 .pl(cx.anchor_x)
18000 .child(EditorElement::new(
18001 &rename_editor,
18002 EditorStyle {
18003 background: cx.theme().system().transparent,
18004 local_player: cx.editor_style.local_player,
18005 text: text_style,
18006 scrollbar_width: cx.editor_style.scrollbar_width,
18007 syntax: cx.editor_style.syntax.clone(),
18008 status: cx.editor_style.status.clone(),
18009 inlay_hints_style: HighlightStyle {
18010 font_weight: Some(FontWeight::BOLD),
18011 ..make_inlay_hints_style(cx.app)
18012 },
18013 edit_prediction_styles: make_suggestion_styles(
18014 cx.app,
18015 ),
18016 ..EditorStyle::default()
18017 },
18018 ))
18019 .into_any_element()
18020 }
18021 }),
18022 priority: 0,
18023 }],
18024 Some(Autoscroll::fit()),
18025 cx,
18026 )[0];
18027 this.pending_rename = Some(RenameState {
18028 range,
18029 old_name,
18030 editor: rename_editor,
18031 block_id,
18032 });
18033 })?;
18034 }
18035
18036 Ok(())
18037 }))
18038 }
18039
18040 pub fn confirm_rename(
18041 &mut self,
18042 _: &ConfirmRename,
18043 window: &mut Window,
18044 cx: &mut Context<Self>,
18045 ) -> Option<Task<Result<()>>> {
18046 let rename = self.take_rename(false, window, cx)?;
18047 let workspace = self.workspace()?.downgrade();
18048 let (buffer, start) = self
18049 .buffer
18050 .read(cx)
18051 .text_anchor_for_position(rename.range.start, cx)?;
18052 let (end_buffer, _) = self
18053 .buffer
18054 .read(cx)
18055 .text_anchor_for_position(rename.range.end, cx)?;
18056 if buffer != end_buffer {
18057 return None;
18058 }
18059
18060 let old_name = rename.old_name;
18061 let new_name = rename.editor.read(cx).text(cx);
18062
18063 let rename = self.semantics_provider.as_ref()?.perform_rename(
18064 &buffer,
18065 start,
18066 new_name.clone(),
18067 cx,
18068 )?;
18069
18070 Some(cx.spawn_in(window, async move |editor, cx| {
18071 let project_transaction = rename.await?;
18072 Self::open_project_transaction(
18073 &editor,
18074 workspace,
18075 project_transaction,
18076 format!("Rename: {} → {}", old_name, new_name),
18077 cx,
18078 )
18079 .await?;
18080
18081 editor.update(cx, |editor, cx| {
18082 editor.refresh_document_highlights(cx);
18083 })?;
18084 Ok(())
18085 }))
18086 }
18087
18088 fn take_rename(
18089 &mut self,
18090 moving_cursor: bool,
18091 window: &mut Window,
18092 cx: &mut Context<Self>,
18093 ) -> Option<RenameState> {
18094 let rename = self.pending_rename.take()?;
18095 if rename.editor.focus_handle(cx).is_focused(window) {
18096 window.focus(&self.focus_handle, cx);
18097 }
18098
18099 self.remove_blocks(
18100 [rename.block_id].into_iter().collect(),
18101 Some(Autoscroll::fit()),
18102 cx,
18103 );
18104 self.clear_highlights::<Rename>(cx);
18105 self.show_local_selections = true;
18106
18107 if moving_cursor {
18108 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18109 editor
18110 .selections
18111 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18112 .head()
18113 });
18114
18115 // Update the selection to match the position of the selection inside
18116 // the rename editor.
18117 let snapshot = self.buffer.read(cx).read(cx);
18118 let rename_range = rename.range.to_offset(&snapshot);
18119 let cursor_in_editor = snapshot
18120 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18121 .min(rename_range.end);
18122 drop(snapshot);
18123
18124 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18125 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18126 });
18127 } else {
18128 self.refresh_document_highlights(cx);
18129 }
18130
18131 Some(rename)
18132 }
18133
18134 pub fn pending_rename(&self) -> Option<&RenameState> {
18135 self.pending_rename.as_ref()
18136 }
18137
18138 fn format(
18139 &mut self,
18140 _: &Format,
18141 window: &mut Window,
18142 cx: &mut Context<Self>,
18143 ) -> Option<Task<Result<()>>> {
18144 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18145
18146 let project = match &self.project {
18147 Some(project) => project.clone(),
18148 None => return None,
18149 };
18150
18151 Some(self.perform_format(
18152 project,
18153 FormatTrigger::Manual,
18154 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18155 window,
18156 cx,
18157 ))
18158 }
18159
18160 fn format_selections(
18161 &mut self,
18162 _: &FormatSelections,
18163 window: &mut Window,
18164 cx: &mut Context<Self>,
18165 ) -> Option<Task<Result<()>>> {
18166 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18167
18168 let project = match &self.project {
18169 Some(project) => project.clone(),
18170 None => return None,
18171 };
18172
18173 let ranges = self
18174 .selections
18175 .all_adjusted(&self.display_snapshot(cx))
18176 .into_iter()
18177 .map(|selection| selection.range())
18178 .collect_vec();
18179
18180 Some(self.perform_format(
18181 project,
18182 FormatTrigger::Manual,
18183 FormatTarget::Ranges(ranges),
18184 window,
18185 cx,
18186 ))
18187 }
18188
18189 fn perform_format(
18190 &mut self,
18191 project: Entity<Project>,
18192 trigger: FormatTrigger,
18193 target: FormatTarget,
18194 window: &mut Window,
18195 cx: &mut Context<Self>,
18196 ) -> Task<Result<()>> {
18197 let buffer = self.buffer.clone();
18198 let (buffers, target) = match target {
18199 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18200 FormatTarget::Ranges(selection_ranges) => {
18201 let multi_buffer = buffer.read(cx);
18202 let snapshot = multi_buffer.read(cx);
18203 let mut buffers = HashSet::default();
18204 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18205 BTreeMap::new();
18206 for selection_range in selection_ranges {
18207 for (buffer, buffer_range, _) in
18208 snapshot.range_to_buffer_ranges(selection_range)
18209 {
18210 let buffer_id = buffer.remote_id();
18211 let start = buffer.anchor_before(buffer_range.start);
18212 let end = buffer.anchor_after(buffer_range.end);
18213 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18214 buffer_id_to_ranges
18215 .entry(buffer_id)
18216 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18217 .or_insert_with(|| vec![start..end]);
18218 }
18219 }
18220 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18221 }
18222 };
18223
18224 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18225 let selections_prev = transaction_id_prev
18226 .and_then(|transaction_id_prev| {
18227 // default to selections as they were after the last edit, if we have them,
18228 // instead of how they are now.
18229 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18230 // will take you back to where you made the last edit, instead of staying where you scrolled
18231 self.selection_history
18232 .transaction(transaction_id_prev)
18233 .map(|t| t.0.clone())
18234 })
18235 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18236
18237 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18238 let format = project.update(cx, |project, cx| {
18239 project.format(buffers, target, true, trigger, cx)
18240 });
18241
18242 cx.spawn_in(window, async move |editor, cx| {
18243 let transaction = futures::select_biased! {
18244 transaction = format.log_err().fuse() => transaction,
18245 () = timeout => {
18246 log::warn!("timed out waiting for formatting");
18247 None
18248 }
18249 };
18250
18251 buffer
18252 .update(cx, |buffer, cx| {
18253 if let Some(transaction) = transaction
18254 && !buffer.is_singleton()
18255 {
18256 buffer.push_transaction(&transaction.0, cx);
18257 }
18258 cx.notify();
18259 })
18260 .ok();
18261
18262 if let Some(transaction_id_now) =
18263 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18264 {
18265 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18266 if has_new_transaction {
18267 _ = editor.update(cx, |editor, _| {
18268 editor
18269 .selection_history
18270 .insert_transaction(transaction_id_now, selections_prev);
18271 });
18272 }
18273 }
18274
18275 Ok(())
18276 })
18277 }
18278
18279 fn organize_imports(
18280 &mut self,
18281 _: &OrganizeImports,
18282 window: &mut Window,
18283 cx: &mut Context<Self>,
18284 ) -> Option<Task<Result<()>>> {
18285 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18286 let project = match &self.project {
18287 Some(project) => project.clone(),
18288 None => return None,
18289 };
18290 Some(self.perform_code_action_kind(
18291 project,
18292 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18293 window,
18294 cx,
18295 ))
18296 }
18297
18298 fn perform_code_action_kind(
18299 &mut self,
18300 project: Entity<Project>,
18301 kind: CodeActionKind,
18302 window: &mut Window,
18303 cx: &mut Context<Self>,
18304 ) -> Task<Result<()>> {
18305 let buffer = self.buffer.clone();
18306 let buffers = buffer.read(cx).all_buffers();
18307 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18308 let apply_action = project.update(cx, |project, cx| {
18309 project.apply_code_action_kind(buffers, kind, true, cx)
18310 });
18311 cx.spawn_in(window, async move |_, cx| {
18312 let transaction = futures::select_biased! {
18313 () = timeout => {
18314 log::warn!("timed out waiting for executing code action");
18315 None
18316 }
18317 transaction = apply_action.log_err().fuse() => transaction,
18318 };
18319 buffer
18320 .update(cx, |buffer, cx| {
18321 // check if we need this
18322 if let Some(transaction) = transaction
18323 && !buffer.is_singleton()
18324 {
18325 buffer.push_transaction(&transaction.0, cx);
18326 }
18327 cx.notify();
18328 })
18329 .ok();
18330 Ok(())
18331 })
18332 }
18333
18334 pub fn restart_language_server(
18335 &mut self,
18336 _: &RestartLanguageServer,
18337 _: &mut Window,
18338 cx: &mut Context<Self>,
18339 ) {
18340 if let Some(project) = self.project.clone() {
18341 self.buffer.update(cx, |multi_buffer, cx| {
18342 project.update(cx, |project, cx| {
18343 project.restart_language_servers_for_buffers(
18344 multi_buffer.all_buffers().into_iter().collect(),
18345 HashSet::default(),
18346 cx,
18347 );
18348 });
18349 })
18350 }
18351 }
18352
18353 pub fn stop_language_server(
18354 &mut self,
18355 _: &StopLanguageServer,
18356 _: &mut Window,
18357 cx: &mut Context<Self>,
18358 ) {
18359 if let Some(project) = self.project.clone() {
18360 self.buffer.update(cx, |multi_buffer, cx| {
18361 project.update(cx, |project, cx| {
18362 project.stop_language_servers_for_buffers(
18363 multi_buffer.all_buffers().into_iter().collect(),
18364 HashSet::default(),
18365 cx,
18366 );
18367 });
18368 });
18369 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18370 }
18371 }
18372
18373 fn cancel_language_server_work(
18374 workspace: &mut Workspace,
18375 _: &actions::CancelLanguageServerWork,
18376 _: &mut Window,
18377 cx: &mut Context<Workspace>,
18378 ) {
18379 let project = workspace.project();
18380 let buffers = workspace
18381 .active_item(cx)
18382 .and_then(|item| item.act_as::<Editor>(cx))
18383 .map_or(HashSet::default(), |editor| {
18384 editor.read(cx).buffer.read(cx).all_buffers()
18385 });
18386 project.update(cx, |project, cx| {
18387 project.cancel_language_server_work_for_buffers(buffers, cx);
18388 });
18389 }
18390
18391 fn show_character_palette(
18392 &mut self,
18393 _: &ShowCharacterPalette,
18394 window: &mut Window,
18395 _: &mut Context<Self>,
18396 ) {
18397 window.show_character_palette();
18398 }
18399
18400 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18401 if !self.diagnostics_enabled() {
18402 return;
18403 }
18404
18405 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18406 let buffer = self.buffer.read(cx).snapshot(cx);
18407 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18408 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18409 let is_valid = buffer
18410 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18411 .any(|entry| {
18412 entry.diagnostic.is_primary
18413 && !entry.range.is_empty()
18414 && entry.range.start == primary_range_start
18415 && entry.diagnostic.message == active_diagnostics.active_message
18416 });
18417
18418 if !is_valid {
18419 self.dismiss_diagnostics(cx);
18420 }
18421 }
18422 }
18423
18424 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18425 match &self.active_diagnostics {
18426 ActiveDiagnostic::Group(group) => Some(group),
18427 _ => None,
18428 }
18429 }
18430
18431 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18432 if !self.diagnostics_enabled() {
18433 return;
18434 }
18435 self.dismiss_diagnostics(cx);
18436 self.active_diagnostics = ActiveDiagnostic::All;
18437 }
18438
18439 fn activate_diagnostics(
18440 &mut self,
18441 buffer_id: BufferId,
18442 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18443 window: &mut Window,
18444 cx: &mut Context<Self>,
18445 ) {
18446 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18447 return;
18448 }
18449 self.dismiss_diagnostics(cx);
18450 let snapshot = self.snapshot(window, cx);
18451 let buffer = self.buffer.read(cx).snapshot(cx);
18452 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18453 return;
18454 };
18455
18456 let diagnostic_group = buffer
18457 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18458 .collect::<Vec<_>>();
18459
18460 let language_registry = self
18461 .project()
18462 .map(|project| project.read(cx).languages().clone());
18463
18464 let blocks = renderer.render_group(
18465 diagnostic_group,
18466 buffer_id,
18467 snapshot,
18468 cx.weak_entity(),
18469 language_registry,
18470 cx,
18471 );
18472
18473 let blocks = self.display_map.update(cx, |display_map, cx| {
18474 display_map.insert_blocks(blocks, cx).into_iter().collect()
18475 });
18476 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18477 active_range: buffer.anchor_before(diagnostic.range.start)
18478 ..buffer.anchor_after(diagnostic.range.end),
18479 active_message: diagnostic.diagnostic.message.clone(),
18480 group_id: diagnostic.diagnostic.group_id,
18481 blocks,
18482 });
18483 cx.notify();
18484 }
18485
18486 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18487 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18488 return;
18489 };
18490
18491 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18492 if let ActiveDiagnostic::Group(group) = prev {
18493 self.display_map.update(cx, |display_map, cx| {
18494 display_map.remove_blocks(group.blocks, cx);
18495 });
18496 cx.notify();
18497 }
18498 }
18499
18500 /// Disable inline diagnostics rendering for this editor.
18501 pub fn disable_inline_diagnostics(&mut self) {
18502 self.inline_diagnostics_enabled = false;
18503 self.inline_diagnostics_update = Task::ready(());
18504 self.inline_diagnostics.clear();
18505 }
18506
18507 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18508 self.diagnostics_enabled = false;
18509 self.dismiss_diagnostics(cx);
18510 self.inline_diagnostics_update = Task::ready(());
18511 self.inline_diagnostics.clear();
18512 }
18513
18514 pub fn disable_word_completions(&mut self) {
18515 self.word_completions_enabled = false;
18516 }
18517
18518 pub fn diagnostics_enabled(&self) -> bool {
18519 self.diagnostics_enabled && self.mode.is_full()
18520 }
18521
18522 pub fn inline_diagnostics_enabled(&self) -> bool {
18523 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18524 }
18525
18526 pub fn show_inline_diagnostics(&self) -> bool {
18527 self.show_inline_diagnostics
18528 }
18529
18530 pub fn toggle_inline_diagnostics(
18531 &mut self,
18532 _: &ToggleInlineDiagnostics,
18533 window: &mut Window,
18534 cx: &mut Context<Editor>,
18535 ) {
18536 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18537 self.refresh_inline_diagnostics(false, window, cx);
18538 }
18539
18540 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18541 self.diagnostics_max_severity = severity;
18542 self.display_map.update(cx, |display_map, _| {
18543 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18544 });
18545 }
18546
18547 pub fn toggle_diagnostics(
18548 &mut self,
18549 _: &ToggleDiagnostics,
18550 window: &mut Window,
18551 cx: &mut Context<Editor>,
18552 ) {
18553 if !self.diagnostics_enabled() {
18554 return;
18555 }
18556
18557 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18558 EditorSettings::get_global(cx)
18559 .diagnostics_max_severity
18560 .filter(|severity| severity != &DiagnosticSeverity::Off)
18561 .unwrap_or(DiagnosticSeverity::Hint)
18562 } else {
18563 DiagnosticSeverity::Off
18564 };
18565 self.set_max_diagnostics_severity(new_severity, cx);
18566 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18567 self.active_diagnostics = ActiveDiagnostic::None;
18568 self.inline_diagnostics_update = Task::ready(());
18569 self.inline_diagnostics.clear();
18570 } else {
18571 self.refresh_inline_diagnostics(false, window, cx);
18572 }
18573
18574 cx.notify();
18575 }
18576
18577 pub fn toggle_minimap(
18578 &mut self,
18579 _: &ToggleMinimap,
18580 window: &mut Window,
18581 cx: &mut Context<Editor>,
18582 ) {
18583 if self.supports_minimap(cx) {
18584 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18585 }
18586 }
18587
18588 fn refresh_inline_diagnostics(
18589 &mut self,
18590 debounce: bool,
18591 window: &mut Window,
18592 cx: &mut Context<Self>,
18593 ) {
18594 let max_severity = ProjectSettings::get_global(cx)
18595 .diagnostics
18596 .inline
18597 .max_severity
18598 .unwrap_or(self.diagnostics_max_severity);
18599
18600 if !self.inline_diagnostics_enabled()
18601 || !self.diagnostics_enabled()
18602 || !self.show_inline_diagnostics
18603 || max_severity == DiagnosticSeverity::Off
18604 {
18605 self.inline_diagnostics_update = Task::ready(());
18606 self.inline_diagnostics.clear();
18607 return;
18608 }
18609
18610 let debounce_ms = ProjectSettings::get_global(cx)
18611 .diagnostics
18612 .inline
18613 .update_debounce_ms;
18614 let debounce = if debounce && debounce_ms > 0 {
18615 Some(Duration::from_millis(debounce_ms))
18616 } else {
18617 None
18618 };
18619 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18620 if let Some(debounce) = debounce {
18621 cx.background_executor().timer(debounce).await;
18622 }
18623 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18624 editor
18625 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18626 .ok()
18627 }) else {
18628 return;
18629 };
18630
18631 let new_inline_diagnostics = cx
18632 .background_spawn(async move {
18633 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18634 for diagnostic_entry in
18635 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18636 {
18637 let message = diagnostic_entry
18638 .diagnostic
18639 .message
18640 .split_once('\n')
18641 .map(|(line, _)| line)
18642 .map(SharedString::new)
18643 .unwrap_or_else(|| {
18644 SharedString::new(&*diagnostic_entry.diagnostic.message)
18645 });
18646 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18647 let (Ok(i) | Err(i)) = inline_diagnostics
18648 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18649 inline_diagnostics.insert(
18650 i,
18651 (
18652 start_anchor,
18653 InlineDiagnostic {
18654 message,
18655 group_id: diagnostic_entry.diagnostic.group_id,
18656 start: diagnostic_entry.range.start.to_point(&snapshot),
18657 is_primary: diagnostic_entry.diagnostic.is_primary,
18658 severity: diagnostic_entry.diagnostic.severity,
18659 },
18660 ),
18661 );
18662 }
18663 inline_diagnostics
18664 })
18665 .await;
18666
18667 editor
18668 .update(cx, |editor, cx| {
18669 editor.inline_diagnostics = new_inline_diagnostics;
18670 cx.notify();
18671 })
18672 .ok();
18673 });
18674 }
18675
18676 fn pull_diagnostics(
18677 &mut self,
18678 buffer_id: Option<BufferId>,
18679 window: &Window,
18680 cx: &mut Context<Self>,
18681 ) -> Option<()> {
18682 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18683 return None;
18684 }
18685 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18686 .diagnostics
18687 .lsp_pull_diagnostics;
18688 if !pull_diagnostics_settings.enabled {
18689 return None;
18690 }
18691 let project = self.project()?.downgrade();
18692
18693 let mut edited_buffer_ids = HashSet::default();
18694 let mut edited_worktree_ids = HashSet::default();
18695 let edited_buffers = match buffer_id {
18696 Some(buffer_id) => {
18697 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18698 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18699 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18700 edited_worktree_ids.insert(worktree_id);
18701 vec![buffer]
18702 }
18703 None => self
18704 .buffer()
18705 .read(cx)
18706 .all_buffers()
18707 .into_iter()
18708 .filter(|buffer| {
18709 let buffer = buffer.read(cx);
18710 match buffer.file().map(|f| f.worktree_id(cx)) {
18711 Some(worktree_id) => {
18712 edited_buffer_ids.insert(buffer.remote_id());
18713 edited_worktree_ids.insert(worktree_id);
18714 true
18715 }
18716 None => false,
18717 }
18718 })
18719 .collect::<Vec<_>>(),
18720 };
18721
18722 if edited_buffers.is_empty() {
18723 self.pull_diagnostics_task = Task::ready(());
18724 self.pull_diagnostics_background_task = Task::ready(());
18725 return None;
18726 }
18727
18728 let mut already_used_buffers = HashSet::default();
18729 let related_open_buffers = self
18730 .workspace
18731 .as_ref()
18732 .and_then(|(workspace, _)| workspace.upgrade())
18733 .into_iter()
18734 .flat_map(|workspace| workspace.read(cx).panes())
18735 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18736 .filter(|editor| editor != &cx.entity())
18737 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18738 .filter(|buffer| {
18739 let buffer = buffer.read(cx);
18740 let buffer_id = buffer.remote_id();
18741 if already_used_buffers.insert(buffer_id) {
18742 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18743 return !edited_buffer_ids.contains(&buffer_id)
18744 && !edited_worktree_ids.contains(&worktree_id);
18745 }
18746 }
18747 false
18748 })
18749 .collect::<Vec<_>>();
18750
18751 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18752 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18753 if buffers.is_empty() {
18754 return Task::ready(());
18755 }
18756 let project_weak = project.clone();
18757 cx.spawn_in(window, async move |_, cx| {
18758 cx.background_executor().timer(delay).await;
18759
18760 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18761 buffers
18762 .into_iter()
18763 .filter_map(|buffer| {
18764 project_weak
18765 .update(cx, |project, cx| {
18766 project.lsp_store().update(cx, |lsp_store, cx| {
18767 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18768 })
18769 })
18770 .ok()
18771 })
18772 .collect::<FuturesUnordered<_>>()
18773 }) else {
18774 return;
18775 };
18776
18777 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18778 if let Err(e) = pull_task {
18779 log::error!("Failed to update project diagnostics: {e:#}");
18780 }
18781 }
18782 })
18783 };
18784
18785 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18786 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18787
18788 Some(())
18789 }
18790
18791 pub fn set_selections_from_remote(
18792 &mut self,
18793 selections: Vec<Selection<Anchor>>,
18794 pending_selection: Option<Selection<Anchor>>,
18795 window: &mut Window,
18796 cx: &mut Context<Self>,
18797 ) {
18798 let old_cursor_position = self.selections.newest_anchor().head();
18799 self.selections
18800 .change_with(&self.display_snapshot(cx), |s| {
18801 s.select_anchors(selections);
18802 if let Some(pending_selection) = pending_selection {
18803 s.set_pending(pending_selection, SelectMode::Character);
18804 } else {
18805 s.clear_pending();
18806 }
18807 });
18808 self.selections_did_change(
18809 false,
18810 &old_cursor_position,
18811 SelectionEffects::default(),
18812 window,
18813 cx,
18814 );
18815 }
18816
18817 pub fn transact(
18818 &mut self,
18819 window: &mut Window,
18820 cx: &mut Context<Self>,
18821 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18822 ) -> Option<TransactionId> {
18823 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18824 this.start_transaction_at(Instant::now(), window, cx);
18825 update(this, window, cx);
18826 this.end_transaction_at(Instant::now(), cx)
18827 })
18828 }
18829
18830 pub fn start_transaction_at(
18831 &mut self,
18832 now: Instant,
18833 window: &mut Window,
18834 cx: &mut Context<Self>,
18835 ) -> Option<TransactionId> {
18836 self.end_selection(window, cx);
18837 if let Some(tx_id) = self
18838 .buffer
18839 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18840 {
18841 self.selection_history
18842 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18843 cx.emit(EditorEvent::TransactionBegun {
18844 transaction_id: tx_id,
18845 });
18846 Some(tx_id)
18847 } else {
18848 None
18849 }
18850 }
18851
18852 pub fn end_transaction_at(
18853 &mut self,
18854 now: Instant,
18855 cx: &mut Context<Self>,
18856 ) -> Option<TransactionId> {
18857 if let Some(transaction_id) = self
18858 .buffer
18859 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18860 {
18861 if let Some((_, end_selections)) =
18862 self.selection_history.transaction_mut(transaction_id)
18863 {
18864 *end_selections = Some(self.selections.disjoint_anchors_arc());
18865 } else {
18866 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18867 }
18868
18869 cx.emit(EditorEvent::Edited { transaction_id });
18870 Some(transaction_id)
18871 } else {
18872 None
18873 }
18874 }
18875
18876 pub fn modify_transaction_selection_history(
18877 &mut self,
18878 transaction_id: TransactionId,
18879 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18880 ) -> bool {
18881 self.selection_history
18882 .transaction_mut(transaction_id)
18883 .map(modify)
18884 .is_some()
18885 }
18886
18887 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18888 if self.selection_mark_mode {
18889 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18890 s.move_with(|_, sel| {
18891 sel.collapse_to(sel.head(), SelectionGoal::None);
18892 });
18893 })
18894 }
18895 self.selection_mark_mode = true;
18896 cx.notify();
18897 }
18898
18899 pub fn swap_selection_ends(
18900 &mut self,
18901 _: &actions::SwapSelectionEnds,
18902 window: &mut Window,
18903 cx: &mut Context<Self>,
18904 ) {
18905 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18906 s.move_with(|_, sel| {
18907 if sel.start != sel.end {
18908 sel.reversed = !sel.reversed
18909 }
18910 });
18911 });
18912 self.request_autoscroll(Autoscroll::newest(), cx);
18913 cx.notify();
18914 }
18915
18916 pub fn toggle_focus(
18917 workspace: &mut Workspace,
18918 _: &actions::ToggleFocus,
18919 window: &mut Window,
18920 cx: &mut Context<Workspace>,
18921 ) {
18922 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18923 return;
18924 };
18925 workspace.activate_item(&item, true, true, window, cx);
18926 }
18927
18928 pub fn toggle_fold(
18929 &mut self,
18930 _: &actions::ToggleFold,
18931 window: &mut Window,
18932 cx: &mut Context<Self>,
18933 ) {
18934 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18935 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18936 let selection = self.selections.newest::<Point>(&display_map);
18937
18938 let range = if selection.is_empty() {
18939 let point = selection.head().to_display_point(&display_map);
18940 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18941 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18942 .to_point(&display_map);
18943 start..end
18944 } else {
18945 selection.range()
18946 };
18947 if display_map.folds_in_range(range).next().is_some() {
18948 self.unfold_lines(&Default::default(), window, cx)
18949 } else {
18950 self.fold(&Default::default(), window, cx)
18951 }
18952 } else {
18953 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18954 let buffer_ids: HashSet<_> = self
18955 .selections
18956 .disjoint_anchor_ranges()
18957 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18958 .collect();
18959
18960 let should_unfold = buffer_ids
18961 .iter()
18962 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18963
18964 for buffer_id in buffer_ids {
18965 if should_unfold {
18966 self.unfold_buffer(buffer_id, cx);
18967 } else {
18968 self.fold_buffer(buffer_id, cx);
18969 }
18970 }
18971 }
18972 }
18973
18974 pub fn toggle_fold_recursive(
18975 &mut self,
18976 _: &actions::ToggleFoldRecursive,
18977 window: &mut Window,
18978 cx: &mut Context<Self>,
18979 ) {
18980 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18981
18982 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18983 let range = if selection.is_empty() {
18984 let point = selection.head().to_display_point(&display_map);
18985 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18986 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18987 .to_point(&display_map);
18988 start..end
18989 } else {
18990 selection.range()
18991 };
18992 if display_map.folds_in_range(range).next().is_some() {
18993 self.unfold_recursive(&Default::default(), window, cx)
18994 } else {
18995 self.fold_recursive(&Default::default(), window, cx)
18996 }
18997 }
18998
18999 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19000 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19001 let mut to_fold = Vec::new();
19002 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19003 let selections = self.selections.all_adjusted(&display_map);
19004
19005 for selection in selections {
19006 let range = selection.range().sorted();
19007 let buffer_start_row = range.start.row;
19008
19009 if range.start.row != range.end.row {
19010 let mut found = false;
19011 let mut row = range.start.row;
19012 while row <= range.end.row {
19013 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19014 {
19015 found = true;
19016 row = crease.range().end.row + 1;
19017 to_fold.push(crease);
19018 } else {
19019 row += 1
19020 }
19021 }
19022 if found {
19023 continue;
19024 }
19025 }
19026
19027 for row in (0..=range.start.row).rev() {
19028 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19029 && crease.range().end.row >= buffer_start_row
19030 {
19031 to_fold.push(crease);
19032 if row <= range.start.row {
19033 break;
19034 }
19035 }
19036 }
19037 }
19038
19039 self.fold_creases(to_fold, true, window, cx);
19040 } else {
19041 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19042 let buffer_ids = self
19043 .selections
19044 .disjoint_anchor_ranges()
19045 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19046 .collect::<HashSet<_>>();
19047 for buffer_id in buffer_ids {
19048 self.fold_buffer(buffer_id, cx);
19049 }
19050 }
19051 }
19052
19053 pub fn toggle_fold_all(
19054 &mut self,
19055 _: &actions::ToggleFoldAll,
19056 window: &mut Window,
19057 cx: &mut Context<Self>,
19058 ) {
19059 if self.buffer.read(cx).is_singleton() {
19060 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19061 let has_folds = display_map
19062 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19063 .next()
19064 .is_some();
19065
19066 if has_folds {
19067 self.unfold_all(&actions::UnfoldAll, window, cx);
19068 } else {
19069 self.fold_all(&actions::FoldAll, window, cx);
19070 }
19071 } else {
19072 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19073 let should_unfold = buffer_ids
19074 .iter()
19075 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19076
19077 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19078 editor
19079 .update_in(cx, |editor, _, cx| {
19080 for buffer_id in buffer_ids {
19081 if should_unfold {
19082 editor.unfold_buffer(buffer_id, cx);
19083 } else {
19084 editor.fold_buffer(buffer_id, cx);
19085 }
19086 }
19087 })
19088 .ok();
19089 });
19090 }
19091 }
19092
19093 fn fold_at_level(
19094 &mut self,
19095 fold_at: &FoldAtLevel,
19096 window: &mut Window,
19097 cx: &mut Context<Self>,
19098 ) {
19099 if !self.buffer.read(cx).is_singleton() {
19100 return;
19101 }
19102
19103 let fold_at_level = fold_at.0;
19104 let snapshot = self.buffer.read(cx).snapshot(cx);
19105 let mut to_fold = Vec::new();
19106 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19107
19108 let row_ranges_to_keep: Vec<Range<u32>> = self
19109 .selections
19110 .all::<Point>(&self.display_snapshot(cx))
19111 .into_iter()
19112 .map(|sel| sel.start.row..sel.end.row)
19113 .collect();
19114
19115 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19116 while start_row < end_row {
19117 match self
19118 .snapshot(window, cx)
19119 .crease_for_buffer_row(MultiBufferRow(start_row))
19120 {
19121 Some(crease) => {
19122 let nested_start_row = crease.range().start.row + 1;
19123 let nested_end_row = crease.range().end.row;
19124
19125 if current_level < fold_at_level {
19126 stack.push((nested_start_row, nested_end_row, current_level + 1));
19127 } else if current_level == fold_at_level {
19128 // Fold iff there is no selection completely contained within the fold region
19129 if !row_ranges_to_keep.iter().any(|selection| {
19130 selection.end >= nested_start_row
19131 && selection.start <= nested_end_row
19132 }) {
19133 to_fold.push(crease);
19134 }
19135 }
19136
19137 start_row = nested_end_row + 1;
19138 }
19139 None => start_row += 1,
19140 }
19141 }
19142 }
19143
19144 self.fold_creases(to_fold, true, window, cx);
19145 }
19146
19147 pub fn fold_at_level_1(
19148 &mut self,
19149 _: &actions::FoldAtLevel1,
19150 window: &mut Window,
19151 cx: &mut Context<Self>,
19152 ) {
19153 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19154 }
19155
19156 pub fn fold_at_level_2(
19157 &mut self,
19158 _: &actions::FoldAtLevel2,
19159 window: &mut Window,
19160 cx: &mut Context<Self>,
19161 ) {
19162 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19163 }
19164
19165 pub fn fold_at_level_3(
19166 &mut self,
19167 _: &actions::FoldAtLevel3,
19168 window: &mut Window,
19169 cx: &mut Context<Self>,
19170 ) {
19171 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19172 }
19173
19174 pub fn fold_at_level_4(
19175 &mut self,
19176 _: &actions::FoldAtLevel4,
19177 window: &mut Window,
19178 cx: &mut Context<Self>,
19179 ) {
19180 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19181 }
19182
19183 pub fn fold_at_level_5(
19184 &mut self,
19185 _: &actions::FoldAtLevel5,
19186 window: &mut Window,
19187 cx: &mut Context<Self>,
19188 ) {
19189 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19190 }
19191
19192 pub fn fold_at_level_6(
19193 &mut self,
19194 _: &actions::FoldAtLevel6,
19195 window: &mut Window,
19196 cx: &mut Context<Self>,
19197 ) {
19198 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19199 }
19200
19201 pub fn fold_at_level_7(
19202 &mut self,
19203 _: &actions::FoldAtLevel7,
19204 window: &mut Window,
19205 cx: &mut Context<Self>,
19206 ) {
19207 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19208 }
19209
19210 pub fn fold_at_level_8(
19211 &mut self,
19212 _: &actions::FoldAtLevel8,
19213 window: &mut Window,
19214 cx: &mut Context<Self>,
19215 ) {
19216 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19217 }
19218
19219 pub fn fold_at_level_9(
19220 &mut self,
19221 _: &actions::FoldAtLevel9,
19222 window: &mut Window,
19223 cx: &mut Context<Self>,
19224 ) {
19225 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19226 }
19227
19228 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19229 if self.buffer.read(cx).is_singleton() {
19230 let mut fold_ranges = Vec::new();
19231 let snapshot = self.buffer.read(cx).snapshot(cx);
19232
19233 for row in 0..snapshot.max_row().0 {
19234 if let Some(foldable_range) = self
19235 .snapshot(window, cx)
19236 .crease_for_buffer_row(MultiBufferRow(row))
19237 {
19238 fold_ranges.push(foldable_range);
19239 }
19240 }
19241
19242 self.fold_creases(fold_ranges, true, window, cx);
19243 } else {
19244 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19245 editor
19246 .update_in(cx, |editor, _, cx| {
19247 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19248 editor.fold_buffer(buffer_id, cx);
19249 }
19250 })
19251 .ok();
19252 });
19253 }
19254 }
19255
19256 pub fn fold_function_bodies(
19257 &mut self,
19258 _: &actions::FoldFunctionBodies,
19259 window: &mut Window,
19260 cx: &mut Context<Self>,
19261 ) {
19262 let snapshot = self.buffer.read(cx).snapshot(cx);
19263
19264 let ranges = snapshot
19265 .text_object_ranges(
19266 MultiBufferOffset(0)..snapshot.len(),
19267 TreeSitterOptions::default(),
19268 )
19269 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19270 .collect::<Vec<_>>();
19271
19272 let creases = ranges
19273 .into_iter()
19274 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19275 .collect();
19276
19277 self.fold_creases(creases, true, window, cx);
19278 }
19279
19280 pub fn fold_recursive(
19281 &mut self,
19282 _: &actions::FoldRecursive,
19283 window: &mut Window,
19284 cx: &mut Context<Self>,
19285 ) {
19286 let mut to_fold = Vec::new();
19287 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19288 let selections = self.selections.all_adjusted(&display_map);
19289
19290 for selection in selections {
19291 let range = selection.range().sorted();
19292 let buffer_start_row = range.start.row;
19293
19294 if range.start.row != range.end.row {
19295 let mut found = false;
19296 for row in range.start.row..=range.end.row {
19297 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19298 found = true;
19299 to_fold.push(crease);
19300 }
19301 }
19302 if found {
19303 continue;
19304 }
19305 }
19306
19307 for row in (0..=range.start.row).rev() {
19308 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19309 if crease.range().end.row >= buffer_start_row {
19310 to_fold.push(crease);
19311 } else {
19312 break;
19313 }
19314 }
19315 }
19316 }
19317
19318 self.fold_creases(to_fold, true, window, cx);
19319 }
19320
19321 pub fn fold_at(
19322 &mut self,
19323 buffer_row: MultiBufferRow,
19324 window: &mut Window,
19325 cx: &mut Context<Self>,
19326 ) {
19327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19328
19329 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19330 let autoscroll = self
19331 .selections
19332 .all::<Point>(&display_map)
19333 .iter()
19334 .any(|selection| crease.range().overlaps(&selection.range()));
19335
19336 self.fold_creases(vec![crease], autoscroll, window, cx);
19337 }
19338 }
19339
19340 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19341 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19342 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19343 let buffer = display_map.buffer_snapshot();
19344 let selections = self.selections.all::<Point>(&display_map);
19345 let ranges = selections
19346 .iter()
19347 .map(|s| {
19348 let range = s.display_range(&display_map).sorted();
19349 let mut start = range.start.to_point(&display_map);
19350 let mut end = range.end.to_point(&display_map);
19351 start.column = 0;
19352 end.column = buffer.line_len(MultiBufferRow(end.row));
19353 start..end
19354 })
19355 .collect::<Vec<_>>();
19356
19357 self.unfold_ranges(&ranges, true, true, cx);
19358 } else {
19359 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19360 let buffer_ids = self
19361 .selections
19362 .disjoint_anchor_ranges()
19363 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19364 .collect::<HashSet<_>>();
19365 for buffer_id in buffer_ids {
19366 self.unfold_buffer(buffer_id, cx);
19367 }
19368 }
19369 }
19370
19371 pub fn unfold_recursive(
19372 &mut self,
19373 _: &UnfoldRecursive,
19374 _window: &mut Window,
19375 cx: &mut Context<Self>,
19376 ) {
19377 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19378 let selections = self.selections.all::<Point>(&display_map);
19379 let ranges = selections
19380 .iter()
19381 .map(|s| {
19382 let mut range = s.display_range(&display_map).sorted();
19383 *range.start.column_mut() = 0;
19384 *range.end.column_mut() = display_map.line_len(range.end.row());
19385 let start = range.start.to_point(&display_map);
19386 let end = range.end.to_point(&display_map);
19387 start..end
19388 })
19389 .collect::<Vec<_>>();
19390
19391 self.unfold_ranges(&ranges, true, true, cx);
19392 }
19393
19394 pub fn unfold_at(
19395 &mut self,
19396 buffer_row: MultiBufferRow,
19397 _window: &mut Window,
19398 cx: &mut Context<Self>,
19399 ) {
19400 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19401
19402 let intersection_range = Point::new(buffer_row.0, 0)
19403 ..Point::new(
19404 buffer_row.0,
19405 display_map.buffer_snapshot().line_len(buffer_row),
19406 );
19407
19408 let autoscroll = self
19409 .selections
19410 .all::<Point>(&display_map)
19411 .iter()
19412 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19413
19414 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19415 }
19416
19417 pub fn unfold_all(
19418 &mut self,
19419 _: &actions::UnfoldAll,
19420 _window: &mut Window,
19421 cx: &mut Context<Self>,
19422 ) {
19423 if self.buffer.read(cx).is_singleton() {
19424 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19425 self.unfold_ranges(
19426 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19427 true,
19428 true,
19429 cx,
19430 );
19431 } else {
19432 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19433 editor
19434 .update(cx, |editor, cx| {
19435 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19436 editor.unfold_buffer(buffer_id, cx);
19437 }
19438 })
19439 .ok();
19440 });
19441 }
19442 }
19443
19444 pub fn fold_selected_ranges(
19445 &mut self,
19446 _: &FoldSelectedRanges,
19447 window: &mut Window,
19448 cx: &mut Context<Self>,
19449 ) {
19450 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19451 let selections = self.selections.all_adjusted(&display_map);
19452 let ranges = selections
19453 .into_iter()
19454 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19455 .collect::<Vec<_>>();
19456 self.fold_creases(ranges, true, window, cx);
19457 }
19458
19459 pub fn fold_ranges<T: ToOffset + Clone>(
19460 &mut self,
19461 ranges: Vec<Range<T>>,
19462 auto_scroll: bool,
19463 window: &mut Window,
19464 cx: &mut Context<Self>,
19465 ) {
19466 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19467 let ranges = ranges
19468 .into_iter()
19469 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19470 .collect::<Vec<_>>();
19471 self.fold_creases(ranges, auto_scroll, window, cx);
19472 }
19473
19474 pub fn fold_creases<T: ToOffset + Clone>(
19475 &mut self,
19476 creases: Vec<Crease<T>>,
19477 auto_scroll: bool,
19478 _window: &mut Window,
19479 cx: &mut Context<Self>,
19480 ) {
19481 if creases.is_empty() {
19482 return;
19483 }
19484
19485 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19486
19487 if auto_scroll {
19488 self.request_autoscroll(Autoscroll::fit(), cx);
19489 }
19490
19491 cx.notify();
19492
19493 self.scrollbar_marker_state.dirty = true;
19494 self.folds_did_change(cx);
19495 }
19496
19497 /// Removes any folds whose ranges intersect any of the given ranges.
19498 pub fn unfold_ranges<T: ToOffset + Clone>(
19499 &mut self,
19500 ranges: &[Range<T>],
19501 inclusive: bool,
19502 auto_scroll: bool,
19503 cx: &mut Context<Self>,
19504 ) {
19505 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19506 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19507 });
19508 self.folds_did_change(cx);
19509 }
19510
19511 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19512 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19513 return;
19514 }
19515
19516 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19517 self.display_map.update(cx, |display_map, cx| {
19518 display_map.fold_buffers([buffer_id], cx)
19519 });
19520
19521 let snapshot = self.display_snapshot(cx);
19522 self.selections.change_with(&snapshot, |selections| {
19523 selections.remove_selections_from_buffer(buffer_id);
19524 });
19525
19526 cx.emit(EditorEvent::BufferFoldToggled {
19527 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19528 folded: true,
19529 });
19530 cx.notify();
19531 }
19532
19533 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19534 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19535 return;
19536 }
19537 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19538 self.display_map.update(cx, |display_map, cx| {
19539 display_map.unfold_buffers([buffer_id], cx);
19540 });
19541 cx.emit(EditorEvent::BufferFoldToggled {
19542 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19543 folded: false,
19544 });
19545 cx.notify();
19546 }
19547
19548 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19549 self.display_map.read(cx).is_buffer_folded(buffer)
19550 }
19551
19552 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19553 self.display_map.read(cx).folded_buffers()
19554 }
19555
19556 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19557 self.display_map.update(cx, |display_map, cx| {
19558 display_map.disable_header_for_buffer(buffer_id, cx);
19559 });
19560 cx.notify();
19561 }
19562
19563 /// Removes any folds with the given ranges.
19564 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19565 &mut self,
19566 ranges: &[Range<T>],
19567 type_id: TypeId,
19568 auto_scroll: bool,
19569 cx: &mut Context<Self>,
19570 ) {
19571 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19572 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19573 });
19574 self.folds_did_change(cx);
19575 }
19576
19577 fn remove_folds_with<T: ToOffset + Clone>(
19578 &mut self,
19579 ranges: &[Range<T>],
19580 auto_scroll: bool,
19581 cx: &mut Context<Self>,
19582 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19583 ) {
19584 if ranges.is_empty() {
19585 return;
19586 }
19587
19588 let mut buffers_affected = HashSet::default();
19589 let multi_buffer = self.buffer().read(cx);
19590 for range in ranges {
19591 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19592 buffers_affected.insert(buffer.read(cx).remote_id());
19593 };
19594 }
19595
19596 self.display_map.update(cx, update);
19597
19598 if auto_scroll {
19599 self.request_autoscroll(Autoscroll::fit(), cx);
19600 }
19601
19602 cx.notify();
19603 self.scrollbar_marker_state.dirty = true;
19604 self.active_indent_guides_state.dirty = true;
19605 }
19606
19607 pub fn update_renderer_widths(
19608 &mut self,
19609 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19610 cx: &mut Context<Self>,
19611 ) -> bool {
19612 self.display_map
19613 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19614 }
19615
19616 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19617 self.display_map.read(cx).fold_placeholder.clone()
19618 }
19619
19620 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19621 self.use_base_text_line_numbers = show;
19622 }
19623
19624 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19625 self.buffer.update(cx, |buffer, cx| {
19626 buffer.set_all_diff_hunks_expanded(cx);
19627 });
19628 }
19629
19630 pub fn expand_all_diff_hunks(
19631 &mut self,
19632 _: &ExpandAllDiffHunks,
19633 _window: &mut Window,
19634 cx: &mut Context<Self>,
19635 ) {
19636 self.buffer.update(cx, |buffer, cx| {
19637 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19638 });
19639 }
19640
19641 pub fn collapse_all_diff_hunks(
19642 &mut self,
19643 _: &CollapseAllDiffHunks,
19644 _window: &mut Window,
19645 cx: &mut Context<Self>,
19646 ) {
19647 self.buffer.update(cx, |buffer, cx| {
19648 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19649 });
19650 }
19651
19652 pub fn toggle_selected_diff_hunks(
19653 &mut self,
19654 _: &ToggleSelectedDiffHunks,
19655 _window: &mut Window,
19656 cx: &mut Context<Self>,
19657 ) {
19658 let ranges: Vec<_> = self
19659 .selections
19660 .disjoint_anchors()
19661 .iter()
19662 .map(|s| s.range())
19663 .collect();
19664 self.toggle_diff_hunks_in_ranges(ranges, cx);
19665 }
19666
19667 pub fn diff_hunks_in_ranges<'a>(
19668 &'a self,
19669 ranges: &'a [Range<Anchor>],
19670 buffer: &'a MultiBufferSnapshot,
19671 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19672 ranges.iter().flat_map(move |range| {
19673 let end_excerpt_id = range.end.excerpt_id;
19674 let range = range.to_point(buffer);
19675 let mut peek_end = range.end;
19676 if range.end.row < buffer.max_row().0 {
19677 peek_end = Point::new(range.end.row + 1, 0);
19678 }
19679 buffer
19680 .diff_hunks_in_range(range.start..peek_end)
19681 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19682 })
19683 }
19684
19685 pub fn has_stageable_diff_hunks_in_ranges(
19686 &self,
19687 ranges: &[Range<Anchor>],
19688 snapshot: &MultiBufferSnapshot,
19689 ) -> bool {
19690 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19691 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19692 }
19693
19694 pub fn toggle_staged_selected_diff_hunks(
19695 &mut self,
19696 _: &::git::ToggleStaged,
19697 _: &mut Window,
19698 cx: &mut Context<Self>,
19699 ) {
19700 let snapshot = self.buffer.read(cx).snapshot(cx);
19701 let ranges: Vec<_> = self
19702 .selections
19703 .disjoint_anchors()
19704 .iter()
19705 .map(|s| s.range())
19706 .collect();
19707 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19708 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19709 }
19710
19711 pub fn set_render_diff_hunk_controls(
19712 &mut self,
19713 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19714 cx: &mut Context<Self>,
19715 ) {
19716 self.render_diff_hunk_controls = render_diff_hunk_controls;
19717 cx.notify();
19718 }
19719
19720 pub fn stage_and_next(
19721 &mut self,
19722 _: &::git::StageAndNext,
19723 window: &mut Window,
19724 cx: &mut Context<Self>,
19725 ) {
19726 self.do_stage_or_unstage_and_next(true, window, cx);
19727 }
19728
19729 pub fn unstage_and_next(
19730 &mut self,
19731 _: &::git::UnstageAndNext,
19732 window: &mut Window,
19733 cx: &mut Context<Self>,
19734 ) {
19735 self.do_stage_or_unstage_and_next(false, window, cx);
19736 }
19737
19738 pub fn stage_or_unstage_diff_hunks(
19739 &mut self,
19740 stage: bool,
19741 ranges: Vec<Range<Anchor>>,
19742 cx: &mut Context<Self>,
19743 ) {
19744 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19745 cx.spawn(async move |this, cx| {
19746 task.await?;
19747 this.update(cx, |this, cx| {
19748 let snapshot = this.buffer.read(cx).snapshot(cx);
19749 let chunk_by = this
19750 .diff_hunks_in_ranges(&ranges, &snapshot)
19751 .chunk_by(|hunk| hunk.buffer_id);
19752 for (buffer_id, hunks) in &chunk_by {
19753 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19754 }
19755 })
19756 })
19757 .detach_and_log_err(cx);
19758 }
19759
19760 fn save_buffers_for_ranges_if_needed(
19761 &mut self,
19762 ranges: &[Range<Anchor>],
19763 cx: &mut Context<Editor>,
19764 ) -> Task<Result<()>> {
19765 let multibuffer = self.buffer.read(cx);
19766 let snapshot = multibuffer.read(cx);
19767 let buffer_ids: HashSet<_> = ranges
19768 .iter()
19769 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19770 .collect();
19771 drop(snapshot);
19772
19773 let mut buffers = HashSet::default();
19774 for buffer_id in buffer_ids {
19775 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19776 let buffer = buffer_entity.read(cx);
19777 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19778 {
19779 buffers.insert(buffer_entity);
19780 }
19781 }
19782 }
19783
19784 if let Some(project) = &self.project {
19785 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19786 } else {
19787 Task::ready(Ok(()))
19788 }
19789 }
19790
19791 fn do_stage_or_unstage_and_next(
19792 &mut self,
19793 stage: bool,
19794 window: &mut Window,
19795 cx: &mut Context<Self>,
19796 ) {
19797 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19798
19799 if ranges.iter().any(|range| range.start != range.end) {
19800 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19801 return;
19802 }
19803
19804 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19805 let snapshot = self.snapshot(window, cx);
19806 let position = self
19807 .selections
19808 .newest::<Point>(&snapshot.display_snapshot)
19809 .head();
19810 let mut row = snapshot
19811 .buffer_snapshot()
19812 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19813 .find(|hunk| hunk.row_range.start.0 > position.row)
19814 .map(|hunk| hunk.row_range.start);
19815
19816 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19817 // Outside of the project diff editor, wrap around to the beginning.
19818 if !all_diff_hunks_expanded {
19819 row = row.or_else(|| {
19820 snapshot
19821 .buffer_snapshot()
19822 .diff_hunks_in_range(Point::zero()..position)
19823 .find(|hunk| hunk.row_range.end.0 < position.row)
19824 .map(|hunk| hunk.row_range.start)
19825 });
19826 }
19827
19828 if let Some(row) = row {
19829 let destination = Point::new(row.0, 0);
19830 let autoscroll = Autoscroll::center();
19831
19832 self.unfold_ranges(&[destination..destination], false, false, cx);
19833 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19834 s.select_ranges([destination..destination]);
19835 });
19836 }
19837 }
19838
19839 fn do_stage_or_unstage(
19840 &self,
19841 stage: bool,
19842 buffer_id: BufferId,
19843 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19844 cx: &mut App,
19845 ) -> Option<()> {
19846 let project = self.project()?;
19847 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19848 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19849 let buffer_snapshot = buffer.read(cx).snapshot();
19850 let file_exists = buffer_snapshot
19851 .file()
19852 .is_some_and(|file| file.disk_state().exists());
19853 diff.update(cx, |diff, cx| {
19854 diff.stage_or_unstage_hunks(
19855 stage,
19856 &hunks
19857 .map(|hunk| buffer_diff::DiffHunk {
19858 buffer_range: hunk.buffer_range,
19859 // We don't need to pass in word diffs here because they're only used for rendering and
19860 // this function changes internal state
19861 base_word_diffs: Vec::default(),
19862 buffer_word_diffs: Vec::default(),
19863 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19864 ..hunk.diff_base_byte_range.end.0,
19865 secondary_status: hunk.secondary_status,
19866 range: Point::zero()..Point::zero(), // unused
19867 })
19868 .collect::<Vec<_>>(),
19869 &buffer_snapshot,
19870 file_exists,
19871 cx,
19872 )
19873 });
19874 None
19875 }
19876
19877 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19878 let ranges: Vec<_> = self
19879 .selections
19880 .disjoint_anchors()
19881 .iter()
19882 .map(|s| s.range())
19883 .collect();
19884 self.buffer
19885 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19886 }
19887
19888 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19889 self.buffer.update(cx, |buffer, cx| {
19890 let ranges = vec![Anchor::min()..Anchor::max()];
19891 if !buffer.all_diff_hunks_expanded()
19892 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19893 {
19894 buffer.collapse_diff_hunks(ranges, cx);
19895 true
19896 } else {
19897 false
19898 }
19899 })
19900 }
19901
19902 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19903 if self.buffer.read(cx).all_diff_hunks_expanded() {
19904 return true;
19905 }
19906 let ranges = vec![Anchor::min()..Anchor::max()];
19907 self.buffer
19908 .read(cx)
19909 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19910 }
19911
19912 fn toggle_diff_hunks_in_ranges(
19913 &mut self,
19914 ranges: Vec<Range<Anchor>>,
19915 cx: &mut Context<Editor>,
19916 ) {
19917 self.buffer.update(cx, |buffer, cx| {
19918 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19919 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19920 })
19921 }
19922
19923 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19924 self.buffer.update(cx, |buffer, cx| {
19925 let snapshot = buffer.snapshot(cx);
19926 let excerpt_id = range.end.excerpt_id;
19927 let point_range = range.to_point(&snapshot);
19928 let expand = !buffer.single_hunk_is_expanded(range, cx);
19929 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19930 })
19931 }
19932
19933 pub(crate) fn apply_all_diff_hunks(
19934 &mut self,
19935 _: &ApplyAllDiffHunks,
19936 window: &mut Window,
19937 cx: &mut Context<Self>,
19938 ) {
19939 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19940
19941 let buffers = self.buffer.read(cx).all_buffers();
19942 for branch_buffer in buffers {
19943 branch_buffer.update(cx, |branch_buffer, cx| {
19944 branch_buffer.merge_into_base(Vec::new(), cx);
19945 });
19946 }
19947
19948 if let Some(project) = self.project.clone() {
19949 self.save(
19950 SaveOptions {
19951 format: true,
19952 autosave: false,
19953 },
19954 project,
19955 window,
19956 cx,
19957 )
19958 .detach_and_log_err(cx);
19959 }
19960 }
19961
19962 pub(crate) fn apply_selected_diff_hunks(
19963 &mut self,
19964 _: &ApplyDiffHunk,
19965 window: &mut Window,
19966 cx: &mut Context<Self>,
19967 ) {
19968 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19969 let snapshot = self.snapshot(window, cx);
19970 let hunks = snapshot.hunks_for_ranges(
19971 self.selections
19972 .all(&snapshot.display_snapshot)
19973 .into_iter()
19974 .map(|selection| selection.range()),
19975 );
19976 let mut ranges_by_buffer = HashMap::default();
19977 self.transact(window, cx, |editor, _window, cx| {
19978 for hunk in hunks {
19979 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19980 ranges_by_buffer
19981 .entry(buffer.clone())
19982 .or_insert_with(Vec::new)
19983 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19984 }
19985 }
19986
19987 for (buffer, ranges) in ranges_by_buffer {
19988 buffer.update(cx, |buffer, cx| {
19989 buffer.merge_into_base(ranges, cx);
19990 });
19991 }
19992 });
19993
19994 if let Some(project) = self.project.clone() {
19995 self.save(
19996 SaveOptions {
19997 format: true,
19998 autosave: false,
19999 },
20000 project,
20001 window,
20002 cx,
20003 )
20004 .detach_and_log_err(cx);
20005 }
20006 }
20007
20008 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20009 if hovered != self.gutter_hovered {
20010 self.gutter_hovered = hovered;
20011 cx.notify();
20012 }
20013 }
20014
20015 pub fn insert_blocks(
20016 &mut self,
20017 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20018 autoscroll: Option<Autoscroll>,
20019 cx: &mut Context<Self>,
20020 ) -> Vec<CustomBlockId> {
20021 let blocks = self
20022 .display_map
20023 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20024 if let Some(autoscroll) = autoscroll {
20025 self.request_autoscroll(autoscroll, cx);
20026 }
20027 cx.notify();
20028 blocks
20029 }
20030
20031 pub fn resize_blocks(
20032 &mut self,
20033 heights: HashMap<CustomBlockId, u32>,
20034 autoscroll: Option<Autoscroll>,
20035 cx: &mut Context<Self>,
20036 ) {
20037 self.display_map
20038 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20039 if let Some(autoscroll) = autoscroll {
20040 self.request_autoscroll(autoscroll, cx);
20041 }
20042 cx.notify();
20043 }
20044
20045 pub fn replace_blocks(
20046 &mut self,
20047 renderers: HashMap<CustomBlockId, RenderBlock>,
20048 autoscroll: Option<Autoscroll>,
20049 cx: &mut Context<Self>,
20050 ) {
20051 self.display_map
20052 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20053 if let Some(autoscroll) = autoscroll {
20054 self.request_autoscroll(autoscroll, cx);
20055 }
20056 cx.notify();
20057 }
20058
20059 pub fn remove_blocks(
20060 &mut self,
20061 block_ids: HashSet<CustomBlockId>,
20062 autoscroll: Option<Autoscroll>,
20063 cx: &mut Context<Self>,
20064 ) {
20065 self.display_map.update(cx, |display_map, cx| {
20066 display_map.remove_blocks(block_ids, cx)
20067 });
20068 if let Some(autoscroll) = autoscroll {
20069 self.request_autoscroll(autoscroll, cx);
20070 }
20071 cx.notify();
20072 }
20073
20074 pub fn row_for_block(
20075 &self,
20076 block_id: CustomBlockId,
20077 cx: &mut Context<Self>,
20078 ) -> Option<DisplayRow> {
20079 self.display_map
20080 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20081 }
20082
20083 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20084 self.focused_block = Some(focused_block);
20085 }
20086
20087 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20088 self.focused_block.take()
20089 }
20090
20091 pub fn insert_creases(
20092 &mut self,
20093 creases: impl IntoIterator<Item = Crease<Anchor>>,
20094 cx: &mut Context<Self>,
20095 ) -> Vec<CreaseId> {
20096 self.display_map
20097 .update(cx, |map, cx| map.insert_creases(creases, cx))
20098 }
20099
20100 pub fn remove_creases(
20101 &mut self,
20102 ids: impl IntoIterator<Item = CreaseId>,
20103 cx: &mut Context<Self>,
20104 ) -> Vec<(CreaseId, Range<Anchor>)> {
20105 self.display_map
20106 .update(cx, |map, cx| map.remove_creases(ids, cx))
20107 }
20108
20109 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20110 self.display_map
20111 .update(cx, |map, cx| map.snapshot(cx))
20112 .longest_row()
20113 }
20114
20115 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20116 self.display_map
20117 .update(cx, |map, cx| map.snapshot(cx))
20118 .max_point()
20119 }
20120
20121 pub fn text(&self, cx: &App) -> String {
20122 self.buffer.read(cx).read(cx).text()
20123 }
20124
20125 pub fn is_empty(&self, cx: &App) -> bool {
20126 self.buffer.read(cx).read(cx).is_empty()
20127 }
20128
20129 pub fn text_option(&self, cx: &App) -> Option<String> {
20130 let text = self.text(cx);
20131 let text = text.trim();
20132
20133 if text.is_empty() {
20134 return None;
20135 }
20136
20137 Some(text.to_string())
20138 }
20139
20140 pub fn set_text(
20141 &mut self,
20142 text: impl Into<Arc<str>>,
20143 window: &mut Window,
20144 cx: &mut Context<Self>,
20145 ) {
20146 self.transact(window, cx, |this, _, cx| {
20147 this.buffer
20148 .read(cx)
20149 .as_singleton()
20150 .expect("you can only call set_text on editors for singleton buffers")
20151 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20152 });
20153 }
20154
20155 pub fn display_text(&self, cx: &mut App) -> String {
20156 self.display_map
20157 .update(cx, |map, cx| map.snapshot(cx))
20158 .text()
20159 }
20160
20161 fn create_minimap(
20162 &self,
20163 minimap_settings: MinimapSettings,
20164 window: &mut Window,
20165 cx: &mut Context<Self>,
20166 ) -> Option<Entity<Self>> {
20167 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20168 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20169 }
20170
20171 fn initialize_new_minimap(
20172 &self,
20173 minimap_settings: MinimapSettings,
20174 window: &mut Window,
20175 cx: &mut Context<Self>,
20176 ) -> Entity<Self> {
20177 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20178
20179 let mut minimap = Editor::new_internal(
20180 EditorMode::Minimap {
20181 parent: cx.weak_entity(),
20182 },
20183 self.buffer.clone(),
20184 None,
20185 Some(self.display_map.clone()),
20186 window,
20187 cx,
20188 );
20189 minimap.scroll_manager.clone_state(&self.scroll_manager);
20190 minimap.set_text_style_refinement(TextStyleRefinement {
20191 font_size: Some(MINIMAP_FONT_SIZE),
20192 font_weight: Some(MINIMAP_FONT_WEIGHT),
20193 ..Default::default()
20194 });
20195 minimap.update_minimap_configuration(minimap_settings, cx);
20196 cx.new(|_| minimap)
20197 }
20198
20199 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20200 let current_line_highlight = minimap_settings
20201 .current_line_highlight
20202 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20203 self.set_current_line_highlight(Some(current_line_highlight));
20204 }
20205
20206 pub fn minimap(&self) -> Option<&Entity<Self>> {
20207 self.minimap
20208 .as_ref()
20209 .filter(|_| self.minimap_visibility.visible())
20210 }
20211
20212 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20213 let mut wrap_guides = smallvec![];
20214
20215 if self.show_wrap_guides == Some(false) {
20216 return wrap_guides;
20217 }
20218
20219 let settings = self.buffer.read(cx).language_settings(cx);
20220 if settings.show_wrap_guides {
20221 match self.soft_wrap_mode(cx) {
20222 SoftWrap::Column(soft_wrap) => {
20223 wrap_guides.push((soft_wrap as usize, true));
20224 }
20225 SoftWrap::Bounded(soft_wrap) => {
20226 wrap_guides.push((soft_wrap as usize, true));
20227 }
20228 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20229 }
20230 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20231 }
20232
20233 wrap_guides
20234 }
20235
20236 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20237 let settings = self.buffer.read(cx).language_settings(cx);
20238 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20239 match mode {
20240 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20241 SoftWrap::None
20242 }
20243 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20244 language_settings::SoftWrap::PreferredLineLength => {
20245 SoftWrap::Column(settings.preferred_line_length)
20246 }
20247 language_settings::SoftWrap::Bounded => {
20248 SoftWrap::Bounded(settings.preferred_line_length)
20249 }
20250 }
20251 }
20252
20253 pub fn set_soft_wrap_mode(
20254 &mut self,
20255 mode: language_settings::SoftWrap,
20256
20257 cx: &mut Context<Self>,
20258 ) {
20259 self.soft_wrap_mode_override = Some(mode);
20260 cx.notify();
20261 }
20262
20263 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20264 self.hard_wrap = hard_wrap;
20265 cx.notify();
20266 }
20267
20268 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20269 self.text_style_refinement = Some(style);
20270 }
20271
20272 /// called by the Element so we know what style we were most recently rendered with.
20273 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20274 // We intentionally do not inform the display map about the minimap style
20275 // so that wrapping is not recalculated and stays consistent for the editor
20276 // and its linked minimap.
20277 if !self.mode.is_minimap() {
20278 let font = style.text.font();
20279 let font_size = style.text.font_size.to_pixels(window.rem_size());
20280 let display_map = self
20281 .placeholder_display_map
20282 .as_ref()
20283 .filter(|_| self.is_empty(cx))
20284 .unwrap_or(&self.display_map);
20285
20286 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20287 }
20288 self.style = Some(style);
20289 }
20290
20291 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20292 if self.style.is_none() {
20293 self.style = Some(self.create_style(cx));
20294 }
20295 self.style.as_ref().unwrap()
20296 }
20297
20298 // Called by the element. This method is not designed to be called outside of the editor
20299 // element's layout code because it does not notify when rewrapping is computed synchronously.
20300 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20301 if self.is_empty(cx) {
20302 self.placeholder_display_map
20303 .as_ref()
20304 .map_or(false, |display_map| {
20305 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20306 })
20307 } else {
20308 self.display_map
20309 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20310 }
20311 }
20312
20313 pub fn set_soft_wrap(&mut self) {
20314 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20315 }
20316
20317 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20318 if self.soft_wrap_mode_override.is_some() {
20319 self.soft_wrap_mode_override.take();
20320 } else {
20321 let soft_wrap = match self.soft_wrap_mode(cx) {
20322 SoftWrap::GitDiff => return,
20323 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20324 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20325 language_settings::SoftWrap::None
20326 }
20327 };
20328 self.soft_wrap_mode_override = Some(soft_wrap);
20329 }
20330 cx.notify();
20331 }
20332
20333 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20334 let Some(workspace) = self.workspace() else {
20335 return;
20336 };
20337 let fs = workspace.read(cx).app_state().fs.clone();
20338 let current_show = TabBarSettings::get_global(cx).show;
20339 update_settings_file(fs, cx, move |setting, _| {
20340 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20341 });
20342 }
20343
20344 pub fn toggle_indent_guides(
20345 &mut self,
20346 _: &ToggleIndentGuides,
20347 _: &mut Window,
20348 cx: &mut Context<Self>,
20349 ) {
20350 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20351 self.buffer
20352 .read(cx)
20353 .language_settings(cx)
20354 .indent_guides
20355 .enabled
20356 });
20357 self.show_indent_guides = Some(!currently_enabled);
20358 cx.notify();
20359 }
20360
20361 fn should_show_indent_guides(&self) -> Option<bool> {
20362 self.show_indent_guides
20363 }
20364
20365 pub fn disable_indent_guides_for_buffer(
20366 &mut self,
20367 buffer_id: BufferId,
20368 cx: &mut Context<Self>,
20369 ) {
20370 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20371 cx.notify();
20372 }
20373
20374 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20375 self.buffers_with_disabled_indent_guides
20376 .contains(&buffer_id)
20377 }
20378
20379 pub fn toggle_line_numbers(
20380 &mut self,
20381 _: &ToggleLineNumbers,
20382 _: &mut Window,
20383 cx: &mut Context<Self>,
20384 ) {
20385 let mut editor_settings = EditorSettings::get_global(cx).clone();
20386 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20387 EditorSettings::override_global(editor_settings, cx);
20388 }
20389
20390 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20391 if let Some(show_line_numbers) = self.show_line_numbers {
20392 return show_line_numbers;
20393 }
20394 EditorSettings::get_global(cx).gutter.line_numbers
20395 }
20396
20397 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20398 match (
20399 self.use_relative_line_numbers,
20400 EditorSettings::get_global(cx).relative_line_numbers,
20401 ) {
20402 (None, setting) => setting,
20403 (Some(false), _) => RelativeLineNumbers::Disabled,
20404 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20405 (Some(true), _) => RelativeLineNumbers::Enabled,
20406 }
20407 }
20408
20409 pub fn toggle_relative_line_numbers(
20410 &mut self,
20411 _: &ToggleRelativeLineNumbers,
20412 _: &mut Window,
20413 cx: &mut Context<Self>,
20414 ) {
20415 let is_relative = self.relative_line_numbers(cx);
20416 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20417 }
20418
20419 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20420 self.use_relative_line_numbers = is_relative;
20421 cx.notify();
20422 }
20423
20424 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20425 self.show_gutter = show_gutter;
20426 cx.notify();
20427 }
20428
20429 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20430 self.show_scrollbars = ScrollbarAxes {
20431 horizontal: show,
20432 vertical: show,
20433 };
20434 cx.notify();
20435 }
20436
20437 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20438 self.show_scrollbars.vertical = show;
20439 cx.notify();
20440 }
20441
20442 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20443 self.show_scrollbars.horizontal = show;
20444 cx.notify();
20445 }
20446
20447 pub fn set_minimap_visibility(
20448 &mut self,
20449 minimap_visibility: MinimapVisibility,
20450 window: &mut Window,
20451 cx: &mut Context<Self>,
20452 ) {
20453 if self.minimap_visibility != minimap_visibility {
20454 if minimap_visibility.visible() && self.minimap.is_none() {
20455 let minimap_settings = EditorSettings::get_global(cx).minimap;
20456 self.minimap =
20457 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20458 }
20459 self.minimap_visibility = minimap_visibility;
20460 cx.notify();
20461 }
20462 }
20463
20464 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20465 self.set_show_scrollbars(false, cx);
20466 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20467 }
20468
20469 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20470 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20471 }
20472
20473 /// Normally the text in full mode and auto height editors is padded on the
20474 /// left side by roughly half a character width for improved hit testing.
20475 ///
20476 /// Use this method to disable this for cases where this is not wanted (e.g.
20477 /// if you want to align the editor text with some other text above or below)
20478 /// or if you want to add this padding to single-line editors.
20479 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20480 self.offset_content = offset_content;
20481 cx.notify();
20482 }
20483
20484 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20485 self.show_line_numbers = Some(show_line_numbers);
20486 cx.notify();
20487 }
20488
20489 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20490 self.disable_expand_excerpt_buttons = true;
20491 cx.notify();
20492 }
20493
20494 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20495 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20496 cx.notify();
20497 }
20498
20499 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20500 self.show_code_actions = Some(show_code_actions);
20501 cx.notify();
20502 }
20503
20504 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20505 self.show_runnables = Some(show_runnables);
20506 cx.notify();
20507 }
20508
20509 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20510 self.show_breakpoints = Some(show_breakpoints);
20511 cx.notify();
20512 }
20513
20514 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20515 if self.display_map.read(cx).masked != masked {
20516 self.display_map.update(cx, |map, _| map.masked = masked);
20517 }
20518 cx.notify()
20519 }
20520
20521 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20522 self.show_wrap_guides = Some(show_wrap_guides);
20523 cx.notify();
20524 }
20525
20526 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20527 self.show_indent_guides = Some(show_indent_guides);
20528 cx.notify();
20529 }
20530
20531 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20532 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20533 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20534 && let Some(dir) = file.abs_path(cx).parent()
20535 {
20536 return Some(dir.to_owned());
20537 }
20538 }
20539
20540 None
20541 }
20542
20543 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20544 self.active_excerpt(cx)?
20545 .1
20546 .read(cx)
20547 .file()
20548 .and_then(|f| f.as_local())
20549 }
20550
20551 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20552 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20553 let buffer = buffer.read(cx);
20554 if let Some(project_path) = buffer.project_path(cx) {
20555 let project = self.project()?.read(cx);
20556 project.absolute_path(&project_path, cx)
20557 } else {
20558 buffer
20559 .file()
20560 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20561 }
20562 })
20563 }
20564
20565 pub fn reveal_in_finder(
20566 &mut self,
20567 _: &RevealInFileManager,
20568 _window: &mut Window,
20569 cx: &mut Context<Self>,
20570 ) {
20571 if let Some(target) = self.target_file(cx) {
20572 cx.reveal_path(&target.abs_path(cx));
20573 }
20574 }
20575
20576 pub fn copy_path(
20577 &mut self,
20578 _: &zed_actions::workspace::CopyPath,
20579 _window: &mut Window,
20580 cx: &mut Context<Self>,
20581 ) {
20582 if let Some(path) = self.target_file_abs_path(cx)
20583 && let Some(path) = path.to_str()
20584 {
20585 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20586 } else {
20587 cx.propagate();
20588 }
20589 }
20590
20591 pub fn copy_relative_path(
20592 &mut self,
20593 _: &zed_actions::workspace::CopyRelativePath,
20594 _window: &mut Window,
20595 cx: &mut Context<Self>,
20596 ) {
20597 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20598 let project = self.project()?.read(cx);
20599 let path = buffer.read(cx).file()?.path();
20600 let path = path.display(project.path_style(cx));
20601 Some(path)
20602 }) {
20603 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20604 } else {
20605 cx.propagate();
20606 }
20607 }
20608
20609 /// Returns the project path for the editor's buffer, if any buffer is
20610 /// opened in the editor.
20611 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20612 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20613 buffer.read(cx).project_path(cx)
20614 } else {
20615 None
20616 }
20617 }
20618
20619 // Returns true if the editor handled a go-to-line request
20620 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20621 maybe!({
20622 let breakpoint_store = self.breakpoint_store.as_ref()?;
20623
20624 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20625 else {
20626 self.clear_row_highlights::<ActiveDebugLine>();
20627 return None;
20628 };
20629
20630 let position = active_stack_frame.position;
20631 let buffer_id = position.buffer_id?;
20632 let snapshot = self
20633 .project
20634 .as_ref()?
20635 .read(cx)
20636 .buffer_for_id(buffer_id, cx)?
20637 .read(cx)
20638 .snapshot();
20639
20640 let mut handled = false;
20641 for (id, ExcerptRange { context, .. }) in
20642 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20643 {
20644 if context.start.cmp(&position, &snapshot).is_ge()
20645 || context.end.cmp(&position, &snapshot).is_lt()
20646 {
20647 continue;
20648 }
20649 let snapshot = self.buffer.read(cx).snapshot(cx);
20650 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20651
20652 handled = true;
20653 self.clear_row_highlights::<ActiveDebugLine>();
20654
20655 self.go_to_line::<ActiveDebugLine>(
20656 multibuffer_anchor,
20657 Some(cx.theme().colors().editor_debugger_active_line_background),
20658 window,
20659 cx,
20660 );
20661
20662 cx.notify();
20663 }
20664
20665 handled.then_some(())
20666 })
20667 .is_some()
20668 }
20669
20670 pub fn copy_file_name_without_extension(
20671 &mut self,
20672 _: &CopyFileNameWithoutExtension,
20673 _: &mut Window,
20674 cx: &mut Context<Self>,
20675 ) {
20676 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20677 let file = buffer.read(cx).file()?;
20678 file.path().file_stem()
20679 }) {
20680 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20681 }
20682 }
20683
20684 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20685 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20686 let file = buffer.read(cx).file()?;
20687 Some(file.file_name(cx))
20688 }) {
20689 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20690 }
20691 }
20692
20693 pub fn toggle_git_blame(
20694 &mut self,
20695 _: &::git::Blame,
20696 window: &mut Window,
20697 cx: &mut Context<Self>,
20698 ) {
20699 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20700
20701 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20702 self.start_git_blame(true, window, cx);
20703 }
20704
20705 cx.notify();
20706 }
20707
20708 pub fn toggle_git_blame_inline(
20709 &mut self,
20710 _: &ToggleGitBlameInline,
20711 window: &mut Window,
20712 cx: &mut Context<Self>,
20713 ) {
20714 self.toggle_git_blame_inline_internal(true, window, cx);
20715 cx.notify();
20716 }
20717
20718 pub fn open_git_blame_commit(
20719 &mut self,
20720 _: &OpenGitBlameCommit,
20721 window: &mut Window,
20722 cx: &mut Context<Self>,
20723 ) {
20724 self.open_git_blame_commit_internal(window, cx);
20725 }
20726
20727 fn open_git_blame_commit_internal(
20728 &mut self,
20729 window: &mut Window,
20730 cx: &mut Context<Self>,
20731 ) -> Option<()> {
20732 let blame = self.blame.as_ref()?;
20733 let snapshot = self.snapshot(window, cx);
20734 let cursor = self
20735 .selections
20736 .newest::<Point>(&snapshot.display_snapshot)
20737 .head();
20738 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20739 let (_, blame_entry) = blame
20740 .update(cx, |blame, cx| {
20741 blame
20742 .blame_for_rows(
20743 &[RowInfo {
20744 buffer_id: Some(buffer.remote_id()),
20745 buffer_row: Some(point.row),
20746 ..Default::default()
20747 }],
20748 cx,
20749 )
20750 .next()
20751 })
20752 .flatten()?;
20753 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20754 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20755 let workspace = self.workspace()?.downgrade();
20756 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20757 None
20758 }
20759
20760 pub fn git_blame_inline_enabled(&self) -> bool {
20761 self.git_blame_inline_enabled
20762 }
20763
20764 pub fn toggle_selection_menu(
20765 &mut self,
20766 _: &ToggleSelectionMenu,
20767 _: &mut Window,
20768 cx: &mut Context<Self>,
20769 ) {
20770 self.show_selection_menu = self
20771 .show_selection_menu
20772 .map(|show_selections_menu| !show_selections_menu)
20773 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20774
20775 cx.notify();
20776 }
20777
20778 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20779 self.show_selection_menu
20780 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20781 }
20782
20783 fn start_git_blame(
20784 &mut self,
20785 user_triggered: bool,
20786 window: &mut Window,
20787 cx: &mut Context<Self>,
20788 ) {
20789 if let Some(project) = self.project() {
20790 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20791 && buffer.read(cx).file().is_none()
20792 {
20793 return;
20794 }
20795
20796 let focused = self.focus_handle(cx).contains_focused(window, cx);
20797
20798 let project = project.clone();
20799 let blame = cx
20800 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20801 self.blame_subscription =
20802 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20803 self.blame = Some(blame);
20804 }
20805 }
20806
20807 fn toggle_git_blame_inline_internal(
20808 &mut self,
20809 user_triggered: bool,
20810 window: &mut Window,
20811 cx: &mut Context<Self>,
20812 ) {
20813 if self.git_blame_inline_enabled {
20814 self.git_blame_inline_enabled = false;
20815 self.show_git_blame_inline = false;
20816 self.show_git_blame_inline_delay_task.take();
20817 } else {
20818 self.git_blame_inline_enabled = true;
20819 self.start_git_blame_inline(user_triggered, window, cx);
20820 }
20821
20822 cx.notify();
20823 }
20824
20825 fn start_git_blame_inline(
20826 &mut self,
20827 user_triggered: bool,
20828 window: &mut Window,
20829 cx: &mut Context<Self>,
20830 ) {
20831 self.start_git_blame(user_triggered, window, cx);
20832
20833 if ProjectSettings::get_global(cx)
20834 .git
20835 .inline_blame_delay()
20836 .is_some()
20837 {
20838 self.start_inline_blame_timer(window, cx);
20839 } else {
20840 self.show_git_blame_inline = true
20841 }
20842 }
20843
20844 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20845 self.blame.as_ref()
20846 }
20847
20848 pub fn show_git_blame_gutter(&self) -> bool {
20849 self.show_git_blame_gutter
20850 }
20851
20852 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20853 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20854 }
20855
20856 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20857 self.show_git_blame_inline
20858 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20859 && !self.newest_selection_head_on_empty_line(cx)
20860 && self.has_blame_entries(cx)
20861 }
20862
20863 fn has_blame_entries(&self, cx: &App) -> bool {
20864 self.blame()
20865 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20866 }
20867
20868 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20869 let cursor_anchor = self.selections.newest_anchor().head();
20870
20871 let snapshot = self.buffer.read(cx).snapshot(cx);
20872 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20873
20874 snapshot.line_len(buffer_row) == 0
20875 }
20876
20877 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20878 let buffer_and_selection = maybe!({
20879 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20880 let selection_range = selection.range();
20881
20882 let multi_buffer = self.buffer().read(cx);
20883 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20884 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20885
20886 let (buffer, range, _) = if selection.reversed {
20887 buffer_ranges.first()
20888 } else {
20889 buffer_ranges.last()
20890 }?;
20891
20892 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
20893 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
20894
20895 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
20896 let selection = start_row_in_buffer..end_row_in_buffer;
20897
20898 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
20899 };
20900
20901 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
20902
20903 Some((
20904 multi_buffer.buffer(buffer.remote_id()).unwrap(),
20905 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, buffer)
20906 ..buffer_diff_snapshot.row_to_base_text_row(end_row_in_buffer, buffer),
20907 ))
20908 });
20909
20910 let Some((buffer, selection)) = buffer_and_selection else {
20911 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20912 };
20913
20914 let Some(project) = self.project() else {
20915 return Task::ready(Err(anyhow!("editor does not have project")));
20916 };
20917
20918 project.update(cx, |project, cx| {
20919 project.get_permalink_to_line(&buffer, selection, cx)
20920 })
20921 }
20922
20923 pub fn copy_permalink_to_line(
20924 &mut self,
20925 _: &CopyPermalinkToLine,
20926 window: &mut Window,
20927 cx: &mut Context<Self>,
20928 ) {
20929 let permalink_task = self.get_permalink_to_line(cx);
20930 let workspace = self.workspace();
20931
20932 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20933 Ok(permalink) => {
20934 cx.update(|_, cx| {
20935 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20936 })
20937 .ok();
20938 }
20939 Err(err) => {
20940 let message = format!("Failed to copy permalink: {err}");
20941
20942 anyhow::Result::<()>::Err(err).log_err();
20943
20944 if let Some(workspace) = workspace {
20945 workspace
20946 .update_in(cx, |workspace, _, cx| {
20947 struct CopyPermalinkToLine;
20948
20949 workspace.show_toast(
20950 Toast::new(
20951 NotificationId::unique::<CopyPermalinkToLine>(),
20952 message,
20953 ),
20954 cx,
20955 )
20956 })
20957 .ok();
20958 }
20959 }
20960 })
20961 .detach();
20962 }
20963
20964 pub fn copy_file_location(
20965 &mut self,
20966 _: &CopyFileLocation,
20967 _: &mut Window,
20968 cx: &mut Context<Self>,
20969 ) {
20970 let selection = self
20971 .selections
20972 .newest::<Point>(&self.display_snapshot(cx))
20973 .start
20974 .row
20975 + 1;
20976 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20977 let project = self.project()?.read(cx);
20978 let file = buffer.read(cx).file()?;
20979 let path = file.path().display(project.path_style(cx));
20980
20981 Some(format!("{path}:{selection}"))
20982 }) {
20983 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
20984 }
20985 }
20986
20987 pub fn open_permalink_to_line(
20988 &mut self,
20989 _: &OpenPermalinkToLine,
20990 window: &mut Window,
20991 cx: &mut Context<Self>,
20992 ) {
20993 let permalink_task = self.get_permalink_to_line(cx);
20994 let workspace = self.workspace();
20995
20996 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20997 Ok(permalink) => {
20998 cx.update(|_, cx| {
20999 cx.open_url(permalink.as_ref());
21000 })
21001 .ok();
21002 }
21003 Err(err) => {
21004 let message = format!("Failed to open permalink: {err}");
21005
21006 anyhow::Result::<()>::Err(err).log_err();
21007
21008 if let Some(workspace) = workspace {
21009 workspace
21010 .update(cx, |workspace, cx| {
21011 struct OpenPermalinkToLine;
21012
21013 workspace.show_toast(
21014 Toast::new(
21015 NotificationId::unique::<OpenPermalinkToLine>(),
21016 message,
21017 ),
21018 cx,
21019 )
21020 })
21021 .ok();
21022 }
21023 }
21024 })
21025 .detach();
21026 }
21027
21028 pub fn insert_uuid_v4(
21029 &mut self,
21030 _: &InsertUuidV4,
21031 window: &mut Window,
21032 cx: &mut Context<Self>,
21033 ) {
21034 self.insert_uuid(UuidVersion::V4, window, cx);
21035 }
21036
21037 pub fn insert_uuid_v7(
21038 &mut self,
21039 _: &InsertUuidV7,
21040 window: &mut Window,
21041 cx: &mut Context<Self>,
21042 ) {
21043 self.insert_uuid(UuidVersion::V7, window, cx);
21044 }
21045
21046 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21047 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21048 self.transact(window, cx, |this, window, cx| {
21049 let edits = this
21050 .selections
21051 .all::<Point>(&this.display_snapshot(cx))
21052 .into_iter()
21053 .map(|selection| {
21054 let uuid = match version {
21055 UuidVersion::V4 => uuid::Uuid::new_v4(),
21056 UuidVersion::V7 => uuid::Uuid::now_v7(),
21057 };
21058
21059 (selection.range(), uuid.to_string())
21060 });
21061 this.edit(edits, cx);
21062 this.refresh_edit_prediction(true, false, window, cx);
21063 });
21064 }
21065
21066 pub fn open_selections_in_multibuffer(
21067 &mut self,
21068 _: &OpenSelectionsInMultibuffer,
21069 window: &mut Window,
21070 cx: &mut Context<Self>,
21071 ) {
21072 let multibuffer = self.buffer.read(cx);
21073
21074 let Some(buffer) = multibuffer.as_singleton() else {
21075 return;
21076 };
21077
21078 let Some(workspace) = self.workspace() else {
21079 return;
21080 };
21081
21082 let title = multibuffer.title(cx).to_string();
21083
21084 let locations = self
21085 .selections
21086 .all_anchors(&self.display_snapshot(cx))
21087 .iter()
21088 .map(|selection| {
21089 (
21090 buffer.clone(),
21091 (selection.start.text_anchor..selection.end.text_anchor)
21092 .to_point(buffer.read(cx)),
21093 )
21094 })
21095 .into_group_map();
21096
21097 cx.spawn_in(window, async move |_, cx| {
21098 workspace.update_in(cx, |workspace, window, cx| {
21099 Self::open_locations_in_multibuffer(
21100 workspace,
21101 locations,
21102 format!("Selections for '{title}'"),
21103 false,
21104 false,
21105 MultibufferSelectionMode::All,
21106 window,
21107 cx,
21108 );
21109 })
21110 })
21111 .detach();
21112 }
21113
21114 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21115 /// last highlight added will be used.
21116 ///
21117 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21118 pub fn highlight_rows<T: 'static>(
21119 &mut self,
21120 range: Range<Anchor>,
21121 color: Hsla,
21122 options: RowHighlightOptions,
21123 cx: &mut Context<Self>,
21124 ) {
21125 let snapshot = self.buffer().read(cx).snapshot(cx);
21126 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21127 let ix = row_highlights.binary_search_by(|highlight| {
21128 Ordering::Equal
21129 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21130 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21131 });
21132
21133 if let Err(mut ix) = ix {
21134 let index = post_inc(&mut self.highlight_order);
21135
21136 // If this range intersects with the preceding highlight, then merge it with
21137 // the preceding highlight. Otherwise insert a new highlight.
21138 let mut merged = false;
21139 if ix > 0 {
21140 let prev_highlight = &mut row_highlights[ix - 1];
21141 if prev_highlight
21142 .range
21143 .end
21144 .cmp(&range.start, &snapshot)
21145 .is_ge()
21146 {
21147 ix -= 1;
21148 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21149 prev_highlight.range.end = range.end;
21150 }
21151 merged = true;
21152 prev_highlight.index = index;
21153 prev_highlight.color = color;
21154 prev_highlight.options = options;
21155 }
21156 }
21157
21158 if !merged {
21159 row_highlights.insert(
21160 ix,
21161 RowHighlight {
21162 range,
21163 index,
21164 color,
21165 options,
21166 type_id: TypeId::of::<T>(),
21167 },
21168 );
21169 }
21170
21171 // If any of the following highlights intersect with this one, merge them.
21172 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21173 let highlight = &row_highlights[ix];
21174 if next_highlight
21175 .range
21176 .start
21177 .cmp(&highlight.range.end, &snapshot)
21178 .is_le()
21179 {
21180 if next_highlight
21181 .range
21182 .end
21183 .cmp(&highlight.range.end, &snapshot)
21184 .is_gt()
21185 {
21186 row_highlights[ix].range.end = next_highlight.range.end;
21187 }
21188 row_highlights.remove(ix + 1);
21189 } else {
21190 break;
21191 }
21192 }
21193 }
21194 }
21195
21196 /// Remove any highlighted row ranges of the given type that intersect the
21197 /// given ranges.
21198 pub fn remove_highlighted_rows<T: 'static>(
21199 &mut self,
21200 ranges_to_remove: Vec<Range<Anchor>>,
21201 cx: &mut Context<Self>,
21202 ) {
21203 let snapshot = self.buffer().read(cx).snapshot(cx);
21204 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21205 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21206 row_highlights.retain(|highlight| {
21207 while let Some(range_to_remove) = ranges_to_remove.peek() {
21208 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21209 Ordering::Less | Ordering::Equal => {
21210 ranges_to_remove.next();
21211 }
21212 Ordering::Greater => {
21213 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21214 Ordering::Less | Ordering::Equal => {
21215 return false;
21216 }
21217 Ordering::Greater => break,
21218 }
21219 }
21220 }
21221 }
21222
21223 true
21224 })
21225 }
21226
21227 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21228 pub fn clear_row_highlights<T: 'static>(&mut self) {
21229 self.highlighted_rows.remove(&TypeId::of::<T>());
21230 }
21231
21232 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21233 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21234 self.highlighted_rows
21235 .get(&TypeId::of::<T>())
21236 .map_or(&[] as &[_], |vec| vec.as_slice())
21237 .iter()
21238 .map(|highlight| (highlight.range.clone(), highlight.color))
21239 }
21240
21241 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21242 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21243 /// Allows to ignore certain kinds of highlights.
21244 pub fn highlighted_display_rows(
21245 &self,
21246 window: &mut Window,
21247 cx: &mut App,
21248 ) -> BTreeMap<DisplayRow, LineHighlight> {
21249 let snapshot = self.snapshot(window, cx);
21250 let mut used_highlight_orders = HashMap::default();
21251 self.highlighted_rows
21252 .iter()
21253 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21254 .fold(
21255 BTreeMap::<DisplayRow, LineHighlight>::new(),
21256 |mut unique_rows, highlight| {
21257 let start = highlight.range.start.to_display_point(&snapshot);
21258 let end = highlight.range.end.to_display_point(&snapshot);
21259 let start_row = start.row().0;
21260 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21261 {
21262 end.row().0.saturating_sub(1)
21263 } else {
21264 end.row().0
21265 };
21266 for row in start_row..=end_row {
21267 let used_index =
21268 used_highlight_orders.entry(row).or_insert(highlight.index);
21269 if highlight.index >= *used_index {
21270 *used_index = highlight.index;
21271 unique_rows.insert(
21272 DisplayRow(row),
21273 LineHighlight {
21274 include_gutter: highlight.options.include_gutter,
21275 border: None,
21276 background: highlight.color.into(),
21277 type_id: Some(highlight.type_id),
21278 },
21279 );
21280 }
21281 }
21282 unique_rows
21283 },
21284 )
21285 }
21286
21287 pub fn highlighted_display_row_for_autoscroll(
21288 &self,
21289 snapshot: &DisplaySnapshot,
21290 ) -> Option<DisplayRow> {
21291 self.highlighted_rows
21292 .values()
21293 .flat_map(|highlighted_rows| highlighted_rows.iter())
21294 .filter_map(|highlight| {
21295 if highlight.options.autoscroll {
21296 Some(highlight.range.start.to_display_point(snapshot).row())
21297 } else {
21298 None
21299 }
21300 })
21301 .min()
21302 }
21303
21304 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21305 self.highlight_background::<SearchWithinRange>(
21306 ranges,
21307 |_, colors| colors.colors().editor_document_highlight_read_background,
21308 cx,
21309 )
21310 }
21311
21312 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21313 self.breadcrumb_header = Some(new_header);
21314 }
21315
21316 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21317 self.clear_background_highlights::<SearchWithinRange>(cx);
21318 }
21319
21320 pub fn highlight_background<T: 'static>(
21321 &mut self,
21322 ranges: &[Range<Anchor>],
21323 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21324 cx: &mut Context<Self>,
21325 ) {
21326 self.background_highlights.insert(
21327 HighlightKey::Type(TypeId::of::<T>()),
21328 (Arc::new(color_fetcher), Arc::from(ranges)),
21329 );
21330 self.scrollbar_marker_state.dirty = true;
21331 cx.notify();
21332 }
21333
21334 pub fn highlight_background_key<T: 'static>(
21335 &mut self,
21336 key: usize,
21337 ranges: &[Range<Anchor>],
21338 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21339 cx: &mut Context<Self>,
21340 ) {
21341 self.background_highlights.insert(
21342 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21343 (Arc::new(color_fetcher), Arc::from(ranges)),
21344 );
21345 self.scrollbar_marker_state.dirty = true;
21346 cx.notify();
21347 }
21348
21349 pub fn clear_background_highlights<T: 'static>(
21350 &mut self,
21351 cx: &mut Context<Self>,
21352 ) -> Option<BackgroundHighlight> {
21353 let text_highlights = self
21354 .background_highlights
21355 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21356 if !text_highlights.1.is_empty() {
21357 self.scrollbar_marker_state.dirty = true;
21358 cx.notify();
21359 }
21360 Some(text_highlights)
21361 }
21362
21363 pub fn highlight_gutter<T: 'static>(
21364 &mut self,
21365 ranges: impl Into<Vec<Range<Anchor>>>,
21366 color_fetcher: fn(&App) -> Hsla,
21367 cx: &mut Context<Self>,
21368 ) {
21369 self.gutter_highlights
21370 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21371 cx.notify();
21372 }
21373
21374 pub fn clear_gutter_highlights<T: 'static>(
21375 &mut self,
21376 cx: &mut Context<Self>,
21377 ) -> Option<GutterHighlight> {
21378 cx.notify();
21379 self.gutter_highlights.remove(&TypeId::of::<T>())
21380 }
21381
21382 pub fn insert_gutter_highlight<T: 'static>(
21383 &mut self,
21384 range: Range<Anchor>,
21385 color_fetcher: fn(&App) -> Hsla,
21386 cx: &mut Context<Self>,
21387 ) {
21388 let snapshot = self.buffer().read(cx).snapshot(cx);
21389 let mut highlights = self
21390 .gutter_highlights
21391 .remove(&TypeId::of::<T>())
21392 .map(|(_, highlights)| highlights)
21393 .unwrap_or_default();
21394 let ix = highlights.binary_search_by(|highlight| {
21395 Ordering::Equal
21396 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21397 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21398 });
21399 if let Err(ix) = ix {
21400 highlights.insert(ix, range);
21401 }
21402 self.gutter_highlights
21403 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21404 }
21405
21406 pub fn remove_gutter_highlights<T: 'static>(
21407 &mut self,
21408 ranges_to_remove: Vec<Range<Anchor>>,
21409 cx: &mut Context<Self>,
21410 ) {
21411 let snapshot = self.buffer().read(cx).snapshot(cx);
21412 let Some((color_fetcher, mut gutter_highlights)) =
21413 self.gutter_highlights.remove(&TypeId::of::<T>())
21414 else {
21415 return;
21416 };
21417 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21418 gutter_highlights.retain(|highlight| {
21419 while let Some(range_to_remove) = ranges_to_remove.peek() {
21420 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21421 Ordering::Less | Ordering::Equal => {
21422 ranges_to_remove.next();
21423 }
21424 Ordering::Greater => {
21425 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21426 Ordering::Less | Ordering::Equal => {
21427 return false;
21428 }
21429 Ordering::Greater => break,
21430 }
21431 }
21432 }
21433 }
21434
21435 true
21436 });
21437 self.gutter_highlights
21438 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21439 }
21440
21441 #[cfg(feature = "test-support")]
21442 pub fn all_text_highlights(
21443 &self,
21444 window: &mut Window,
21445 cx: &mut Context<Self>,
21446 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21447 let snapshot = self.snapshot(window, cx);
21448 self.display_map.update(cx, |display_map, _| {
21449 display_map
21450 .all_text_highlights()
21451 .map(|highlight| {
21452 let (style, ranges) = highlight.as_ref();
21453 (
21454 *style,
21455 ranges
21456 .iter()
21457 .map(|range| range.clone().to_display_points(&snapshot))
21458 .collect(),
21459 )
21460 })
21461 .collect()
21462 })
21463 }
21464
21465 #[cfg(feature = "test-support")]
21466 pub fn all_text_background_highlights(
21467 &self,
21468 window: &mut Window,
21469 cx: &mut Context<Self>,
21470 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21471 let snapshot = self.snapshot(window, cx);
21472 let buffer = &snapshot.buffer_snapshot();
21473 let start = buffer.anchor_before(MultiBufferOffset(0));
21474 let end = buffer.anchor_after(buffer.len());
21475 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21476 }
21477
21478 #[cfg(any(test, feature = "test-support"))]
21479 pub fn sorted_background_highlights_in_range(
21480 &self,
21481 search_range: Range<Anchor>,
21482 display_snapshot: &DisplaySnapshot,
21483 theme: &Theme,
21484 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21485 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21486 res.sort_by(|a, b| {
21487 a.0.start
21488 .cmp(&b.0.start)
21489 .then_with(|| a.0.end.cmp(&b.0.end))
21490 .then_with(|| a.1.cmp(&b.1))
21491 });
21492 res
21493 }
21494
21495 #[cfg(feature = "test-support")]
21496 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21497 let snapshot = self.buffer().read(cx).snapshot(cx);
21498
21499 let highlights = self
21500 .background_highlights
21501 .get(&HighlightKey::Type(TypeId::of::<
21502 items::BufferSearchHighlights,
21503 >()));
21504
21505 if let Some((_color, ranges)) = highlights {
21506 ranges
21507 .iter()
21508 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21509 .collect_vec()
21510 } else {
21511 vec![]
21512 }
21513 }
21514
21515 fn document_highlights_for_position<'a>(
21516 &'a self,
21517 position: Anchor,
21518 buffer: &'a MultiBufferSnapshot,
21519 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21520 let read_highlights = self
21521 .background_highlights
21522 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21523 .map(|h| &h.1);
21524 let write_highlights = self
21525 .background_highlights
21526 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21527 .map(|h| &h.1);
21528 let left_position = position.bias_left(buffer);
21529 let right_position = position.bias_right(buffer);
21530 read_highlights
21531 .into_iter()
21532 .chain(write_highlights)
21533 .flat_map(move |ranges| {
21534 let start_ix = match ranges.binary_search_by(|probe| {
21535 let cmp = probe.end.cmp(&left_position, buffer);
21536 if cmp.is_ge() {
21537 Ordering::Greater
21538 } else {
21539 Ordering::Less
21540 }
21541 }) {
21542 Ok(i) | Err(i) => i,
21543 };
21544
21545 ranges[start_ix..]
21546 .iter()
21547 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21548 })
21549 }
21550
21551 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21552 self.background_highlights
21553 .get(&HighlightKey::Type(TypeId::of::<T>()))
21554 .is_some_and(|(_, highlights)| !highlights.is_empty())
21555 }
21556
21557 /// Returns all background highlights for a given range.
21558 ///
21559 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21560 pub fn background_highlights_in_range(
21561 &self,
21562 search_range: Range<Anchor>,
21563 display_snapshot: &DisplaySnapshot,
21564 theme: &Theme,
21565 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21566 let mut results = Vec::new();
21567 for (color_fetcher, ranges) in self.background_highlights.values() {
21568 let start_ix = match ranges.binary_search_by(|probe| {
21569 let cmp = probe
21570 .end
21571 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21572 if cmp.is_gt() {
21573 Ordering::Greater
21574 } else {
21575 Ordering::Less
21576 }
21577 }) {
21578 Ok(i) | Err(i) => i,
21579 };
21580 for (index, range) in ranges[start_ix..].iter().enumerate() {
21581 if range
21582 .start
21583 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21584 .is_ge()
21585 {
21586 break;
21587 }
21588
21589 let color = color_fetcher(&(start_ix + index), theme);
21590 let start = range.start.to_display_point(display_snapshot);
21591 let end = range.end.to_display_point(display_snapshot);
21592 results.push((start..end, color))
21593 }
21594 }
21595 results
21596 }
21597
21598 pub fn gutter_highlights_in_range(
21599 &self,
21600 search_range: Range<Anchor>,
21601 display_snapshot: &DisplaySnapshot,
21602 cx: &App,
21603 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21604 let mut results = Vec::new();
21605 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21606 let color = color_fetcher(cx);
21607 let start_ix = match ranges.binary_search_by(|probe| {
21608 let cmp = probe
21609 .end
21610 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21611 if cmp.is_gt() {
21612 Ordering::Greater
21613 } else {
21614 Ordering::Less
21615 }
21616 }) {
21617 Ok(i) | Err(i) => i,
21618 };
21619 for range in &ranges[start_ix..] {
21620 if range
21621 .start
21622 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21623 .is_ge()
21624 {
21625 break;
21626 }
21627
21628 let start = range.start.to_display_point(display_snapshot);
21629 let end = range.end.to_display_point(display_snapshot);
21630 results.push((start..end, color))
21631 }
21632 }
21633 results
21634 }
21635
21636 /// Get the text ranges corresponding to the redaction query
21637 pub fn redacted_ranges(
21638 &self,
21639 search_range: Range<Anchor>,
21640 display_snapshot: &DisplaySnapshot,
21641 cx: &App,
21642 ) -> Vec<Range<DisplayPoint>> {
21643 display_snapshot
21644 .buffer_snapshot()
21645 .redacted_ranges(search_range, |file| {
21646 if let Some(file) = file {
21647 file.is_private()
21648 && EditorSettings::get(
21649 Some(SettingsLocation {
21650 worktree_id: file.worktree_id(cx),
21651 path: file.path().as_ref(),
21652 }),
21653 cx,
21654 )
21655 .redact_private_values
21656 } else {
21657 false
21658 }
21659 })
21660 .map(|range| {
21661 range.start.to_display_point(display_snapshot)
21662 ..range.end.to_display_point(display_snapshot)
21663 })
21664 .collect()
21665 }
21666
21667 pub fn highlight_text_key<T: 'static>(
21668 &mut self,
21669 key: usize,
21670 ranges: Vec<Range<Anchor>>,
21671 style: HighlightStyle,
21672 merge: bool,
21673 cx: &mut Context<Self>,
21674 ) {
21675 self.display_map.update(cx, |map, cx| {
21676 map.highlight_text(
21677 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21678 ranges,
21679 style,
21680 merge,
21681 cx,
21682 );
21683 });
21684 cx.notify();
21685 }
21686
21687 pub fn highlight_text<T: 'static>(
21688 &mut self,
21689 ranges: Vec<Range<Anchor>>,
21690 style: HighlightStyle,
21691 cx: &mut Context<Self>,
21692 ) {
21693 self.display_map.update(cx, |map, cx| {
21694 map.highlight_text(
21695 HighlightKey::Type(TypeId::of::<T>()),
21696 ranges,
21697 style,
21698 false,
21699 cx,
21700 )
21701 });
21702 cx.notify();
21703 }
21704
21705 pub fn text_highlights<'a, T: 'static>(
21706 &'a self,
21707 cx: &'a App,
21708 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21709 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21710 }
21711
21712 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21713 let cleared = self
21714 .display_map
21715 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21716 if cleared {
21717 cx.notify();
21718 }
21719 }
21720
21721 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21722 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21723 && self.focus_handle.is_focused(window)
21724 }
21725
21726 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21727 self.show_cursor_when_unfocused = is_enabled;
21728 cx.notify();
21729 }
21730
21731 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21732 cx.notify();
21733 }
21734
21735 fn on_debug_session_event(
21736 &mut self,
21737 _session: Entity<Session>,
21738 event: &SessionEvent,
21739 cx: &mut Context<Self>,
21740 ) {
21741 if let SessionEvent::InvalidateInlineValue = event {
21742 self.refresh_inline_values(cx);
21743 }
21744 }
21745
21746 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21747 let Some(project) = self.project.clone() else {
21748 return;
21749 };
21750
21751 if !self.inline_value_cache.enabled {
21752 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21753 self.splice_inlays(&inlays, Vec::new(), cx);
21754 return;
21755 }
21756
21757 let current_execution_position = self
21758 .highlighted_rows
21759 .get(&TypeId::of::<ActiveDebugLine>())
21760 .and_then(|lines| lines.last().map(|line| line.range.end));
21761
21762 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21763 let inline_values = editor
21764 .update(cx, |editor, cx| {
21765 let Some(current_execution_position) = current_execution_position else {
21766 return Some(Task::ready(Ok(Vec::new())));
21767 };
21768
21769 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21770 let snapshot = buffer.snapshot(cx);
21771
21772 let excerpt = snapshot.excerpt_containing(
21773 current_execution_position..current_execution_position,
21774 )?;
21775
21776 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21777 })?;
21778
21779 let range =
21780 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21781
21782 project.inline_values(buffer, range, cx)
21783 })
21784 .ok()
21785 .flatten()?
21786 .await
21787 .context("refreshing debugger inlays")
21788 .log_err()?;
21789
21790 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21791
21792 for (buffer_id, inline_value) in inline_values
21793 .into_iter()
21794 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21795 {
21796 buffer_inline_values
21797 .entry(buffer_id)
21798 .or_default()
21799 .push(inline_value);
21800 }
21801
21802 editor
21803 .update(cx, |editor, cx| {
21804 let snapshot = editor.buffer.read(cx).snapshot(cx);
21805 let mut new_inlays = Vec::default();
21806
21807 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21808 let buffer_id = buffer_snapshot.remote_id();
21809 buffer_inline_values
21810 .get(&buffer_id)
21811 .into_iter()
21812 .flatten()
21813 .for_each(|hint| {
21814 let inlay = Inlay::debugger(
21815 post_inc(&mut editor.next_inlay_id),
21816 Anchor::in_buffer(excerpt_id, hint.position),
21817 hint.text(),
21818 );
21819 if !inlay.text().chars().contains(&'\n') {
21820 new_inlays.push(inlay);
21821 }
21822 });
21823 }
21824
21825 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21826 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21827
21828 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21829 })
21830 .ok()?;
21831 Some(())
21832 });
21833 }
21834
21835 fn on_buffer_event(
21836 &mut self,
21837 multibuffer: &Entity<MultiBuffer>,
21838 event: &multi_buffer::Event,
21839 window: &mut Window,
21840 cx: &mut Context<Self>,
21841 ) {
21842 match event {
21843 multi_buffer::Event::Edited { edited_buffer } => {
21844 self.scrollbar_marker_state.dirty = true;
21845 self.active_indent_guides_state.dirty = true;
21846 self.refresh_active_diagnostics(cx);
21847 self.refresh_code_actions(window, cx);
21848 self.refresh_single_line_folds(window, cx);
21849 self.refresh_matching_bracket_highlights(window, cx);
21850 if self.has_active_edit_prediction() {
21851 self.update_visible_edit_prediction(window, cx);
21852 }
21853
21854 if let Some(buffer) = edited_buffer {
21855 if buffer.read(cx).file().is_none() {
21856 cx.emit(EditorEvent::TitleChanged);
21857 }
21858
21859 if self.project.is_some() {
21860 let buffer_id = buffer.read(cx).remote_id();
21861 self.register_buffer(buffer_id, cx);
21862 self.update_lsp_data(Some(buffer_id), window, cx);
21863 self.refresh_inlay_hints(
21864 InlayHintRefreshReason::BufferEdited(buffer_id),
21865 cx,
21866 );
21867 }
21868 }
21869
21870 cx.emit(EditorEvent::BufferEdited);
21871 cx.emit(SearchEvent::MatchesInvalidated);
21872
21873 let Some(project) = &self.project else { return };
21874 let (telemetry, is_via_ssh) = {
21875 let project = project.read(cx);
21876 let telemetry = project.client().telemetry().clone();
21877 let is_via_ssh = project.is_via_remote_server();
21878 (telemetry, is_via_ssh)
21879 };
21880 telemetry.log_edit_event("editor", is_via_ssh);
21881 }
21882 multi_buffer::Event::ExcerptsAdded {
21883 buffer,
21884 predecessor,
21885 excerpts,
21886 } => {
21887 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21888 let buffer_id = buffer.read(cx).remote_id();
21889 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21890 && let Some(project) = &self.project
21891 {
21892 update_uncommitted_diff_for_buffer(
21893 cx.entity(),
21894 project,
21895 [buffer.clone()],
21896 self.buffer.clone(),
21897 cx,
21898 )
21899 .detach();
21900 }
21901 self.update_lsp_data(Some(buffer_id), window, cx);
21902 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21903 self.colorize_brackets(false, cx);
21904 cx.emit(EditorEvent::ExcerptsAdded {
21905 buffer: buffer.clone(),
21906 predecessor: *predecessor,
21907 excerpts: excerpts.clone(),
21908 });
21909 }
21910 multi_buffer::Event::ExcerptsRemoved {
21911 ids,
21912 removed_buffer_ids,
21913 } => {
21914 if let Some(inlay_hints) = &mut self.inlay_hints {
21915 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21916 }
21917 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21918 for buffer_id in removed_buffer_ids {
21919 self.registered_buffers.remove(buffer_id);
21920 }
21921 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21922 cx.emit(EditorEvent::ExcerptsRemoved {
21923 ids: ids.clone(),
21924 removed_buffer_ids: removed_buffer_ids.clone(),
21925 });
21926 }
21927 multi_buffer::Event::ExcerptsEdited {
21928 excerpt_ids,
21929 buffer_ids,
21930 } => {
21931 self.display_map.update(cx, |map, cx| {
21932 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21933 });
21934 cx.emit(EditorEvent::ExcerptsEdited {
21935 ids: excerpt_ids.clone(),
21936 });
21937 }
21938 multi_buffer::Event::ExcerptsExpanded { ids } => {
21939 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21940 self.refresh_document_highlights(cx);
21941 for id in ids {
21942 self.fetched_tree_sitter_chunks.remove(id);
21943 }
21944 self.colorize_brackets(false, cx);
21945 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21946 }
21947 multi_buffer::Event::Reparsed(buffer_id) => {
21948 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21949 self.refresh_selected_text_highlights(true, window, cx);
21950 self.colorize_brackets(true, cx);
21951 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21952
21953 cx.emit(EditorEvent::Reparsed(*buffer_id));
21954 }
21955 multi_buffer::Event::DiffHunksToggled => {
21956 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21957 }
21958 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
21959 if !is_fresh_language {
21960 self.registered_buffers.remove(&buffer_id);
21961 }
21962 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21963 cx.emit(EditorEvent::Reparsed(*buffer_id));
21964 cx.notify();
21965 }
21966 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21967 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21968 multi_buffer::Event::FileHandleChanged
21969 | multi_buffer::Event::Reloaded
21970 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21971 multi_buffer::Event::DiagnosticsUpdated => {
21972 self.update_diagnostics_state(window, cx);
21973 }
21974 _ => {}
21975 };
21976 }
21977
21978 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21979 if !self.diagnostics_enabled() {
21980 return;
21981 }
21982 self.refresh_active_diagnostics(cx);
21983 self.refresh_inline_diagnostics(true, window, cx);
21984 self.scrollbar_marker_state.dirty = true;
21985 cx.notify();
21986 }
21987
21988 pub fn start_temporary_diff_override(&mut self) {
21989 self.load_diff_task.take();
21990 self.temporary_diff_override = true;
21991 }
21992
21993 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21994 self.temporary_diff_override = false;
21995 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21996 self.buffer.update(cx, |buffer, cx| {
21997 buffer.set_all_diff_hunks_collapsed(cx);
21998 });
21999
22000 if let Some(project) = self.project.clone() {
22001 self.load_diff_task = Some(
22002 update_uncommitted_diff_for_buffer(
22003 cx.entity(),
22004 &project,
22005 self.buffer.read(cx).all_buffers(),
22006 self.buffer.clone(),
22007 cx,
22008 )
22009 .shared(),
22010 );
22011 }
22012 }
22013
22014 fn on_display_map_changed(
22015 &mut self,
22016 _: Entity<DisplayMap>,
22017 _: &mut Window,
22018 cx: &mut Context<Self>,
22019 ) {
22020 cx.notify();
22021 }
22022
22023 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22024 if !self.mode.is_full() {
22025 return None;
22026 }
22027
22028 let theme_settings = theme::ThemeSettings::get_global(cx);
22029 let theme = cx.theme();
22030 let accent_colors = theme.accents().clone();
22031
22032 let accent_overrides = theme_settings
22033 .theme_overrides
22034 .get(theme.name.as_ref())
22035 .map(|theme_style| &theme_style.accents)
22036 .into_iter()
22037 .flatten()
22038 .chain(
22039 theme_settings
22040 .experimental_theme_overrides
22041 .as_ref()
22042 .map(|overrides| &overrides.accents)
22043 .into_iter()
22044 .flatten(),
22045 )
22046 .flat_map(|accent| accent.0.clone())
22047 .collect();
22048
22049 Some(AccentData {
22050 colors: accent_colors,
22051 overrides: accent_overrides,
22052 })
22053 }
22054
22055 fn fetch_applicable_language_settings(
22056 &self,
22057 cx: &App,
22058 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22059 if !self.mode.is_full() {
22060 return HashMap::default();
22061 }
22062
22063 self.buffer().read(cx).all_buffers().into_iter().fold(
22064 HashMap::default(),
22065 |mut acc, buffer| {
22066 let buffer = buffer.read(cx);
22067 let language = buffer.language().map(|language| language.name());
22068 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22069 let file = buffer.file();
22070 v.insert(language_settings(language, file, cx).into_owned());
22071 }
22072 acc
22073 },
22074 )
22075 }
22076
22077 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22078 let new_language_settings = self.fetch_applicable_language_settings(cx);
22079 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22080 self.applicable_language_settings = new_language_settings;
22081
22082 let new_accents = self.fetch_accent_data(cx);
22083 let accents_changed = new_accents != self.accent_data;
22084 self.accent_data = new_accents;
22085
22086 if self.diagnostics_enabled() {
22087 let new_severity = EditorSettings::get_global(cx)
22088 .diagnostics_max_severity
22089 .unwrap_or(DiagnosticSeverity::Hint);
22090 self.set_max_diagnostics_severity(new_severity, cx);
22091 }
22092 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22093 self.update_edit_prediction_settings(cx);
22094 self.refresh_edit_prediction(true, false, window, cx);
22095 self.refresh_inline_values(cx);
22096 self.refresh_inlay_hints(
22097 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22098 self.selections.newest_anchor().head(),
22099 &self.buffer.read(cx).snapshot(cx),
22100 cx,
22101 )),
22102 cx,
22103 );
22104
22105 let old_cursor_shape = self.cursor_shape;
22106 let old_show_breadcrumbs = self.show_breadcrumbs;
22107
22108 {
22109 let editor_settings = EditorSettings::get_global(cx);
22110 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22111 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22112 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22113 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22114 }
22115
22116 if old_cursor_shape != self.cursor_shape {
22117 cx.emit(EditorEvent::CursorShapeChanged);
22118 }
22119
22120 if old_show_breadcrumbs != self.show_breadcrumbs {
22121 cx.emit(EditorEvent::BreadcrumbsChanged);
22122 }
22123
22124 let project_settings = ProjectSettings::get_global(cx);
22125 self.buffer_serialization = self
22126 .should_serialize_buffer()
22127 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22128
22129 if self.mode.is_full() {
22130 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22131 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22132 if self.show_inline_diagnostics != show_inline_diagnostics {
22133 self.show_inline_diagnostics = show_inline_diagnostics;
22134 self.refresh_inline_diagnostics(false, window, cx);
22135 }
22136
22137 if self.git_blame_inline_enabled != inline_blame_enabled {
22138 self.toggle_git_blame_inline_internal(false, window, cx);
22139 }
22140
22141 let minimap_settings = EditorSettings::get_global(cx).minimap;
22142 if self.minimap_visibility != MinimapVisibility::Disabled {
22143 if self.minimap_visibility.settings_visibility()
22144 != minimap_settings.minimap_enabled()
22145 {
22146 self.set_minimap_visibility(
22147 MinimapVisibility::for_mode(self.mode(), cx),
22148 window,
22149 cx,
22150 );
22151 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22152 minimap_entity.update(cx, |minimap_editor, cx| {
22153 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22154 })
22155 }
22156 }
22157
22158 if language_settings_changed || accents_changed {
22159 self.colorize_brackets(true, cx);
22160 }
22161
22162 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22163 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22164 }) {
22165 if !inlay_splice.is_empty() {
22166 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22167 }
22168 self.refresh_colors_for_visible_range(None, window, cx);
22169 }
22170 }
22171
22172 cx.notify();
22173 }
22174
22175 pub fn set_searchable(&mut self, searchable: bool) {
22176 self.searchable = searchable;
22177 }
22178
22179 pub fn searchable(&self) -> bool {
22180 self.searchable
22181 }
22182
22183 pub fn open_excerpts_in_split(
22184 &mut self,
22185 _: &OpenExcerptsSplit,
22186 window: &mut Window,
22187 cx: &mut Context<Self>,
22188 ) {
22189 self.open_excerpts_common(None, true, window, cx)
22190 }
22191
22192 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22193 self.open_excerpts_common(None, false, window, cx)
22194 }
22195
22196 fn open_excerpts_common(
22197 &mut self,
22198 jump_data: Option<JumpData>,
22199 split: bool,
22200 window: &mut Window,
22201 cx: &mut Context<Self>,
22202 ) {
22203 let Some(workspace) = self.workspace() else {
22204 cx.propagate();
22205 return;
22206 };
22207
22208 if self.buffer.read(cx).is_singleton() {
22209 cx.propagate();
22210 return;
22211 }
22212
22213 let mut new_selections_by_buffer = HashMap::default();
22214 match &jump_data {
22215 Some(JumpData::MultiBufferPoint {
22216 excerpt_id,
22217 position,
22218 anchor,
22219 line_offset_from_top,
22220 }) => {
22221 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22222 if let Some(buffer) = multi_buffer_snapshot
22223 .buffer_id_for_excerpt(*excerpt_id)
22224 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22225 {
22226 let buffer_snapshot = buffer.read(cx).snapshot();
22227 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22228 language::ToPoint::to_point(anchor, &buffer_snapshot)
22229 } else {
22230 buffer_snapshot.clip_point(*position, Bias::Left)
22231 };
22232 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22233 new_selections_by_buffer.insert(
22234 buffer,
22235 (
22236 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22237 Some(*line_offset_from_top),
22238 ),
22239 );
22240 }
22241 }
22242 Some(JumpData::MultiBufferRow {
22243 row,
22244 line_offset_from_top,
22245 }) => {
22246 let point = MultiBufferPoint::new(row.0, 0);
22247 if let Some((buffer, buffer_point, _)) =
22248 self.buffer.read(cx).point_to_buffer_point(point, cx)
22249 {
22250 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22251 new_selections_by_buffer
22252 .entry(buffer)
22253 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22254 .0
22255 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22256 }
22257 }
22258 None => {
22259 let selections = self
22260 .selections
22261 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22262 let multi_buffer = self.buffer.read(cx);
22263 for selection in selections {
22264 for (snapshot, range, _, anchor) in multi_buffer
22265 .snapshot(cx)
22266 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22267 {
22268 if let Some(anchor) = anchor {
22269 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22270 else {
22271 continue;
22272 };
22273 let offset = text::ToOffset::to_offset(
22274 &anchor.text_anchor,
22275 &buffer_handle.read(cx).snapshot(),
22276 );
22277 let range = BufferOffset(offset)..BufferOffset(offset);
22278 new_selections_by_buffer
22279 .entry(buffer_handle)
22280 .or_insert((Vec::new(), None))
22281 .0
22282 .push(range)
22283 } else {
22284 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22285 else {
22286 continue;
22287 };
22288 new_selections_by_buffer
22289 .entry(buffer_handle)
22290 .or_insert((Vec::new(), None))
22291 .0
22292 .push(range)
22293 }
22294 }
22295 }
22296 }
22297 }
22298
22299 new_selections_by_buffer
22300 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22301
22302 if new_selections_by_buffer.is_empty() {
22303 return;
22304 }
22305
22306 // We defer the pane interaction because we ourselves are a workspace item
22307 // and activating a new item causes the pane to call a method on us reentrantly,
22308 // which panics if we're on the stack.
22309 window.defer(cx, move |window, cx| {
22310 workspace.update(cx, |workspace, cx| {
22311 let pane = if split {
22312 workspace.adjacent_pane(window, cx)
22313 } else {
22314 workspace.active_pane().clone()
22315 };
22316
22317 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22318 let buffer_read = buffer.read(cx);
22319 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22320 (true, project::File::from_dyn(Some(file)).is_some())
22321 } else {
22322 (false, false)
22323 };
22324
22325 // If project file is none workspace.open_project_item will fail to open the excerpt
22326 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22327 // so we check if there's a tab match in that case first
22328 let editor = (!has_file || !is_project_file)
22329 .then(|| {
22330 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22331 // so `workspace.open_project_item` will never find them, always opening a new editor.
22332 // Instead, we try to activate the existing editor in the pane first.
22333 let (editor, pane_item_index, pane_item_id) =
22334 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22335 let editor = item.downcast::<Editor>()?;
22336 let singleton_buffer =
22337 editor.read(cx).buffer().read(cx).as_singleton()?;
22338 if singleton_buffer == buffer {
22339 Some((editor, i, item.item_id()))
22340 } else {
22341 None
22342 }
22343 })?;
22344 pane.update(cx, |pane, cx| {
22345 pane.activate_item(pane_item_index, true, true, window, cx);
22346 if !PreviewTabsSettings::get_global(cx)
22347 .enable_preview_from_multibuffer
22348 {
22349 pane.unpreview_item_if_preview(pane_item_id);
22350 }
22351 });
22352 Some(editor)
22353 })
22354 .flatten()
22355 .unwrap_or_else(|| {
22356 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22357 .enable_keep_preview_on_code_navigation;
22358 let allow_new_preview =
22359 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22360 workspace.open_project_item::<Self>(
22361 pane.clone(),
22362 buffer,
22363 true,
22364 true,
22365 keep_old_preview,
22366 allow_new_preview,
22367 window,
22368 cx,
22369 )
22370 });
22371
22372 editor.update(cx, |editor, cx| {
22373 if has_file && !is_project_file {
22374 editor.set_read_only(true);
22375 }
22376 let autoscroll = match scroll_offset {
22377 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22378 None => Autoscroll::newest(),
22379 };
22380 let nav_history = editor.nav_history.take();
22381 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22382 let Some((&excerpt_id, _, buffer_snapshot)) =
22383 multibuffer_snapshot.as_singleton()
22384 else {
22385 return;
22386 };
22387 editor.change_selections(
22388 SelectionEffects::scroll(autoscroll),
22389 window,
22390 cx,
22391 |s| {
22392 s.select_ranges(ranges.into_iter().map(|range| {
22393 let range = buffer_snapshot.anchor_before(range.start)
22394 ..buffer_snapshot.anchor_after(range.end);
22395 multibuffer_snapshot
22396 .anchor_range_in_excerpt(excerpt_id, range)
22397 .unwrap()
22398 }));
22399 },
22400 );
22401 editor.nav_history = nav_history;
22402 });
22403 }
22404 })
22405 });
22406 }
22407
22408 // Allow opening excerpts for buffers that either belong to the current project
22409 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22410 // are also supported so tests and other in-memory views keep working.
22411 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22412 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22413 }
22414
22415 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22416 let snapshot = self.buffer.read(cx).read(cx);
22417 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22418 Some(
22419 ranges
22420 .iter()
22421 .map(move |range| {
22422 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22423 })
22424 .collect(),
22425 )
22426 }
22427
22428 fn selection_replacement_ranges(
22429 &self,
22430 range: Range<MultiBufferOffsetUtf16>,
22431 cx: &mut App,
22432 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22433 let selections = self
22434 .selections
22435 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22436 let newest_selection = selections
22437 .iter()
22438 .max_by_key(|selection| selection.id)
22439 .unwrap();
22440 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22441 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22442 let snapshot = self.buffer.read(cx).read(cx);
22443 selections
22444 .into_iter()
22445 .map(|mut selection| {
22446 selection.start.0.0 =
22447 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22448 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22449 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22450 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22451 })
22452 .collect()
22453 }
22454
22455 fn report_editor_event(
22456 &self,
22457 reported_event: ReportEditorEvent,
22458 file_extension: Option<String>,
22459 cx: &App,
22460 ) {
22461 if cfg!(any(test, feature = "test-support")) {
22462 return;
22463 }
22464
22465 let Some(project) = &self.project else { return };
22466
22467 // If None, we are in a file without an extension
22468 let file = self
22469 .buffer
22470 .read(cx)
22471 .as_singleton()
22472 .and_then(|b| b.read(cx).file());
22473 let file_extension = file_extension.or(file
22474 .as_ref()
22475 .and_then(|file| Path::new(file.file_name(cx)).extension())
22476 .and_then(|e| e.to_str())
22477 .map(|a| a.to_string()));
22478
22479 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22480 .map(|vim_mode| vim_mode.0)
22481 .unwrap_or(false);
22482
22483 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22484 let copilot_enabled = edit_predictions_provider
22485 == language::language_settings::EditPredictionProvider::Copilot;
22486 let copilot_enabled_for_language = self
22487 .buffer
22488 .read(cx)
22489 .language_settings(cx)
22490 .show_edit_predictions;
22491
22492 let project = project.read(cx);
22493 let event_type = reported_event.event_type();
22494
22495 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22496 telemetry::event!(
22497 event_type,
22498 type = if auto_saved {"autosave"} else {"manual"},
22499 file_extension,
22500 vim_mode,
22501 copilot_enabled,
22502 copilot_enabled_for_language,
22503 edit_predictions_provider,
22504 is_via_ssh = project.is_via_remote_server(),
22505 );
22506 } else {
22507 telemetry::event!(
22508 event_type,
22509 file_extension,
22510 vim_mode,
22511 copilot_enabled,
22512 copilot_enabled_for_language,
22513 edit_predictions_provider,
22514 is_via_ssh = project.is_via_remote_server(),
22515 );
22516 };
22517 }
22518
22519 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22520 /// with each line being an array of {text, highlight} objects.
22521 fn copy_highlight_json(
22522 &mut self,
22523 _: &CopyHighlightJson,
22524 window: &mut Window,
22525 cx: &mut Context<Self>,
22526 ) {
22527 #[derive(Serialize)]
22528 struct Chunk<'a> {
22529 text: String,
22530 highlight: Option<&'a str>,
22531 }
22532
22533 let snapshot = self.buffer.read(cx).snapshot(cx);
22534 let range = self
22535 .selected_text_range(false, window, cx)
22536 .and_then(|selection| {
22537 if selection.range.is_empty() {
22538 None
22539 } else {
22540 Some(
22541 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22542 selection.range.start,
22543 )))
22544 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22545 selection.range.end,
22546 ))),
22547 )
22548 }
22549 })
22550 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22551
22552 let chunks = snapshot.chunks(range, true);
22553 let mut lines = Vec::new();
22554 let mut line: VecDeque<Chunk> = VecDeque::new();
22555
22556 let Some(style) = self.style.as_ref() else {
22557 return;
22558 };
22559
22560 for chunk in chunks {
22561 let highlight = chunk
22562 .syntax_highlight_id
22563 .and_then(|id| id.name(&style.syntax));
22564 let mut chunk_lines = chunk.text.split('\n').peekable();
22565 while let Some(text) = chunk_lines.next() {
22566 let mut merged_with_last_token = false;
22567 if let Some(last_token) = line.back_mut()
22568 && last_token.highlight == highlight
22569 {
22570 last_token.text.push_str(text);
22571 merged_with_last_token = true;
22572 }
22573
22574 if !merged_with_last_token {
22575 line.push_back(Chunk {
22576 text: text.into(),
22577 highlight,
22578 });
22579 }
22580
22581 if chunk_lines.peek().is_some() {
22582 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22583 line.pop_front();
22584 }
22585 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22586 line.pop_back();
22587 }
22588
22589 lines.push(mem::take(&mut line));
22590 }
22591 }
22592 }
22593
22594 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22595 return;
22596 };
22597 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22598 }
22599
22600 pub fn open_context_menu(
22601 &mut self,
22602 _: &OpenContextMenu,
22603 window: &mut Window,
22604 cx: &mut Context<Self>,
22605 ) {
22606 self.request_autoscroll(Autoscroll::newest(), cx);
22607 let position = self
22608 .selections
22609 .newest_display(&self.display_snapshot(cx))
22610 .start;
22611 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22612 }
22613
22614 pub fn replay_insert_event(
22615 &mut self,
22616 text: &str,
22617 relative_utf16_range: Option<Range<isize>>,
22618 window: &mut Window,
22619 cx: &mut Context<Self>,
22620 ) {
22621 if !self.input_enabled {
22622 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22623 return;
22624 }
22625 if let Some(relative_utf16_range) = relative_utf16_range {
22626 let selections = self
22627 .selections
22628 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22629 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22630 let new_ranges = selections.into_iter().map(|range| {
22631 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22632 range
22633 .head()
22634 .0
22635 .0
22636 .saturating_add_signed(relative_utf16_range.start),
22637 ));
22638 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22639 range
22640 .head()
22641 .0
22642 .0
22643 .saturating_add_signed(relative_utf16_range.end),
22644 ));
22645 start..end
22646 });
22647 s.select_ranges(new_ranges);
22648 });
22649 }
22650
22651 self.handle_input(text, window, cx);
22652 }
22653
22654 pub fn is_focused(&self, window: &Window) -> bool {
22655 self.focus_handle.is_focused(window)
22656 }
22657
22658 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22659 cx.emit(EditorEvent::Focused);
22660
22661 if let Some(descendant) = self
22662 .last_focused_descendant
22663 .take()
22664 .and_then(|descendant| descendant.upgrade())
22665 {
22666 window.focus(&descendant, cx);
22667 } else {
22668 if let Some(blame) = self.blame.as_ref() {
22669 blame.update(cx, GitBlame::focus)
22670 }
22671
22672 self.blink_manager.update(cx, BlinkManager::enable);
22673 self.show_cursor_names(window, cx);
22674 self.buffer.update(cx, |buffer, cx| {
22675 buffer.finalize_last_transaction(cx);
22676 if self.leader_id.is_none() {
22677 buffer.set_active_selections(
22678 &self.selections.disjoint_anchors_arc(),
22679 self.selections.line_mode(),
22680 self.cursor_shape,
22681 cx,
22682 );
22683 }
22684 });
22685
22686 if let Some(position_map) = self.last_position_map.clone() {
22687 EditorElement::mouse_moved(
22688 self,
22689 &MouseMoveEvent {
22690 position: window.mouse_position(),
22691 pressed_button: None,
22692 modifiers: window.modifiers(),
22693 },
22694 &position_map,
22695 window,
22696 cx,
22697 );
22698 }
22699 }
22700 }
22701
22702 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22703 cx.emit(EditorEvent::FocusedIn)
22704 }
22705
22706 fn handle_focus_out(
22707 &mut self,
22708 event: FocusOutEvent,
22709 _window: &mut Window,
22710 cx: &mut Context<Self>,
22711 ) {
22712 if event.blurred != self.focus_handle {
22713 self.last_focused_descendant = Some(event.blurred);
22714 }
22715 self.selection_drag_state = SelectionDragState::None;
22716 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22717 }
22718
22719 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22720 self.blink_manager.update(cx, BlinkManager::disable);
22721 self.buffer
22722 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22723
22724 if let Some(blame) = self.blame.as_ref() {
22725 blame.update(cx, GitBlame::blur)
22726 }
22727 if !self.hover_state.focused(window, cx) {
22728 hide_hover(self, cx);
22729 }
22730 if !self
22731 .context_menu
22732 .borrow()
22733 .as_ref()
22734 .is_some_and(|context_menu| context_menu.focused(window, cx))
22735 {
22736 self.hide_context_menu(window, cx);
22737 }
22738 self.take_active_edit_prediction(cx);
22739 cx.emit(EditorEvent::Blurred);
22740 cx.notify();
22741 }
22742
22743 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22744 let mut pending: String = window
22745 .pending_input_keystrokes()
22746 .into_iter()
22747 .flatten()
22748 .filter_map(|keystroke| keystroke.key_char.clone())
22749 .collect();
22750
22751 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22752 pending = "".to_string();
22753 }
22754
22755 let existing_pending = self
22756 .text_highlights::<PendingInput>(cx)
22757 .map(|(_, ranges)| ranges.to_vec());
22758 if existing_pending.is_none() && pending.is_empty() {
22759 return;
22760 }
22761 let transaction =
22762 self.transact(window, cx, |this, window, cx| {
22763 let selections = this
22764 .selections
22765 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22766 let edits = selections
22767 .iter()
22768 .map(|selection| (selection.end..selection.end, pending.clone()));
22769 this.edit(edits, cx);
22770 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22771 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22772 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22773 }));
22774 });
22775 if let Some(existing_ranges) = existing_pending {
22776 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22777 this.edit(edits, cx);
22778 }
22779 });
22780
22781 let snapshot = self.snapshot(window, cx);
22782 let ranges = self
22783 .selections
22784 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22785 .into_iter()
22786 .map(|selection| {
22787 snapshot.buffer_snapshot().anchor_after(selection.end)
22788 ..snapshot
22789 .buffer_snapshot()
22790 .anchor_before(selection.end + pending.len())
22791 })
22792 .collect();
22793
22794 if pending.is_empty() {
22795 self.clear_highlights::<PendingInput>(cx);
22796 } else {
22797 self.highlight_text::<PendingInput>(
22798 ranges,
22799 HighlightStyle {
22800 underline: Some(UnderlineStyle {
22801 thickness: px(1.),
22802 color: None,
22803 wavy: false,
22804 }),
22805 ..Default::default()
22806 },
22807 cx,
22808 );
22809 }
22810
22811 self.ime_transaction = self.ime_transaction.or(transaction);
22812 if let Some(transaction) = self.ime_transaction {
22813 self.buffer.update(cx, |buffer, cx| {
22814 buffer.group_until_transaction(transaction, cx);
22815 });
22816 }
22817
22818 if self.text_highlights::<PendingInput>(cx).is_none() {
22819 self.ime_transaction.take();
22820 }
22821 }
22822
22823 pub fn register_action_renderer(
22824 &mut self,
22825 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22826 ) -> Subscription {
22827 let id = self.next_editor_action_id.post_inc();
22828 self.editor_actions
22829 .borrow_mut()
22830 .insert(id, Box::new(listener));
22831
22832 let editor_actions = self.editor_actions.clone();
22833 Subscription::new(move || {
22834 editor_actions.borrow_mut().remove(&id);
22835 })
22836 }
22837
22838 pub fn register_action<A: Action>(
22839 &mut self,
22840 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22841 ) -> Subscription {
22842 let id = self.next_editor_action_id.post_inc();
22843 let listener = Arc::new(listener);
22844 self.editor_actions.borrow_mut().insert(
22845 id,
22846 Box::new(move |_, window, _| {
22847 let listener = listener.clone();
22848 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22849 let action = action.downcast_ref().unwrap();
22850 if phase == DispatchPhase::Bubble {
22851 listener(action, window, cx)
22852 }
22853 })
22854 }),
22855 );
22856
22857 let editor_actions = self.editor_actions.clone();
22858 Subscription::new(move || {
22859 editor_actions.borrow_mut().remove(&id);
22860 })
22861 }
22862
22863 pub fn file_header_size(&self) -> u32 {
22864 FILE_HEADER_HEIGHT
22865 }
22866
22867 pub fn restore(
22868 &mut self,
22869 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22870 window: &mut Window,
22871 cx: &mut Context<Self>,
22872 ) {
22873 self.buffer().update(cx, |multi_buffer, cx| {
22874 for (buffer_id, changes) in revert_changes {
22875 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22876 buffer.update(cx, |buffer, cx| {
22877 buffer.edit(
22878 changes
22879 .into_iter()
22880 .map(|(range, text)| (range, text.to_string())),
22881 None,
22882 cx,
22883 );
22884 });
22885 }
22886 }
22887 });
22888 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22889 selections.refresh()
22890 });
22891 }
22892
22893 pub fn to_pixel_point(
22894 &mut self,
22895 source: multi_buffer::Anchor,
22896 editor_snapshot: &EditorSnapshot,
22897 window: &mut Window,
22898 cx: &App,
22899 ) -> Option<gpui::Point<Pixels>> {
22900 let source_point = source.to_display_point(editor_snapshot);
22901 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
22902 }
22903
22904 pub fn display_to_pixel_point(
22905 &mut self,
22906 source: DisplayPoint,
22907 editor_snapshot: &EditorSnapshot,
22908 window: &mut Window,
22909 cx: &App,
22910 ) -> Option<gpui::Point<Pixels>> {
22911 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
22912 let text_layout_details = self.text_layout_details(window);
22913 let scroll_top = text_layout_details
22914 .scroll_anchor
22915 .scroll_position(editor_snapshot)
22916 .y;
22917
22918 if source.row().as_f64() < scroll_top.floor() {
22919 return None;
22920 }
22921 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22922 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22923 Some(gpui::Point::new(source_x, source_y))
22924 }
22925
22926 pub fn has_visible_completions_menu(&self) -> bool {
22927 !self.edit_prediction_preview_is_active()
22928 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22929 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22930 })
22931 }
22932
22933 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22934 if self.mode.is_minimap() {
22935 return;
22936 }
22937 self.addons
22938 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22939 }
22940
22941 pub fn unregister_addon<T: Addon>(&mut self) {
22942 self.addons.remove(&std::any::TypeId::of::<T>());
22943 }
22944
22945 pub fn addon<T: Addon>(&self) -> Option<&T> {
22946 let type_id = std::any::TypeId::of::<T>();
22947 self.addons
22948 .get(&type_id)
22949 .and_then(|item| item.to_any().downcast_ref::<T>())
22950 }
22951
22952 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22953 let type_id = std::any::TypeId::of::<T>();
22954 self.addons
22955 .get_mut(&type_id)
22956 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22957 }
22958
22959 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22960 let text_layout_details = self.text_layout_details(window);
22961 let style = &text_layout_details.editor_style;
22962 let font_id = window.text_system().resolve_font(&style.text.font());
22963 let font_size = style.text.font_size.to_pixels(window.rem_size());
22964 let line_height = style.text.line_height_in_pixels(window.rem_size());
22965 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22966 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22967
22968 CharacterDimensions {
22969 em_width,
22970 em_advance,
22971 line_height,
22972 }
22973 }
22974
22975 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22976 self.load_diff_task.clone()
22977 }
22978
22979 fn read_metadata_from_db(
22980 &mut self,
22981 item_id: u64,
22982 workspace_id: WorkspaceId,
22983 window: &mut Window,
22984 cx: &mut Context<Editor>,
22985 ) {
22986 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22987 && !self.mode.is_minimap()
22988 && WorkspaceSettings::get(None, cx).restore_on_startup
22989 != RestoreOnStartupBehavior::EmptyTab
22990 {
22991 let buffer_snapshot = OnceCell::new();
22992
22993 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22994 && !folds.is_empty()
22995 {
22996 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22997 self.fold_ranges(
22998 folds
22999 .into_iter()
23000 .map(|(start, end)| {
23001 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23002 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23003 })
23004 .collect(),
23005 false,
23006 window,
23007 cx,
23008 );
23009 }
23010
23011 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23012 && !selections.is_empty()
23013 {
23014 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23015 // skip adding the initial selection to selection history
23016 self.selection_history.mode = SelectionHistoryMode::Skipping;
23017 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23018 s.select_ranges(selections.into_iter().map(|(start, end)| {
23019 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23020 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23021 }));
23022 });
23023 self.selection_history.mode = SelectionHistoryMode::Normal;
23024 };
23025 }
23026
23027 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23028 }
23029
23030 fn update_lsp_data(
23031 &mut self,
23032 for_buffer: Option<BufferId>,
23033 window: &mut Window,
23034 cx: &mut Context<'_, Self>,
23035 ) {
23036 self.pull_diagnostics(for_buffer, window, cx);
23037 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23038 }
23039
23040 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23041 if self.ignore_lsp_data() {
23042 return;
23043 }
23044 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23045 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23046 }
23047 }
23048
23049 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23050 if self.ignore_lsp_data() {
23051 return;
23052 }
23053
23054 if !self.registered_buffers.contains_key(&buffer_id)
23055 && let Some(project) = self.project.as_ref()
23056 {
23057 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23058 project.update(cx, |project, cx| {
23059 self.registered_buffers.insert(
23060 buffer_id,
23061 project.register_buffer_with_language_servers(&buffer, cx),
23062 );
23063 });
23064 } else {
23065 self.registered_buffers.remove(&buffer_id);
23066 }
23067 }
23068 }
23069
23070 fn ignore_lsp_data(&self) -> bool {
23071 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23072 // skip any LSP updates for it.
23073 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23074 }
23075
23076 fn create_style(&self, cx: &App) -> EditorStyle {
23077 let settings = ThemeSettings::get_global(cx);
23078
23079 let mut text_style = match self.mode {
23080 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23081 color: cx.theme().colors().editor_foreground,
23082 font_family: settings.ui_font.family.clone(),
23083 font_features: settings.ui_font.features.clone(),
23084 font_fallbacks: settings.ui_font.fallbacks.clone(),
23085 font_size: rems(0.875).into(),
23086 font_weight: settings.ui_font.weight,
23087 line_height: relative(settings.buffer_line_height.value()),
23088 ..Default::default()
23089 },
23090 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23091 color: cx.theme().colors().editor_foreground,
23092 font_family: settings.buffer_font.family.clone(),
23093 font_features: settings.buffer_font.features.clone(),
23094 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23095 font_size: settings.buffer_font_size(cx).into(),
23096 font_weight: settings.buffer_font.weight,
23097 line_height: relative(settings.buffer_line_height.value()),
23098 ..Default::default()
23099 },
23100 };
23101 if let Some(text_style_refinement) = &self.text_style_refinement {
23102 text_style.refine(text_style_refinement)
23103 }
23104
23105 let background = match self.mode {
23106 EditorMode::SingleLine => cx.theme().system().transparent,
23107 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23108 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23109 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23110 };
23111
23112 EditorStyle {
23113 background,
23114 border: cx.theme().colors().border,
23115 local_player: cx.theme().players().local(),
23116 text: text_style,
23117 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23118 syntax: cx.theme().syntax().clone(),
23119 status: cx.theme().status().clone(),
23120 inlay_hints_style: make_inlay_hints_style(cx),
23121 edit_prediction_styles: make_suggestion_styles(cx),
23122 unnecessary_code_fade: settings.unnecessary_code_fade,
23123 show_underlines: self.diagnostics_enabled(),
23124 }
23125 }
23126}
23127
23128fn edit_for_markdown_paste<'a>(
23129 buffer: &MultiBufferSnapshot,
23130 range: Range<MultiBufferOffset>,
23131 to_insert: &'a str,
23132 url: Option<url::Url>,
23133) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23134 if url.is_none() {
23135 return (range, Cow::Borrowed(to_insert));
23136 };
23137
23138 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23139
23140 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23141 Cow::Borrowed(to_insert)
23142 } else {
23143 Cow::Owned(format!("[{old_text}]({to_insert})"))
23144 };
23145 (range, new_text)
23146}
23147
23148fn process_completion_for_edit(
23149 completion: &Completion,
23150 intent: CompletionIntent,
23151 buffer: &Entity<Buffer>,
23152 cursor_position: &text::Anchor,
23153 cx: &mut Context<Editor>,
23154) -> CompletionEdit {
23155 let buffer = buffer.read(cx);
23156 let buffer_snapshot = buffer.snapshot();
23157 let (snippet, new_text) = if completion.is_snippet() {
23158 let mut snippet_source = completion.new_text.clone();
23159 // Workaround for typescript language server issues so that methods don't expand within
23160 // strings and functions with type expressions. The previous point is used because the query
23161 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23162 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23163 let previous_point = if previous_point.column > 0 {
23164 cursor_position.to_previous_offset(&buffer_snapshot)
23165 } else {
23166 cursor_position.to_offset(&buffer_snapshot)
23167 };
23168 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23169 && scope.prefers_label_for_snippet_in_completion()
23170 && let Some(label) = completion.label()
23171 && matches!(
23172 completion.kind(),
23173 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23174 )
23175 {
23176 snippet_source = label;
23177 }
23178 match Snippet::parse(&snippet_source).log_err() {
23179 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23180 None => (None, completion.new_text.clone()),
23181 }
23182 } else {
23183 (None, completion.new_text.clone())
23184 };
23185
23186 let mut range_to_replace = {
23187 let replace_range = &completion.replace_range;
23188 if let CompletionSource::Lsp {
23189 insert_range: Some(insert_range),
23190 ..
23191 } = &completion.source
23192 {
23193 debug_assert_eq!(
23194 insert_range.start, replace_range.start,
23195 "insert_range and replace_range should start at the same position"
23196 );
23197 debug_assert!(
23198 insert_range
23199 .start
23200 .cmp(cursor_position, &buffer_snapshot)
23201 .is_le(),
23202 "insert_range should start before or at cursor position"
23203 );
23204 debug_assert!(
23205 replace_range
23206 .start
23207 .cmp(cursor_position, &buffer_snapshot)
23208 .is_le(),
23209 "replace_range should start before or at cursor position"
23210 );
23211
23212 let should_replace = match intent {
23213 CompletionIntent::CompleteWithInsert => false,
23214 CompletionIntent::CompleteWithReplace => true,
23215 CompletionIntent::Complete | CompletionIntent::Compose => {
23216 let insert_mode =
23217 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23218 .completions
23219 .lsp_insert_mode;
23220 match insert_mode {
23221 LspInsertMode::Insert => false,
23222 LspInsertMode::Replace => true,
23223 LspInsertMode::ReplaceSubsequence => {
23224 let mut text_to_replace = buffer.chars_for_range(
23225 buffer.anchor_before(replace_range.start)
23226 ..buffer.anchor_after(replace_range.end),
23227 );
23228 let mut current_needle = text_to_replace.next();
23229 for haystack_ch in completion.label.text.chars() {
23230 if let Some(needle_ch) = current_needle
23231 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23232 {
23233 current_needle = text_to_replace.next();
23234 }
23235 }
23236 current_needle.is_none()
23237 }
23238 LspInsertMode::ReplaceSuffix => {
23239 if replace_range
23240 .end
23241 .cmp(cursor_position, &buffer_snapshot)
23242 .is_gt()
23243 {
23244 let range_after_cursor = *cursor_position..replace_range.end;
23245 let text_after_cursor = buffer
23246 .text_for_range(
23247 buffer.anchor_before(range_after_cursor.start)
23248 ..buffer.anchor_after(range_after_cursor.end),
23249 )
23250 .collect::<String>()
23251 .to_ascii_lowercase();
23252 completion
23253 .label
23254 .text
23255 .to_ascii_lowercase()
23256 .ends_with(&text_after_cursor)
23257 } else {
23258 true
23259 }
23260 }
23261 }
23262 }
23263 };
23264
23265 if should_replace {
23266 replace_range.clone()
23267 } else {
23268 insert_range.clone()
23269 }
23270 } else {
23271 replace_range.clone()
23272 }
23273 };
23274
23275 if range_to_replace
23276 .end
23277 .cmp(cursor_position, &buffer_snapshot)
23278 .is_lt()
23279 {
23280 range_to_replace.end = *cursor_position;
23281 }
23282
23283 let replace_range = range_to_replace.to_offset(buffer);
23284 CompletionEdit {
23285 new_text,
23286 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23287 snippet,
23288 }
23289}
23290
23291struct CompletionEdit {
23292 new_text: String,
23293 replace_range: Range<BufferOffset>,
23294 snippet: Option<Snippet>,
23295}
23296
23297fn comment_delimiter_for_newline(
23298 start_point: &Point,
23299 buffer: &MultiBufferSnapshot,
23300 language: &LanguageScope,
23301) -> Option<Arc<str>> {
23302 let delimiters = language.line_comment_prefixes();
23303 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
23304 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23305
23306 let num_of_whitespaces = snapshot
23307 .chars_for_range(range.clone())
23308 .take_while(|c| c.is_whitespace())
23309 .count();
23310 let comment_candidate = snapshot
23311 .chars_for_range(range.clone())
23312 .skip(num_of_whitespaces)
23313 .take(max_len_of_delimiter)
23314 .collect::<String>();
23315 let (delimiter, trimmed_len) = delimiters
23316 .iter()
23317 .filter_map(|delimiter| {
23318 let prefix = delimiter.trim_end();
23319 if comment_candidate.starts_with(prefix) {
23320 Some((delimiter, prefix.len()))
23321 } else {
23322 None
23323 }
23324 })
23325 .max_by_key(|(_, len)| *len)?;
23326
23327 if let Some(BlockCommentConfig {
23328 start: block_start, ..
23329 }) = language.block_comment()
23330 {
23331 let block_start_trimmed = block_start.trim_end();
23332 if block_start_trimmed.starts_with(delimiter.trim_end()) {
23333 let line_content = snapshot
23334 .chars_for_range(range)
23335 .skip(num_of_whitespaces)
23336 .take(block_start_trimmed.len())
23337 .collect::<String>();
23338
23339 if line_content.starts_with(block_start_trimmed) {
23340 return None;
23341 }
23342 }
23343 }
23344
23345 let cursor_is_placed_after_comment_marker =
23346 num_of_whitespaces + trimmed_len <= start_point.column as usize;
23347 if cursor_is_placed_after_comment_marker {
23348 Some(delimiter.clone())
23349 } else {
23350 None
23351 }
23352}
23353
23354fn documentation_delimiter_for_newline(
23355 start_point: &Point,
23356 buffer: &MultiBufferSnapshot,
23357 language: &LanguageScope,
23358 newline_formatting: &mut NewlineFormatting,
23359) -> Option<Arc<str>> {
23360 let BlockCommentConfig {
23361 start: start_tag,
23362 end: end_tag,
23363 prefix: delimiter,
23364 tab_size: len,
23365 } = language.documentation_comment()?;
23366 let is_within_block_comment = buffer
23367 .language_scope_at(*start_point)
23368 .is_some_and(|scope| scope.override_name() == Some("comment"));
23369 if !is_within_block_comment {
23370 return None;
23371 }
23372
23373 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23374
23375 let num_of_whitespaces = snapshot
23376 .chars_for_range(range.clone())
23377 .take_while(|c| c.is_whitespace())
23378 .count();
23379
23380 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
23381 let column = start_point.column;
23382 let cursor_is_after_start_tag = {
23383 let start_tag_len = start_tag.len();
23384 let start_tag_line = snapshot
23385 .chars_for_range(range.clone())
23386 .skip(num_of_whitespaces)
23387 .take(start_tag_len)
23388 .collect::<String>();
23389 if start_tag_line.starts_with(start_tag.as_ref()) {
23390 num_of_whitespaces + start_tag_len <= column as usize
23391 } else {
23392 false
23393 }
23394 };
23395
23396 let cursor_is_after_delimiter = {
23397 let delimiter_trim = delimiter.trim_end();
23398 let delimiter_line = snapshot
23399 .chars_for_range(range.clone())
23400 .skip(num_of_whitespaces)
23401 .take(delimiter_trim.len())
23402 .collect::<String>();
23403 if delimiter_line.starts_with(delimiter_trim) {
23404 num_of_whitespaces + delimiter_trim.len() <= column as usize
23405 } else {
23406 false
23407 }
23408 };
23409
23410 let cursor_is_before_end_tag_if_exists = {
23411 let mut char_position = 0u32;
23412 let mut end_tag_offset = None;
23413
23414 'outer: for chunk in snapshot.text_for_range(range) {
23415 if let Some(byte_pos) = chunk.find(&**end_tag) {
23416 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
23417 end_tag_offset = Some(char_position + chars_before_match);
23418 break 'outer;
23419 }
23420 char_position += chunk.chars().count() as u32;
23421 }
23422
23423 if let Some(end_tag_offset) = end_tag_offset {
23424 let cursor_is_before_end_tag = column <= end_tag_offset;
23425 if cursor_is_after_start_tag {
23426 if cursor_is_before_end_tag {
23427 newline_formatting.insert_extra_newline = true;
23428 }
23429 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
23430 if cursor_is_at_start_of_end_tag {
23431 newline_formatting.indent_on_extra_newline.len = *len;
23432 }
23433 }
23434 cursor_is_before_end_tag
23435 } else {
23436 true
23437 }
23438 };
23439
23440 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
23441 && cursor_is_before_end_tag_if_exists
23442 {
23443 if cursor_is_after_start_tag {
23444 newline_formatting.indent_on_newline.len = *len;
23445 }
23446 Some(delimiter.clone())
23447 } else {
23448 None
23449 }
23450}
23451
23452#[derive(Debug, Default)]
23453struct NewlineFormatting {
23454 insert_extra_newline: bool,
23455 indent_on_newline: IndentSize,
23456 indent_on_extra_newline: IndentSize,
23457}
23458
23459impl NewlineFormatting {
23460 fn new(
23461 buffer: &MultiBufferSnapshot,
23462 range: Range<MultiBufferOffset>,
23463 language: &LanguageScope,
23464 ) -> Self {
23465 Self {
23466 insert_extra_newline: Self::insert_extra_newline_brackets(
23467 buffer,
23468 range.clone(),
23469 language,
23470 ) || Self::insert_extra_newline_tree_sitter(buffer, range),
23471 indent_on_newline: IndentSize::spaces(0),
23472 indent_on_extra_newline: IndentSize::spaces(0),
23473 }
23474 }
23475
23476 fn insert_extra_newline_brackets(
23477 buffer: &MultiBufferSnapshot,
23478 range: Range<MultiBufferOffset>,
23479 language: &language::LanguageScope,
23480 ) -> bool {
23481 let leading_whitespace_len = buffer
23482 .reversed_chars_at(range.start)
23483 .take_while(|c| c.is_whitespace() && *c != '\n')
23484 .map(|c| c.len_utf8())
23485 .sum::<usize>();
23486 let trailing_whitespace_len = buffer
23487 .chars_at(range.end)
23488 .take_while(|c| c.is_whitespace() && *c != '\n')
23489 .map(|c| c.len_utf8())
23490 .sum::<usize>();
23491 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23492
23493 language.brackets().any(|(pair, enabled)| {
23494 let pair_start = pair.start.trim_end();
23495 let pair_end = pair.end.trim_start();
23496
23497 enabled
23498 && pair.newline
23499 && buffer.contains_str_at(range.end, pair_end)
23500 && buffer.contains_str_at(
23501 range.start.saturating_sub_usize(pair_start.len()),
23502 pair_start,
23503 )
23504 })
23505 }
23506
23507 fn insert_extra_newline_tree_sitter(
23508 buffer: &MultiBufferSnapshot,
23509 range: Range<MultiBufferOffset>,
23510 ) -> bool {
23511 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23512 [(buffer, range, _)] => (*buffer, range.clone()),
23513 _ => return false,
23514 };
23515 let pair = {
23516 let mut result: Option<BracketMatch<usize>> = None;
23517
23518 for pair in buffer
23519 .all_bracket_ranges(range.start.0..range.end.0)
23520 .filter(move |pair| {
23521 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23522 })
23523 {
23524 let len = pair.close_range.end - pair.open_range.start;
23525
23526 if let Some(existing) = &result {
23527 let existing_len = existing.close_range.end - existing.open_range.start;
23528 if len > existing_len {
23529 continue;
23530 }
23531 }
23532
23533 result = Some(pair);
23534 }
23535
23536 result
23537 };
23538 let Some(pair) = pair else {
23539 return false;
23540 };
23541 pair.newline_only
23542 && buffer
23543 .chars_for_range(pair.open_range.end..range.start.0)
23544 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23545 .all(|c| c.is_whitespace() && c != '\n')
23546 }
23547}
23548
23549fn update_uncommitted_diff_for_buffer(
23550 editor: Entity<Editor>,
23551 project: &Entity<Project>,
23552 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23553 buffer: Entity<MultiBuffer>,
23554 cx: &mut App,
23555) -> Task<()> {
23556 let mut tasks = Vec::new();
23557 project.update(cx, |project, cx| {
23558 for buffer in buffers {
23559 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23560 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23561 }
23562 }
23563 });
23564 cx.spawn(async move |cx| {
23565 let diffs = future::join_all(tasks).await;
23566 if editor
23567 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23568 .unwrap_or(false)
23569 {
23570 return;
23571 }
23572
23573 buffer
23574 .update(cx, |buffer, cx| {
23575 for diff in diffs.into_iter().flatten() {
23576 buffer.add_diff(diff, cx);
23577 }
23578 })
23579 .ok();
23580 })
23581}
23582
23583fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23584 let tab_size = tab_size.get() as usize;
23585 let mut width = offset;
23586
23587 for ch in text.chars() {
23588 width += if ch == '\t' {
23589 tab_size - (width % tab_size)
23590 } else {
23591 1
23592 };
23593 }
23594
23595 width - offset
23596}
23597
23598#[cfg(test)]
23599mod tests {
23600 use super::*;
23601
23602 #[test]
23603 fn test_string_size_with_expanded_tabs() {
23604 let nz = |val| NonZeroU32::new(val).unwrap();
23605 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23606 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23607 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23608 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23609 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23610 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23611 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23612 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23613 }
23614}
23615
23616/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23617struct WordBreakingTokenizer<'a> {
23618 input: &'a str,
23619}
23620
23621impl<'a> WordBreakingTokenizer<'a> {
23622 fn new(input: &'a str) -> Self {
23623 Self { input }
23624 }
23625}
23626
23627fn is_char_ideographic(ch: char) -> bool {
23628 use unicode_script::Script::*;
23629 use unicode_script::UnicodeScript;
23630 matches!(ch.script(), Han | Tangut | Yi)
23631}
23632
23633fn is_grapheme_ideographic(text: &str) -> bool {
23634 text.chars().any(is_char_ideographic)
23635}
23636
23637fn is_grapheme_whitespace(text: &str) -> bool {
23638 text.chars().any(|x| x.is_whitespace())
23639}
23640
23641fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23642 text.chars()
23643 .next()
23644 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23645}
23646
23647#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23648enum WordBreakToken<'a> {
23649 Word { token: &'a str, grapheme_len: usize },
23650 InlineWhitespace { token: &'a str, grapheme_len: usize },
23651 Newline,
23652}
23653
23654impl<'a> Iterator for WordBreakingTokenizer<'a> {
23655 /// Yields a span, the count of graphemes in the token, and whether it was
23656 /// whitespace. Note that it also breaks at word boundaries.
23657 type Item = WordBreakToken<'a>;
23658
23659 fn next(&mut self) -> Option<Self::Item> {
23660 use unicode_segmentation::UnicodeSegmentation;
23661 if self.input.is_empty() {
23662 return None;
23663 }
23664
23665 let mut iter = self.input.graphemes(true).peekable();
23666 let mut offset = 0;
23667 let mut grapheme_len = 0;
23668 if let Some(first_grapheme) = iter.next() {
23669 let is_newline = first_grapheme == "\n";
23670 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23671 offset += first_grapheme.len();
23672 grapheme_len += 1;
23673 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23674 if let Some(grapheme) = iter.peek().copied()
23675 && should_stay_with_preceding_ideograph(grapheme)
23676 {
23677 offset += grapheme.len();
23678 grapheme_len += 1;
23679 }
23680 } else {
23681 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23682 let mut next_word_bound = words.peek().copied();
23683 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23684 next_word_bound = words.next();
23685 }
23686 while let Some(grapheme) = iter.peek().copied() {
23687 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23688 break;
23689 };
23690 if is_grapheme_whitespace(grapheme) != is_whitespace
23691 || (grapheme == "\n") != is_newline
23692 {
23693 break;
23694 };
23695 offset += grapheme.len();
23696 grapheme_len += 1;
23697 iter.next();
23698 }
23699 }
23700 let token = &self.input[..offset];
23701 self.input = &self.input[offset..];
23702 if token == "\n" {
23703 Some(WordBreakToken::Newline)
23704 } else if is_whitespace {
23705 Some(WordBreakToken::InlineWhitespace {
23706 token,
23707 grapheme_len,
23708 })
23709 } else {
23710 Some(WordBreakToken::Word {
23711 token,
23712 grapheme_len,
23713 })
23714 }
23715 } else {
23716 None
23717 }
23718 }
23719}
23720
23721#[test]
23722fn test_word_breaking_tokenizer() {
23723 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23724 ("", &[]),
23725 (" ", &[whitespace(" ", 2)]),
23726 ("Ʒ", &[word("Ʒ", 1)]),
23727 ("Ǽ", &[word("Ǽ", 1)]),
23728 ("⋑", &[word("⋑", 1)]),
23729 ("⋑⋑", &[word("⋑⋑", 2)]),
23730 (
23731 "原理,进而",
23732 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23733 ),
23734 (
23735 "hello world",
23736 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23737 ),
23738 (
23739 "hello, world",
23740 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23741 ),
23742 (
23743 " hello world",
23744 &[
23745 whitespace(" ", 2),
23746 word("hello", 5),
23747 whitespace(" ", 1),
23748 word("world", 5),
23749 ],
23750 ),
23751 (
23752 "这是什么 \n 钢笔",
23753 &[
23754 word("这", 1),
23755 word("是", 1),
23756 word("什", 1),
23757 word("么", 1),
23758 whitespace(" ", 1),
23759 newline(),
23760 whitespace(" ", 1),
23761 word("钢", 1),
23762 word("笔", 1),
23763 ],
23764 ),
23765 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23766 ];
23767
23768 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23769 WordBreakToken::Word {
23770 token,
23771 grapheme_len,
23772 }
23773 }
23774
23775 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23776 WordBreakToken::InlineWhitespace {
23777 token,
23778 grapheme_len,
23779 }
23780 }
23781
23782 fn newline() -> WordBreakToken<'static> {
23783 WordBreakToken::Newline
23784 }
23785
23786 for (input, result) in tests {
23787 assert_eq!(
23788 WordBreakingTokenizer::new(input)
23789 .collect::<Vec<_>>()
23790 .as_slice(),
23791 *result,
23792 );
23793 }
23794}
23795
23796fn wrap_with_prefix(
23797 first_line_prefix: String,
23798 subsequent_lines_prefix: String,
23799 unwrapped_text: String,
23800 wrap_column: usize,
23801 tab_size: NonZeroU32,
23802 preserve_existing_whitespace: bool,
23803) -> String {
23804 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23805 let subsequent_lines_prefix_len =
23806 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23807 let mut wrapped_text = String::new();
23808 let mut current_line = first_line_prefix;
23809 let mut is_first_line = true;
23810
23811 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23812 let mut current_line_len = first_line_prefix_len;
23813 let mut in_whitespace = false;
23814 for token in tokenizer {
23815 let have_preceding_whitespace = in_whitespace;
23816 match token {
23817 WordBreakToken::Word {
23818 token,
23819 grapheme_len,
23820 } => {
23821 in_whitespace = false;
23822 let current_prefix_len = if is_first_line {
23823 first_line_prefix_len
23824 } else {
23825 subsequent_lines_prefix_len
23826 };
23827 if current_line_len + grapheme_len > wrap_column
23828 && current_line_len != current_prefix_len
23829 {
23830 wrapped_text.push_str(current_line.trim_end());
23831 wrapped_text.push('\n');
23832 is_first_line = false;
23833 current_line = subsequent_lines_prefix.clone();
23834 current_line_len = subsequent_lines_prefix_len;
23835 }
23836 current_line.push_str(token);
23837 current_line_len += grapheme_len;
23838 }
23839 WordBreakToken::InlineWhitespace {
23840 mut token,
23841 mut grapheme_len,
23842 } => {
23843 in_whitespace = true;
23844 if have_preceding_whitespace && !preserve_existing_whitespace {
23845 continue;
23846 }
23847 if !preserve_existing_whitespace {
23848 // Keep a single whitespace grapheme as-is
23849 if let Some(first) =
23850 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23851 {
23852 token = first;
23853 } else {
23854 token = " ";
23855 }
23856 grapheme_len = 1;
23857 }
23858 let current_prefix_len = if is_first_line {
23859 first_line_prefix_len
23860 } else {
23861 subsequent_lines_prefix_len
23862 };
23863 if current_line_len + grapheme_len > wrap_column {
23864 wrapped_text.push_str(current_line.trim_end());
23865 wrapped_text.push('\n');
23866 is_first_line = false;
23867 current_line = subsequent_lines_prefix.clone();
23868 current_line_len = subsequent_lines_prefix_len;
23869 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23870 current_line.push_str(token);
23871 current_line_len += grapheme_len;
23872 }
23873 }
23874 WordBreakToken::Newline => {
23875 in_whitespace = true;
23876 let current_prefix_len = if is_first_line {
23877 first_line_prefix_len
23878 } else {
23879 subsequent_lines_prefix_len
23880 };
23881 if preserve_existing_whitespace {
23882 wrapped_text.push_str(current_line.trim_end());
23883 wrapped_text.push('\n');
23884 is_first_line = false;
23885 current_line = subsequent_lines_prefix.clone();
23886 current_line_len = subsequent_lines_prefix_len;
23887 } else if have_preceding_whitespace {
23888 continue;
23889 } else if current_line_len + 1 > wrap_column
23890 && current_line_len != current_prefix_len
23891 {
23892 wrapped_text.push_str(current_line.trim_end());
23893 wrapped_text.push('\n');
23894 is_first_line = false;
23895 current_line = subsequent_lines_prefix.clone();
23896 current_line_len = subsequent_lines_prefix_len;
23897 } else if current_line_len != current_prefix_len {
23898 current_line.push(' ');
23899 current_line_len += 1;
23900 }
23901 }
23902 }
23903 }
23904
23905 if !current_line.is_empty() {
23906 wrapped_text.push_str(¤t_line);
23907 }
23908 wrapped_text
23909}
23910
23911#[test]
23912fn test_wrap_with_prefix() {
23913 assert_eq!(
23914 wrap_with_prefix(
23915 "# ".to_string(),
23916 "# ".to_string(),
23917 "abcdefg".to_string(),
23918 4,
23919 NonZeroU32::new(4).unwrap(),
23920 false,
23921 ),
23922 "# abcdefg"
23923 );
23924 assert_eq!(
23925 wrap_with_prefix(
23926 "".to_string(),
23927 "".to_string(),
23928 "\thello world".to_string(),
23929 8,
23930 NonZeroU32::new(4).unwrap(),
23931 false,
23932 ),
23933 "hello\nworld"
23934 );
23935 assert_eq!(
23936 wrap_with_prefix(
23937 "// ".to_string(),
23938 "// ".to_string(),
23939 "xx \nyy zz aa bb cc".to_string(),
23940 12,
23941 NonZeroU32::new(4).unwrap(),
23942 false,
23943 ),
23944 "// xx yy zz\n// aa bb cc"
23945 );
23946 assert_eq!(
23947 wrap_with_prefix(
23948 String::new(),
23949 String::new(),
23950 "这是什么 \n 钢笔".to_string(),
23951 3,
23952 NonZeroU32::new(4).unwrap(),
23953 false,
23954 ),
23955 "这是什\n么 钢\n笔"
23956 );
23957 assert_eq!(
23958 wrap_with_prefix(
23959 String::new(),
23960 String::new(),
23961 format!("foo{}bar", '\u{2009}'), // thin space
23962 80,
23963 NonZeroU32::new(4).unwrap(),
23964 false,
23965 ),
23966 format!("foo{}bar", '\u{2009}')
23967 );
23968}
23969
23970pub trait CollaborationHub {
23971 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23972 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23973 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23974}
23975
23976impl CollaborationHub for Entity<Project> {
23977 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23978 self.read(cx).collaborators()
23979 }
23980
23981 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23982 self.read(cx).user_store().read(cx).participant_indices()
23983 }
23984
23985 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23986 let this = self.read(cx);
23987 let user_ids = this.collaborators().values().map(|c| c.user_id);
23988 this.user_store().read(cx).participant_names(user_ids, cx)
23989 }
23990}
23991
23992pub trait SemanticsProvider {
23993 fn hover(
23994 &self,
23995 buffer: &Entity<Buffer>,
23996 position: text::Anchor,
23997 cx: &mut App,
23998 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23999
24000 fn inline_values(
24001 &self,
24002 buffer_handle: Entity<Buffer>,
24003 range: Range<text::Anchor>,
24004 cx: &mut App,
24005 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
24006
24007 fn applicable_inlay_chunks(
24008 &self,
24009 buffer: &Entity<Buffer>,
24010 ranges: &[Range<text::Anchor>],
24011 cx: &mut App,
24012 ) -> Vec<Range<BufferRow>>;
24013
24014 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
24015
24016 fn inlay_hints(
24017 &self,
24018 invalidate: InvalidationStrategy,
24019 buffer: Entity<Buffer>,
24020 ranges: Vec<Range<text::Anchor>>,
24021 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24022 cx: &mut App,
24023 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
24024
24025 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24026
24027 fn document_highlights(
24028 &self,
24029 buffer: &Entity<Buffer>,
24030 position: text::Anchor,
24031 cx: &mut App,
24032 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24033
24034 fn definitions(
24035 &self,
24036 buffer: &Entity<Buffer>,
24037 position: text::Anchor,
24038 kind: GotoDefinitionKind,
24039 cx: &mut App,
24040 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24041
24042 fn range_for_rename(
24043 &self,
24044 buffer: &Entity<Buffer>,
24045 position: text::Anchor,
24046 cx: &mut App,
24047 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24048
24049 fn perform_rename(
24050 &self,
24051 buffer: &Entity<Buffer>,
24052 position: text::Anchor,
24053 new_name: String,
24054 cx: &mut App,
24055 ) -> Option<Task<Result<ProjectTransaction>>>;
24056}
24057
24058pub trait CompletionProvider {
24059 fn completions(
24060 &self,
24061 excerpt_id: ExcerptId,
24062 buffer: &Entity<Buffer>,
24063 buffer_position: text::Anchor,
24064 trigger: CompletionContext,
24065 window: &mut Window,
24066 cx: &mut Context<Editor>,
24067 ) -> Task<Result<Vec<CompletionResponse>>>;
24068
24069 fn resolve_completions(
24070 &self,
24071 _buffer: Entity<Buffer>,
24072 _completion_indices: Vec<usize>,
24073 _completions: Rc<RefCell<Box<[Completion]>>>,
24074 _cx: &mut Context<Editor>,
24075 ) -> Task<Result<bool>> {
24076 Task::ready(Ok(false))
24077 }
24078
24079 fn apply_additional_edits_for_completion(
24080 &self,
24081 _buffer: Entity<Buffer>,
24082 _completions: Rc<RefCell<Box<[Completion]>>>,
24083 _completion_index: usize,
24084 _push_to_history: bool,
24085 _cx: &mut Context<Editor>,
24086 ) -> Task<Result<Option<language::Transaction>>> {
24087 Task::ready(Ok(None))
24088 }
24089
24090 fn is_completion_trigger(
24091 &self,
24092 buffer: &Entity<Buffer>,
24093 position: language::Anchor,
24094 text: &str,
24095 trigger_in_words: bool,
24096 cx: &mut Context<Editor>,
24097 ) -> bool;
24098
24099 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24100
24101 fn sort_completions(&self) -> bool {
24102 true
24103 }
24104
24105 fn filter_completions(&self) -> bool {
24106 true
24107 }
24108
24109 fn show_snippets(&self) -> bool {
24110 false
24111 }
24112}
24113
24114pub trait CodeActionProvider {
24115 fn id(&self) -> Arc<str>;
24116
24117 fn code_actions(
24118 &self,
24119 buffer: &Entity<Buffer>,
24120 range: Range<text::Anchor>,
24121 window: &mut Window,
24122 cx: &mut App,
24123 ) -> Task<Result<Vec<CodeAction>>>;
24124
24125 fn apply_code_action(
24126 &self,
24127 buffer_handle: Entity<Buffer>,
24128 action: CodeAction,
24129 excerpt_id: ExcerptId,
24130 push_to_history: bool,
24131 window: &mut Window,
24132 cx: &mut App,
24133 ) -> Task<Result<ProjectTransaction>>;
24134}
24135
24136impl CodeActionProvider for Entity<Project> {
24137 fn id(&self) -> Arc<str> {
24138 "project".into()
24139 }
24140
24141 fn code_actions(
24142 &self,
24143 buffer: &Entity<Buffer>,
24144 range: Range<text::Anchor>,
24145 _window: &mut Window,
24146 cx: &mut App,
24147 ) -> Task<Result<Vec<CodeAction>>> {
24148 self.update(cx, |project, cx| {
24149 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24150 let code_actions = project.code_actions(buffer, range, None, cx);
24151 cx.background_spawn(async move {
24152 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24153 Ok(code_lens_actions
24154 .context("code lens fetch")?
24155 .into_iter()
24156 .flatten()
24157 .chain(
24158 code_actions
24159 .context("code action fetch")?
24160 .into_iter()
24161 .flatten(),
24162 )
24163 .collect())
24164 })
24165 })
24166 }
24167
24168 fn apply_code_action(
24169 &self,
24170 buffer_handle: Entity<Buffer>,
24171 action: CodeAction,
24172 _excerpt_id: ExcerptId,
24173 push_to_history: bool,
24174 _window: &mut Window,
24175 cx: &mut App,
24176 ) -> Task<Result<ProjectTransaction>> {
24177 self.update(cx, |project, cx| {
24178 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24179 })
24180 }
24181}
24182
24183fn snippet_completions(
24184 project: &Project,
24185 buffer: &Entity<Buffer>,
24186 buffer_anchor: text::Anchor,
24187 classifier: CharClassifier,
24188 cx: &mut App,
24189) -> Task<Result<CompletionResponse>> {
24190 let languages = buffer.read(cx).languages_at(buffer_anchor);
24191 let snippet_store = project.snippets().read(cx);
24192
24193 let scopes: Vec<_> = languages
24194 .iter()
24195 .filter_map(|language| {
24196 let language_name = language.lsp_id();
24197 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24198
24199 if snippets.is_empty() {
24200 None
24201 } else {
24202 Some((language.default_scope(), snippets))
24203 }
24204 })
24205 .collect();
24206
24207 if scopes.is_empty() {
24208 return Task::ready(Ok(CompletionResponse {
24209 completions: vec![],
24210 display_options: CompletionDisplayOptions::default(),
24211 is_incomplete: false,
24212 }));
24213 }
24214
24215 let snapshot = buffer.read(cx).text_snapshot();
24216 let executor = cx.background_executor().clone();
24217
24218 cx.background_spawn(async move {
24219 let is_word_char = |c| classifier.is_word(c);
24220
24221 let mut is_incomplete = false;
24222 let mut completions: Vec<Completion> = Vec::new();
24223
24224 const MAX_PREFIX_LEN: usize = 128;
24225 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24226 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24227 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24228
24229 let max_buffer_window: String = snapshot
24230 .text_for_range(window_start..buffer_offset)
24231 .collect();
24232
24233 if max_buffer_window.is_empty() {
24234 return Ok(CompletionResponse {
24235 completions: vec![],
24236 display_options: CompletionDisplayOptions::default(),
24237 is_incomplete: true,
24238 });
24239 }
24240
24241 for (_scope, snippets) in scopes.into_iter() {
24242 // Sort snippets by word count to match longer snippet prefixes first.
24243 let mut sorted_snippet_candidates = snippets
24244 .iter()
24245 .enumerate()
24246 .flat_map(|(snippet_ix, snippet)| {
24247 snippet
24248 .prefix
24249 .iter()
24250 .enumerate()
24251 .map(move |(prefix_ix, prefix)| {
24252 let word_count =
24253 snippet_candidate_suffixes(prefix, is_word_char).count();
24254 ((snippet_ix, prefix_ix), prefix, word_count)
24255 })
24256 })
24257 .collect_vec();
24258 sorted_snippet_candidates
24259 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24260
24261 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24262
24263 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24264 .take(
24265 sorted_snippet_candidates
24266 .first()
24267 .map(|(_, _, word_count)| *word_count)
24268 .unwrap_or_default(),
24269 )
24270 .collect_vec();
24271
24272 const MAX_RESULTS: usize = 100;
24273 // Each match also remembers how many characters from the buffer it consumed
24274 let mut matches: Vec<(StringMatch, usize)> = vec![];
24275
24276 let mut snippet_list_cutoff_index = 0;
24277 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24278 let word_count = buffer_index + 1;
24279 // Increase `snippet_list_cutoff_index` until we have all of the
24280 // snippets with sufficiently many words.
24281 while sorted_snippet_candidates
24282 .get(snippet_list_cutoff_index)
24283 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24284 *snippet_word_count >= word_count
24285 })
24286 {
24287 snippet_list_cutoff_index += 1;
24288 }
24289
24290 // Take only the candidates with at least `word_count` many words
24291 let snippet_candidates_at_word_len =
24292 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24293
24294 let candidates = snippet_candidates_at_word_len
24295 .iter()
24296 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24297 .enumerate() // index in `sorted_snippet_candidates`
24298 // First char must match
24299 .filter(|(_ix, prefix)| {
24300 itertools::equal(
24301 prefix
24302 .chars()
24303 .next()
24304 .into_iter()
24305 .flat_map(|c| c.to_lowercase()),
24306 buffer_window
24307 .chars()
24308 .next()
24309 .into_iter()
24310 .flat_map(|c| c.to_lowercase()),
24311 )
24312 })
24313 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24314 .collect::<Vec<StringMatchCandidate>>();
24315
24316 matches.extend(
24317 fuzzy::match_strings(
24318 &candidates,
24319 &buffer_window,
24320 buffer_window.chars().any(|c| c.is_uppercase()),
24321 true,
24322 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24323 &Default::default(),
24324 executor.clone(),
24325 )
24326 .await
24327 .into_iter()
24328 .map(|string_match| (string_match, buffer_window.len())),
24329 );
24330
24331 if matches.len() >= MAX_RESULTS {
24332 break;
24333 }
24334 }
24335
24336 let to_lsp = |point: &text::Anchor| {
24337 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24338 point_to_lsp(end)
24339 };
24340 let lsp_end = to_lsp(&buffer_anchor);
24341
24342 if matches.len() >= MAX_RESULTS {
24343 is_incomplete = true;
24344 }
24345
24346 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24347 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24348 sorted_snippet_candidates[string_match.candidate_id];
24349 let snippet = &snippets[snippet_index];
24350 let start = buffer_offset - buffer_window_len;
24351 let start = snapshot.anchor_before(start);
24352 let range = start..buffer_anchor;
24353 let lsp_start = to_lsp(&start);
24354 let lsp_range = lsp::Range {
24355 start: lsp_start,
24356 end: lsp_end,
24357 };
24358 Completion {
24359 replace_range: range,
24360 new_text: snippet.body.clone(),
24361 source: CompletionSource::Lsp {
24362 insert_range: None,
24363 server_id: LanguageServerId(usize::MAX),
24364 resolved: true,
24365 lsp_completion: Box::new(lsp::CompletionItem {
24366 label: snippet.prefix.first().unwrap().clone(),
24367 kind: Some(CompletionItemKind::SNIPPET),
24368 label_details: snippet.description.as_ref().map(|description| {
24369 lsp::CompletionItemLabelDetails {
24370 detail: Some(description.clone()),
24371 description: None,
24372 }
24373 }),
24374 insert_text_format: Some(InsertTextFormat::SNIPPET),
24375 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24376 lsp::InsertReplaceEdit {
24377 new_text: snippet.body.clone(),
24378 insert: lsp_range,
24379 replace: lsp_range,
24380 },
24381 )),
24382 filter_text: Some(snippet.body.clone()),
24383 sort_text: Some(char::MAX.to_string()),
24384 ..lsp::CompletionItem::default()
24385 }),
24386 lsp_defaults: None,
24387 },
24388 label: CodeLabel {
24389 text: matching_prefix.clone(),
24390 runs: Vec::new(),
24391 filter_range: 0..matching_prefix.len(),
24392 },
24393 icon_path: None,
24394 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24395 single_line: snippet.name.clone().into(),
24396 plain_text: snippet
24397 .description
24398 .clone()
24399 .map(|description| description.into()),
24400 }),
24401 insert_text_mode: None,
24402 confirm: None,
24403 match_start: Some(start),
24404 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24405 }
24406 }));
24407 }
24408
24409 Ok(CompletionResponse {
24410 completions,
24411 display_options: CompletionDisplayOptions::default(),
24412 is_incomplete,
24413 })
24414 })
24415}
24416
24417impl CompletionProvider for Entity<Project> {
24418 fn completions(
24419 &self,
24420 _excerpt_id: ExcerptId,
24421 buffer: &Entity<Buffer>,
24422 buffer_position: text::Anchor,
24423 options: CompletionContext,
24424 _window: &mut Window,
24425 cx: &mut Context<Editor>,
24426 ) -> Task<Result<Vec<CompletionResponse>>> {
24427 self.update(cx, |project, cx| {
24428 let task = project.completions(buffer, buffer_position, options, cx);
24429 cx.background_spawn(task)
24430 })
24431 }
24432
24433 fn resolve_completions(
24434 &self,
24435 buffer: Entity<Buffer>,
24436 completion_indices: Vec<usize>,
24437 completions: Rc<RefCell<Box<[Completion]>>>,
24438 cx: &mut Context<Editor>,
24439 ) -> Task<Result<bool>> {
24440 self.update(cx, |project, cx| {
24441 project.lsp_store().update(cx, |lsp_store, cx| {
24442 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24443 })
24444 })
24445 }
24446
24447 fn apply_additional_edits_for_completion(
24448 &self,
24449 buffer: Entity<Buffer>,
24450 completions: Rc<RefCell<Box<[Completion]>>>,
24451 completion_index: usize,
24452 push_to_history: bool,
24453 cx: &mut Context<Editor>,
24454 ) -> Task<Result<Option<language::Transaction>>> {
24455 self.update(cx, |project, cx| {
24456 project.lsp_store().update(cx, |lsp_store, cx| {
24457 lsp_store.apply_additional_edits_for_completion(
24458 buffer,
24459 completions,
24460 completion_index,
24461 push_to_history,
24462 cx,
24463 )
24464 })
24465 })
24466 }
24467
24468 fn is_completion_trigger(
24469 &self,
24470 buffer: &Entity<Buffer>,
24471 position: language::Anchor,
24472 text: &str,
24473 trigger_in_words: bool,
24474 cx: &mut Context<Editor>,
24475 ) -> bool {
24476 let mut chars = text.chars();
24477 let char = if let Some(char) = chars.next() {
24478 char
24479 } else {
24480 return false;
24481 };
24482 if chars.next().is_some() {
24483 return false;
24484 }
24485
24486 let buffer = buffer.read(cx);
24487 let snapshot = buffer.snapshot();
24488 let classifier = snapshot
24489 .char_classifier_at(position)
24490 .scope_context(Some(CharScopeContext::Completion));
24491 if trigger_in_words && classifier.is_word(char) {
24492 return true;
24493 }
24494
24495 buffer.completion_triggers().contains(text)
24496 }
24497
24498 fn show_snippets(&self) -> bool {
24499 true
24500 }
24501}
24502
24503impl SemanticsProvider for Entity<Project> {
24504 fn hover(
24505 &self,
24506 buffer: &Entity<Buffer>,
24507 position: text::Anchor,
24508 cx: &mut App,
24509 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24510 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24511 }
24512
24513 fn document_highlights(
24514 &self,
24515 buffer: &Entity<Buffer>,
24516 position: text::Anchor,
24517 cx: &mut App,
24518 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24519 Some(self.update(cx, |project, cx| {
24520 project.document_highlights(buffer, position, cx)
24521 }))
24522 }
24523
24524 fn definitions(
24525 &self,
24526 buffer: &Entity<Buffer>,
24527 position: text::Anchor,
24528 kind: GotoDefinitionKind,
24529 cx: &mut App,
24530 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24531 Some(self.update(cx, |project, cx| match kind {
24532 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24533 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24534 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24535 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24536 }))
24537 }
24538
24539 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24540 self.update(cx, |project, cx| {
24541 if project
24542 .active_debug_session(cx)
24543 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24544 {
24545 return true;
24546 }
24547
24548 buffer.update(cx, |buffer, cx| {
24549 project.any_language_server_supports_inlay_hints(buffer, cx)
24550 })
24551 })
24552 }
24553
24554 fn inline_values(
24555 &self,
24556 buffer_handle: Entity<Buffer>,
24557 range: Range<text::Anchor>,
24558 cx: &mut App,
24559 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24560 self.update(cx, |project, cx| {
24561 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24562
24563 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24564 })
24565 }
24566
24567 fn applicable_inlay_chunks(
24568 &self,
24569 buffer: &Entity<Buffer>,
24570 ranges: &[Range<text::Anchor>],
24571 cx: &mut App,
24572 ) -> Vec<Range<BufferRow>> {
24573 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24574 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24575 })
24576 }
24577
24578 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24579 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24580 lsp_store.invalidate_inlay_hints(for_buffers)
24581 });
24582 }
24583
24584 fn inlay_hints(
24585 &self,
24586 invalidate: InvalidationStrategy,
24587 buffer: Entity<Buffer>,
24588 ranges: Vec<Range<text::Anchor>>,
24589 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24590 cx: &mut App,
24591 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24592 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24593 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24594 }))
24595 }
24596
24597 fn range_for_rename(
24598 &self,
24599 buffer: &Entity<Buffer>,
24600 position: text::Anchor,
24601 cx: &mut App,
24602 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24603 Some(self.update(cx, |project, cx| {
24604 let buffer = buffer.clone();
24605 let task = project.prepare_rename(buffer.clone(), position, cx);
24606 cx.spawn(async move |_, cx| {
24607 Ok(match task.await? {
24608 PrepareRenameResponse::Success(range) => Some(range),
24609 PrepareRenameResponse::InvalidPosition => None,
24610 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24611 // Fallback on using TreeSitter info to determine identifier range
24612 buffer.read_with(cx, |buffer, _| {
24613 let snapshot = buffer.snapshot();
24614 let (range, kind) = snapshot.surrounding_word(position, None);
24615 if kind != Some(CharKind::Word) {
24616 return None;
24617 }
24618 Some(
24619 snapshot.anchor_before(range.start)
24620 ..snapshot.anchor_after(range.end),
24621 )
24622 })?
24623 }
24624 })
24625 })
24626 }))
24627 }
24628
24629 fn perform_rename(
24630 &self,
24631 buffer: &Entity<Buffer>,
24632 position: text::Anchor,
24633 new_name: String,
24634 cx: &mut App,
24635 ) -> Option<Task<Result<ProjectTransaction>>> {
24636 Some(self.update(cx, |project, cx| {
24637 project.perform_rename(buffer.clone(), position, new_name, cx)
24638 }))
24639 }
24640}
24641
24642fn consume_contiguous_rows(
24643 contiguous_row_selections: &mut Vec<Selection<Point>>,
24644 selection: &Selection<Point>,
24645 display_map: &DisplaySnapshot,
24646 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24647) -> (MultiBufferRow, MultiBufferRow) {
24648 contiguous_row_selections.push(selection.clone());
24649 let start_row = starting_row(selection, display_map);
24650 let mut end_row = ending_row(selection, display_map);
24651
24652 while let Some(next_selection) = selections.peek() {
24653 if next_selection.start.row <= end_row.0 {
24654 end_row = ending_row(next_selection, display_map);
24655 contiguous_row_selections.push(selections.next().unwrap().clone());
24656 } else {
24657 break;
24658 }
24659 }
24660 (start_row, end_row)
24661}
24662
24663fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24664 if selection.start.column > 0 {
24665 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24666 } else {
24667 MultiBufferRow(selection.start.row)
24668 }
24669}
24670
24671fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24672 if next_selection.end.column > 0 || next_selection.is_empty() {
24673 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24674 } else {
24675 MultiBufferRow(next_selection.end.row)
24676 }
24677}
24678
24679impl EditorSnapshot {
24680 pub fn remote_selections_in_range<'a>(
24681 &'a self,
24682 range: &'a Range<Anchor>,
24683 collaboration_hub: &dyn CollaborationHub,
24684 cx: &'a App,
24685 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24686 let participant_names = collaboration_hub.user_names(cx);
24687 let participant_indices = collaboration_hub.user_participant_indices(cx);
24688 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24689 let collaborators_by_replica_id = collaborators_by_peer_id
24690 .values()
24691 .map(|collaborator| (collaborator.replica_id, collaborator))
24692 .collect::<HashMap<_, _>>();
24693 self.buffer_snapshot()
24694 .selections_in_range(range, false)
24695 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24696 if replica_id == ReplicaId::AGENT {
24697 Some(RemoteSelection {
24698 replica_id,
24699 selection,
24700 cursor_shape,
24701 line_mode,
24702 collaborator_id: CollaboratorId::Agent,
24703 user_name: Some("Agent".into()),
24704 color: cx.theme().players().agent(),
24705 })
24706 } else {
24707 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24708 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24709 let user_name = participant_names.get(&collaborator.user_id).cloned();
24710 Some(RemoteSelection {
24711 replica_id,
24712 selection,
24713 cursor_shape,
24714 line_mode,
24715 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24716 user_name,
24717 color: if let Some(index) = participant_index {
24718 cx.theme().players().color_for_participant(index.0)
24719 } else {
24720 cx.theme().players().absent()
24721 },
24722 })
24723 }
24724 })
24725 }
24726
24727 pub fn hunks_for_ranges(
24728 &self,
24729 ranges: impl IntoIterator<Item = Range<Point>>,
24730 ) -> Vec<MultiBufferDiffHunk> {
24731 let mut hunks = Vec::new();
24732 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24733 HashMap::default();
24734 for query_range in ranges {
24735 let query_rows =
24736 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24737 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24738 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24739 ) {
24740 // Include deleted hunks that are adjacent to the query range, because
24741 // otherwise they would be missed.
24742 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24743 if hunk.status().is_deleted() {
24744 intersects_range |= hunk.row_range.start == query_rows.end;
24745 intersects_range |= hunk.row_range.end == query_rows.start;
24746 }
24747 if intersects_range {
24748 if !processed_buffer_rows
24749 .entry(hunk.buffer_id)
24750 .or_default()
24751 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24752 {
24753 continue;
24754 }
24755 hunks.push(hunk);
24756 }
24757 }
24758 }
24759
24760 hunks
24761 }
24762
24763 fn display_diff_hunks_for_rows<'a>(
24764 &'a self,
24765 display_rows: Range<DisplayRow>,
24766 folded_buffers: &'a HashSet<BufferId>,
24767 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24768 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24769 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24770
24771 self.buffer_snapshot()
24772 .diff_hunks_in_range(buffer_start..buffer_end)
24773 .filter_map(|hunk| {
24774 if folded_buffers.contains(&hunk.buffer_id) {
24775 return None;
24776 }
24777
24778 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24779 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24780
24781 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24782 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24783
24784 let display_hunk = if hunk_display_start.column() != 0 {
24785 DisplayDiffHunk::Folded {
24786 display_row: hunk_display_start.row(),
24787 }
24788 } else {
24789 let mut end_row = hunk_display_end.row();
24790 if hunk_display_end.column() > 0 {
24791 end_row.0 += 1;
24792 }
24793 let is_created_file = hunk.is_created_file();
24794
24795 DisplayDiffHunk::Unfolded {
24796 status: hunk.status(),
24797 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24798 ..hunk.diff_base_byte_range.end.0,
24799 word_diffs: hunk.word_diffs,
24800 display_row_range: hunk_display_start.row()..end_row,
24801 multi_buffer_range: Anchor::range_in_buffer(
24802 hunk.excerpt_id,
24803 hunk.buffer_range,
24804 ),
24805 is_created_file,
24806 }
24807 };
24808
24809 Some(display_hunk)
24810 })
24811 }
24812
24813 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24814 self.display_snapshot
24815 .buffer_snapshot()
24816 .language_at(position)
24817 }
24818
24819 pub fn is_focused(&self) -> bool {
24820 self.is_focused
24821 }
24822
24823 pub fn placeholder_text(&self) -> Option<String> {
24824 self.placeholder_display_snapshot
24825 .as_ref()
24826 .map(|display_map| display_map.text())
24827 }
24828
24829 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24830 self.scroll_anchor.scroll_position(&self.display_snapshot)
24831 }
24832
24833 pub fn gutter_dimensions(
24834 &self,
24835 font_id: FontId,
24836 font_size: Pixels,
24837 style: &EditorStyle,
24838 window: &mut Window,
24839 cx: &App,
24840 ) -> GutterDimensions {
24841 if self.show_gutter
24842 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
24843 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
24844 {
24845 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24846 matches!(
24847 ProjectSettings::get_global(cx).git.git_gutter,
24848 GitGutterSetting::TrackedFiles
24849 )
24850 });
24851 let gutter_settings = EditorSettings::get_global(cx).gutter;
24852 let show_line_numbers = self
24853 .show_line_numbers
24854 .unwrap_or(gutter_settings.line_numbers);
24855 let line_gutter_width = if show_line_numbers {
24856 // Avoid flicker-like gutter resizes when the line number gains another digit by
24857 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24858 let min_width_for_number_on_gutter =
24859 ch_advance * gutter_settings.min_line_number_digits as f32;
24860 self.max_line_number_width(style, window)
24861 .max(min_width_for_number_on_gutter)
24862 } else {
24863 0.0.into()
24864 };
24865
24866 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24867 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24868
24869 let git_blame_entries_width =
24870 self.git_blame_gutter_max_author_length
24871 .map(|max_author_length| {
24872 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24873 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24874
24875 /// The number of characters to dedicate to gaps and margins.
24876 const SPACING_WIDTH: usize = 4;
24877
24878 let max_char_count = max_author_length.min(renderer.max_author_length())
24879 + ::git::SHORT_SHA_LENGTH
24880 + MAX_RELATIVE_TIMESTAMP.len()
24881 + SPACING_WIDTH;
24882
24883 ch_advance * max_char_count
24884 });
24885
24886 let is_singleton = self.buffer_snapshot().is_singleton();
24887
24888 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24889 left_padding += if !is_singleton {
24890 ch_width * 4.0
24891 } else if show_runnables || show_breakpoints {
24892 ch_width * 3.0
24893 } else if show_git_gutter && show_line_numbers {
24894 ch_width * 2.0
24895 } else if show_git_gutter || show_line_numbers {
24896 ch_width
24897 } else {
24898 px(0.)
24899 };
24900
24901 let shows_folds = is_singleton && gutter_settings.folds;
24902
24903 let right_padding = if shows_folds && show_line_numbers {
24904 ch_width * 4.0
24905 } else if shows_folds || (!is_singleton && show_line_numbers) {
24906 ch_width * 3.0
24907 } else if show_line_numbers {
24908 ch_width
24909 } else {
24910 px(0.)
24911 };
24912
24913 GutterDimensions {
24914 left_padding,
24915 right_padding,
24916 width: line_gutter_width + left_padding + right_padding,
24917 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24918 git_blame_entries_width,
24919 }
24920 } else if self.offset_content {
24921 GutterDimensions::default_with_margin(font_id, font_size, cx)
24922 } else {
24923 GutterDimensions::default()
24924 }
24925 }
24926
24927 pub fn render_crease_toggle(
24928 &self,
24929 buffer_row: MultiBufferRow,
24930 row_contains_cursor: bool,
24931 editor: Entity<Editor>,
24932 window: &mut Window,
24933 cx: &mut App,
24934 ) -> Option<AnyElement> {
24935 let folded = self.is_line_folded(buffer_row);
24936 let mut is_foldable = false;
24937
24938 if let Some(crease) = self
24939 .crease_snapshot
24940 .query_row(buffer_row, self.buffer_snapshot())
24941 {
24942 is_foldable = true;
24943 match crease {
24944 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24945 if let Some(render_toggle) = render_toggle {
24946 let toggle_callback =
24947 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24948 if folded {
24949 editor.update(cx, |editor, cx| {
24950 editor.fold_at(buffer_row, window, cx)
24951 });
24952 } else {
24953 editor.update(cx, |editor, cx| {
24954 editor.unfold_at(buffer_row, window, cx)
24955 });
24956 }
24957 });
24958 return Some((render_toggle)(
24959 buffer_row,
24960 folded,
24961 toggle_callback,
24962 window,
24963 cx,
24964 ));
24965 }
24966 }
24967 }
24968 }
24969
24970 is_foldable |= self.starts_indent(buffer_row);
24971
24972 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24973 Some(
24974 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24975 .toggle_state(folded)
24976 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24977 if folded {
24978 this.unfold_at(buffer_row, window, cx);
24979 } else {
24980 this.fold_at(buffer_row, window, cx);
24981 }
24982 }))
24983 .into_any_element(),
24984 )
24985 } else {
24986 None
24987 }
24988 }
24989
24990 pub fn render_crease_trailer(
24991 &self,
24992 buffer_row: MultiBufferRow,
24993 window: &mut Window,
24994 cx: &mut App,
24995 ) -> Option<AnyElement> {
24996 let folded = self.is_line_folded(buffer_row);
24997 if let Crease::Inline { render_trailer, .. } = self
24998 .crease_snapshot
24999 .query_row(buffer_row, self.buffer_snapshot())?
25000 {
25001 let render_trailer = render_trailer.as_ref()?;
25002 Some(render_trailer(buffer_row, folded, window, cx))
25003 } else {
25004 None
25005 }
25006 }
25007
25008 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
25009 let digit_count = self.widest_line_number().ilog10() + 1;
25010 column_pixels(style, digit_count as usize, window)
25011 }
25012}
25013
25014pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25015 let font_size = style.text.font_size.to_pixels(window.rem_size());
25016 let layout = window.text_system().shape_line(
25017 SharedString::from(" ".repeat(column)),
25018 font_size,
25019 &[TextRun {
25020 len: column,
25021 font: style.text.font(),
25022 color: Hsla::default(),
25023 ..Default::default()
25024 }],
25025 None,
25026 );
25027
25028 layout.width
25029}
25030
25031impl Deref for EditorSnapshot {
25032 type Target = DisplaySnapshot;
25033
25034 fn deref(&self) -> &Self::Target {
25035 &self.display_snapshot
25036 }
25037}
25038
25039#[derive(Clone, Debug, PartialEq, Eq)]
25040pub enum EditorEvent {
25041 InputIgnored {
25042 text: Arc<str>,
25043 },
25044 InputHandled {
25045 utf16_range_to_replace: Option<Range<isize>>,
25046 text: Arc<str>,
25047 },
25048 ExcerptsAdded {
25049 buffer: Entity<Buffer>,
25050 predecessor: ExcerptId,
25051 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25052 },
25053 ExcerptsRemoved {
25054 ids: Vec<ExcerptId>,
25055 removed_buffer_ids: Vec<BufferId>,
25056 },
25057 BufferFoldToggled {
25058 ids: Vec<ExcerptId>,
25059 folded: bool,
25060 },
25061 ExcerptsEdited {
25062 ids: Vec<ExcerptId>,
25063 },
25064 ExcerptsExpanded {
25065 ids: Vec<ExcerptId>,
25066 },
25067 BufferEdited,
25068 Edited {
25069 transaction_id: clock::Lamport,
25070 },
25071 Reparsed(BufferId),
25072 Focused,
25073 FocusedIn,
25074 Blurred,
25075 DirtyChanged,
25076 Saved,
25077 TitleChanged,
25078 SelectionsChanged {
25079 local: bool,
25080 },
25081 ScrollPositionChanged {
25082 local: bool,
25083 autoscroll: bool,
25084 },
25085 TransactionUndone {
25086 transaction_id: clock::Lamport,
25087 },
25088 TransactionBegun {
25089 transaction_id: clock::Lamport,
25090 },
25091 CursorShapeChanged,
25092 BreadcrumbsChanged,
25093 PushedToNavHistory {
25094 anchor: Anchor,
25095 is_deactivate: bool,
25096 },
25097}
25098
25099impl EventEmitter<EditorEvent> for Editor {}
25100
25101impl Focusable for Editor {
25102 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25103 self.focus_handle.clone()
25104 }
25105}
25106
25107impl Render for Editor {
25108 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25109 EditorElement::new(&cx.entity(), self.create_style(cx))
25110 }
25111}
25112
25113impl EntityInputHandler for Editor {
25114 fn text_for_range(
25115 &mut self,
25116 range_utf16: Range<usize>,
25117 adjusted_range: &mut Option<Range<usize>>,
25118 _: &mut Window,
25119 cx: &mut Context<Self>,
25120 ) -> Option<String> {
25121 let snapshot = self.buffer.read(cx).read(cx);
25122 let start = snapshot.clip_offset_utf16(
25123 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25124 Bias::Left,
25125 );
25126 let end = snapshot.clip_offset_utf16(
25127 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25128 Bias::Right,
25129 );
25130 if (start.0.0..end.0.0) != range_utf16 {
25131 adjusted_range.replace(start.0.0..end.0.0);
25132 }
25133 Some(snapshot.text_for_range(start..end).collect())
25134 }
25135
25136 fn selected_text_range(
25137 &mut self,
25138 ignore_disabled_input: bool,
25139 _: &mut Window,
25140 cx: &mut Context<Self>,
25141 ) -> Option<UTF16Selection> {
25142 // Prevent the IME menu from appearing when holding down an alphabetic key
25143 // while input is disabled.
25144 if !ignore_disabled_input && !self.input_enabled {
25145 return None;
25146 }
25147
25148 let selection = self
25149 .selections
25150 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25151 let range = selection.range();
25152
25153 Some(UTF16Selection {
25154 range: range.start.0.0..range.end.0.0,
25155 reversed: selection.reversed,
25156 })
25157 }
25158
25159 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25160 let snapshot = self.buffer.read(cx).read(cx);
25161 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25162 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25163 }
25164
25165 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25166 self.clear_highlights::<InputComposition>(cx);
25167 self.ime_transaction.take();
25168 }
25169
25170 fn replace_text_in_range(
25171 &mut self,
25172 range_utf16: Option<Range<usize>>,
25173 text: &str,
25174 window: &mut Window,
25175 cx: &mut Context<Self>,
25176 ) {
25177 if !self.input_enabled {
25178 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25179 return;
25180 }
25181
25182 self.transact(window, cx, |this, window, cx| {
25183 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25184 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25185 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25186 Some(this.selection_replacement_ranges(range_utf16, cx))
25187 } else {
25188 this.marked_text_ranges(cx)
25189 };
25190
25191 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25192 let newest_selection_id = this.selections.newest_anchor().id;
25193 this.selections
25194 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25195 .iter()
25196 .zip(ranges_to_replace.iter())
25197 .find_map(|(selection, range)| {
25198 if selection.id == newest_selection_id {
25199 Some(
25200 (range.start.0.0 as isize - selection.head().0.0 as isize)
25201 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25202 )
25203 } else {
25204 None
25205 }
25206 })
25207 });
25208
25209 cx.emit(EditorEvent::InputHandled {
25210 utf16_range_to_replace: range_to_replace,
25211 text: text.into(),
25212 });
25213
25214 if let Some(new_selected_ranges) = new_selected_ranges {
25215 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25216 selections.select_ranges(new_selected_ranges)
25217 });
25218 this.backspace(&Default::default(), window, cx);
25219 }
25220
25221 this.handle_input(text, window, cx);
25222 });
25223
25224 if let Some(transaction) = self.ime_transaction {
25225 self.buffer.update(cx, |buffer, cx| {
25226 buffer.group_until_transaction(transaction, cx);
25227 });
25228 }
25229
25230 self.unmark_text(window, cx);
25231 }
25232
25233 fn replace_and_mark_text_in_range(
25234 &mut self,
25235 range_utf16: Option<Range<usize>>,
25236 text: &str,
25237 new_selected_range_utf16: Option<Range<usize>>,
25238 window: &mut Window,
25239 cx: &mut Context<Self>,
25240 ) {
25241 if !self.input_enabled {
25242 return;
25243 }
25244
25245 let transaction = self.transact(window, cx, |this, window, cx| {
25246 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25247 let snapshot = this.buffer.read(cx).read(cx);
25248 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25249 for marked_range in &mut marked_ranges {
25250 marked_range.end = marked_range.start + relative_range_utf16.end;
25251 marked_range.start += relative_range_utf16.start;
25252 marked_range.start =
25253 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25254 marked_range.end =
25255 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25256 }
25257 }
25258 Some(marked_ranges)
25259 } else if let Some(range_utf16) = range_utf16 {
25260 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25261 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25262 Some(this.selection_replacement_ranges(range_utf16, cx))
25263 } else {
25264 None
25265 };
25266
25267 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25268 let newest_selection_id = this.selections.newest_anchor().id;
25269 this.selections
25270 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25271 .iter()
25272 .zip(ranges_to_replace.iter())
25273 .find_map(|(selection, range)| {
25274 if selection.id == newest_selection_id {
25275 Some(
25276 (range.start.0.0 as isize - selection.head().0.0 as isize)
25277 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25278 )
25279 } else {
25280 None
25281 }
25282 })
25283 });
25284
25285 cx.emit(EditorEvent::InputHandled {
25286 utf16_range_to_replace: range_to_replace,
25287 text: text.into(),
25288 });
25289
25290 if let Some(ranges) = ranges_to_replace {
25291 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25292 s.select_ranges(ranges)
25293 });
25294 }
25295
25296 let marked_ranges = {
25297 let snapshot = this.buffer.read(cx).read(cx);
25298 this.selections
25299 .disjoint_anchors_arc()
25300 .iter()
25301 .map(|selection| {
25302 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25303 })
25304 .collect::<Vec<_>>()
25305 };
25306
25307 if text.is_empty() {
25308 this.unmark_text(window, cx);
25309 } else {
25310 this.highlight_text::<InputComposition>(
25311 marked_ranges.clone(),
25312 HighlightStyle {
25313 underline: Some(UnderlineStyle {
25314 thickness: px(1.),
25315 color: None,
25316 wavy: false,
25317 }),
25318 ..Default::default()
25319 },
25320 cx,
25321 );
25322 }
25323
25324 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25325 let use_autoclose = this.use_autoclose;
25326 let use_auto_surround = this.use_auto_surround;
25327 this.set_use_autoclose(false);
25328 this.set_use_auto_surround(false);
25329 this.handle_input(text, window, cx);
25330 this.set_use_autoclose(use_autoclose);
25331 this.set_use_auto_surround(use_auto_surround);
25332
25333 if let Some(new_selected_range) = new_selected_range_utf16 {
25334 let snapshot = this.buffer.read(cx).read(cx);
25335 let new_selected_ranges = marked_ranges
25336 .into_iter()
25337 .map(|marked_range| {
25338 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25339 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25340 insertion_start.0 + new_selected_range.start,
25341 ));
25342 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25343 insertion_start.0 + new_selected_range.end,
25344 ));
25345 snapshot.clip_offset_utf16(new_start, Bias::Left)
25346 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25347 })
25348 .collect::<Vec<_>>();
25349
25350 drop(snapshot);
25351 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25352 selections.select_ranges(new_selected_ranges)
25353 });
25354 }
25355 });
25356
25357 self.ime_transaction = self.ime_transaction.or(transaction);
25358 if let Some(transaction) = self.ime_transaction {
25359 self.buffer.update(cx, |buffer, cx| {
25360 buffer.group_until_transaction(transaction, cx);
25361 });
25362 }
25363
25364 if self.text_highlights::<InputComposition>(cx).is_none() {
25365 self.ime_transaction.take();
25366 }
25367 }
25368
25369 fn bounds_for_range(
25370 &mut self,
25371 range_utf16: Range<usize>,
25372 element_bounds: gpui::Bounds<Pixels>,
25373 window: &mut Window,
25374 cx: &mut Context<Self>,
25375 ) -> Option<gpui::Bounds<Pixels>> {
25376 let text_layout_details = self.text_layout_details(window);
25377 let CharacterDimensions {
25378 em_width,
25379 em_advance,
25380 line_height,
25381 } = self.character_dimensions(window);
25382
25383 let snapshot = self.snapshot(window, cx);
25384 let scroll_position = snapshot.scroll_position();
25385 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25386
25387 let start =
25388 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25389 let x = Pixels::from(
25390 ScrollOffset::from(
25391 snapshot.x_for_display_point(start, &text_layout_details)
25392 + self.gutter_dimensions.full_width(),
25393 ) - scroll_left,
25394 );
25395 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25396
25397 Some(Bounds {
25398 origin: element_bounds.origin + point(x, y),
25399 size: size(em_width, line_height),
25400 })
25401 }
25402
25403 fn character_index_for_point(
25404 &mut self,
25405 point: gpui::Point<Pixels>,
25406 _window: &mut Window,
25407 _cx: &mut Context<Self>,
25408 ) -> Option<usize> {
25409 let position_map = self.last_position_map.as_ref()?;
25410 if !position_map.text_hitbox.contains(&point) {
25411 return None;
25412 }
25413 let display_point = position_map.point_for_position(point).previous_valid;
25414 let anchor = position_map
25415 .snapshot
25416 .display_point_to_anchor(display_point, Bias::Left);
25417 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25418 Some(utf16_offset.0.0)
25419 }
25420
25421 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25422 self.input_enabled
25423 }
25424}
25425
25426trait SelectionExt {
25427 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25428 fn spanned_rows(
25429 &self,
25430 include_end_if_at_line_start: bool,
25431 map: &DisplaySnapshot,
25432 ) -> Range<MultiBufferRow>;
25433}
25434
25435impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25436 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25437 let start = self
25438 .start
25439 .to_point(map.buffer_snapshot())
25440 .to_display_point(map);
25441 let end = self
25442 .end
25443 .to_point(map.buffer_snapshot())
25444 .to_display_point(map);
25445 if self.reversed {
25446 end..start
25447 } else {
25448 start..end
25449 }
25450 }
25451
25452 fn spanned_rows(
25453 &self,
25454 include_end_if_at_line_start: bool,
25455 map: &DisplaySnapshot,
25456 ) -> Range<MultiBufferRow> {
25457 let start = self.start.to_point(map.buffer_snapshot());
25458 let mut end = self.end.to_point(map.buffer_snapshot());
25459 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25460 end.row -= 1;
25461 }
25462
25463 let buffer_start = map.prev_line_boundary(start).0;
25464 let buffer_end = map.next_line_boundary(end).0;
25465 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25466 }
25467}
25468
25469impl<T: InvalidationRegion> InvalidationStack<T> {
25470 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25471 where
25472 S: Clone + ToOffset,
25473 {
25474 while let Some(region) = self.last() {
25475 let all_selections_inside_invalidation_ranges =
25476 if selections.len() == region.ranges().len() {
25477 selections
25478 .iter()
25479 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25480 .all(|(selection, invalidation_range)| {
25481 let head = selection.head().to_offset(buffer);
25482 invalidation_range.start <= head && invalidation_range.end >= head
25483 })
25484 } else {
25485 false
25486 };
25487
25488 if all_selections_inside_invalidation_ranges {
25489 break;
25490 } else {
25491 self.pop();
25492 }
25493 }
25494 }
25495}
25496
25497impl<T> Default for InvalidationStack<T> {
25498 fn default() -> Self {
25499 Self(Default::default())
25500 }
25501}
25502
25503impl<T> Deref for InvalidationStack<T> {
25504 type Target = Vec<T>;
25505
25506 fn deref(&self) -> &Self::Target {
25507 &self.0
25508 }
25509}
25510
25511impl<T> DerefMut for InvalidationStack<T> {
25512 fn deref_mut(&mut self) -> &mut Self::Target {
25513 &mut self.0
25514 }
25515}
25516
25517impl InvalidationRegion for SnippetState {
25518 fn ranges(&self) -> &[Range<Anchor>] {
25519 &self.ranges[self.active_index]
25520 }
25521}
25522
25523fn edit_prediction_edit_text(
25524 current_snapshot: &BufferSnapshot,
25525 edits: &[(Range<Anchor>, impl AsRef<str>)],
25526 edit_preview: &EditPreview,
25527 include_deletions: bool,
25528 cx: &App,
25529) -> HighlightedText {
25530 let edits = edits
25531 .iter()
25532 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25533 .collect::<Vec<_>>();
25534
25535 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25536}
25537
25538fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25539 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25540 // Just show the raw edit text with basic styling
25541 let mut text = String::new();
25542 let mut highlights = Vec::new();
25543
25544 let insertion_highlight_style = HighlightStyle {
25545 color: Some(cx.theme().colors().text),
25546 ..Default::default()
25547 };
25548
25549 for (_, edit_text) in edits {
25550 let start_offset = text.len();
25551 text.push_str(edit_text);
25552 let end_offset = text.len();
25553
25554 if start_offset < end_offset {
25555 highlights.push((start_offset..end_offset, insertion_highlight_style));
25556 }
25557 }
25558
25559 HighlightedText {
25560 text: text.into(),
25561 highlights,
25562 }
25563}
25564
25565pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25566 match severity {
25567 lsp::DiagnosticSeverity::ERROR => colors.error,
25568 lsp::DiagnosticSeverity::WARNING => colors.warning,
25569 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25570 lsp::DiagnosticSeverity::HINT => colors.info,
25571 _ => colors.ignored,
25572 }
25573}
25574
25575pub fn styled_runs_for_code_label<'a>(
25576 label: &'a CodeLabel,
25577 syntax_theme: &'a theme::SyntaxTheme,
25578 local_player: &'a theme::PlayerColor,
25579) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25580 let fade_out = HighlightStyle {
25581 fade_out: Some(0.35),
25582 ..Default::default()
25583 };
25584
25585 let mut prev_end = label.filter_range.end;
25586 label
25587 .runs
25588 .iter()
25589 .enumerate()
25590 .flat_map(move |(ix, (range, highlight_id))| {
25591 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25592 HighlightStyle {
25593 color: Some(local_player.cursor),
25594 ..Default::default()
25595 }
25596 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25597 HighlightStyle {
25598 background_color: Some(local_player.selection),
25599 ..Default::default()
25600 }
25601 } else if let Some(style) = highlight_id.style(syntax_theme) {
25602 style
25603 } else {
25604 return Default::default();
25605 };
25606 let muted_style = style.highlight(fade_out);
25607
25608 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25609 if range.start >= label.filter_range.end {
25610 if range.start > prev_end {
25611 runs.push((prev_end..range.start, fade_out));
25612 }
25613 runs.push((range.clone(), muted_style));
25614 } else if range.end <= label.filter_range.end {
25615 runs.push((range.clone(), style));
25616 } else {
25617 runs.push((range.start..label.filter_range.end, style));
25618 runs.push((label.filter_range.end..range.end, muted_style));
25619 }
25620 prev_end = cmp::max(prev_end, range.end);
25621
25622 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25623 runs.push((prev_end..label.text.len(), fade_out));
25624 }
25625
25626 runs
25627 })
25628}
25629
25630pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25631 let mut prev_index = 0;
25632 let mut prev_codepoint: Option<char> = None;
25633 text.char_indices()
25634 .chain([(text.len(), '\0')])
25635 .filter_map(move |(index, codepoint)| {
25636 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25637 let is_boundary = index == text.len()
25638 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25639 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25640 if is_boundary {
25641 let chunk = &text[prev_index..index];
25642 prev_index = index;
25643 Some(chunk)
25644 } else {
25645 None
25646 }
25647 })
25648}
25649
25650/// Given a string of text immediately before the cursor, iterates over possible
25651/// strings a snippet could match to. More precisely: returns an iterator over
25652/// suffixes of `text` created by splitting at word boundaries (before & after
25653/// every non-word character).
25654///
25655/// Shorter suffixes are returned first.
25656pub(crate) fn snippet_candidate_suffixes(
25657 text: &str,
25658 is_word_char: impl Fn(char) -> bool,
25659) -> impl std::iter::Iterator<Item = &str> {
25660 let mut prev_index = text.len();
25661 let mut prev_codepoint = None;
25662 text.char_indices()
25663 .rev()
25664 .chain([(0, '\0')])
25665 .filter_map(move |(index, codepoint)| {
25666 let prev_index = std::mem::replace(&mut prev_index, index);
25667 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25668 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25669 None
25670 } else {
25671 let chunk = &text[prev_index..]; // go to end of string
25672 Some(chunk)
25673 }
25674 })
25675}
25676
25677pub trait RangeToAnchorExt: Sized {
25678 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25679
25680 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25681 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25682 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25683 }
25684}
25685
25686impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25687 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25688 let start_offset = self.start.to_offset(snapshot);
25689 let end_offset = self.end.to_offset(snapshot);
25690 if start_offset == end_offset {
25691 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25692 } else {
25693 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25694 }
25695 }
25696}
25697
25698pub trait RowExt {
25699 fn as_f64(&self) -> f64;
25700
25701 fn next_row(&self) -> Self;
25702
25703 fn previous_row(&self) -> Self;
25704
25705 fn minus(&self, other: Self) -> u32;
25706}
25707
25708impl RowExt for DisplayRow {
25709 fn as_f64(&self) -> f64 {
25710 self.0 as _
25711 }
25712
25713 fn next_row(&self) -> Self {
25714 Self(self.0 + 1)
25715 }
25716
25717 fn previous_row(&self) -> Self {
25718 Self(self.0.saturating_sub(1))
25719 }
25720
25721 fn minus(&self, other: Self) -> u32 {
25722 self.0 - other.0
25723 }
25724}
25725
25726impl RowExt for MultiBufferRow {
25727 fn as_f64(&self) -> f64 {
25728 self.0 as _
25729 }
25730
25731 fn next_row(&self) -> Self {
25732 Self(self.0 + 1)
25733 }
25734
25735 fn previous_row(&self) -> Self {
25736 Self(self.0.saturating_sub(1))
25737 }
25738
25739 fn minus(&self, other: Self) -> u32 {
25740 self.0 - other.0
25741 }
25742}
25743
25744trait RowRangeExt {
25745 type Row;
25746
25747 fn len(&self) -> usize;
25748
25749 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25750}
25751
25752impl RowRangeExt for Range<MultiBufferRow> {
25753 type Row = MultiBufferRow;
25754
25755 fn len(&self) -> usize {
25756 (self.end.0 - self.start.0) as usize
25757 }
25758
25759 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25760 (self.start.0..self.end.0).map(MultiBufferRow)
25761 }
25762}
25763
25764impl RowRangeExt for Range<DisplayRow> {
25765 type Row = DisplayRow;
25766
25767 fn len(&self) -> usize {
25768 (self.end.0 - self.start.0) as usize
25769 }
25770
25771 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25772 (self.start.0..self.end.0).map(DisplayRow)
25773 }
25774}
25775
25776/// If select range has more than one line, we
25777/// just point the cursor to range.start.
25778fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25779 if range.start.row == range.end.row {
25780 range
25781 } else {
25782 range.start..range.start
25783 }
25784}
25785pub struct KillRing(ClipboardItem);
25786impl Global for KillRing {}
25787
25788const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25789
25790enum BreakpointPromptEditAction {
25791 Log,
25792 Condition,
25793 HitCondition,
25794}
25795
25796struct BreakpointPromptEditor {
25797 pub(crate) prompt: Entity<Editor>,
25798 editor: WeakEntity<Editor>,
25799 breakpoint_anchor: Anchor,
25800 breakpoint: Breakpoint,
25801 edit_action: BreakpointPromptEditAction,
25802 block_ids: HashSet<CustomBlockId>,
25803 editor_margins: Arc<Mutex<EditorMargins>>,
25804 _subscriptions: Vec<Subscription>,
25805}
25806
25807impl BreakpointPromptEditor {
25808 const MAX_LINES: u8 = 4;
25809
25810 fn new(
25811 editor: WeakEntity<Editor>,
25812 breakpoint_anchor: Anchor,
25813 breakpoint: Breakpoint,
25814 edit_action: BreakpointPromptEditAction,
25815 window: &mut Window,
25816 cx: &mut Context<Self>,
25817 ) -> Self {
25818 let base_text = match edit_action {
25819 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25820 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25821 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25822 }
25823 .map(|msg| msg.to_string())
25824 .unwrap_or_default();
25825
25826 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25827 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25828
25829 let prompt = cx.new(|cx| {
25830 let mut prompt = Editor::new(
25831 EditorMode::AutoHeight {
25832 min_lines: 1,
25833 max_lines: Some(Self::MAX_LINES as usize),
25834 },
25835 buffer,
25836 None,
25837 window,
25838 cx,
25839 );
25840 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25841 prompt.set_show_cursor_when_unfocused(false, cx);
25842 prompt.set_placeholder_text(
25843 match edit_action {
25844 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25845 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25846 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25847 },
25848 window,
25849 cx,
25850 );
25851
25852 prompt
25853 });
25854
25855 Self {
25856 prompt,
25857 editor,
25858 breakpoint_anchor,
25859 breakpoint,
25860 edit_action,
25861 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25862 block_ids: Default::default(),
25863 _subscriptions: vec![],
25864 }
25865 }
25866
25867 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25868 self.block_ids.extend(block_ids)
25869 }
25870
25871 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25872 if let Some(editor) = self.editor.upgrade() {
25873 let message = self
25874 .prompt
25875 .read(cx)
25876 .buffer
25877 .read(cx)
25878 .as_singleton()
25879 .expect("A multi buffer in breakpoint prompt isn't possible")
25880 .read(cx)
25881 .as_rope()
25882 .to_string();
25883
25884 editor.update(cx, |editor, cx| {
25885 editor.edit_breakpoint_at_anchor(
25886 self.breakpoint_anchor,
25887 self.breakpoint.clone(),
25888 match self.edit_action {
25889 BreakpointPromptEditAction::Log => {
25890 BreakpointEditAction::EditLogMessage(message.into())
25891 }
25892 BreakpointPromptEditAction::Condition => {
25893 BreakpointEditAction::EditCondition(message.into())
25894 }
25895 BreakpointPromptEditAction::HitCondition => {
25896 BreakpointEditAction::EditHitCondition(message.into())
25897 }
25898 },
25899 cx,
25900 );
25901
25902 editor.remove_blocks(self.block_ids.clone(), None, cx);
25903 cx.focus_self(window);
25904 });
25905 }
25906 }
25907
25908 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25909 self.editor
25910 .update(cx, |editor, cx| {
25911 editor.remove_blocks(self.block_ids.clone(), None, cx);
25912 window.focus(&editor.focus_handle, cx);
25913 })
25914 .log_err();
25915 }
25916
25917 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25918 let settings = ThemeSettings::get_global(cx);
25919 let text_style = TextStyle {
25920 color: if self.prompt.read(cx).read_only(cx) {
25921 cx.theme().colors().text_disabled
25922 } else {
25923 cx.theme().colors().text
25924 },
25925 font_family: settings.buffer_font.family.clone(),
25926 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25927 font_size: settings.buffer_font_size(cx).into(),
25928 font_weight: settings.buffer_font.weight,
25929 line_height: relative(settings.buffer_line_height.value()),
25930 ..Default::default()
25931 };
25932 EditorElement::new(
25933 &self.prompt,
25934 EditorStyle {
25935 background: cx.theme().colors().editor_background,
25936 local_player: cx.theme().players().local(),
25937 text: text_style,
25938 ..Default::default()
25939 },
25940 )
25941 }
25942}
25943
25944impl Render for BreakpointPromptEditor {
25945 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25946 let editor_margins = *self.editor_margins.lock();
25947 let gutter_dimensions = editor_margins.gutter;
25948 h_flex()
25949 .key_context("Editor")
25950 .bg(cx.theme().colors().editor_background)
25951 .border_y_1()
25952 .border_color(cx.theme().status().info_border)
25953 .size_full()
25954 .py(window.line_height() / 2.5)
25955 .on_action(cx.listener(Self::confirm))
25956 .on_action(cx.listener(Self::cancel))
25957 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25958 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25959 }
25960}
25961
25962impl Focusable for BreakpointPromptEditor {
25963 fn focus_handle(&self, cx: &App) -> FocusHandle {
25964 self.prompt.focus_handle(cx)
25965 }
25966}
25967
25968fn all_edits_insertions_or_deletions(
25969 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25970 snapshot: &MultiBufferSnapshot,
25971) -> bool {
25972 let mut all_insertions = true;
25973 let mut all_deletions = true;
25974
25975 for (range, new_text) in edits.iter() {
25976 let range_is_empty = range.to_offset(snapshot).is_empty();
25977 let text_is_empty = new_text.is_empty();
25978
25979 if range_is_empty != text_is_empty {
25980 if range_is_empty {
25981 all_deletions = false;
25982 } else {
25983 all_insertions = false;
25984 }
25985 } else {
25986 return false;
25987 }
25988
25989 if !all_insertions && !all_deletions {
25990 return false;
25991 }
25992 }
25993 all_insertions || all_deletions
25994}
25995
25996struct MissingEditPredictionKeybindingTooltip;
25997
25998impl Render for MissingEditPredictionKeybindingTooltip {
25999 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26000 ui::tooltip_container(cx, |container, cx| {
26001 container
26002 .flex_shrink_0()
26003 .max_w_80()
26004 .min_h(rems_from_px(124.))
26005 .justify_between()
26006 .child(
26007 v_flex()
26008 .flex_1()
26009 .text_ui_sm(cx)
26010 .child(Label::new("Conflict with Accept Keybinding"))
26011 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
26012 )
26013 .child(
26014 h_flex()
26015 .pb_1()
26016 .gap_1()
26017 .items_end()
26018 .w_full()
26019 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26020 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26021 }))
26022 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26023 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26024 })),
26025 )
26026 })
26027 }
26028}
26029
26030#[derive(Debug, Clone, Copy, PartialEq)]
26031pub struct LineHighlight {
26032 pub background: Background,
26033 pub border: Option<gpui::Hsla>,
26034 pub include_gutter: bool,
26035 pub type_id: Option<TypeId>,
26036}
26037
26038struct LineManipulationResult {
26039 pub new_text: String,
26040 pub line_count_before: usize,
26041 pub line_count_after: usize,
26042}
26043
26044fn render_diff_hunk_controls(
26045 row: u32,
26046 status: &DiffHunkStatus,
26047 hunk_range: Range<Anchor>,
26048 is_created_file: bool,
26049 line_height: Pixels,
26050 editor: &Entity<Editor>,
26051 _window: &mut Window,
26052 cx: &mut App,
26053) -> AnyElement {
26054 h_flex()
26055 .h(line_height)
26056 .mr_1()
26057 .gap_1()
26058 .px_0p5()
26059 .pb_1()
26060 .border_x_1()
26061 .border_b_1()
26062 .border_color(cx.theme().colors().border_variant)
26063 .rounded_b_lg()
26064 .bg(cx.theme().colors().editor_background)
26065 .gap_1()
26066 .block_mouse_except_scroll()
26067 .shadow_md()
26068 .child(if status.has_secondary_hunk() {
26069 Button::new(("stage", row as u64), "Stage")
26070 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26071 .tooltip({
26072 let focus_handle = editor.focus_handle(cx);
26073 move |_window, cx| {
26074 Tooltip::for_action_in(
26075 "Stage Hunk",
26076 &::git::ToggleStaged,
26077 &focus_handle,
26078 cx,
26079 )
26080 }
26081 })
26082 .on_click({
26083 let editor = editor.clone();
26084 move |_event, _window, cx| {
26085 editor.update(cx, |editor, cx| {
26086 editor.stage_or_unstage_diff_hunks(
26087 true,
26088 vec![hunk_range.start..hunk_range.start],
26089 cx,
26090 );
26091 });
26092 }
26093 })
26094 } else {
26095 Button::new(("unstage", row as u64), "Unstage")
26096 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26097 .tooltip({
26098 let focus_handle = editor.focus_handle(cx);
26099 move |_window, cx| {
26100 Tooltip::for_action_in(
26101 "Unstage Hunk",
26102 &::git::ToggleStaged,
26103 &focus_handle,
26104 cx,
26105 )
26106 }
26107 })
26108 .on_click({
26109 let editor = editor.clone();
26110 move |_event, _window, cx| {
26111 editor.update(cx, |editor, cx| {
26112 editor.stage_or_unstage_diff_hunks(
26113 false,
26114 vec![hunk_range.start..hunk_range.start],
26115 cx,
26116 );
26117 });
26118 }
26119 })
26120 })
26121 .child(
26122 Button::new(("restore", row as u64), "Restore")
26123 .tooltip({
26124 let focus_handle = editor.focus_handle(cx);
26125 move |_window, cx| {
26126 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26127 }
26128 })
26129 .on_click({
26130 let editor = editor.clone();
26131 move |_event, window, cx| {
26132 editor.update(cx, |editor, cx| {
26133 let snapshot = editor.snapshot(window, cx);
26134 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26135 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26136 });
26137 }
26138 })
26139 .disabled(is_created_file),
26140 )
26141 .when(
26142 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26143 |el| {
26144 el.child(
26145 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26146 .shape(IconButtonShape::Square)
26147 .icon_size(IconSize::Small)
26148 // .disabled(!has_multiple_hunks)
26149 .tooltip({
26150 let focus_handle = editor.focus_handle(cx);
26151 move |_window, cx| {
26152 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26153 }
26154 })
26155 .on_click({
26156 let editor = editor.clone();
26157 move |_event, window, cx| {
26158 editor.update(cx, |editor, cx| {
26159 let snapshot = editor.snapshot(window, cx);
26160 let position =
26161 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26162 editor.go_to_hunk_before_or_after_position(
26163 &snapshot,
26164 position,
26165 Direction::Next,
26166 window,
26167 cx,
26168 );
26169 editor.expand_selected_diff_hunks(cx);
26170 });
26171 }
26172 }),
26173 )
26174 .child(
26175 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26176 .shape(IconButtonShape::Square)
26177 .icon_size(IconSize::Small)
26178 // .disabled(!has_multiple_hunks)
26179 .tooltip({
26180 let focus_handle = editor.focus_handle(cx);
26181 move |_window, cx| {
26182 Tooltip::for_action_in(
26183 "Previous Hunk",
26184 &GoToPreviousHunk,
26185 &focus_handle,
26186 cx,
26187 )
26188 }
26189 })
26190 .on_click({
26191 let editor = editor.clone();
26192 move |_event, window, cx| {
26193 editor.update(cx, |editor, cx| {
26194 let snapshot = editor.snapshot(window, cx);
26195 let point =
26196 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26197 editor.go_to_hunk_before_or_after_position(
26198 &snapshot,
26199 point,
26200 Direction::Prev,
26201 window,
26202 cx,
26203 );
26204 editor.expand_selected_diff_hunks(cx);
26205 });
26206 }
26207 }),
26208 )
26209 },
26210 )
26211 .into_any_element()
26212}
26213
26214pub fn multibuffer_context_lines(cx: &App) -> u32 {
26215 EditorSettings::try_get(cx)
26216 .map(|settings| settings.excerpt_context_lines)
26217 .unwrap_or(2)
26218 .min(32)
26219}